python: tkinter afficher la vidéo de webcam et faire un QR scan

j'ai essayé de créer une fenêtre de haut niveau tkinter qui diffuse des vidéos de webcam et de faire un QR scan. J'ai eu ce QR code scan de et un autre code qui ne fait que mettre à jour les images de webcam au lieu de diffuser la vidéo sur une étiquette tkinter.

et j'ai essayé de combiner les deux pour qu'une fenêtre de niveau supérieur avec une étiquette mettant à jour l'image de webcam et un bouton de fermeture pour fermer la fenêtre de niveau supérieur. Et pendant qu'il diffuse les images, il peut scan pour le code QR et si un scan est réussi, la webcam et la fenêtre de niveau supérieur se ferme.

voici ce que j'ai essayé.

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

Problème est,

  1. je voulais en fait afficher la vidéo sur la fenêtre de haut niveau, pas la fenêtre OpenCV
  2. en même temps faire un QR Scan, si une lecture est réussie, la fenêtre de niveau supérieur devrait fermer sans fermer brusquement webcam (parce que, quand j'essaie d'utiliser self.cam.release() ou cv2.destroyAllWindows() mes webcams s'allument ou s'allument même si je mets fin avec force à la compilation des programmes).

maintenant ce que j'obtiens c'est une fenêtre séparée créée par OpenCV qui diffuse la vidéo à l'intérieur. Mais je ne veux pas de cette fenêtre, au lieu de cela je veux que la vidéo soit affichée sur la fenêtre de haut niveau de tkinter. aussi quand il ya une lecture réussie, la webcam STACKS à l'image finale qu'il lit.

j'ai essayé de supprimer la ligne qui était responsable de la fenêtre OpenCV, à l'intérieur du run méthode BarcodeScanner classe

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

il est toujours apparu avec une fenêtre différente sans sortie, et si j'essaie de fermer cette fenêtre, il en a créé une autre similaire et récursivement.

UPDATE:

comme je l'ai remarqué j'ai fait quelques erreurs stupides sans comprendre certaines lignes dans cv2, j'ai fait quelques modification sur le code en ajoutant la fenêtre toplevel code dans le run méthode de la classe (Je ne suis pas sûr si c'est une bonne façon).

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

maintenant, je peux obtenir l'image sur la fenêtre de niveau supérieur, mais je ne sais pas comment fermer la webcam.

condition 1: quand je montre un code QR à scanner, il lit avec succès et webcam quitte sans aucune erreur.

condition 2: quand je clique sur le bouton Fermer sur la fenêtre de niveau supérieur (dire si l'utilisateur ne veut pas faire de scan et que je veux juste fermer la webcam) j'obtiens l'erreur en disant:

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)

j'écris cette demande pour Linux,Mac et Windows de la machine. Comment puis-je fermer ou terminer la webcam en toute sécurité.

9
demandé sur Community 2016-05-07 16:36:37

1 réponses

votre programme a deux threads, le thread principal et le worker thread qui lit les cadres de la caméra. Lorsque le bouton Fermer est cliqué, cela se produit dans le thread principal. Après self.cam.release() l'objet self.cam est probablement dans un état inutilisable, et lorsqu'une méthode de self.cam est appelé par le fil ouvrier, il peut y avoir des problèmes. Peut-être que la mise en œuvre de cv2.VideoCapture est défectueux et il devrait jeter une exception quand cela se produit.

accès aux widgets tkinter à partir d'autres thread que le thread principal peut également causer des problèmes.

pour la fin du programme clean, créant une instance de threading.Event et puis de vérifier, pour event.is_set() à un moment donné dans le fil de travail pourrait fonctionner. Par exemple,

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

et puis dans le fil ouvrier

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

il y a plusieurs choses qui pourraient être faites autrement, ce qui suit est une version modifiée du code. Il évite d'appeler les méthodes tkinter à partir d'un autre thread que le thread principal, event_generate() étant la seule méthode tkinter appelée par le worker thread. Le vote explicite est évité en émettant des événements virtuels, par exemple <<ScannerQuit>>, qui sont placés dans la file d'attente d'événements 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()
1
répondu J.J. Hakala 2016-07-02 17:01:01