python:tkinter显示来自网络摄像头的视频并进行QR扫描

时间:2016-05-07 13:36:37

标签: python opencv tkinter webcam python-multithreading

我一直在尝试创建一个tkinter顶级窗口,用于流式传输视频表单网络摄像头并进行QR扫描。我从SOanother code获得了此QR扫描代码,该代码只是更新来自网络摄像头的图像,而不是在tkinter标签上流式传输视频。

我尝试将这两者结合起来,以便使用标签更新来自网络摄像头的图像的顶层窗口和关闭顶层窗口的关闭按钮。当它流式传输图像时,它可以扫描QR码,如果扫描成功,网络摄像头和顶层窗口将关闭。

这是我试过的。

import cv2
import cv2.cv as cv
import numpy
import zbar
import time
import threading
import Tkinter
from PIL import Image, ImageTk

class BarCodeScanner(threading.Thread, Tkinter.Toplevel):
    def __init__(self):
        # i made this as a global variable so i can access this image
        # outside ie,. beyond the thread to update the image on to the  tkinter window
        global imgtk
        imgtk = None
        threading.Thread.__init__(self)
        self.WINDOW_NAME = 'Camera'
        self.CV_SYSTEM_CACHE_CNT = 5 # Cv has 5-frame cache
        self.LOOP_INTERVAL_TIME = 0.2 
        cv.NamedWindow(self.WINDOW_NAME, cv.CV_WINDOW_NORMAL)
        self.cam = cv2.VideoCapture(-1)
        self.confirm = 0

    def scan(self, aframe):
        imgray = cv2.cvtColor(aframe, cv2.COLOR_BGR2GRAY)
        # to show coloured image, as from the other code mentioned in the other code
        imgcol = cv2.cvtColor(aframe, cv2.COLOR_BGR2RGBA)
        imgcol_array = Image.fromarray(imgcol)
        imgtk = ImageTk.PhotoImage(image=imgcol_array)

        raw = str(imgray.data)
        scanner = zbar.ImageScanner()
        scanner.parse_config('enable')
        width = int(self.cam.get(cv.CV_CAP_PROP_FRAME_WIDTH))
        height = int(self.cam.get(cv.CV_CAP_PROP_FRAME_HEIGHT))
        imageZbar = zbar.Image(width, height,'Y800', raw)
        scanner.scan(imageZbar)

        for symbol in imageZbar:
            print 'decoded', symbol.type, 'symbol', '"%s"' % symbol.data
            return symbol.data

    def run(self):
        self.datalst = []
        print 'BarCodeScanner run', time.time()
        while True:                
            for i in range(0,self.CV_SYSTEM_CACHE_CNT):
                self.cam.read()
            img = self.cam.read()
            self.data = self.scan(img[1])

            cv2.imshow(self.WINDOW_NAME, img[1])
            cv.WaitKey(1)
            time.sleep(self.LOOP_INTERVAL_TIME)
            if self.data:
                self.datalst.append(self.data)
            # i have added this section so that it waits for scan
            # if a scan is made it and if gets same value after 2 scans
            # it has to stop webcam
            if len(self.datalst) == 2 and len(set(self.datalst)) <= 1:
                # I want to close the webcam before closing the toplevel window
                #self.cam.release()
                #cv2.destroyAllWindows()
                break
        self.cam.release()

def Video_Window():
    video_window = Tkinter.Toplevel()
    video_window.title('QR Scan !!')
    img_label = Tkinter.Label(video_window)
    img_label.pack(side=Tkinter.TOP)
    close_button = Tkinter.Button(video_window, text='close', command = video_window.destroy)
    close_button.pack(side=Tkinter.TOP)

    def update_frame():
        global imgtk
        img_label.configure(image=imgtk)
        img_label.after(10,update_frame)
    update_frame()

def main():
    root = Tkinter.Tk()
    button_scanQr = Tkinter.Button(root, text='QR Scan', command=start_scan)
    button_scanQr.pack()
    root.mainloop()

def start_scan():
    scanner = BarCodeScanner()
    scanner.start()

    Video_Window()
    #scanner.join()

main()

问题是,

  1. 我实际上想在Toplevel窗口上显示视频,而不是OpenCV窗口
  2. 同时进行QR扫描,如果读取成功,Toplevel窗口应关闭而不会突然关闭网络摄像头(因为,当我尝试使用self.cam.release()cv2.destroyAllWindows()我的网络摄像头灯或即使我强行终止程序编译)。
  3. 现在我得到的是一个由OpenCV创建的单独窗口,用于在内部传输视频。但我不想要那个窗口,而是希望视频显示在tkinter的顶层窗口上。当有一个成功阅读时,网络摄像头卡在它读取的最终图像上。

    我试图在run类的BarcodeScanner方法中删除负责OpenCV窗口的行

    cv2.imshow(self.WINDOW_NAME, img[1])
    

    它仍然出现了一个没有输出的不同窗口,如果我试图关闭该窗口,它会创建另一个类似的并递归。

    更新

    正如我注意到我在没有理解cv2中的某些行的情况下犯了一些愚蠢的错误,我通过将顶层窗口代码添加到类的run方法中对代码进行了一些更改(im not not确定这是否正确。)

    import cv2
    import cv2.cv as cv
    import numpy
    import zbar
    import time
    import threading
    import Tkinter
    from multiprocessing import Process, Queue
    from Queue import Empty
    from PIL import Image, ImageTk
    
    class BarCodeScanner(threading.Thread, Tkinter.Toplevel):
        def __init__(self):
            threading.Thread.__init__(self)
            #self.WINDOW_NAME = 'Camera'
            self.CV_SYSTEM_CACHE_CNT = 5 # Cv has 5-frame cache
            self.LOOP_INTERVAL_TIME = 0.2
            #cv.NamedWindow(self.WINDOW_NAME, cv.CV_WINDOW_NORMAL)
            self.cam = cv2.VideoCapture(-1)
            # check if webcam device is free
            self.proceede = self.cam.isOpened()
            if not self.proceede:
                return
            self.confirm = 0
    
        def scan(self, aframe):
            imgray = cv2.cvtColor(aframe, cv2.COLOR_BGR2GRAY)
            raw = str(imgray.data)
            scanner = zbar.ImageScanner()
            scanner.parse_config('enable')          
            width = int(self.cam.get(cv.CV_CAP_PROP_FRAME_WIDTH))
            height = int(self.cam.get(cv.CV_CAP_PROP_FRAME_HEIGHT))
            imageZbar = zbar.Image(width, height,'Y800', raw)
            scanner.scan(imageZbar)
            for symbol in imageZbar:
                print 'decoded', symbol.type, 'symbol', '"%s"' % symbol.data
                return symbol.data
    
        def run(self):
            if not self.proceede:
                return
            def show_frame():
                _, img = self.cam.read()
                img = cv2.flip(img,1)
                cv2image = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA)
                img = Image.fromarray(cv2image)
                imgtk = ImageTk.PhotoImage(image=img)
                img_label.imgtk = imgtk
                img_label.configure(image=imgtk)
                video_window.after(250, show_frame)
    
            def destroy_video_window():
                self.cam.release()
                video_window.destroy()
    
            # Toplevel GUI
            video_window = Tkinter.Toplevel()
            video_window.title('QR Scan !!')
            img_label = Tkinter.Label(video_window)
            img_label.pack(side=Tkinter.TOP)
            close_button = Tkinter.Button(video_window, text='close', command = destroy_video_window)
            close_button.pack(side=Tkinter.RIGHT)
            show_frame()
    
            self.datalst = []
            print 'BarCodeScanner run', time.time()
            while True:
                for i in range(0,self.CV_SYSTEM_CACHE_CNT):
                    self.cam.read()
                img = self.cam.read()
                self.data = self.scan(img[1])
                time.sleep(self.LOOP_INTERVAL_TIME)
                if self.data:
                    self.datalst.append(self.data)
                if len(self.datalst) == 2 and len(set(self.datalst)) <= 1:
                    video_window.destroy()
                    break
            self.cam.release()
    
    def main():
        root = Tkinter.Tk()
        button_scanQr = Tkinter.Button(root, text='QR Scan', command=scaner)
        button_scanQr.pack()
        root.mainloop()
    
    def scaner():
        scanner = BarCodeScanner()
        scanner.start()
    
    main()
    

    现在,我可以在Toplevel窗口上获取图像,但我不知道如何关闭网络摄像头。

    条件1: 当我显示要扫描的二维码时,它会成功读取并且网络摄像头退出而没有任何错误。

    当我点击顶层窗口上的关闭按钮时,

    条件2: (例如,如果用户不想进行任何扫描,只想关闭网络摄像头)我得到错误说

    libv4l2: error dequeuing buf: Invalid argument
    VIDIOC_DQBUF: Invalid argument
    select: Bad file descriptor
    VIDIOC_DQBUF: Bad file descriptor
    select: Bad file descriptor
    VIDIOC_DQBUF: Bad file descriptor
    Segmentation fault (core dumped)
    

    我正在为LinuxMacWindows机器编写此应用程序。如何安全地关闭或终止网络摄像头。

1 个答案:

答案 0 :(得分:1)

你的程序有两个线程,主线程和工作线程从相机读取帧。单击关闭按钮时,它将在主线程中发生。在self.cam之后,对象cv2.VideoCapture可能处于不可用状态,并且当工作线程调用threading.Event的方法时,可能会有一些问题。也许event.is_set()的实现是错误的,当发生这种情况时应该抛出一些异常。

从主线程以外的其他线程访问tkinter小部件也可能导致问题。

为了清除程序终止,创建def destroy_video_window(): self.stop_event.set() video_window.destroy() 的实例,然后在工作线程中的某个点检查while True: if self.stop_event.is_set(): break for i in range(0, self.CV_SYSTEM_CACHE_CNT): self.cam.read() 可能会有效。例如

event_generate()

然后在工作线程中

<<ScannerQuit>>

有几件事可以通过其他方式完成,以下是代码的修改版本。它避免了从主线程以外的其他线程调用tkinter方法,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() 是工作线程调用的唯一tkinter方法。通过发出放置在tkinter事件队列中的虚拟事件(例如var oTr = $(this).closest('tr'); )来避免显式轮询。

var row = table.row(oTr);