降低网络摄像头的延迟 cv2.VideoCapture

Lower latency from webcam cv2.VideoCapture

我正在构建一个应用程序,使用网络摄像头来控制视频游戏(有点像 kinect)。它使用网络摄像头 (cv2.VideoCapture(0))、AI 姿势估计 (mediapipe) 和自定义逻辑将输入管道输入到海豚模拟器中。

问题是延迟。我用 phone 的高速相机记录了自己的快照,发现我的手和屏幕上的帧之间有大约 32 帧 ~133ms 的延迟。这是在任何附加代码之前,只是一个带有视频 readcv2.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

应用程序需要 运行 尽可能接近实时,因此延迟的任何减少,无论多么小都会很棒。目前,在网络摄像头延迟(~133 毫秒)、姿势估计和逻辑(~25 毫秒)以及移动到正确姿势所需的实际时间之间,延迟大约为 350-400 毫秒。当我尝试玩游戏时绝对不理想。

编辑: 这是我用来测试延迟的代码(运行我笔记本电脑上的代码,记录我的手和屏幕,并计算捕捉时的帧差):

if __name__ == "__main__":
    cap = WebcamStream().start()
    while(True):
        frame = cap.read()
        cv2.imshow('frame', frame)
        cv2.waitKey(1)

欢迎使用 War-on-Latency ( shaving-off )

你上面描述的经验是一个很好的例子,累积的延迟如何破坏任何保持控制回路足够紧密的机会,以真正控制有意义的稳定的东西,就像在人机界面中一样我们希望保留的系统:

用户的-动作 | CAM-捕捉 | IMG-处理 | GUI-显示 |用户视觉皮层场景捕捉 |用户的决定+行动 | 循环

显示 OpenCV 分析的真实情况,以“感知”我们在相应的采集-存储-转换-post处理-GUI 管道实际阶段花费了多少时间 (根据需要放大)


我们使用哪些导致延迟的步骤?

暂时请原谅我们在何处累积每个特定延迟相关成本的原始草图:


   CAM \____/                                     python code GIL-awaiting ~ 100 [ms] chopping
        |::|                                      python code calling a cv2.<function>()
        |::|   __________________________________________-----!!!!!!!-----------
        |::|    ^     2x                                 NNNNN!!!!!!!MOVES DATA!
        |::|    | per-call                               NNNNN!!!!!!!    1.THERE
        |::|    |     COST                               NNNNN!!!!!!!    2.BACK
        |::|    |           TTTT-openCV::MAT into python numpy.array
        |::|    |          ////       forMAT TRANSFORMER TRANSFORMATIONS
        USBx    |         ////                           TRANSFORMATIONS
        |::|    |        ////                            TRANSFORMATIONS
        |::|    |       ////                             TRANSFORMATIONS
        |::|    |      ////                              TRANSFORMATIONS
        |::|    |     ////                               TRANSFORMATIONS
    H/W oooo   _v____TTTT in-RAM openCV::MAT storage     TRANSFORMATIONS
       /    \        oooo ------ openCV::MAT object-mapper
       \    /        xxxx
 O/S--- °°°°         xxxx
 driver """" _____   xxxx
         \\    ^   xxxx ...... openCV {signed|unsigned}-{size}-{N-channels}
 _________\\___|___++++ __________________________________________
 openCV I/O      ^   PPPP                                 PROCESSING
            as F |   ....                                 PROCESSING
               A |   ...                                  PROCESSING
               S |   ..                                   PROCESSING
               T |   .                                    PROCESSING
            as   |   PPPP                                 PROCESSING
      possible___v___PPPP _____ openCV::MAT NATIVE-object PROCESSING


我们/我们可以对抗什么延迟这里反对?

硬件 延迟可能会有所帮助,但更换已经获得的硬件可能会变得昂贵

软件 已经延迟优化的工具箱的延迟是可能的,但越来越难

设计 低效是最后也是最常见的地方,延迟可以被削减


OpenCV ?
这里没什么可做的。问题在于 OpenCV-Python 绑定细节:

... So when you call a function, say res = equalizeHist(img1,img2) in Python, you pass two numpy arrays and you expect another numpy array as the output. So these numpy arrays are converted to cv::Mat and then calls the equalizeHist() function in C++. Final result, res will be converted back into a Numpy array. So in short, almost all operations are done in C++ which gives us almost same speed as that of C++.

这在控制循环“外部”工作得很好,但在我们的例子中却不行,因为两种传输成本、转换成本和任何新的或临时数据存储 RAM 分配成本都会导致恶化我们的控制回路 TAT.

因此,请避免从 Python(在绑定的延迟额外英里后)端调用任何和所有 OpenCV 原生函数,无论这些第一眼看起来多么诱人或甜美。

数百 [ms] 是忽视此建议的相当惨痛的代价。


Python ?
是的,Python。使用 Python 解释器本身会引入延迟,加上避免并发处理的问题,无论我们的硬件在多少个内核上运行(而最近的 Py3 尝试在解释器级软件下降低这些成本) .

我们可以测试并从(2022 年仍然不可避免的)GIL 锁交错中挤出最大值 - 检查 sys.getswitchinterval() 并测试增加此数量以减少交错 python 端处理(调整取决于您的其他 python-应用程序目标(GUI、 任务、python network-I/O 工作负载、python-HW-I/O-s,如果适用,等等)


RAM-memory-I/O 成本 ?
我们的下一个主要敌人。使用最不足够的图像数据格式,MediaPipe 可以使用是这一领域的前进方向。


可避免的损失
所有其他(我们的)罪恶都属于这个部分。避免任何图像数据格式转换(见上文,成本可能很容易增长到 [us] 的数十万,只是为了将已经获取的-&-格式化-&-存储的 numpy.array 转换为另一个颜色图)

MediaPipe
列出了它可以使用的枚举格式:

 // ImageFormat

  SRGB: sRGB, interleaved:   one byte for R,
                        then one byte for G,
                        then one byte for B for each pixel.

  SRGBA: sRGBA, interleaved: one byte for R,
                             one byte for G,
                             one byte for B,
                             one byte for alpha or unused.

  SBGRA: sBGRA, interleaved: one byte for B,
                             one byte for G,
                             one byte for R,
                             one byte for alpha or unused.

  GRAY8:        Grayscale,   one byte per pixel.

  GRAY16:       Grayscale,   one uint16 per pixel.

  SRGB48:  sRGB,interleaved, each component is a uint16.

  SRGBA64: sRGBA,interleaved,each component is a uint16.

  VEC32F1:                   One float per pixel.

  VEC32F2:                   Two floats per pixel.

因此,选择 MVF——最小可行格式——以便手势识别工作并尽可能缩小像素数量(400x600-GRAY8 将是我的热门候选)

预配置(不遗漏 cv.CAP_PROP_FOURCC )本机端 OpenCV::VideoCapture 处理只是简单地将此 MVF 以 RAW 格式存储在采集和预处理链的本机端,因此不会发生其他 post 过程格式化。

如果确实被迫接触 python 侧 numpy.array 对象,更喜欢使用矢量化和步幅技巧驱动的操作而不是 .view()-s 或 .data -缓冲区,以避免任何不必要的附加延迟成本增加控制循环 TAT.


选项?

  • 通过精确配置本机端消除任何 python 端调用(因为这些成本是 data-I/O 的 --2 倍成本 + 转换成本) OpenCV 处理以匹配所需的 MediaPipe 数据格式

  • 最小化,最好避免任何阻塞,如果控制回路仍然太偏斜,请尝试使用 with moving raw-data into other process ( not necessarily a Python-interpreter ) on localhost or within a sub-ms LAN domain ( further tips )

  • 尝试使热数据 RAM 占用空间与您相匹配 CPU-缓存层次结构缓存行的大小和关联性详细信息(参见 this