OpenCV 实时流式视频捕获很慢。如何丢帧或实时同步?

Posted

技术标签:

【中文标题】OpenCV 实时流式视频捕获很慢。如何丢帧或实时同步?【英文标题】:OpenCV real time streaming video capture is slow. How to drop frames or get synced with real time? 【发布时间】:2020-02-06 03:27:42 【问题描述】:

目标和问题

我想设置一个 opencv 系统来处理 HLS 流或 RMTP 流,但是,我遇到了一个关于帧速率降低和累积延迟的奇怪问题。就好像视频离它应该在流中的位置越来越远。

我正在寻找一种方法来与实时源保持同步,即使这意味着丢帧。

目前的做法

import cv2

cap = cv2.VideoCapture()
cap.open('https://videos3.earthcam.com/fecnetwork/9974.flv/chunklist_w1421640637.m3u8')

while (True):
    _, frame = cap.read()
    cv2.imshow("camCapture", frame)
    cv2.waitKey(1)

我已经在 VLC 上验证了流的质量,它似乎在那里运行良好。

cv2 速度

现实/预期速度

问题:

我在这里做错了什么? 为什么这么慢? 如何将其同步到实时速度?

【问题讨论】:

一个原因可能是由于 I/O 延迟问题。一个解决方案可能是使用线程。看看this 在我的机器上,我用你的代码加速了视频大约 2 秒。然后停止约 2 秒。重复。我试图用 cap.set(cv2.CAP_PROP_POS_AVI_RATIO, 1) 强制视频流显示最新帧。它似乎有效,但我每 秒得到一帧。 print(cap.get(cv2.CAP_PROP_FPS)) 的 FPS 是 180,000.0,太高了。但是如果你能得到一个准确的 FPS,也许你可以用多个 cap.grab() 来做一些诡计多端的操作来推进到你应该在流中的位置,然后一个 cap.retrieve 来获取帧。 我看到您在 python 中获取视频的最少代码。所以,提高运行时间是不可能的。可能问题出在视频分辨率上,如果 W 和 H 尺寸较长,则缓冲区读取和显示视频帧会延迟更多。一个简单的解决方案是在阅读之前降低视频的分辨率。另一种解决方案是创建第二个线程来仅获取视频。另一种解决方案是使用 C++ 读取视频。我比较了性能,它快了大约 3 倍。 另请注意,您的问题可能出在硬件上:内存和数据总线带宽,以及处理器速度。 使用更快的设备(更多的处理能力)或使用硬件加速或优化代码来接收、解码和显示流。 OpenCV 可能不是接收、解码和渲染最快的库(但它非常好用) 【参考方案1】:

我的假设是,抖动很可能是由于网络限制,并且发生在丢帧数据包时。当一个帧被丢弃时,这会导致程序显示最后一个“好”帧,从而导致显示冻结。这可能是硬件或带宽问题,但我们可以通过软件缓解其中的一些问题。以下是一些可能的变化:

1.设置最大缓冲区大小

我们使用cv2.CAP_PROP_BUFFERSIZE 参数将cv2.videoCapture() 对象设置为有限的缓冲区大小。这个想法是通过限制缓冲区,我们将始终拥有最新的帧。这也有助于缓解帧随机向前跳的问题。

2。设置帧检索延迟

目前,我认为read() 的读取速度过快,即使它位于自己的专用线程中。这可能是为什么所有帧看起来都集中起来并在下一帧突然爆裂的一个原因。例如,假设在一秒的时间间隔内,它可能会产生 15 个新帧,但在接下来的一秒间隔内,只返回 3 个帧。这可能是由于网络丢包导致的,因此为了确保我们获得恒定的帧速率,我们只需在帧检索线程中添加一个延迟。获得大致~30 FPS 的延迟可以很好地“标准化”帧速率并平滑帧之间的过渡,以防丢包。

注意:我们应该尝试匹配流的帧速率,但我不确定网络摄像头的 FPS 是多少,所以我只是猜测30 FPS。此外,通常有一个“直接”流链接,而不是通过中间网络服务器,这可以大大提高性能。


如果您尝试使用保存的.mp4 视频文件,您会发现没有抖动。这证实了我的怀疑,即问题很可能是由于网络延迟造成的。

from threading import Thread
import cv2, time

class ThreadedCamera(object):
    def __init__(self, src=0):
        self.capture = cv2.VideoCapture(src)
        self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 2)

        # FPS = 1/X
        # X = desired FPS
        self.FPS = 1/30
        self.FPS_MS = int(self.FPS * 1000)

        # Start frame retrieval thread
        self.thread = Thread(target=self.update, args=())
        self.thread.daemon = True
        self.thread.start()

    def update(self):
        while True:
            if self.capture.isOpened():
                (self.status, self.frame) = self.capture.read()
            time.sleep(self.FPS)

    def show_frame(self):
        cv2.imshow('frame', self.frame)
        cv2.waitKey(self.FPS_MS)

if __name__ == '__main__':
    src = 'https://videos3.earthcam.com/fecnetwork/9974.flv/chunklist_w1421640637.m3u8'
    threaded_camera = ThreadedCamera(src)
    while True:
        try:
            threaded_camera.show_frame()
        except AttributeError:
            pass

【讨论】:

您能告诉我为什么需要等待(FPS 同步)吗?缓冲区的目的不是在帧到来时捕获帧吗?感觉从缓冲区顶部抓取的内容并不重要。建立一个人为的延迟对我来说是违反直觉的。那是关于线程中的资源共享还是什么?或者是图像不是实时出现的,而是因为它们能够被重建或重新传递。不管怎样,谢谢你的详细回复。这很好用。 @Conic 需要 FPS 同步,因为通常您希望与源同步。如果您的轮询速度太慢,您将有一段时间没有新框架,因此您将显示旧框架。如果轮询太快(像以前一样),您将立即显示所有帧。在这两种情况下,这都可能导致抖动/冻结,因此通过插入延迟,您可以平衡显示的帧分布。由于有两个单独的线程,一旦接收到一帧,它就会立即显示,这也是我们需要延迟的另一个原因。 打个比方,假设您正在以 2 倍速观看 Youtube 视频,但您使用的是拨号上网。缓冲区的加载速度将比实际播放的视频慢,因此最终,帧将赶上缓冲区,视频将冻结。这就是 FPS 未同步时发生的情况。如果我们恢复到 1x 速度,那么帧将被均匀化并与缓冲速率的速度相匹配,因此我们不会出现任何冻结/抖动。然后视频看起来很流畅 OpenCV VideoCapture 根本没有轮询。你能解释一下你的意思吗? @BenZayed 对于单个摄像头,只需设置缓冲区大小和延迟即可,但如果您有多个摄像头同时运行,您可能需要使用线程(我将这种技术用于 8 个同时摄像头流)【参考方案2】:

尝试线程化

我尝试了来自nathancy 的this 解决方案,但取得了一些成功。

涉及到:

为从源捕获图像创建单独的线程 仅使用主线程进行显示。

代码:

import cv2
from threading import Thread

class ThreadedCamera(object):
    def __init__(self, source = 0):

        self.capture = cv2.VideoCapture(source)

        self.thread = Thread(target = self.update, args = ())
        self.thread.daemon = True
        self.thread.start()

        self.status = False
        self.frame  = None

    def update(self):
        while True:
            if self.capture.isOpened():
                (self.status, self.frame) = self.capture.read()

    def grab_frame(self):
        if self.status:
            return self.frame
        return None  
if __name__ == '__main__':
    stream_link = "https://videos3.earthcam.com/fecnetwork/9974.flv/chunklist_w1421640637.m3u8"
    streamer = ThreadedCamera(stream_link)

    while True:
        frame = streamer.grab_frame()
        if frame is not None:
            cv2.imshow("Context", frame)
        cv2.waitKey(1) 

紧张,但实时结果

.

流式传输有效。它保持实时。然而,就好像所有的帧都聚集在一起,突然爆发到视频中。我希望有人解释一下。

改进空间

实时流可以在这里找到。

https://www.earthcam.com/usa/newyork/timessquare/?cam=tsstreet

使用 python 的 streamlink 流刮板为 m3u8 刮取此站点。


import streamlink

streams = streamlink.streams("https://www.earthcam.com/usa/newyork/timessquare/?cam=tsstreet")
print(streams)

产生:

OrderedDict([

('720p',<HLSStream('https://videos3.earthcam.com/fecnetwork/9974.flv/chunklist_w202109066.m3u8')>),

('live', <RTMPStream('rtmp': 'rtmp://videos3.earthcam.com/fecnetwork/', 'playpath': '9974.flv', 'pageUrl': 'https://www.earthcam.com/usa/newyork/timessquare/?cam=tsstreet','swfUrl': 'http://static.earthcam.com/swf/streaming/stream_viewer_v3.swf', 'live': 'true', redirect=False>),

('worst', <HLSStream('https://videos3.earthcam.com/fecnetwork/9974.flv/chunklist_w202109066.m3u8')>),

('best', <RTMPStream('rtmp': 'rtmp://videos3.earthcam.com/fecnetwork/', 'playpath': '9974.flv', 'pageUrl': 'https://www.earthcam.com/usa/newyork/timessquare/?cam=tsstreet', 'swfUrl': 'http://static.earthcam.com/swf/streaming/stream_viewer_v3.swf', 'live': 'true', redirect=False>)

])


流被读取错误的可能性。

【讨论】:

您找到解决方案了吗? 是的,我做到了。这是 nathancy 旁边带有绿色复选标记的答案。 糟糕,抱歉。既然你说 nathancy 的回答是小成功。我以为你的意思是这个问题的答案(没有检查链接)。反正。谢谢。 我知道这可能会产生误导。我指的是他的另一个堆栈溢出帖子的不同解决方案。然后他回复了一个更新的答案,该答案进行了 FPS 同步。这最终成为了对我有用的解决方案。 是的。谢谢:)

以上是关于OpenCV 实时流式视频捕获很慢。如何丢帧或实时同步?的主要内容,如果未能解决你的问题,请参考以下文章

从 localHost 端口 (http://192.168.1.1:8080) 在 openCv 中流式传输实时视频

如何使用 opencv VideoCapture 方法获取实时帧?

使用 OpenCV 从 youtube 捕获实时视频

在 iOS 设备上捕获视频并将其实时流式传输到服务器(或其他移动设备)

如何使用OpenCV获得实时视频输入的实时直方图?

从网络摄像机捕获实时图像