首页 > 解决方案 > tkinter + OpenCV WebCam 非常慢的视频流

问题描述

我第一次使用 tkinter 和 opencv 并成功为我的项目构建了 GUI,但是,我无法弄清楚为什么我的视频流更新如此缓慢。我抓帧的速度非常快,但屏幕上的更新似乎呈指数级变慢。当我第一次启动程序时,我看到大约 30 秒的延迟,但它最终会减慢到停止。我正在连接三个摄像头,但一次只显示一个。摄像机全部显示并且选择按钮起作用。我唯一的问题是显示刷新率。

这是在树莓派 pi4 上的 Python3.7 中运行的。我可以通过网络浏览器连接到相机,它似乎没有延迟。

我一直在寻找答案,但似乎找不到任何有帮助的东西。任何人都可以提供一些帮助吗?

这是我的程序(我删除了不相关的代码):

#!/usr/bin/env python3

import time
from tkinter import *
import cv2
from PIL import Image, ImageTk


#GUI
class robotGUI:

    def __init__(self):

        self.selectedCam = "front"
        self.window = Tk()

        #Setup the window to fit the Raspberry Pi Touch Display = 800x400 and align top left
        self.window.geometry("800x480+0+0")
        self.window.overrideredirect(True)
        self.window.fullScreenState = False

        #Create Frame for Video Window
        self.videoFrame = Frame(self.window, relief=SUNKEN, bd=2)
        self.videoFrame.place(x=0, y=0, height=457, width=650)

        #Create the Video Window
        self.video = Label(self.videoFrame, bd=0, relief=FLAT, width=644, height=451)
        self.video.place(x=0, y=0)
        self.vid = VideoCapture()
        self.camUpdateFreq = 250
        self.updateCams()

        #Create the Button Frame
        self.buttonFrame = Frame(self.window, relief=FLAT)
        self.buttonFrame.place(x=651, y=0, height=457, width=149)

        #Create Buttons
        #Select Front Camera Button
        self.frontCamButton = Button(self.buttonFrame, text="Front Camera", command=lambda: self.selectCam("front"))
        self.frontCamButton.place(x=24, y=50, height=30, width=100)

        #Select Boom Camera Button
        self.boomCamButton = Button(self.buttonFrame, text="Boom Camera", command=lambda: self.selectCam("boom"))
        self.boomCamButton.place(x=24, y=130, height=30, width=100)

        #Select Rear Camera Button
        self.rearCamButton = Button(self.buttonFrame, text="Rear Camera", command=lambda: self.selectCam("rear"))
        self.rearCamButton.place(x=24, y=210, height=30, width=100)

        #Close Button
        self.exitButton = Button(self.buttonFrame, text="Close", command=self.window.destroy)
        self.exitButton.place(x=24, y=400, height=30, width=100)

        #Start the main loop for the gui
        self.window.mainloop()

    def selectCam(self, cam):

        if (cam.lower() == "front"):
            self.selectedCam = "front"
            self.statusBarLeft['text'] = "Front Camera Selected"
        elif (cam.lower() == "boom"):
            self.selectedCam = "boom"
            self.statusBarLeft['text'] = "Boom Camera Selected"
        elif (cam.lower() == "rear"):
            self.selectedCam = "rear"
            self.statusBarLeft['text'] = "Rear Camera Selected"

    def updateCams(self):

        #Get a frame from the selected camera
        ret, frame = self.vid.get_frame(self.selectedCam)

        if ret:
            imageCV2 = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = Image.fromarray(imageCV2)
            imgPhoto = ImageTk.PhotoImage(image=img)
            self.video.imgPhoto = imgPhoto
            self.video.configure(image=imgPhoto)

        self.window.after(self.camUpdateFreq, self.updateCams)
        


#Video Camera Class
class VideoCapture:

    def __init__(self):

        #Define Locals
        FrontCameraAddress = "rtsp://admin:password@192.168.5.20:8554/12"
        BoomCameraAddress = "rtsp://admin:password@192.168.5.21:8554/12"
        RearCameraAddress = "rtsp://admin:password@192.168.5.22:8554/12"

        #Open Front Video Camera Source
        self.vidFront = cv2.VideoCapture(FrontCameraAddress)
        self.vidBoom = cv2.VideoCapture(BoomCameraAddress)
        self.vidRear = cv2.VideoCapture(RearCameraAddress)

        #Verify that the Camera Streams Opened
        if not self.vidFront.isOpened():
            raise ValueError("Unable to open video source to Front Camera")

        if not self.vidBoom.isOpened():
            raise ValueError("Unable to open video source to Boom Camera")

        if not self.vidRear.isOpened():
            raise ValueError("Unable to open video source to Rear Camera")

    #Get One Frame from the Selected Camera
    def get_frame(self, camera="front"):

        #Attempt to Get Front Camera Frame
        if (camera.lower() == "front"):

            #If Stream Still Open Return a Frame
            if self.vidFront.isOpened():
                ret, frame = self.vidFront.read()
                if ret:
                    #Return a boolean success flag and the current frame converted to BGR
                    return (ret, frame)                
                else:
                    return (ret, None)
            else:
                return (ret, None)

        #Attempt to Get Boom Camera Frame
        elif (camera.lower() == "boom"):

            #If Stream Still Open Return a Frame
            if self.vidBoom.isOpened():
                ret, frame = self.vidBoom.read()
                if ret:
                    #Return a boolean success flag and the current frame converted to BGR
                    return (ret, frame)                
                else:
                    return (ret, None)
            else:
                return (ret, None)

        #Attempt to Get Rear Camera Frame
        elif (camera.lower() == "rear"):

            #If Stream Still Open Return a Frame
            if self.vidRear.isOpened():
                ret, frame = self.vidRear.read()
                if ret:
                    #Return a boolean success flag and the current frame converted to BGR
                    return (ret, frame)                
                else:
                    return (ret, None)
            else:
                return (ret, None)

        else:
            return (False, None)

    #Release the video sources when the object is destroyed
    def __del__(self):
        if self.vidFront.isOpened():
            self.vidFront.release()
        if self.vidBoom.isOpened():
            self.vidBoom.release()
        if self.vidRear.isOpened():
            self.vidRear.release()
            

#Main Routine  - Only run if called from main program instance
if __name__ == '__main__':

    try:

        #Create GUI Object
        app = robotGUI()            

    except Exception as e:
        print("Exception: " + str(e))

    finally:
        print("Cleaning Up")

注意:在这个程序副本中,我每 250 毫秒更新一次,但我尝试将较小的数字降至 3 左右,但帧似乎仍然落后。有一个更好的方法吗?

注意 2:在今天更多地工作之后,我意识到 openCV 肯定会在为每个摄像头调用 cv2.VideoCapture() 函数时开始为每个摄像头缓冲帧。read() 函数似乎确实从缓冲区中拉出下一帧,这解释了为什么更新需要这么长时间以及为什么我在屏幕上看到的图像永远赶不上现实。我将测试代码更改为一次仅连接到一台相机,并在我不主动查看相机的任何时候使用 cv2.release() 函数。这使事情有了很大的改善。我还将更新函数设置为每 1 毫秒运行一次,并且我使用 grab() 函数在每个周期抓取一帧,但我只在每 10 个周期处理和显示一次,这也有所改进。如果有人有任何建议,我仍然希望消除一些滞后。

在 Web 浏览器中查看时,我的 RTSP 流显示零明显延迟。有谁知道我如何在 tkinter 中获得相同的效果?我没有嫁给openCV。

标签: pythonopencvtkinterraspberry-pi

解决方案


推荐阅读