Home>

I made an application to display the image of the pc camera in tkinter every 1 mm.
Therefore, I would like to add a function to execute another loop processing without interrupting the video display.
The point is, I want to use two root.after in parallel.

Since the video processing is looped every 1 millisecond in root.after in the main thread, I tried to write it using threading to execute another loop processing (xfunc) in another thread, but I executed xfunc During that time, the video processing of the main thread stops. I would like to know if the usage of threading is correct.

Corresponding source code
import tkinter as tk
import cv2
from PIL import Image
from PIL import ImageTk
import sys
import threading
import time
wi = 600
he = 400
root = tk.Tk ()
root.geometry ("600x400")
canvas = tk.Canvas (root, width = wi, height = he)
canvas.place (x = 0, y = 0)
def capStart (): #Camera capture process
    global c, w, h, img
    c = cv2.VideoCapture (0)
    c.set (cv2.CAP_PROP_FRAME_WIDTH, wi)
    c.set (cv2.CAP_PROP_FRAME_HEIGHT, he)
    w, h = c.get (cv2.CAP_PROP_FRAME_WIDTH), c.get (cv2.CAP_PROP_FRAME_HEIGHT)
def update (): #Display captured video on tkinter (I don't want to stop processing)
    global img
    ret, frame = c.read ()
    if ret:
        img = ImageTk.PhotoImage (Image.fromarray (cv2.cvtColor (frame, cv2.COLOR_BGR2RGB)))
        canvas.create_image (wi * 0.5, he * 0.5, image = img)
    else: else:
        print ("Fail")
    root.after (1, update)
def xfunc (): # Time-consuming looping. I want to perform this process in parallel without stopping updata ().
    for i in range (10000000):
        con = i
    print (con)
    root.after (1000, th1)
# Root.after (1000, xfunc) # This was also useless
def subthreadRun ():
    thread_1 = threading.Thread (target = xfunc)
    thread_1.setDaemon (True)
    thread_1.start ()
capStart ()
update ()
subthreadRun ()
root.mainloop ()
What I tried

I couldn't even use concurrent.futures.

Supplementary information (FW/tool version, etc.)

windows10
python3.7
tkinter8.7

  • Answer # 1

    Since the video processing is looped every 1 millisecond in root.after

    If you are updating the video, estimate the FPS and set an appropriate value.
    Since 1ms->1000fps, around 60fps (16ms) should usually be sufficient.

    While running xfunc, the video processing of the main thread stops.

    The cause is mainly

    Update interval is too fast → Loop every 1ms

    Because the main loop is doing time-consuming processing

    As a countermeasure, in addition to loosening the update interval
    Preprocessing is done on the sub thread side as much as possible. In particular

    Processing performed on the Tk side (main thread): ImageTk.PhotoImage

    Processing performed on the sub thread side: Image.fromarray (cv2.cvtColor (frame, cv2.COLOR_BGR2RGB)))

    There are several ways to notify tkinter (main thread) from a sub thread

    How to use the queue 1:
    Read from queue using root.after on the main thread side

    Event notification with event_generate from the subthread side. Read from queue.

    Notify with root.after_idle from the sub thread side ・ Execute in the main thread


    Time-consuming loop processing. I want to perform this process in parallel without stopping updata ().

    It is better to make a part of the process of update itself a subthread.

    I didn't know what th1 in root.after (1000, th1) was,
    Functions that are specified and executed in root.after are executed in the main thread.
    So time-consuming processes will block the mainloop,
    It connects without GUI response.


    * This is not the answer code.
    A sample logging and an indication of using root.after in a thread.

    #!/usr/bin/env python3.8
    import tkinter as tk
    from threading import Thread
    import logging
    # POINT: You can see that the second and subsequent xfunc are executed by MainThread.
    def xfunc (root):
        logging.info ("xfunc")
        root.after (1000, xfunc, root)
    def xfunc2 (root): # If running in another thread, you can use time.sleep to wait 1 second
        import time
        while True:
            logging.info ("xfunc2")
            time.sleep (1)
    def main ():
        root = tk.Tk ()
        button = tk.Button (root, text = "Quit", command = root.quit)
        button.pack ()
        thread = Thread (target = xfunc, args = (root,), daemon = True)
        thread.start ()
        root.mainloop ()
    if __name__ == "__main__":
        logging.basicConfig (
            level = logging.DEBUG,
            format = "[% (threadName) -10s]% (message) s")
        main ()

    Debugging points,

    Logging makes it easier to see in which thread the function is running.

    It stops because the tkinter event loop stops.
    All functions executed by root.after, functions registered by bind, etc.
    It runs in root.mainloop. Since the GUI is drawn and event processing is done in the mainloop,
    These functions must finish processing immediately and return control to the mainloop.
    Take a log and see if the main thread is doing any time-consuming work.


    Postscript
    Just recently, there was a code I wrote to check the operation with cv2.VideoCapture/tkinter, so
    I can't explain it, but I hope it will be helpful.
    https://repl.it/@MiKLTea/tkVideoCanvas#main.py

    I think it will be helpful when separating the update () function into the sub thread side (cv2) and the main thread (tkinter).

    If you get an error at the end of the program, depending on the situation,
    The order of releasing resources must be explicit.

  • Answer # 2

    I wonder if this is the only answer after organizing the situation a little.
    Apart from the performance issues of the update () function, only the main points.

    def xfunc (): # Time-consuming looping. I want to perform this process in parallel without stopping updata ().
        for i in range (10000000):
            con = i
        print (con)
        root.after (1000, th1)
    # Root.after (1000, xfunc) # This was also useless

    Problem: Functions executed in root.after are executed by tkinter in the main thread.
    It is only executed the first time in another thread.

    Solution: You can use time.sleep to wait 1 second for another thread

    def xfunc ():
        import time
        while True:
            for i in range (10000000):
                con = i
            print (con)
            time.sleep (1)