Yon*_*arp 4 python webcam opencv video-capture low-latency
我正在构建一个使用网络摄像头来控制视频游戏的应用程序(有点像 kinect)。它使用网络摄像头 (cv2.VideoCapture(0))、AI 姿势估计 ( mediapipe ) 和自定义逻辑将输入传输到 dolphin 模拟器中。
问题是延迟。我使用手机的高速摄像头记录自己的抓拍,发现我的手和屏幕上的帧之间存在约 32 帧~133 毫秒的延迟。这是在任何附加代码之前,只是一个视频循环read
(cv2.imshow
大约 15 毫秒)
有什么办法可以减少这个延迟吗?
我已经在单独的线程中抓取帧,将CAP_PROP_BUFFERSIZE设置为 0,并降低 CAP_PROP_FRAME_HEIGHT 和 CAP_PROP_FRAME_WIDTH,但我仍然得到约 133 毫秒的延迟。我还有什么可以做的吗?
下面是我的代码:
class WebcamStream:
def __init__(self, src=0):
self.stopped = False
self.stream = cv2.VideoCapture(src)
self.stream.set(cv2.CAP_PROP_BUFFERSIZE, 0)
self.stream.set(cv2.CAP_PROP_FRAME_HEIGHT, 400)
self.stream.set(cv2.CAP_PROP_FRAME_WIDTH, 600)
(self.grabbed, self.frame) = self.stream.read()
self.hasNew = self.grabbed
self.condition = Condition()
def start(self):
Thread(target=self.update, args=()).start()
return self
def update(self,):
while True:
if self.stopped: return
(self.grabbed, self.frame) = self.stream.read()
with self.condition:
self.hasNew = True
self.condition.notify_all()
def read(self):
if not self.hasNew:
with self.condition:
self.condition.wait()
self.hasNew = False
return self.frame
def stop(self):
self.stopped = True
Run Code Online (Sandbox Code Playgroud)
应用程序需要尽可能接近实时地运行,因此延迟的任何减少,无论多小都是很好的。目前,在网络摄像头延迟(约 133 毫秒)、姿势估计和逻辑(约 25 毫秒)以及移动到正确姿势所需的实际时间之间,延迟高达约 350-400 毫秒。当我尝试玩游戏时绝对不理想。
编辑:这是我用来测试延迟的代码(在我的笔记本电脑上运行代码,记录我的手和屏幕,并计算捕捉中的帧差异):
if __name__ == "__main__":
cap = WebcamStream().start()
while(True):
frame = cap.read()
cv2.imshow('frame', frame)
cv2.waitKey(1)
Run Code Online (Sandbox Code Playgroud)
您上面描述的经验是一个很好的例子,累积的延迟可能会破坏任何保持控制环路足够紧的机会,从而真正控制一些有意义的稳定的东西,就像在我们希望保持的人机接口系统中一样:
\n用户动议| CAM-捕捉| IMG-加工| GUI-显示| 用户视觉皮层场景捕捉| 用户的决定+行动| 环形
\n显示 OpenCV 分析的真实情况,以“感知”我们在各个采集-存储-转换-后处理-GUI 管道实际阶段花费了多少时间(根据需要放大)
\n\n请原谅我们在哪里累计每个特定的延迟相关成本的原始草图:
\n\n CAM \\____/ python code GIL-awaiting ~ 100 [ms] chopping\n |::| python code calling a cv2.<function>()\n |::| __________________________________________-----!!!!!!!-----------\n |::| ^ 2x NNNNN!!!!!!!MOVES DATA!\n |::| | per-call NNNNN!!!!!!! 1.THERE\n |::| | COST NNNNN!!!!!!! 2.BACK\n |::| | TTTT-openCV::MAT into python numpy.array\n |::| | //// forMAT TRANSFORMER TRANSFORMATIONS\n USBx | //// TRANSFORMATIONS\n |::| | //// TRANSFORMATIONS\n |::| | //// TRANSFORMATIONS\n |::| | //// TRANSFORMATIONS\n |::| | //// TRANSFORMATIONS\n H/W oooo _v____TTTT in-RAM openCV::MAT storage TRANSFORMATIONS\n / \\ oooo ------ openCV::MAT object-mapper\n \\ / xxxx\n O/S--- \xc2\xb0\xc2\xb0\xc2\xb0\xc2\xb0 xxxx\n driver """" _____ xxxx\n \\\\\\\\ ^ xxxx ...... openCV {signed|unsigned}-{size}-{N-channels}\n _________\\\\\\\\___|___++++ __________________________________________\n openCV I/O ^ PPPP PROCESSING\n as F | .... PROCESSING\n A | ... PROCESSING\n S | .. PROCESSING\n T | . PROCESSING\n as | PPPP PROCESSING\n possible___v___PPPP _____ openCV::MAT NATIVE-object PROCESSING\n\n
Run Code Online (Sandbox Code Playgroud)\n硬件延迟可能会有所帮助,但更改已购买的硬件可能会变得昂贵
\n软件延迟是可能的,但越来越难
\n设计效率低下是最后也是最常见的地方,延迟可以被削减
\n开放式CV?
这里没什么可做的。问题在于 OpenCV-Python 绑定细节:
\n\n...所以当你调用一个函数时,比如
\nres = equalizeHist(img1,img2)
在 Python 中,你传递两个 numpy 数组,并且你期望另一个numpy
数组作为输出。所以这些numpy数组被转换为C++中的函数cv::Mat
然后调用equalizeHist()
。最终结果,res 将被转换回 Numpy 数组。简而言之,几乎所有操作都是在 C++ 中完成的,这给了我们几乎与 C++ 相同的速度。
这在控制循环“外部”工作得很好,但在我们的例子中则不然,在我们的例子中,两个传输成本、转换成本和任何新的或临时数据存储 RAM 分配成本都会导致我们的控制循环恶化TAT
。
因此,请避免从 Python 端(在绑定的延迟额外里程后面)调用任何 OpenCV 原生函数,无论这些函数第一眼看起来多么诱人或甜蜜。
\n忽视这一建议将[ms]
带来相当惨痛的代价。
Python ?
是的,Python。使用 Python 解释器本身会带来延迟,而且会增加并发避免处理的问题,无论我们的硬件在多少个内核上运行(而最近的 Py3 尝试在解释器级软件下降低这些成本)。
我们可以测试并挤出最大的(在 2022 年仍然不可避免的)GIL 锁交错 - 检查并sys.getswitchinterval()
测试增加此数量以减少交错的 python 端处理(调整取决于您的其他 python 应用程序野心(GUI,分布式计算任务、python 网络 I/O 工作负载、python-HW-I/O(如果适用)等)
RAM-内存-I/O成本?
我们的下一个主要敌人。使用MediaPipe可以使用的最不足够的图像数据格式是该领域的前进方向。
可避免的损失
所有其他(我们的)罪孽都属于这个部分。避免任何图像数据格式转换(参见上文,[us]
仅将已获取并格式化并存储numpy.array
为另一个颜色图的成本可能很容易增长到数十万)
MediaPipe
列出了它可以使用的枚举格式:
// ImageFormat\n\n SRGB: sRGB, interleaved: one byte for R,\n then one byte for G,\n then one byte for B for each pixel.\n\n SRGBA: sRGBA, interleaved: one byte for R,\n one byte for G,\n one byte for B,\n one byte for alpha or unused.\n\n SBGRA: sBGRA, interleaved: one byte for B,\n one byte for G,\n one byte for R,\n one byte for alpha or unused.\n\n GRAY8: Grayscale, one byte per pixel.\n\n GRAY16: Grayscale, one uint16 per pixel.\n\n SRGB48: sRGB,interleaved, each component is a uint16.\n\n SRGBA64: sRGBA,interleaved,each component is a uint16.\n\n VEC32F1: One float per pixel.\n\n VEC32F2: Two floats per pixel.\n
Run Code Online (Sandbox Code Playgroud)\n因此,选择 MVF——最小可行格式——让手势识别发挥作用并尽可能缩小像素数量(400x600-GRAY8
将是我的热门候选)
预配置(不要遗漏cv.CAP_PROP_FOURCC
细节)本机端OpenCV::VideoCapture
处理仅以 RAW 格式简单地将此 MVF 存储在采集和预处理链的本机端上,这样就不需要其他帖子- 进行格式化处理。
如果确实被迫接触 python 端numpy.array
对象,则更喜欢使用矢量化和跨步技巧驱动的操作而不是.view()
-s 或.data
-buffers,以避免任何不必要的附加延迟成本增加控制循环TAT
。
通过精确配置本机端 OpenCV 处理以匹配所需的 MediaPipe 数据格式,消除任何 Python 端调用(因为这些调用会花费您 2 倍的数据 I/O 成本 + 转换成本)
\n最小化,更好地避免任何阻塞,如果控制循环仍然过于倾斜,请尝试使用分布式处理将原始数据移动到本地主机上或亚毫秒 LAN 域内的其他进程(不一定是 Python 解释器)(提供更多提示)这里)
\n尝试调整热数据 RAM 占用空间以匹配您的 CPU 缓存层次结构缓存行的大小和关联性详细信息(请参阅此)
\n