I ran into a strange problem using Python (3.8.6) Tkinter and OpenCV. I'm trying to play a video in a loop, until another video is selected.
When I open a video the first time, it starts playback normally. When I then open another video (same video or other video), the first video stops, but the image of the second video is not displayed (it seems to play in background). Strangely, it does display the video when the tkinter filedialog shows to open yet another video. But when the dialog is cancelled, the display stops again.
If I manually close the playback ('p') and reopen a video ('o') it works normally.
If the video is finished (without looping) before a new video is opened it also works normally.
import os
import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
import cv2
VIDEO_RATE = int(1000.0/30.0)
class VideoPlayback:
def __init__(self):
self.cv2Video = None
def __del__(self):
if self.cv2Video and self.cv2Video.isOpened():
self.cv2Video.release()
def openVideo(self, src, width=405, height=720):
self.cv2Video = cv2.VideoCapture(src)
if not self.cv2Video.isOpened():
raise ValueError("Unable to open video source", src)
self.resizeDim = (width,height)
def closeVideo(self):
if self.cv2Video:
self.cv2Video.release()
def readVideoFrame(self, retry=5):
if self.cv2Video and self.cv2Video.isOpened():
ret, frame = self.cv2Video.read()
if ret:
return (ret, cv2.cvtColor(cv2.resize(frame, self.resizeDim), cv2.COLOR_BGR2RGB))
else:
if retry:
self.cv2Video.set(cv2.CAP_PROP_POS_FRAMES, 0)
return self.readVideoFrame(retry=retry-1)
else:
return (False, None)
else:
return (False, None)
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self,"Minimal working example")
self.geometry("1280x740+2+2")
self.attributes("-fullscreen",True)
frTop = tk.Frame(master=self, width=1280, height=720)
frTop.pack(side='top', fill='both', expand=1)
# Playback canvas
self.cnvVideo = tk.Canvas(master=frTop, width=405, height=720)
self.cnvVideoImg = self.cnvVideo.create_image(0,0, anchor='nw')
self.cnvVideo.pack(side='right', fill='both', expand=1)
# Buttons
self.bind("o",self.openVideoPlayback)
self.bind("p",self.closeVideoPlayback)
# reference video playback
self.video = VideoPlayback()
self.afterVideo = None
def openVideoPlayback(self, *args):
# show file dialog
videoSrc = filedialog.askopenfilename(title = 'Open video', filetypes=(("Video files (*.mp4)","*.mp4"),("All files (*.*)","*.*")))
if videoSrc:
# open selected video for playback
self.video.openVideo(videoSrc, width=405, height=720)
# start update loop
self.afterVideo = self.after(VIDEO_RATE, self.updateVideoPlayback)
def updateVideoPlayback(self):
(ret, frame) = self.video.readVideoFrame()
if ret:
image = Image.fromarray(frame)
image = ImageTk.PhotoImage(image, master=self)
self.cnvVideo.itemconfig(self.cnvVideoImg, image=image)
self.cnvVideo.image = image
self.cnvVideo.after(VIDEO_RATE, self.updateVideoPlayback)
else:
print("Stop rescheduling")
def closeVideoPlayback(self, *args):
# stop video playback
if self.video:
self.video.closeVideo()
app = MainWindow()
app.mainloop()
I tried adding self.video.closeVideo() before opening a new video.
I tried adding self.after_cancel(self.afterVideo) before opening a new video.
The code is part of a larger Tkinter interface, but stripped to minimal (non)working example.
Related
I am working on an app that basically turns the raspberry pi 4 into a camera, since I've been learning opencv i thought it would be a cool way to show off everything that I've done so far,
I do manage to retrieve the feed of the raspicam, but it's on the top layer so it covers up everything, including my cursor and i can't even stop the app.
I retrieve the image with this code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# camera_pi.py
#
#
#
import time
import io
import threading
import picamera
class Camera(object):
thread = None # background thread that reads frames from camera
frame = None # current frame is stored here by background thread
last_access = 0 # time of last client access to the camera
def initialize(self):
if Camera.thread is None:
# start background frame thread
Camera.thread = threading.Thread(target=self._thread)
Camera.thread.start()
# wait until frames start to be available
while self.frame is None:
time.sleep(0)
def get_frame(self):
Camera.last_access = time.time()
self.initialize()
return self.frame
#classmethod
def _thread(cls):
with picamera.PiCamera() as camera:
# camera setup
camera.resolution = (1920, 1080)
camera.hflip = True
camera.vflip = True
#camera.zoom = (0.22,0,0.7,0.7) # (x, y, w, h)
# let camera warm up
camera.start_preview()
time.sleep(2)
stream = io.BytesIO()
for foo in camera.capture_continuous(stream, 'jpeg',
use_video_port=True):
# store frame
stream.seek(0)
cls.frame = stream.read()
# reset stream for next frame
stream.seek(0)
stream.truncate()
# if there hasn't been any clients asking for frames in
# the last 10 seconds stop the thread
#if time.time() - cls.last_access > 10:
#break
cls.thread = None
And then make it into a full screen Tkinter widget with this code:
import cv2
from camera_pi import *
import sys, os
if sys.version_info[0] == 2: # the tkinter library changed it's name from Python 2 to 3.
import Tkinter
tkinter = Tkinter #I decided to use a library reference to avoid potential naming conflicts with people's programs.
else:
import tkinter
from PIL import Image, ImageTk
import time
camera = Camera()
feed = camera.get_frame()
frame = cv2.imread(feed)
#cv2.imshow(frame)
#cv2.waitKey(0)
#cv2.destroyAllWindows()
root = tkinter.Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
canvas = tkinter.Canvas(root,width=w,height=h)
canvas.pack()
canvas.configure(background='black')
SetButton = Button(root,text="Settings", command=root.destroy)
SetButton.place(x=0,y=0)
def showPIL(pilImage):
imgWidth, imgHeight = pilImage.size
# resize photo to full screen
ratio = min(w/imgWidth, h/imgHeight)
imgWidth = int(imgWidth*ratio)
imgHeight = int(imgHeight*ratio)
pilImage = pilImage.resize((imgWidth,imgHeight), Image.ANTIALIAS)
image = ImageTk.PhotoImage(pilImage)
imagesprite = canvas.create_image(w/2,h/2,image=image)
imagesprite.lower()
root.update_idletasks()
root.update()
root.bind("<Escape>", lambda e: (e.widget.withdraw(), e.widget.quit()))
try:
showPIL(frame)
except KeyboardInterrupt:
root.destroy
I'd like to add a button on the corner to open up a settings window where i could modify the camera parameters, or start one of the opencv modes ive been working on but being that i can't see the cursor that doesn't work, i also tried to use the canvas function to lower, and tried moving it around in the code but i think its because the image keeps refreshing, or i simply did it wrong.
I want to display images from an Allied Vision camera inside a tkinter frame using OpenCV and the SDK for the camera, VimbaPython.
The only possible way to initialize the camera is with a Python with statement:
with Vimba.get_instance() as vimba:
cams = vimba.get_all_cameras()
with cams[0] as camera:
camera.get_frame()
# Convert frame to opencv image, then use Image.fromarray and ImageTk.PhotoImage to
# display it on the tkinter GUI
Everything works fine so far. But I don't only need a single frame. Instead, I need to continuously get frames and display them on the screen so that it is streaming.
I found that one way to do it is to call the .after(delay, function) method from a tkinter Label widget.
So, after obtaining one frame, I want to call the same function to get a new frame and display it again. The code would look like that:
with Vimba.get_instance() as vimba:
cams = vimba.get_all_cameras()
with cams[0] as camera:
def show_frame():
frame = camera.get_frame()
frame = frame.as_opencv_image()
im = Image.fromarray(frame)
img = Image.PhotoImage(im)
lblVideo.configure(image=img) # this is the Tkinter Label Widget
lblVideo.image = img
show_frame()
lblVideo.after(20, show_frame)
Then this shows the first frame and stops, throwing an error saying that Vimba needs to be initialized with a with statement. I don't know much about Python, but it looks like when I call the function with the .after() method it ends the with statement.
I would like to know if it is possible to execute this show_frame() function without ending the with. Also, I can't initialize the camera every time because the program goes really slow.
Thank you
I know this question is pretty old, but I ran into a similar problem with the Allied Vision cameras and found the solution to be relatively robust. So I hope this helps someone, even if not the OP.
An alternative to using with statements is using __enter__ and __exit__ (see sample here). With this, I created a class for the Vimba camera and during the __init__ I used these functions twice: once to initialize the Vimba instance, and once to open the camera itself. An example as follows...
vimba_handle = Vimba.get_instance().__enter__()
camera = vimba_handle.get_all_cameras()[0].__enter__()
I'll include a longer snippet as code as well, but please note my purpose was slightly different the OP's intent. Hopefully, it is still useful.
class VimbaCam:
def __init__(self, device_id=0):
# Variables
self.current_frame = np.array([])
self.device = None
self.device_id = device_id
self.vimba_handle = Vimba.get_instance().__enter__()
self.is_streaming = False
self.scale_window = 4
self.stream_thread = threading.Thread(target=self.thread_stream, daemon=True)
# Default settings
self.auto_exposure = "Off"
self.auto_gain = "Off"
self.acquisition = "Continuous"
self.exposure_us = 200000
self.fps = 6.763
self.gain = 0
self.gamma = 1
self.open()
def close(self):
if self.device is not None:
if self.is_streaming:
self.stop_stream()
time.sleep(1)
self.device.__exit__(None, None, None)
self.vimba_handle.__exit__(None, None, None)
def open(self):
cams = self.vimba_handle.get_all_cameras()
if not cams:
error_check(151, currentframe())
else:
self.device = cams[self.device_id].__enter__()
self.set_defaults()
self.start_stream()
def start_stream(self):
if self.device is not None:
self.is_streaming = True
self.stream_thread.start()
time.sleep(1)
def thread_stream(self):
while self.is_streaming:
current_frame = self.device.get_frame().as_opencv_image()
h, w, _ = current_frame.shape
self.current_frame = current_frame.reshape((h, w))
self.stream_thread = threading.Thread(target=self.thread_stream, daemon=True)
def stop_stream(self):
if self.device is not None:
self.is_streaming = False
def live_video(self):
if self.device is not None:
window_name = "Allied Vision"
h, w = self.current_frame.shape
w = int(w / self.scale_window)
h = int(h / self.scale_window)
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
cv2.resizeWindow(window_name, w, h)
while 1:
cv2.imshow(window_name, self.current_frame)
cv2.waitKey(1)
if cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE) < 1:
break
cv2.destroyAllWindows()
def set_defaults(self):
if self.device is not None:
# Exposure time settings
self.device.ExposureAuto.set(self.auto_exposure)
self.device.ExposureTimeAbs.set(self.exposure_us)
# Gain settings
self.device.GainAuto.set(self.auto_gain)
self.device.Gain.set(self.gain)
# Gamma settings
self.device.Gamma.set(self.gamma)
self.device.AcquisitionMode.set(self.acquisition)
self.device.AcquisitionFrameRateAbs.set(self.fps)
# Try to adjust GeV packet size (available for GigE only)
try:
self.device.GVSPAdjustPacketSize.run()
while not self.device.GVSPAdjustPacketSize.is_done():
pass
except (AttributeError, VimbaFeatureError):
pass
# Color formatting (tries mono first, then color)
cv_formats = intersect_pixel_formats(self.device.get_pixel_formats(), OPENCV_PIXEL_FORMATS)
mono_formats = intersect_pixel_formats(cv_formats, MONO_PIXEL_FORMATS)
color_formats = intersect_pixel_formats(cv_formats, COLOR_PIXEL_FORMATS)
if mono_formats:
self.device.set_pixel_format(mono_formats[0])
elif color_formats:
self.device.set_pixel_format(color_formats[0])
if __name__ == "__main__":
dev = VimbaCam()
dev.live_video()
dev.close()
You need to use thread to run the capture code and pass the frames read via queue. Then the main tkinter application reads the queue and show the frames periodically using .after().
Below is an example based on your posted code:
import threading
from queue import SimpleQueue
import tkinter as tk
from PIL import Image, ImageTk
from vimba import Vimba
def camera_streaming(queue):
global is_streaming
is_streaming = True
print("streaming started")
with Vimba.get_instance() as vimba:
with vimba.get_all_cameras()[0] as camera:
while is_streaming:
frame = camera.get_frame()
frame = frame.as_opencv_image()
im = Image.fromarray(frame)
img = ImageTk.PhotoImage(im)
queue.put(img) # put the capture image into queue
print("streaming stopped")
def start_streaming():
start_btn["state"] = "disabled" # disable start button to avoid running the threaded task more than once
stop_btn["state"] = "normal" # enable stop button to allow user to stop the threaded task
show_streaming()
threading.Thread(target=camera_streaming, args=(queue,), daemon=True).start()
def stop_streaming():
global is_streaming, after_id
is_streaming = False # terminate the streaming thread
if after_id:
lblVideo.after_cancel(after_id) # cancel the showing task
after_id = None
stop_btn["state"] = "disabled" # disable stop button
start_btn["state"] = "normal" # enable start button
# periodical task to show frames in queue
def show_streaming():
global after_id
if not queue.empty():
image = queue.get()
lblVideo.config(image=image)
lblVideo.image = image
after_id = lblVideo.after(20, show_streaming)
queue = SimpleQueue() # queue for video frames
after_id = None
root = tk.Tk()
lblVideo = tk.Label(root, image=tk.PhotoImage(), width=640, height=480)
lblVideo.grid(row=0, column=0, columnspan=2)
start_btn = tk.Button(root, text="Start", width=10, command=start_streaming)
start_btn.grid(row=1, column=0)
stop_btn = tk.Button(root, text="Stop", width=10, command=stop_streaming, state="disabled")
stop_btn.grid(row=1, column=1)
root.mainloop()
Note that I don't have the camera and the SDK installed, the above code may not work for you. I just demonstrate how to use thread, queue and .after().
Below is a testing vimba module (saved as vimba.py) I use to simulate VimbaPython module using OpenCV and a webcam:
import cv2
class Frame:
def __init__(self, frame):
self.frame = frame
def as_opencv_image(self):
return self.frame
class Camera:
def __init__(self, cam_id=0):
self.cap = cv2.VideoCapture(cam_id, cv2.CAP_DSHOW)
def __enter__(self):
return self
def __exit__(self, *args):
self.cap.release()
return self
def get_frame(self):
ret, frame = self.cap.read()
if ret:
return Frame(frame)
class Vimba:
_instance = None
#classmethod
def get_instance(self):
if self._instance is None:
self._instance = Vimba()
return self._instance
def __enter__(self):
return self
def __exit__(self, *args):
return self
def get_all_cameras(self):
return (Camera(),)
I tried to read the frames in openCV and display them in tkinter label. I was able to do so using the below code:
import tkinter as tk
import cv2
from PIL import ImageTk, Image
video_path = "SAMPLE/STORED_VIDEO/PATH"
root = tk.Tk()
base_img = Image.open("PATH/TO/DEFAULT/LABLE/IMAGE")
img_obj = ImageTk.PhotoImage(base_img)
lblVideo = tk.Label(root, image=img_obj)
lblVideo.pack()
cap = cv2.VideoCapture(video_path)
if cap.isOpened():
def show_frame():
_, frame = cap.read()
im = Image.fromarray(frame)
img = ImageTk.PhotoImage(im)
lblVideo.configure(image=img)
lblVideo.image = img
lblVideo.after(1, show_frame) # Need to create callback here
show_frame()
root.mainloop()
Although this doesnot contain the with statement, you can try replacing the after() callback inside the show_frame function itself.
I am using tkinter and opencv for the first time and have successfully built a GUI for my project, however, I cannot figure out why my video stream is updating so extremely slow. I am grabbing frames very quickly but it seems that the update on the screen gets exponentially slower. I am seeing somewhere around 30 seconds of lag when I first launch the program but it eventually slows to a halt. I am connecting to three cameras but only displaying one at a time. The cameras all display and the selection buttons work. My only issue is the display refresh rate.
This is running in Python3.7 on a raspberry pi4. I can connect to the camera via web browser and it appears to have no lag.
I have been searching for answers but cannot seem to find anything that helps. Can anyone offer some help with this?
Here's my program (I have removed unrelated code):
#!/usr/bin/env python3
import time
from tkinter import *
import cv2
from PIL import Image, ImageTk
#GUI
class robotGUI:
def __init__(self):
self.selectedCam = "front"
self.window = Tk()
#Setup the window to fit the Raspberry Pi Touch Display = 800x400 and align top left
self.window.geometry("800x480+0+0")
self.window.overrideredirect(True)
self.window.fullScreenState = False
#Create Frame for Video Window
self.videoFrame = Frame(self.window, relief=SUNKEN, bd=2)
self.videoFrame.place(x=0, y=0, height=457, width=650)
#Create the Video Window
self.video = Label(self.videoFrame, bd=0, relief=FLAT, width=644, height=451)
self.video.place(x=0, y=0)
self.vid = VideoCapture()
self.camUpdateFreq = 250
self.updateCams()
#Create the Button Frame
self.buttonFrame = Frame(self.window, relief=FLAT)
self.buttonFrame.place(x=651, y=0, height=457, width=149)
#Create Buttons
#Select Front Camera Button
self.frontCamButton = Button(self.buttonFrame, text="Front Camera", command=lambda: self.selectCam("front"))
self.frontCamButton.place(x=24, y=50, height=30, width=100)
#Select Boom Camera Button
self.boomCamButton = Button(self.buttonFrame, text="Boom Camera", command=lambda: self.selectCam("boom"))
self.boomCamButton.place(x=24, y=130, height=30, width=100)
#Select Rear Camera Button
self.rearCamButton = Button(self.buttonFrame, text="Rear Camera", command=lambda: self.selectCam("rear"))
self.rearCamButton.place(x=24, y=210, height=30, width=100)
#Close Button
self.exitButton = Button(self.buttonFrame, text="Close", command=self.window.destroy)
self.exitButton.place(x=24, y=400, height=30, width=100)
#Start the main loop for the gui
self.window.mainloop()
def selectCam(self, cam):
if (cam.lower() == "front"):
self.selectedCam = "front"
self.statusBarLeft['text'] = "Front Camera Selected"
elif (cam.lower() == "boom"):
self.selectedCam = "boom"
self.statusBarLeft['text'] = "Boom Camera Selected"
elif (cam.lower() == "rear"):
self.selectedCam = "rear"
self.statusBarLeft['text'] = "Rear Camera Selected"
def updateCams(self):
#Get a frame from the selected camera
ret, frame = self.vid.get_frame(self.selectedCam)
if ret:
imageCV2 = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = Image.fromarray(imageCV2)
imgPhoto = ImageTk.PhotoImage(image=img)
self.video.imgPhoto = imgPhoto
self.video.configure(image=imgPhoto)
self.window.after(self.camUpdateFreq, self.updateCams)
#Video Camera Class
class VideoCapture:
def __init__(self):
#Define Locals
FrontCameraAddress = "rtsp://admin:password#192.168.5.20:8554/12"
BoomCameraAddress = "rtsp://admin:password#192.168.5.21:8554/12"
RearCameraAddress = "rtsp://admin:password#192.168.5.22:8554/12"
#Open Front Video Camera Source
self.vidFront = cv2.VideoCapture(FrontCameraAddress)
self.vidBoom = cv2.VideoCapture(BoomCameraAddress)
self.vidRear = cv2.VideoCapture(RearCameraAddress)
#Verify that the Camera Streams Opened
if not self.vidFront.isOpened():
raise ValueError("Unable to open video source to Front Camera")
if not self.vidBoom.isOpened():
raise ValueError("Unable to open video source to Boom Camera")
if not self.vidRear.isOpened():
raise ValueError("Unable to open video source to Rear Camera")
#Get One Frame from the Selected Camera
def get_frame(self, camera="front"):
#Attempt to Get Front Camera Frame
if (camera.lower() == "front"):
#If Stream Still Open Return a Frame
if self.vidFront.isOpened():
ret, frame = self.vidFront.read()
if ret:
#Return a boolean success flag and the current frame converted to BGR
return (ret, frame)
else:
return (ret, None)
else:
return (ret, None)
#Attempt to Get Boom Camera Frame
elif (camera.lower() == "boom"):
#If Stream Still Open Return a Frame
if self.vidBoom.isOpened():
ret, frame = self.vidBoom.read()
if ret:
#Return a boolean success flag and the current frame converted to BGR
return (ret, frame)
else:
return (ret, None)
else:
return (ret, None)
#Attempt to Get Rear Camera Frame
elif (camera.lower() == "rear"):
#If Stream Still Open Return a Frame
if self.vidRear.isOpened():
ret, frame = self.vidRear.read()
if ret:
#Return a boolean success flag and the current frame converted to BGR
return (ret, frame)
else:
return (ret, None)
else:
return (ret, None)
else:
return (False, None)
#Release the video sources when the object is destroyed
def __del__(self):
if self.vidFront.isOpened():
self.vidFront.release()
if self.vidBoom.isOpened():
self.vidBoom.release()
if self.vidRear.isOpened():
self.vidRear.release()
#Main Routine - Only run if called from main program instance
if __name__ == '__main__':
try:
#Create GUI Object
app = robotGUI()
except Exception as e:
print("Exception: " + str(e))
finally:
print("Cleaning Up")
NOTE: In this copy of the program, I am updating every 250ms but I have tried smaller numbers down to around 3 but the frames still seem to be behind. Is there a better way to do this?
NOTE2: After working with this more today, I realize that openCV is definitely buffering frames for each camera starting when the cv2.VideoCapture() function is called for each camera. The read() function does seem to be pulling the next frame from the buffer which explains why it is taking so long to update and why the image I see on the screen never catches up to reality. I changed my test code to only connect to one camera at a time and use the cv2.release() function any time I am not actively viewing a camera. This improved things quite a bit. I also set the update function to run every 1ms and I am using the grab() function to grab a frame every cycle but I am only processing and displaying every 10th cycle which has also improved some. I still have some lag that I would love to remove if anyone has any suggestions.
My RTSP stream shows with zero noticeable lag when viewed in a web browser. Does anyone know how I can get the same effect in tkinter? I am not married to openCV.
I want to record video (not audio) from webcam.
I have put two buttons for start recording & stop recording.
As program starts it pick image from camera & shows on screen.works perfect.
My problem is when i click start recording & after some times stop recording,
only avi file created ,with 0K or 6K size found. No further recording found.
import tkinter
import cv2
import PIL.Image, PIL.ImageTk
stopb = None
class App():
def __init__(self, window, window_title):
self.window = window
self.window.title = window_title
self.ok = False
self.video = cv2.VideoCapture(0)
self.width = self.video.get(cv2.CAP_PROP_FRAME_WIDTH)
self.height = self.video.get(cv2.CAP_PROP_FRAME_HEIGHT)
#create videowriter
self.fourcc = cv2.VideoWriter_fourcc(*'XVID')
self.out = cv2.VideoWriter('output.avi',self.fourcc,10,(640,480))
# Create a canvas that can fit the above video source size
self.canvas = tkinter.Canvas(window, width=self.width, height=self.height)
self.canvas.pack()
self.opencamera = tkinter.Button(window, text="open camera", command=self.open_camera)
self.opencamera.pack()
self.closecamera = tkinter.Button(window, text="close camera", command=self.close_camera)
self.closecamera.pack()
self.delay = 10
self.update()
# After it is called once, the update method will be automatically called every delay milliseconds
self.window.mainloop()
def update(self):
ret, frame = self.video.read()
if self.ok == 'T':
self.out.write(frame)
if ret:
self.photo = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(frame))
self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
self.window.after(self.delay, self.update)
def open_camera(self):
self.ok = True
print("camera opened")
print(self.ok)
def close_camera(self):
print("camera closed")
self.ok = False
self.video.release()
self.out.release()
def __del__(self):
if self.video.isOpened():
self.video.release()
self.out.release()
App(tkinter.Tk(), "mywindow")
Your problem is that you're never writing anything to your output, as if self.ok == 'T' will never evaluate to true. You should change it to just if self.ok, the same thing you did with ret.
def update(self):
ret, frame = self.video.read()
if self.ok:
self.out.write(frame)
if ret:
self.photo = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(frame))
self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
self.window.after(self.delay, self.update)
One more issue I found was- after clicking the close camera button followed by the open camera button, the new recording won't start.
Meaning you will just be able to record the first video captured before clicking the close camera button.
This is because the close camera function releases the video object and output object thus video.read() of the update function won't work.
A quick fix is to create a method of set_camera() which creates the video object. Call this function every time in open camera.
https://replit.com/#AmitYadav4/video-record-in-tkinker-canvas
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.
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.
here is what i tried.
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,
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() or cv2.destroyAllWindows() my webcams lights or on even if i forcefully terminate the programs compilation).
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.
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:
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.
condition 1: when i show a QR code to scan, it reads it successfully and webcam quits without any error.
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)
I am writing this application for Linux, Mac and Windows machine. How can i close or terminate the webcam safely.
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.
Accessing tkinter widgets from other thread than the main thread may also cause problems.
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()
and then in the worker thread
while True:
if self.stop_event.is_set():
break
for i in range(0, self.CV_SYSTEM_CACHE_CNT):
self.cam.read()
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()