简体   繁体   中英

python opencv and tkinter capture webcam problem

hello everyone i have a code for read video from webcam and show it into the tkinter window with timer threading. when user click show button, app make a thread and run it every x second to show frame by frame. here's the problem : every several frame app shows first frame that captured from video source. that's weird i know but i cant find out why!!! here's my code:

import tkinter as tk
from tkinter import ttk 
from tkinter import *
import threading
import cv2
import PIL.Image, PIL.ImageTk
from PIL import Image
from PIL import ImageTk
import time

class App(threading.Thread):
        def __init__(self, root, window_title, video_source=0):
            self.root = root
            self.root.title(window_title)
            self.video_source = video_source
            self.show_switch=False
            self.showButton = Button(self.root, text="PlayStream",command=self.showStram,width=15, padx="2", pady="3",compound=LEFT)
            self.showButton.pack()
            # Create a canvas that can fit the above video source size
            self.canvas = tk.Canvas(root, width = 530, height = 397, cursor="crosshair")
            self.canvas.pack()
            self.root.mainloop()

        def updateShow(self):

            # Get a frame from the video source
            cap=cv2.VideoCapture(0)
            while True:    
                if(cap.isOpened()):
                    #read the frame from cap
                    ret, frame = cap.read()

                    if ret:

                        #show frame in main window
                        self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(frame))
                        self.canvas.create_image(0, 0, image = self.photo, anchor = tk.NW)


                    else:
                        break
                        raise ValueError("Unable to open video source", video_source)


                    if self.show_switch==False:
                        cap.release()
                        return False
                time.sleep(0.0416666666666667)

            #release the cap
            cap.release()

        def showStram(self):

            if self.show_switch:
                self.showButton["text"]="StartStream"
                # self.showButton.configure(image=self.playIcon)
                self.show_switch=False

            else:
                self.showButton["text"]="StopStream"
                self.show_switch=True
                # self.showButton.configure(image=self.stopIcon)
                self.showTimer=threading.Thread(target=self.updateShow,args=())
                #self.showTimer.daemon=True
                self.showTimer.start()

App(tk.Tk(), "Main")

This is a complicated question, because your example code is extensive and combines multiple things that can go wrong. I cannot test your code in its current form.

First off, you are accessing your Tk instance from a thread that is not the MainThread. This can cause all sorts of issues. There are also bugs present in the implementation of supposed thread-safety in Tkinter, and the solution has not yet been merged . Checkout mtTkinter if you really need to use multiple threads with Tkinter, and even then, it is better not to, especially not if you are building a new application and have the option to use Queues or some other system instead.

Second, you create a (subclass) instance of threading.Thread , but you never call threading.Thread.__init__ in App.__init__ (which is an absolute requirement if you want to use it as a Thread!). Then you create a new Thread in def showStream(self) , while you actually already had a thread. Now, this does not break your code, but you do not need to subclass threading.Thread if you do not intend to use your class as a Thread . As you create a Thread in your class, there is no need to make a Thread out of your class.

Then, moving on through your code, you do start the Thread, so updateShow gets run. However, in your while loop, there is a problem:

while True:
if (cap.isOpened()):
    ret, frame = cap.read()
    if ret:
        ...
    else:
        break
        # Error will never be raised because of break
        # break exits the loop immediately, so the next line is never evaluated 
        raise ValueError()
cap.release()
# No notification of the loop having ended

There may be two things going wrong here. The first is that maybe your loop just ends because of the break in the else -clause. Break immediately exits the loop code. Anything following it will never be executed. If it does exit the loop because it failed to get the next frame, you cannot know for sure as you do not check whether the thread is still alive ( threading.Thread.is_alive ) or have any print statements to indicate that the loop has ended.

Secondly, your program might actually be hard-crashing on you accessing Tkinter from a second thread. Doing this causes undefined behaviour, including weird errors and Python-interpreter lockups because the Tk interpreter and Python interpreter are fighting for flow control in a deadlock (simply put).

Last but not least, there is a problem with the way you create your images:

self.canvas.create_image(0, 0, image = self.photo, anchor = tk.NW)

In this line, you create a new image on the Canvas. However, if an image already exists on the Canvas in the location where you are creating a new image, it will appear under the image already shown. If you do not delete your old image (which is in any case advisable to prevent a huge memory leak ), it will remain visible on the Canvas.

def __init__(...):
    ...
    self.im = None

def updateShow(self):
    ...
    while True:
        if (cap.isOpened()):
            ...
            if ret:
                if self.im is not None:
                    self.canvas.delete(self.im)
                ...
                self.im: str = self.canvas.create_image(0, 0, image=self.photo, anchor=tk.NW)

Summarizing: With your current posted code, there are multiple things that can be going wrong. Without additional information, it is impossible to know. However, if you fix accessing Tkinter from a different Thread, adjust your while loop to not break but raise the error, you adjust your Canvas image creation code and your video source actually does work properly, it should work out.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM