python:tkinter显示来自网络摄像头的视频并进行QR扫描 [英] python: tkinter to display video from webcam and do a QR scan
问题描述
我一直在尝试创建一个tkinter顶级窗口,该窗口可以流式传输视频格式的网络摄像头并进行QR扫描.我从 SO 和
I have been trying to create a tkinter top level window that streams video form webcam and do a QR scan. I got this QR scan code from SO and another code that just updates images from webcam instead of streaming the video on a tkinter label.
,我试图将两者结合起来,以使顶层窗口具有从网络摄像头更新图像的标签,并带有关闭按钮以关闭顶层窗口.在流式传输图像的同时,它可以扫描QR码,如果扫描成功,则网络摄像头和顶层窗口将关闭.
and i tried to combine these both so that a toplevel window with a label updating image from webcam and a close button to close the toplevel window. And while it streams the images, it can scan for QR code and if a scan is successful, the webcam and the toplevel window gets closed.
这是我尝试过的.
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 is,
- 我实际上是想在顶级"窗口而不是OpenCV窗口上显示视频
- 同时执行QR扫描,如果读取成功,则顶级窗口应关闭而不会突然关闭摄像头(因为当我尝试使用
self.cam.release()
或cv2.destroyAllWindows()
时,即使我强制终止程序编译.)
- I actually wanted to display the video on the Toplevel window, not the OpenCV window
- at the same time do a QR Scan,if a read is sucessfull, the Toplevel window should close without abruptly closing webcam(because, when i try to use
self.cam.release()
orcv2.destroyAllWindows()
my webcams lights or on even if i forcefully terminate the programs compilation).
现在我得到的是OpenCV创建的一个单独的窗口,该窗口在其中流传输视频.但是我不想要那个窗口,相反,我希望视频显示在tkinter的顶级窗口中.同样,如果读取成功,则网络摄像头会卡在读取的最终图像上.
Now what i get is a separate window created by OpenCV that streams video inside. But i don’t want that window, instead i want the video to be displayed on the tkinter's toplevel window. also when there is a sucessfull read, the webcam stucks at the final image it reads.
我试图在BarcodeScanner
类的run
方法内删除负责OpenCV窗口的行
i tried to remove the line that was responsible for OpenCV window, inside the run
method of BarcodeScanner
class
cv2.imshow(self.WINDOW_NAME, img[1])
它仍然出现在没有输出的其他窗口中,如果我尝试关闭该窗口,它会递归地创建另一个类似的窗口.
it still showed up with a different window with no output, and if i try to close that window, it created another one similar and recursively.
UPDATE :
UPDATE:
正如我注意到的那样,我在不理解cv2
中的某些行的情况下犯了一些愚蠢的错误,我通过将顶级窗口代码添加到该类的run
方法中对代码进行了一些更改(我不确定这是否是正确的方法.)
As i noticed i made some silly mistakes without understanding of some lines in cv2
, i made some change on the code by adding the toplevel window code into the run
method of the class(im not sure if this is a right way).
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()
现在,我可以在顶级"窗口上获取图像,但是我不知道如何关闭摄像头.
now, I can get the image on the Toplevel window, But i dont know how to close the webcam.
条件1: :当我显示要扫描的QR码时,它会成功读取并退出网络摄像头,而不会出现任何错误.
condition 1: when i show a QR code to scan, it reads it successfully and webcam quits without any error.
条件2: :当我单击顶级窗口上的关闭"按钮时(例如,用户不想进行任何扫描而只想关闭摄像头)我说错了
condition 2: when i click the close button on the toplevel window(say if user doesn't want to do any scan and just want to close the webcam) i get error saying
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)
我正在为Linux
,Mac
和Windows
机器编写此应用程序.如何安全关闭或终止网络摄像头.
I am writing this application for Linux
, Mac
and Windows
machine. How can i close or terminate the webcam safely.
推荐答案
您的程序有两个线程,即主线程和从相机读取帧的辅助线程.单击关闭按钮时,它会在主线程中发生.在self.cam.release()
之后,对象self.cam
可能处于无法使用的状态,并且当工作线程调用self.cam
的方法时,可能会遇到一些麻烦.也许cv2.VideoCapture
的实现是错误的,并且在发生这种情况时应该抛出一些异常.
Your program has two threads, the main thread and the worker thread that reads frames from the camera. When the close button is clicked, it happens in the main thread. After self.cam.release()
the object self.cam
is probably in an unusable state, and when a method of self.cam
is called by the worker thread, there may be some trouble. Maybe the implementation of cv2.VideoCapture
is faulty and it should throw some exception when that happens.
从主线程以外的其他线程访问tkinter窗口小部件也可能会导致问题.
Accessing tkinter widgets from other thread than the main thread may also cause problems.
对于干净的程序终止,创建threading.Event
的实例,然后在工作线程中的某个时间检查event.is_set()
可能有效.例如
For clean program termination, creating an instance of threading.Event
and then checking for event.is_set()
at some point in the work thread could work. For example
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>>
),可以避免显式轮询.
There are several things that could be done in other way, the following is a modified version of the code. It avoids calling tkinter methods from other thread than the main thread, event_generate()
being the only tkinter method called by the worker thread. Explicit polling is avoided by emitting virtual events, for example <<ScannerQuit>>
, that are placed in the tkinter event queue.
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()
这篇关于python:tkinter显示来自网络摄像头的视频并进行QR扫描的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!