2016-05-07 189 views
9

我一直在嘗試創建一個tkinter頂級窗口,用於流式傳輸視頻表單攝像頭並執行QR掃描。我從SOanother code得到這個QR掃描代碼,它只是從網絡攝像頭更新圖像,而不是在tkinter標籤上流式傳輸視頻。python:tkinter顯示來自網絡攝像頭的視頻並執行QR掃描

我試圖將這兩者結合起來,這樣一個頂層窗口帶有標籤,可以從網絡攝像頭更新圖像,關閉按鈕可以關閉頂層窗口。當它傳輸圖像時,它可以掃描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. 我其實是想顯示頂層窗口上的視頻,而不是OpenCV的窗口
  2. 在同一時間做了QR掃描,如果閱讀是成功的,Toplevel窗口應該關閉而不會突然關閉攝像頭(因爲,當我嘗試使用self.cam.release()cv2.destroyAllWindows()我的攝像頭指示燈亮起,即使我強制終止程序編譯)。

現在我所得到的是一個由OpenCV創建的獨立窗口,可以將視頻內部進行流式傳輸。但我不想要那個窗口,而是我想讓視頻顯示在tkinter的頂層窗口中。當有一個成功的閱讀時,網絡攝像機就會讀取它讀取的最終圖像。

我試圖刪除負責OpenCV的窗口行,方法BarcodeScannerrun

​​

它仍然沒有輸出不同的窗口出現了裏面,如果我嘗試關閉窗口,它創建了另一個類似的和遞歸的。

UPDATE

正如我發現我在cv2取得了一些愚蠢的錯誤在沒有一些線的理解,我通過將頂層窗口代碼插入到類的run方法制造上的代碼的某些變化(我不知道這是否是一種正確的方式)。

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:當我顯示的QR碼進行掃描,它成功地讀出它並且攝像頭退出沒有任何錯誤。

條件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機寫這個應用程序。我怎樣才能安全地關閉或終止攝像頭。

+0

[使用的OpenCV與Tkinter的]的可能的複製(http://stackoverflow.com/questions/32342935/using-opencv-with-tkinter) – tfv

+0

@tfv。它看起來相似,但不一樣。在那裏使用函數將視頻顯示到窗口上,但是由於我在同時進行QR掃描以及顯示視頻的同時,使用類和線程將它們組合起來使得更難以通過 – arvindh

回答

1

你的程序有兩個線程,主線程和從攝像頭讀取幀的工作線程。當關閉按鈕被點擊時,它發生在主線程中。在self.cam.release()之後,對象self.cam可能處於不可用狀態,並且當工作線程調用self.cam的方法時,可能會出現一些問題。也許cv2.VideoCapture的實現有問題,並且在發生這種情況時應該拋出一些異常。

從主線程以外的其他線程訪問tkinter小部件也可能導致問題。

對於乾淨的程序終止,創建一個threading.Event的實例,然後在工作線程的某個點檢查event.is_set()可以工作。例如

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

有幾件事情,可以在其他的方式來完成,下面是代碼的修改版本。它避免了從主線程以外的其他線程調用tkinter方法,event_generate()是工作線程調用的唯一tkinter方法。顯式輪詢通過發送置於tkinter事件隊列中的虛擬事件(例如<<ScannerQuit>>)來避免。

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() 
相關問題