python: tkinter to display video from webcam and do a QR scan

这一生的挚爱 提交于 2019-12-05 02:04:47

Your program has two threads, the main thread and the worker thread that reads frames from the camera. When the close button is clicked, it happens in the main thread. After self.cam.release() the object self.cam is probably in an unusable state, and when a method of self.cam is called by the worker thread, there may be some trouble. Maybe the implementation of cv2.VideoCapture is faulty and it should throw some exception when that happens.

Accessing tkinter widgets from other thread than the main thread may also cause problems.

For clean program termination, creating an instance of threading.Event and then checking for event.is_set() at some point in the work thread could work. For example

def destroy_video_window():
    self.stop_event.set()
    video_window.destroy()

and then in the worker thread

while True:
    if self.stop_event.is_set(): 
        break
    for i in range(0, self.CV_SYSTEM_CACHE_CNT):
        self.cam.read()

There are several things that could be done in other way, the following is a modified version of the code. It avoids calling tkinter methods from other thread than the main thread, event_generate() being the only tkinter method called by the worker thread. Explicit polling is avoided by emitting virtual events, for example <<ScannerQuit>>, that are placed in the tkinter event queue.

import cv2
import cv2.cv as cv
import zbar
import time
import threading
import Tkinter as tk

from PIL import Image, ImageTk

class Scanner(object):
    def __init__(self, handler, *args, **kw):
        self.thread = threading.Thread(target=self.run)
        self.handler = handler

        self.CV_SYSTEM_CACHE_CNT = 5 # Cv has 5-frame cache
        self.LOOP_INTERVAL_TIME = 0.2
        self.cam = cv2.VideoCapture(-1)

        self.scanner = zbar.ImageScanner()
        self.scanner.parse_config('enable')
        self.cam_width = int(self.cam.get(cv.CV_CAP_PROP_FRAME_WIDTH))
        self.cam_height = int(self.cam.get(cv.CV_CAP_PROP_FRAME_HEIGHT))

        self.last_symbol = None

    def start(self):
        self.thread.start()

    def scan(self, aframe):
        imgray = cv2.cvtColor(aframe, cv2.COLOR_BGR2GRAY)
        raw = str(imgray.data)
        image_zbar = zbar.Image(self.cam_width, self.cam_height, 'Y800', raw)
        self.scanner.scan(image_zbar)

        for symbol in image_zbar:
            return symbol.data

    def run(self):
        print 'starting scanner'

        while True:
            if self.handler.need_stop():
                break

            # explanation for this in
            # http://stackoverflow.com/a/35283646/5781248
            for i in range(0, self.CV_SYSTEM_CACHE_CNT):
                self.cam.read()

            img = self.cam.read()

            self.handler.send_frame(img)

            self.data = self.scan(img[1])

            if self.handler.need_stop():
                break

            if self.data is not None and (self.last_symbol is None
                                          or self.last_symbol <> self.data):
                # print 'decoded', symbol.type, 'symbol', '"%s"' % symbol.data
                self.handler.send_symbol(self.data)
                self.last_symbol = self.data

            time.sleep(self.LOOP_INTERVAL_TIME)

        self.cam.release()

class ScanWindow(tk.Toplevel):
    def __init__(self, parent, gui, *args, **kw):
        tk.Toplevel.__init__(self, master=parent, *args, **kw)

        self.parent = parent
        self.gui = gui
        self.scanner = None

        self.lock = threading.Lock()
        self.stop_event = threading.Event()

        self.img_label = tk.Label(self)
        self.img_label.pack(side=tk.TOP)

        self.close_button = tk.Button(self, text='close', command=self._stop)
        self.close_button.pack()

        self.bind('<Escape>', self._stop)

        parent.bind('<<ScannerFrame>>', self.on_frame)
        parent.bind('<<ScannerEnd>>', self.quit)
        parent.bind('<<ScannerSymbol>>', self.on_symbol)

    def start(self):
        self.frames = []
        self.symbols = []

        class Handler(object):
            def need_stop(self_):
                return self.stop_event.is_set()

            def send_frame(self_, frame):
                self.lock.acquire(True)
                self.frames.append(frame)
                self.lock.release()

                self.parent.event_generate('<<ScannerFrame>>', when='tail')

            def send_symbol(self_, data):
                self.lock.acquire(True)
                self.symbols.append(data)
                self.lock.release()

                self.parent.event_generate('<<ScannerSymbol>>', when='tail')

        self.stop_event.clear()
        self.scanner = Scanner(Handler())
        self.scanner.start()
        self.deiconify()

    def _stop(self, *args):
        self.gui.stop()

    def stop(self):
        if self.scanner is None:
            return

        self.stop_event.set()

        self.frames = []
        self.symbols = []
        self.scanner = None
        self.iconify()

    def quit(self, *args):
        self.parent.event_generate('<<ScannerQuit>>', when='tail')

    def on_symbol(self, *args):
        self.lock.acquire(True)
        symbol_data = self.symbols.pop(0)
        self.lock.release()

        print 'symbol', '"%s"' % symbol_data
        self.after(500, self.quit)

    def on_frame(self, *args):
        self.lock.acquire(True)
        frame = self.frames.pop(0)
        self.lock.release()

        _, img = frame
        img = cv2.flip(img, 1)
        cv2image = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA)
        img = Image.fromarray(cv2image)
        imgtk = ImageTk.PhotoImage(image=img)
        self.img_label.imgtk = imgtk
        self.img_label.configure(image=imgtk)

class GUI(object):
    def __init__(self, root):
        self.root = root

        self.scan_window = ScanWindow(self.root, self)
        self.scan_window.iconify()

        self.root.title('QR Scan !!')

        self.lframe = tk.Frame(self.root)
        self.lframe.pack(side=tk.TOP)

        self.start_button = tk.Button(self.lframe, text='start', command=self.start)
        self.start_button.pack(side=tk.LEFT)

        self.stop_button = tk.Button(self.lframe, text='stop', command=self.stop)
        self.stop_button.configure(state='disabled')
        self.stop_button.pack(side=tk.LEFT)

        self.close_button = tk.Button(self.root, text='close', command=self.quit)
        self.close_button.pack(side=tk.TOP)

        self.root.bind('<<ScannerQuit>>', self.stop)
        self.root.bind('<Control-s>', self.start)
        self.root.bind('<Control-q>', self.quit)
        self.root.protocol('WM_DELETE_WINDOW', self.quit)

    def start(self, *args):
        self.start_button.configure(state='disabled')
        self.scan_window.start()
        self.stop_button.configure(state='active')

    def stop(self, *args):
        self.scan_window.stop()
        self.start_button.configure(state='active')
        self.stop_button.configure(state='disabled')

    def quit(self, *args):
        self.scan_window.stop()
        self.root.destroy()

def main():
    root = tk.Tk()
    gui = GUI(root)
    root.mainloop()

main()
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!