python: tkinter para mostrar el video de la cámara web y hacer un escaneo QR

He estado tratando de crear una ventana de nivel superior de tkinter que transmita video desde la cámara web y realice un escaneo QR. Obtuve este código de escaneo QR de SO y otro código que solo actualiza las imágenes de la cámara web en lugar de transmitir el video en una etiqueta tkinter.

y traté de combinar estos dos elementos para que una ventana de nivel superior con una etiqueta que actualice la imagen de la cámara web y un botón de cierre para cerrar la ventana de nivel superior. Y mientras transmite las imágenes, puede escanear el código QR y, si el escaneo es exitoso, la cámara web y la ventana de nivel superior se cierran.

Esto es lo que intenté.

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

El problema es,

  1. En realidad quería mostrar el video en la ventana de Toplevel, no en la ventana de OpenCV
  2. al mismo tiempo, realice una Exploración QR, si una lectura es exitosa, la ventana de self.cam.release() debería cerrarse sin cerrar bruscamente la cámara web (porque, cuando bash usar self.cam.release() o cv2.destroyAllWindows() mis cámaras web se encienden o se encienden incluso si termino a la fuerza la comstackción de progtwigs).

Ahora, lo que obtengo es una ventana separada creada por OpenCV que transmite el video al interior. Pero no quiero esa ventana, en cambio quiero que el video se muestre en la ventana de nivel superior del tkinter. también cuando hay una lectura exitosa, la cámara web se atasca en la imagen final que lee.

Intenté eliminar la línea que era responsable de la ventana de OpenCV, dentro del método de BarcodeScanner clase BarcodeScanner

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

Todavía apareció con una ventana diferente sin salida, y si bash cerrar esa ventana, se creó otra similar y recursiva.

ACTUALIZACIÓN :

Como me di cuenta de que cv2 algunos errores tontos sin entender algunas líneas en cv2 , cv2 algunos cambios en el código agregando el código de la ventana de nivel superior al método de run de la clase (no estoy seguro de que esto sea una forma correcta).

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

ahora, puedo obtener la imagen en la ventana de Toplevel, pero no sé cómo cerrar la cámara web.

condición 1: cuando muestro un código QR para escanear, lo lee correctamente y la cámara web se cierra sin ningún error.

condición 2: cuando hago clic en el botón de cerrar en la ventana de nivel superior (por ejemplo, si el usuario no quiere hacer ningún escaneo y solo quiero cerrar la cámara web), me dice un error

 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) 

Estoy escribiendo esta aplicación para Linux , Mac y Windows . ¿Cómo puedo cerrar o terminar la cámara web de forma segura?

Su progtwig tiene dos subprocesos, el subproceso principal y el subproceso de trabajo que lee marcos de la cámara. Cuando se hace clic en el botón de cierre, sucede en el hilo principal. Después de self.cam.release() el objeto self.cam está probablemente en un estado inutilizable, y cuando el subproceso de trabajo llama a un método de self.cam , puede haber algunos problemas. Tal vez la implementación de cv2.VideoCapture sea ​​defectuosa y debería lanzar alguna excepción cuando eso suceda.

Acceder a los widgets tkinter desde otro hilo que no sea el principal también puede causar problemas.

Para la finalización del progtwig limpio, crear una instancia de threading.Event y luego verificar event.is_set() en algún punto del hilo de trabajo podría funcionar. Por ejemplo

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

y luego en el hilo del trabajador

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

Hay varias cosas que se podrían hacer de otra manera, la siguiente es una versión modificada del código. Evita llamar a métodos tkinter desde otro hilo que no sea el hilo principal, siendo event_generate() el único método tkinter llamado por el hilo trabajador. El sondeo explícito se evita emitiendo eventos virtuales, por ejemplo <> , que se colocan en la cola de eventos 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('', self._stop) parent.bind('<>', self.on_frame) parent.bind('<>', self.quit) parent.bind('<>', 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('<>', when='tail') def send_symbol(self_, data): self.lock.acquire(True) self.symbols.append(data) self.lock.release() self.parent.event_generate('<>', 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('<>', 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('<>', self.stop) self.root.bind('', self.start) self.root.bind('', 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()