【发布时间】:2021-11-19 18:38:58
【问题描述】:
在我的 Python 应用程序中,我使用 Detectron2 对图像运行预测并检测图像中所有人类的关键点。
我想对实时流式传输到我的应用程序的帧运行预测(使用 aiortc),但我发现预测时间更糟,因为它现在在新线程上运行(主线程被服务器占用)。
在线程上运行预测需要 1.5 到 4 秒之间的任何时间,这很长。
在主线程上运行预测时(没有视频流部分),我得到的预测时间不到一秒。
我的问题是为什么会发生这种情况,我该如何解决?为什么从新线程中使用 GPU 性能会急剧下降?
注意事项:
-
代码在 Google Colab 中使用 Tesla P100 GPU 进行测试,并通过从视频文件中读取帧来模拟视频流。
-
我使用this question 中的代码计算在帧上运行预测所需的时间。
我尝试切换到多处理,但无法使其与 cuda 一起工作(我尝试了 import multiprocessing 以及 import torch.multiprocessing 和 set_stratup_method('spawn'))它只是在进程上调用 start 时卡住了。
示例代码:
from detectron2 import model_zoo
from detectron2.engine import DefaultPredictor
from detectron2.config import get_cfg
import threading
from typing import List
import numpy as np
import timeit
import cv2
# Prepare the configuration file
cfg = get_cfg()
cfg.merge_from_file(model_zoo.get_config_file("COCO-Keypoints/keypoint_rcnn_R_50_FPN_3x.yaml"))
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.7 # set threshold for this model
cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-Keypoints/keypoint_rcnn_R_50_FPN_3x.yaml")
cfg.MODEL.DEVICE = "cuda"
predictor = DefaultPredictor(cfg)
def get_frames(video: cv2.VideoCapture):
frames = list()
while True:
has_frame, frame = video.read()
if not has_frame:
break
frames.append(frame)
return frames
class CodeTimer:
# Source: https://stackoverflow.com/a/52749808/9977758
def __init__(self, name=None):
self.name = " '" + name + "'" if name else ''
def __enter__(self):
self.start = timeit.default_timer()
def __exit__(self, exc_type, exc_value, traceback):
self.took = (timeit.default_timer() - self.start) * 1000.0
print('Code block' + self.name + ' took: ' + str(self.took) + ' ms')
video = cv2.VideoCapture('DemoVideo.mp4')
num_frames = round(video.get(cv2.CAP_PROP_FRAME_COUNT))
frames_buffer = list()
predictions = list()
def send_frames():
# This function emulates the stream, so here we "get" a frame and add it to our buffer
for frame in get_frames(video):
frames_buffer.append(frame)
# Simulate delays between frames
time.sleep(random.uniform(0.3, 2.1))
def predict_frames():
predicted_frames = 0 # The number of frames predicted so far
while predicted_frames < num_frames: # Stop after we predicted all frames
buffer_length = len(frames_buffer)
if buffer_length <= predicted_frames:
continue # Wait until we get a new frame
# Read all the frames from the point we stopped
for frame in frames_buffer[predicted_frames:]:
# Measure the prediction time
with CodeTimer('In stream prediction'):
predictions.append(predictor(frame))
predicted_frames += 1
t1 = threading.Thread(target=send_frames)
t1.start()
t2 = threading.Thread(target=predict_frames)
t2.start()
t1.join()
t2.join()
【问题讨论】:
-
我有三个问题/建议:1。我不明白你是如何使用线程的,因为看起来你目前有一个线程同时运行检测和
get_frames函数。让一个线程用图像填充缓冲区,而另一个线程处理图像对我来说是有意义的。 -
2.你能在把它变成一个线程之前检查检测模型是否完全初始化吗?通常检测模型需要更长的时间(几秒钟)来处理第一帧。您可以尝试让模型在初始化后直接处理一个虚拟帧/空法师(在此行之后
predictor = DefaultPredictor(cfg))。 3. 能否检查检测模型是否在 GPU 上运行。我没有看到将您的模型或图像移动到 GPU 的代码。也许这是在DefaultPredictor内完成的。但是我不能确定。 -
@ThijsRuigrok 1. 你是对的,我刚刚注意到我过度简化了我的示例代码,它假设在另一个线程上发送帧。 2.我试过了,它似乎确实已经初始化但仍然运行缓慢。 3. 在
cfg中,我指定预测器在cuda上运行,DefaultPredictor将帧移动到 GPU。 -
听起来不错。您是否 100% 确定在实际代码中执行线程不会造成任何问题?是否可以共享(部分)真实代码?
-
Tnx 用于更新代码。考虑到线程部分,您的代码似乎合乎逻辑。我注意到您从不清除帧缓冲区。如果视频/图像流较大,这可能会占用大量内存,这可能会减慢您的系统甚至崩溃(当我加载包含 7200 帧的 4 分钟视频时发生在我身上)。
标签: python multithreading performance gpu