2016-05-07 53 views
9

Próbowałem stworzyć okno najwyższego poziomu, które przesyła strumieniowo wideo w postaci kamery internetowej i zrobić skan QR. Mam ten kod QR code od SO i another code, który tylko aktualizuje obrazy z kamery internetowej zamiast przesyłania strumieniowego wideo na etykiecie tkinter.python: tkinter, aby wyświetlić wideo z kamery internetowej i zrobić skan QR

i starałem się połączyć oba te elementy, aby uzyskać okno z etykietą aktualizującą obraz z kamery internetowej i przycisk zamykania, aby zamknąć okno główne. Podczas przesyłania obrazów może skanować kod QR, a jeśli skanowanie powiedzie się, kamera internetowa i okno główne zostaną zamknięte.

Oto, co próbowałem.

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

Problem jest

  1. I rzeczywiście chciał, aby wyświetlić film na Toplevel oknie, a nie okno OpenCV
  2. jednocześnie zrobić QR skanowania, jeśli czytanie się powiedzie, okno Toplevel powinno się zamknąć bez gwałtownej domykania kamery (ponieważ, kiedy próbuję użyć self.cam.release() lub cv2.destroyAllWindows(), moje kamery internetowe zapalają się lub włączają, nawet jeśli skończę kompilację programów).

Teraz otrzymuję oddzielne okno utworzone przez OpenCV, które przesyła wideo do środka. Ale nie chcę tego okna, zamiast tego chcę, aby film był wyświetlany w oknie trzystu dzwonka. także, gdy następuje pomyślny odczyt, kamera internetowa zatrzymuje się na końcowym obrazie, który odczytuje.

próbowałem usunąć wiersz, który był odpowiedzialny za oknem OpenCV, wewnątrz run metoda BarcodeScanner klasy

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

jeszcze pojawił się z innego okna bez wyjścia, a jeśli próbuję zamknąć że window, utworzył kolejny podobny i rekursywny.

UPDATE:

jak zauważyłem zrobiłem kilka głupich błędów, bez zrozumienia niektórych liniach w cv2, zrobiłem kilka zmian w kodzie, dodając Toplevel kod okno do metody klasy run (nie jestem pewien, czy to jest właściwa droga).

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

Teraz mogę uzyskać obraz w oknie Toplevel, ale nie wiem, jak zamknąć kamerę internetową.

stan 1: kiedy pokazują kod QR skanowania, odczytuje go z powodzeniem i kamera zostaje zamknięty bez jakiegokolwiek błędu.

Warunek 2: Kiedy klikam przycisk zamykania na będąc w głównym oknie (powiedzmy, jeśli użytkownik nie chce robić żadnych skanowania i po prostu chcą, aby zamknąć kamerę) dostaję błąd mówiąc

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) 

Piszę tę aplikację dla maszyny Linux, i Windows. Jak mogę bezpiecznie zamknąć lub zakończyć kamerę internetową.

+0

Możliwe duplikat [Using OpenCV z Tkinter] (http://stackoverflow.com/questions/32342935/using-opencv-with-tkinter) – tfv

+0

@tfv,. wygląda podobnie, ale nie tak samo. tam wideo jest wyświetlane na oknie przy użyciu funkcji, ale tutaj, ponieważ robię jednocześnie skan QR przy jednoczesnym wyświetlaniu wideo, łączenie ich za pomocą klas i wątków znacznie utrudnia przejście – arvindh

Odpowiedz

1

Twój program ma dwa wątki, główny wątek i wątek roboczy odczytujący klatki z kamery. Po kliknięciu przycisku zamykania następuje to w głównym wątku. Po self.cam.release() obiekt self.cam jest prawdopodobnie w stanie nie do użytku, a gdy metoda self.cam jest wywoływana przez wątek roboczy, mogą wystąpić pewne problemy. Być może implementacja cv2.VideoCapture jest wadliwa i powinna spowodować pewien wyjątek.

Uzyskiwanie dostępu do widżetów tkintera z innego wątku niż wątek główny również może powodować problemy.

Dla czystego zakończenia programu, tworzenie instancji threading.Event, a następnie sprawdzanie pod kątem event.is_set() w pewnym punkcie wątku roboczego może działać. Na przykład

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

a następnie w wątku roboczego

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

Istnieje kilka rzeczy, które można zrobić w inny sposób, po to zmodyfikowana wersja kodu. Uniknie wywoływania metod tkintera z innego wątku niż główny wątek, ponieważ jest to jedyna metoda tkintera wywoływana przez wątek roboczy. Unika się jawnego odpytywania, emitując zdarzenia wirtualne, na przykład <<ScannerQuit>>, które są umieszczane w kolejce zdarzeń 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() 
Powiązane problemy