问题2及解决

  • 存在问题02-OpenCV获取的图像有滞后

    因为使用了YOLO去识别图像,会导致两次读取摄像头图像的时间间隔比较大,在飞腾派上大约是900ms,而因为OpenCV的特性是使用了缓冲区,提前读入摄像头的图像并存入缓冲区,在两次读取时间间隔过长时,缓冲区中会存入多张图像,这会导致后面读取的图像还是之前位置拍摄的,并不是实时的,因此会出现滞后。

  • 改进方案02-使用多线程单独管理摄像头

    网上查阅的结果应该是也可以改变缓冲区大小,或者在读一次图像之前先多次读取图片直到将缓冲区读取完。但是这两种方式仍然是在主线程中运行,考虑到读取摄像头如果在主线程的话也会延长两次识别的时间间隔,因此可以使用多线程优化摄像头读取。

    单独开一个线程,以一个较短的时间间隔去读取摄像头,并将最新画面存入一个缓冲区,主线程读取识别图像只从这个缓冲区读取,并不直接调用摄像头。

    import threading
    import time
    
    class CapThread(threading.Thread):   #继承父类threading.Thread
        def __init__(self):
            # 父类初始化
            threading.Thread.__init__(self)
    
            # 用于储存最新图像的缓冲区
            self.frame=None
            
            # 用于结束运行的标志位
            self.runFlag=True
    
        # 对外获取最新图像的调用函数
        def getFrame(self):
            return self.frame
        
        # 对外用于停止运行的调用函数
        def stop_run(self):
            self.runFlag=False
        
        # 父类的方法,线程要执行的代码放在这个函数中
        def run(self):
            # 可以指定线程固定使用的核心
            # 但是因为是线程,所以使用的核心是和主进程一样的
            # 因此这里指定使用的核心用处不大
            # p=psutil.Process(os.getpid())
            # p.cpu_affinity([0])
            cap = cv2.VideoCapture(0)
            # 指定摄像头获取到的图像的长宽大小
            cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) # 长
            cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # 宽
            # 在飞腾派上直接读取摄像头时候会因为速度过慢报一个错误
            # 网上查阅后需要将读取的格式转换一下,就是以下的代码
            cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M','J','P','G'))
            if not cap.isOpened():
                print("无法打开摄像头")
                exit()
            while self.runFlag:
                # 读取摄像头画面,ret为bool类型,表示是否成功,frame为Numpy
                ret, frame = cap.read()
    
                # 翻转图像,跟摄像头实际安装有关,flip中第二个参数表示翻转方式
                # 1:水平翻转,即可理解为数学上关于x轴对称
                # 0:垂直翻转,即可理解为数学上关于y轴对称
                # -1:水平垂直翻转,即可理解为数学上关于原点对称
                self.frame = cv2.flip(frame, -1)
    
                # 这里判断非None不能直接用=,因为Numpy的性质会直接不进行判断,并报错
                # 因此要判断非None需要使用is
                # 并且要通过cv2.imshow现实图像只能在这里使用
                # 因为根据cv2的要求显示画面要和获取图像在同一个线程中,因此不能在主线程显示
                if self.frame is not None:
                    cv2.imshow('frame',self.frame)
                
                # 如果摄像头读取图像失败会停止运行
                if not ret:
                    self.runFlag = False
                
                # 休眠一会再进行下次读取,time.sleep的单位是秒,可以用小数来精确到毫秒
                time.sleep(0.050)
    
                # 与cv2.imshow同理,这个函数也只能和读取摄像头在同一个进程中使用
                if cv2.waitKey(1) & 0xFF == ord("q"):
                    self.runFlag=False
                    break
    

    按照如上的代码定义一个新的线程,并在主线程中开启这个线程,之后就可以通过getFrame()获取到最新的图像了。

    # 生成一个线程对象
    cap = CapThread()
    # 开启线程
    cap.start()
    # 摄像头的开启可能需要一点时间
    # 为了防止后面开始识别时候缓冲区是空
    # 这里先阻塞一会,直到缓冲区不为None再继续执行
    while cap.getFrame() is None:
        time.sleep(0.05)
    # 之后就可以通过cap.getFrame()正常获取图像了
    

    经过测试,上面的代码可以获取到实时的图像,并且在识别的时间上会稍微快一点。