使用opencv线程捕获空白图像

Posted

技术标签:

【中文标题】使用opencv线程捕获空白图像【英文标题】:blank image capture with opencv threading 【发布时间】:2021-04-22 07:16:21 【问题描述】:

我正在尝试将从笔记本电脑摄像头捕获的帧写入循环缓冲区。我想创建一个线程来处理这个任务(以异步方式)。当我尝试获取使用get_last() 方法存储的最后一帧并显示它时,会显示黑屏。我花了相当长的时间找出原因,但一切都在脉络中。恕我直言,这段代码应该有一个小问题,但看不到它。任何帮助将不胜感激。完整的工作代码如下所示:

import cv2
import sys
import time
import threading

class CamBuffer(threading.Thread):
    def __init__(self, stream=0):
        self.current = 0
        self.max_size = 5
        self.buffer = [None] * self.max_size
        self.capture = cv2.VideoCapture(stream)
        if not self.capture.isOpened():
            print("Could not open video")
            sys.exit()

        print("Opened video")
        self.running = True
        super().__init__()

    def run(self):
        print('running thread')
        while self.running:
            print('capturing frame ', self.current)
            _, frame = self.capture.read()
            if _:
                print('saving frame ', self.current)
                self.buffer[self.current] = frame
                self.current = self.current + 1
                if (self.current >= self.max_size):
                    self.current = 0
        self.capture.release()
        print('stopped thread')

    def terminate(self):
        print('terminating thread')
        self.running = False

    def get_last(self):
        current = 0
        if self.current > 0:
            current = self.current - 1

        print('get_last()', current)
        return self.buffer[current]
        

if __name__ == "__main__":
    print('Frame buffer test')
    stream = 0
    cb = CamBuffer(stream)
    cb.start()
    time.sleep(1.25)

    frame = cb.get_last()
    if frame is not None:
        print('showing frame')
        cv2.imshow('Frame', frame)

    time.sleep(3)
    cb.terminate()
    cv2.destroyAllWindows()
    print('Frame buffer test [Done]')

【问题讨论】:

您必须在cv2.imshow() 之后调用cv2.waitKey(),因为它使用这段时间来更新显示-我发现我需要至少30 作为cv2.waitKey() 的参数,而在macOS 上,所以也许从 30 开始。你不应该在任何地方的代码中调用 time.sleep() 【参考方案1】:

对我有用的最简单的解决方案就是在 imshow 之后添加 cv.waitKey(1)

 if frame is not None:
        print('showing frame')
        cv2.imshow('Frame', frame)
        cv2.waitKey(1):

我认为这是 Opencv highgui 模块的问题 - 它是一个 GUI,也有自己的线程,有时它可能会挂起应用程序。尝试过快更新 Highgui 窗口/cv2.imshow() 时会出现灰屏,我在 C++ 中也遇到过。我猜想关于锁定 GUI 线程和图像的内存。

顺便说一句,您还可以在捕获线程中添加等待(如果您不需要最大帧率)。

PS. 在意识到可以使用该行解决之前,我尝试了其他可能的问题:lock.acquire/release、全局缓冲区、发送缓冲区容器作为参数。然而,全局也显示灰屏(尽管在捕获期间它显示捕获的图像)。当我添加了 cv2.waitKey(...) 时,它终于与作为参数发送的缓冲区一起工作了,我意识到无论如何它可能就足够了。

其他实验:

import cv2
import sys
import time
import threading

max_size = 5
buffer = [None] * max_size
frCopyGlobal = None
buff = [None] * max_size #call as param


class CamBuffer(threading.Thread):
    def __init__(self, buff, stream=0):
        self.current = 0
        self.max_size = 5
        self.buffer = [None] * self.max_size
        self.capture = cv2.VideoCapture(stream, apiPreference=cv2.CAP_DSHOW)
        if not self.capture.isOpened():
            print("Could not open video")
            sys.exit()

        print("Opened video")
        self.running = True
        super().__init__()

    def run(self):
        print('running thread')
        
        while self.running:        
            #lock.acquire()
            print('capturing frame ', self.current)
            _, frame = self.capture.read()
            cv2.imshow("F", frame)
            if _:
                #print('saving frame ', self.current%self.max_size, self.current)
                print('saving frame ', self.current)                
                frCopy = frame.copy()
                self.buffer[self.current] = frCopy
                
                frCopyGlobal = frame.copy()
                buffer[self.current] = frCopyGlobal                
                
                buff[self.current] = frame.copy()
                
                #self.buffer[self.current%self.max_size] = frame
                #cv2.imshow("FBUFF", self.buffer[self.current%self.max_size])
                cv2.imshow("FBUFF", self.buffer[self.current])
                cv2.imshow("GLOBAL BUFF", buffer[self.current])
                cv2.imshow("Param BUFF", buff[self.current])
                self.current = self.current + 1
                if (self.current >= self.max_size):
                    self.current = 0
            cv2.waitKey(66)
            #lock.release()
        self.capture.release()
        print('stopped thread')

    def terminate(self):
        print('terminating thread')
        self.running = False

    def get_last(self):
        current = 0
        if self.current > 0:
            current = self.current - 1

        print('get_last()', current)
        for i in self.buffer:
          cv2.imshow("THREAD: "+str(i), i)
        for i in buffer:
          cv2.imshow("GLOBAL: "+str(i), i)
        return self.buffer[current]
        

if __name__ == "__main__":
    lock = threading.Lock() #threading.Lock lock();
    print('Frame buffer test')
    stream = 0    
    cb = CamBuffer(buff, stream) # The buffer must be in common memory in order to read it from the calling thread
    cb.start()
    time.sleep(1.0)
    for i in range(10):
      try:
        cv2.imshow("SAMPLE?", buff[1])
      finally:
              pass
      cv2.waitKey(15)
    time.sleep(1.25)
    #cb.join()
    #cb.sleep(1.)
    #cb.terminate()
    #frame = cb.get_last()
    cb.running = False
    ###lock.acquire()
    #frame = cb.buffer[0]
    frame = buff[0]    
    #frame = buff[0]    
    frame = cb.buffer[0]
    if frame is not None:
        print('Showing frame from Thread')
        cv2.imshow('PARAMETER BUFFER?', frame)
    cv2.waitKey(500)
    #frame = buffer[0] #Global
    #if frame is not None:
    #    print('showing frame From GLOBAL')
    #    cv2.imshow('GLOBAL FRAME', frame)
        
    ###lock.release()
    time.sleep(3)
    cb.terminate()    
    cv2.destroyAllWindows()
    
    print('Frame buffer test [Done]')
    cb.join()
    
    #exit(0)

【讨论】:

应该是 cv2.waitKey(1) 而不是 cv.waitKey(1)。无法提交小修改,所以放入 cmets 顺便说一句,只有当我等待相当长的时间(在 waitkey() 的参数中>100)时,您的简单修复才有效。但是您的逻辑恕我直言不适用,因为我没有以更快的速度更新 highgui。我只是抓取一个帧并显示它,对吗? 在捕获过程中添加等待 segfaults and errors like" Saving frame 4 QObject::startTimer: Timers cannot be started from another thread capture frame 0 Saving frame 0 QObject::startTimer: Timers cannot be started from另一个线程捕获帧 1 保存帧 1 QObject::startTimer: 定时器不能从另一个线程终止线程启动分段错误(核心转储)“ 当然,cv2.waitKey()。在我的设置上运行我的代码,它提出了其他可能的原因,它与waitKey(1)一起工作正常,但我的代码也按照建议等待帧捕获 - 你的代码正在“饿死”任务调度程序,就像While True: ...其他线程(可能包括那个)没有剩余时间段。从 highgui 开始 - 它已更新,imshow() 尝试读取数据,但当它启动时,控件切换到正在使用该数据并锁定它的另一个线程。 使用 cv2.waitKey() 并运行我的代码,它有(但您可能需要清理一些实验)并且没有错误。

以上是关于使用opencv线程捕获空白图像的主要内容,如果未能解决你的问题,请参考以下文章

使用线程刷新opencv问题

使用 OpenCV 捕获图像 - 选择超时错误

捕获 OpenCV 后图像显示为绿色

使用 OpenCV、Boost 线程和多个摄像头

捕获海康威视IPCamera图像,转成OpenCV能够处理的图像

捕获海康威视IPCamera图像,转成OpenCV能够处理的图像