pythonmultithreadingopencvgtk3glib

Threading OpenCV video in GTK and keep events?


I'm trying to get my camera feed into a GTK window, and I'd like to keep the button-press-event and motion-notify-event events working.

I've found how to get the video by refreshing the image in a Gtk.image, in a GLib.idle_add, tried with a thread.Threading, tried with a GLib.timeout_add, but the loop is still blocking the events. I also tried with an OpenCV-headless in a virtual environment...

I read it: https://pygobject.readthedocs.io/en/latest/guide/threading.html

What didn't I understand? Is there a way to fix this ?

Here's my (simplified) code :

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()


Solution

  • I answer myself : I've found a working solution, coupling multiple usages :

    1. Threading a non-blocking GLib.idle_add
    2. Separating event calls with an Gtk.EventBox

    here is the working code (I think it could be useful for someone with the same troubles) :

    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()