在 GTK 中线程 OpenCV 视频并保留事件?

Threading OpenCV video in GTK and keep events?

提问人:s4mdf0o1 提问时间:10/5/2023 最后编辑:s4mdf0o1 更新时间:10/5/2023 访问量:53

问:

我正在尝试将我的相机馈送到 GTK 窗口中, 我想保持按钮按下事件和运动通知事件的工作。

我已经找到了如何通过刷新Gtk.image中的图像来获取视频, 在GLib.idle_add中,尝试使用线程。线程,尝试使用GLib.timeout_add, 但循环仍然阻塞了事件。 我还尝试在虚拟环境中使用 OpenCV-headless......

我读过:https://pygobject.readthedocs.io/en/latest/guide/threading.html

我有什么不明白的?有没有办法解决这个问题?

这是我的(简化)代码:

import cv2

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gtk, GdkPixbuf
#import threading

vdo_url="http://my_cam/vdo.mjpg" # simplified, without security

class Cam_GTK(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Cam GTK")

        self.capture = cv2.VideoCapture(vdo_url)

        self.video = Gtk.Image.new()
        self.video.connect("button-press-event", self.on_video_clicked )
        self.video.connect("motion-notify-event", self.on_video_hover )
        # Also tried with event=Gtk.EventBox.new(), event.add(self.video), then "connect"...
    
        page_box = Gtk.Box( orientation=Gtk.Orientation.VERTICAL, spacing=1 )
        page_box.pack_start( self.video, True, True, 0 )
        self.add(page_box)
    
        GLib.idle_add(self.show_frame)
        #GLib.timeout_add(100, self.show_frame)
        #tried to Thread(target=self.show_frame) w/out loop
    
        self.connect("destroy", Gtk.main_quit)
        self.show_all()
    
    def on_video_hover( self, event, result ):
        print("video hover")
    def on_video_clicked( self, event, button ):
        print("video clicked")
    
    def show_frame(self):
        ret, frame = self.capture.read()
        #tried with a while
        if ret:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            pb = GdkPixbuf.Pixbuf.new_from_data(frame.tobytes(),
                                        GdkPixbuf.Colorspace.RGB,
                                        False,
                                        8,
                                        frame.shape[1],
                                        frame.shape[0],
                                        frame.shape[2]*frame.shape[1])
            self.video.set_from_pixbuf( pb.copy() )
    
        return True #tried changed to False to stop loop

cam = Cam_GTK()
Gtk.main()

python 多线程 opencv gtk3 glib

评论


答:

0赞 Christoph Rackwitz 10/5/2023 #1

capture.read()阻止,直到有新帧可用。这至少会将您的事件处理循环限制到视频源可以提供的任何帧速率。你不想要那样。

您需要生成一个循环线程,然后将新读取的帧发送到您的 GUI。这完全将 GUI 事件处理与读取视频分离。capture.read()

您还必须使用某种形式的消息传递。任何 GUI 函数的直接调用都可能被阻塞,因为它们可能会隐式发布某些事件并等待它被处理......如果您当前正在执行的函数是由同一个事件循环执行的,则不会发生这种情况,该事件循环仍在忙于执行该函数,因此它无法执行其他操作,但您的 API 调用需要它才能完成......所以这就是阻止的方式。Qt(信号和插槽)是这样,GTK也很可能是这样。

看起来很有前途的随机搜索结果:GTK 中的信号和插槽

1赞 s4mdf0o1 10/5/2023 #2

我回答自己:我找到了一个可行的解决方案,耦合了多种用法:

  1. 线程非阻塞GLib.idle_add
  2. 用Gtk.EventBox分离事件调用

这是工作代码(我认为它可能对有同样麻烦的人有用):

import cv2
from time import sleep
import gi

gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, GdkPixbuf, Gdk, GObject

from threading import Thread

vdo_url="http://my_cam/vdo.mjpg" # simplified, without security

class Cam_GTK(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Cam GTK")

        self.capture = cv2.VideoCapture(vdo_url)

        self.video = Gtk.Image.new()

        refresh_vdo = Thread(target = self.refresh_video)
        refresh_vdo.daemon= True
        refresh_vdo.start()

        self.event_vdo = Gtk.EventBox.new() #This gives the new events processing for signals
        self.event_vdo.add(self.video)
        self.event_vdo.set_above_child(True)

        self.event_vdo.connect("button-press-event", self.on_video_clicked )
        self.event_vdo.connect("motion-notify-event", self.on_video_hover )

        page_box = Gtk.Box( orientation=Gtk.Orientation.VERTICAL, spacing=1 )
        page_box.pack_start( self.event_vdo, True, True, 0 )
        self.add(page_box)

        self.connect("destroy", Gtk.main_quit)
        self.show_all()

    def on_video_clicked( self, event, button ):
        print("clicked")

    def on_video_hover(self, widget, event):
        print("hover")

    def refresh_video( self ):
        while not self.done:
            GLib.idle_add(self.show_frame)
            sleep(0.1) #Wait for Gtk.Image refresh (I guess)

    def show_frame(self):
        ret, frame = self.capture.read()
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        pb = GdkPixbuf.Pixbuf.new_from_data(frame.tobytes(),
                                        GdkPixbuf.Colorspace.RGB,
                                        False,
                                        8,
                                        frame.shape[1],
                                        frame.shape[0],
                                        frame.shape[2]*frame.shape[1])
        self.video.set_from_pixbuf(pb.copy())
        return False #Important to be False not to block


camera = Cam_GTK()
Gtk.main()