How can you run loops alongside tkinter? - Python QR code reader GUI - python

I am trying to write a Tkinter application that will also process QR codes. For that to work, I need to have a loop checking if the QR code is valid and I need to make a post request. I'm fully aware that the way I have coded this is highly inefficient. Is there a better way to do this? Here is what I have so far:
import cv2
import tkinter as tk
from PIL import Image, ImageTk
import sys
import os
import pyzbar.pyzbar as zbar
import threading
import requests
import queue
result = []
decodedCode = ""
logo = "logo.png"
settings = "settings.png"
if os.environ.get('DISPLAY','') == '':
print('no display found. Using :0.0')
os.environ.__setitem__('DISPLAY', ':0.0')
#create main window
master = tk.Tk()
master.title("tester")
master.geometry("480x800")
master.configure(bg='white')
ttelogo = tk.PhotoImage(file = logo)
settingslogo = tk.PhotoImage(file = settings)
#settings button
settings_frame = tk.Frame(master,width=50,height=50,bg="white")
settings_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
settingsBtn = tk.Button(settings_frame, image=settingslogo).pack()
settings_frame.place(x=430,y=0)
#logo
img = tk.Label(master, image=ttelogo, bg='white')
img.image = ttelogo
img.place(x=176.5,y=10)
#Name Label
label_frame = tk.Frame(master,width=400,height=100,bg="white")
label_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
tk.Label(label_frame,bg="white",fg="black",text="John Smith Smithington III",font=("Calibri",22)).pack()
label_frame.place(x=40,y=140)
#Instructions Label
instructions_frame = tk.Frame(master,width=440,height=100,bg="white")
instructions_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
tk.Label(instructions_frame,bg="white",fg="black",text="Place your pass under the scanner below.",font=("Calibri",10)).pack()
instructions_frame.place(x=20,y=210)
#Camera Window
cameraFrame = tk.Frame(master, width=440, height=480)
cameraFrame.place(x=20, y=260)
#Camera Feed
lmain = tk.Label(cameraFrame)
lmain.place(x=0, y=0)
cap = cv2.VideoCapture(0)
def startScanning():
global cap
_, frame = cap.read()
frame = cv2.flip(frame, 1)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
lmain.imgtk = imgtk
lmain.configure(image=imgtk)
lmain.after(10, startScanning)
def processScan():
global decodedCode
stopped = False
delay = 1
while(True):
ret = cv2.waitKey(delay) & 0xFF
if ret == ord('c'): # continue
stopped = False
delay = 1
if ret == ord('q'):
break
if stopped or (ret == ord('s')): # stop
stopped = True
delay = 30
continue
# Capture frame-by-frame
ret, frame = cap.read()
decodedObjects = zbar.decode(frame)
if len(decodedObjects) > 0:
stopped = True
for code in decodedObjects:
#print("Data", obj.data)
#API Calls
decodedCode = code.data.decode('utf-8')
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
def checkCode():
global decodedCode
while True:
if decodedCode != "":
print (decodedCode)
result = requests.post("https://example.com/login/index.php", data={'action': 'validate_scan', 'uuid': decodedCode}).text
print(result)
decodedCode = ""
startScanning() #Display 2
threading.Thread(name='background', target=processScan).start()
threading.Thread(name='background2', target=checkCode).start()
master.mainloop() #Starts GUI
Edit: New version in queue form:
# import all the necessary modules
from tkinter import *
from multiprocessing import Process, Queue
from queue import Empty # for excepting a specific error
import numpy as np
import cv2
from PIL import Image, ImageTk
import sys
import os
import pyzbar.pyzbar as zbar
import threading
import requests
# this is the function that will be run in a child process
def processScan(queue_): # pass the queue as an argument
stopped = False
delay = 1
while(True):
ret = cv2.waitKey(delay) & 0xFF
if ret == ord('c'): # continue
stopped = False
delay = 1
if ret == ord('q'):
break
if stopped or (ret == ord('s')): # stop
stopped = True
delay = 30
continue
# Capture frame-by-frame
ret, frame = cap.read()
decodedObjects = zbar.decode(frame)
if len(decodedObjects) > 0:
stopped = True
for code in decodedObjects:
#print("Data", obj.data)
#API Calls
queue_.put(code.data.decode('utf-8'))
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
#master.after(2000, processScan)
#return r
# just a function to not write a `lambda`, just easier
# to read code
def startScanning():
global cap
_, frame = cap.read()
frame = cv2.flip(frame, 1)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
lmain.imgtk = imgtk
lmain.configure(image=imgtk)
lmain.after(10, startScanning)
#processScan()
#threading.Thread(name='background', target=processScan).start()
# set process `daemon = True` so that it gets terminated with the
# main process, this approach is not suggested if you write to file
# but otherwise it shouldn't cause any issues (maybe an error but
# that probably can be handled with `try/except`)
Process(target=processScan, args=(queue, ), daemon=True).start()
# here is the loop for updating the label
# basically get the info from the queue
def update_label():
try:
# try getting data but since it is `block=False`
# if there is nothing in the queue it will not block
# this process waiting for data to appear in the queue
# but it will raise the Empty error
data = queue.get(block=False)
except Empty:
pass
else:
# if no error was raised just config
# label to new data
labelFrame.config(text=data)
finally:
# and simply schedule this function to run again in
# 100 milliseconds (this btw is not recursion)
master.after(100, update_label)
# crucial part is to use this if statement because
# child processes run this whole script again
if __name__ == '__main__':
master = Tk()
logo = "logo.png"
settings = "settings.png"
if os.environ.get('DISPLAY','') == '':
print('no display found. Using :0.0')
os.environ.__setitem__('DISPLAY', ':0.0')
#create main window
master.attributes("-fullscreen", True)
master.title("Title")
master.geometry("480x800")
master.configure(bg='white')
ttelogo = PhotoImage(file = logo)
settingslogo = PhotoImage(file = settings)
#settings button
settings_frame = Frame(master,width=50,height=50,bg="white")
settings_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
settingsBtn = Button(settings_frame, image=settingslogo).pack()
settings_frame.place(x=430,y=0)
#logo
img = Label(master, image=ttelogo, bg='white')
img.image = ttelogo
img.place(x=176.5,y=10)
#Name Label
label_frame = Frame(master,width=400,height=100,bg="white")
label_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
Label(label_frame,bg="white",fg="black",text="John Smith Smithington III",font=("Calibri",22)).pack()
label_frame.place(x=40,y=140)
#Instructions Label
instructions_frame = Frame(master,width=440,height=100,bg="white")
instructions_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
Label(instructions_frame,bg="white",fg="black",text="Place your pass under the scanner below.",font=("Calibri",15)).pack()
instructions_frame.place(x=20,y=210)
#Camera Window
cameraFrame = Frame(master, width=440, height=480)
cameraFrame.place(x=20, y=260)
#Camera Feed
lmain = Label(cameraFrame)
lmain.place(x=0, y=0)
cap = cv2.VideoCapture(0)
# define queue (since it is a global variable now
# it can be easily used in the functions)
queue = Queue()
#label = Label(root)
#label.pack()
# initially start the update function
update_label()
# just a button for starting the process, but you can also simply
# call the function
#Button(root, text='Start', command=start_process).pack()
startScanning()
master.mainloop()
Still running into errors. Also am not sure if this is correct Queue syntax. The Camera feed is not live. Just a static image is showing up at the moment.

This is about as simple as it gets with multiprocessing (explanation in code comments (which is why the code may seem like it has a lot going on when if the comments were removed it would be approx only 40 lines of code for this example)):
# import all the necessary modules
from tkinter import Tk, Label, Button
from multiprocessing import Process, Queue
from queue import Empty # for excepting a specific error
# this is the function that will be run in a child process
def count(queue_): # pass the queue as an argument
# these modules are just to show the functionality
# but time might be necessary otherwise
# if theses are imported in the global namespace
# then you don't need to import them here again
import itertools
import time
for i in itertools.count():
# do the main looping
# put data in the queue
queue_.put(i)
# you may not need to use sleep here depending
# on how long it takes for the
# loop to finish the iteration
# the issue is that the loop may finish
# way faster than the update function runs
# and that will fill up the queue
# I think the queue size can be limited
# but putting sleep like this to allow
# the update loop to update will work too
time.sleep(0.2)
# just a function to not write a `lambda`, just easier
# to read code
def start_process():
# set process `daemon = True` so that it gets terminated with the
# main process, this approach is not suggested if you write to file
# but otherwise it shouldn't cause any issues (maybe an error but
# that probably can be handled with `try/except`)
Process(target=count, args=(queue, ), daemon=True).start()
# here is the loop for updating the label
# basically get the info from the queue
def update_label():
try:
# try getting data but since it is `block=False`
# if there is nothing in the queue it will not block
# this process waiting for data to appear in the queue
# but it will raise the Empty error
data = queue.get(block=False)
except Empty:
pass
else:
# if no error was raised just config
# label to new data
label.config(text=data)
finally:
# and simply schedule this function to run again in
# 100 milliseconds (this btw is not recursion)
root.after(100, update_label)
# crucial part is to use this if statement because
# child processes run this whole script again
if __name__ == '__main__':
root = Tk()
# define queue (since it is a global variable now
# it can be easily used in the functions)
queue = Queue()
label = Label(root)
label.pack()
# initially start the update function
update_label()
# just a button for starting the process, but you can also simply
# call the function
Button(root, text='Start', command=start_process).pack()
root.mainloop()
I am not too familiar with opencv so there might be a chance it won't work in a child process (tho I think I have done that), in that case you will have to either use threading or two alternatives using subprocess: put the opencv part in another file and run that file using python interpreter in the subprocess.Popen (won't work for computers that don't have Python installed) or convert that other python file with the opencv part to an .exe file or sth and run that executable file in the subprocess.Popen (both approaches tho are probably unnecessary and you probably will be fine with either multiprocessing or threading)
To use threading instead change these lines:
# from multiprocessing import Process, Queue
from threading import Thread
from queue import Empty, Queue
and
# Process(target=count, args=(queue, ), daemon=True).start()
Thread(target=count, args=(queue, ), daemon=True).start()

Related

Why does the multithread function not run before the timer?

I am trying to use multithreading to show a loading wheel while doing some background calculations.
I try to start the multithreaded function, and then run a timer to simulate the calculations.
The problem is the thread first starts running when the timer is over, even though I am starting the thread first...
from tkinter import *
from tkinter import messagebox
import time
from threading import *
from PIL import Image, ImageTk
# Create Object
root = Tk()
# Set geometry
root.geometry("400x400")
flag = True
# use threading
def threading():
t1=Thread(target=work)
t1.start()
time.sleep(10)
# work function
def work():
print("sleep time start")
image = Image.open("Static/spinner0.png")
img = ImageTk.PhotoImage(image)
lab = Label(root,image=img)
lab.pack()
while flag:
for i in range(8):
image = Image.open("Static/spinner"+str(i)+".png")
img = ImageTk.PhotoImage(image)
lab['image'] = img
time.sleep(0.2)
#return False
print("sleep time stop")
def on_closing():
if messagebox.askokcancel("Quit", "Do you want to quit?"):
flag = False
root.destroy()
# Create Button
Button(root,text="Click Me",command = threading).pack()
# Execute Tkinter
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
Just putting the answer here to avoid people digging comments for the solution.
One way to do this is to have both the calculations and the updating function running on extra different threads, this way the main process event loop stays uninterrupted and you get to call time.sleep, which is fine so long as it doesn't happen in your main thread.
A thread in python consumes below 100KB of memory (plus memory for variables in code running on it), so it's fine to have multiple threads running.
from tkinter import *
from tkinter import messagebox
import time
from threading import *
from PIL import Image, ImageTk
# Create Object
root = Tk()
# Set geometry
root.geometry("400x400")
flag = True
# use threading
def threading():
t1 = Thread(target=work)
t1.start()
t2 = Thread(target=time.sleep,args=(10,)) # the heavy computation function.
t2.start()
# control is given back to the main event loop
# work function
def work():
print("sleep time start")
image = Image.open("Static/spinner0.png")
img = ImageTk.PhotoImage(image)
lab = Label(root,image=img)
lab.pack()
while flag:
for i in range(8):
image = Image.open("Static/spinner"+str(i)+".png")
img = ImageTk.PhotoImage(image)
lab['image'] = img
time.sleep(0.2)
#return False
print("sleep time stop")
def on_closing():
if messagebox.askokcancel("Quit", "Do you want to quit?"):
flag = False
root.destroy()
# Create Button
Button(root, text="Click Me", command=threading).pack()
# Execute Tkinter
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()

Live Feed from raspicam on canvas covering everything even cursor

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.

How can I preview streaming images in tkinter with Allied Vision camera that uses Vimba SDK?

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.

How to change a variable inside of loop with button ? (Tkinter and python)

I am trying to make object tracking example with user interface. I need a few buttons for changing the way of code. But the code below does not working as I expected. The boolean variables must change because I can see the button fuctions forking when I press buttons but it does't effect the loop that works continiously. If there is someone knows what am I missing could you tell me? Thanks for your help.
import PIL
from PIL import Image, ImageTk
import Tkinter as tk
import argparse
import datetime
import cv2
import serial
import time
import os
import sys
#Start_Tracking = tk.BooleanVar
#Start_Tracking.set(False)
p1 = (310,230)
p2 = (330,250)
global Start_Tracking
Start_Tracking = False
tracker = cv2.TrackerKCF_create()
class Application:
def __init__(self, output_path = "./"):
self.vs = cv2.VideoCapture(0)
self.vs.set(3,640)
self.vs.set(4,320)
self.output_path = output_path
self.current_image = None
self.root = tk.Tk() # initialize root window
self.root.title("Object Tracking GUI") # set window title
# self.destructor function gets fired when the window is closed
self.root.protocol('WM_DELETE_WINDOW', self.destructor)
self.panel = tk.Label(self.root) # initialize image panel
self.panel.pack(padx=10, pady=10)
self.root.config(cursor="arrow")
# create a button, that when pressed, tracking will start
sel_btn = tk.Button(self.root, text='Select A Target', command=self.select_target)
sel_btn.pack(fill="both", expand=True, padx=10, pady=10)
# create a button, that when pressed, tracking will end
rel_btn = tk.Button(self.root, text="Release Selected Target", command=self.release_target)
rel_btn.pack(fill="both", expand=True, padx=10, pady=10)
# start a self.video_loop that constantly pools the video sensor
# for the most recently read frame
self.video_loop()
def video_loop(self):
if not Start_Tracking:
ok, frame = self.vs.read() # read frame from video stream
cv2.rectangle(frame, p1, p2, (0,0,200))
# frame = cv2.resize(frame, (1500,1000))
if ok: # frame captured without any errors
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) # convert colors from BGR to RGBA
self.current_image = Image.fromarray(cv2image) # convert image for PIL
#self.current_image= self.current_image.resize([1280,1024],PIL.Image.ANTIALIAS)
imgtk = ImageTk.PhotoImage(image=self.current_image) # convert image for tkinter
self.panel.imgtk = imgtk # anchor imgtk so it does not be deleted by garbage-collector
self.panel.config(image=imgtk) # show the image
#self.root.attributes("-fullscreen",True)
else:
ok, frame = self.vs.read() # do nothing for now
self.root.after(1, self.video_loop)
def select_target(self):
""" Take snapshot and save it to the file """
Start_Tracking=True
tracker = cv2.TrackerKCF_create()
print("Start Pressed")
def release_target(self):
""" Take snapshot and save it to the file """
Start_Tracking=False
tracker.clear()
print("Release Pressed")
def destructor(self):
""" Destroy the root object and release all resources """
print("[INFO] closing...")
self.root.destroy()
self.vs.release() # release web camera
cv2.destroyAllWindows() # it is not mandatory in this application
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-o", "--output", default="./",
help="path to output directory to store snapshots (default: current folder")
args = vars(ap.parse_args())
# start the app
print("[INFO] starting...")
pba = Application(args["output"])
pba.root.mainloop()
When you assign Start_Tracking = True within your class, you're assigning it locally within the class as opposed to the global attribute as you expected. See this documentation for more details.
To update the global attribute, you have to first declare it is a global variable before assignment:
def select_target(self):
""" Take snapshot and save it to the file """
global Start_Tracking
Start_Tracking = True
tracker = cv2.TrackerKCF_create()
print("Start Pressed")
def release_target(self):
""" Take snapshot and save it to the file """
global Start_Tracking
Start_Tracking=False
tracker.clear()
print("Release Pressed")
However, I would argue it's not a good practice to have an instance method handle global attributes.
A better approach would be having the Start_Tracking as an instance attribute instead so you reference it within the scope of class Application as self.Start_Tracking, that way you have more control and avoid messy namespace handling. You can also define it as a class attribute instead of instance attribute, but that would give you a similar headache so personally I wouldn't do that.

How to Open Multiple Tkinter windows in one window

I am having two or more python Tkinter files. Each file is opening one window, how can run all the Tkinter windows functionality in one main window.
Ex : I have two files one is usbcam.py which will open USB camera and give the video steaming and the other one is ipcam.py it opens the IP camera and give the live streaming.This two files are opening in two windows how can make this to work in one window
usbcam.py
import cv2
import PIL.Image
import PIL.ImageTk
import Tkinter as tk
def update_image(image_label, cv_capture):
cv_image = cv_capture.read()[1]
cv_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
pil_image = PIL.Image.fromarray(cv_image)
pil_image.save('image3.jpg')
tk_image = PIL.ImageTk.PhotoImage(image=pil_image)
image_label.configure(image=tk_image)
image_label._image_cache = tk_image # avoid garbage collection
root.update()
def update_all(root, image_label, cv_capture):
if root.quit_flag:
root.destroy() # this avoids the update event being in limbo
else:
update_image(image_label, cv_capture)
root.after(10, func=lambda: update_all(root, image_label, cv_capture))
if __name__ == '__main__':
cv_capture = cv2.VideoCapture()
cv_capture.open(0) # have to use whatever your camera id actually is
root = tk.Tk()
setattr(root, 'quit_flag', False)
def set_quit_flag():
root.quit_flag = True
root.protocol('WM_DELETE_WINDOW', set_quit_flag) # avoid errors on exit
image_label = tk.Label(master=root) # the video will go here
image_label.pack()
root.after(0, func=lambda: update_all(root, image_label, cv_capture))
root.mainloop()
ipcam.py
import cv2
import numpy as np
import PIL.Image
import PIL.ImageTk
import Tkinter as tk
import urllib
stream = urllib.urlopen("http://192.168.2.195:80/capture/scapture").read()
bytes_ = ''
def update_image(image_label):
global bytes_
bytes_ += stream.read(1024)
a = bytes_.find('\xff\xd8')
b = bytes_.find('\xff\xd9')
if (a != -1) and (b != -1):
jpg = bytes_[a:b+2]
bytes_ = bytes_[b+2:]
cv_image = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8),
cv2.CV_LOAD_IMAGE_COLOR)
cv_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
pil_image = PIL.Image.fromarray(cv_image)
tk_image = PIL.ImageTk.PhotoImage(image=pil_image)
image_label.configure(image=tk_image)
image_label._image_cache = tk_image # avoid garbage collection
root.update()
def update_all(root, image_label):
if root.quit_flag:
print "coming if"
root.destroy() # this avoids the update event being in limbo
else:
print "coming else"
update_image(image_label)
root.after(1, func=lambda: update_all(root, image_label))
def timer(interval = 100):
root.after(0, func=lambda: update_all(root, image_label))
#.................................................................................................
root.after(interval, timer)
if __name__ == '__main__':
root = tk.Tk()
setattr(root, 'quit_flag', False)
def set_quit_flag():
root.quit_flag = True
root.protocol('WM_DELETE_WINDOW', set_quit_flag)
image_label = tk.Label(master=root) # label for the video frame
image_label.pack()
root.after(2, func=lambda: update_all(root, image_label))
# timer()
root.mainloop()
You need to designate one script as the main script, and in that one you can import the other. Here's an example of doing this using a simple subclass of the Frame widget:
The primary script (tkA.py):
from Tkinter import *
from tkB import Right # bring in the class Right from secondary script
class Left(Frame):
'''just a frame widget with a white background'''
def __init__(self, parent):
Frame.__init__(self, parent, width=200, height=200)
self.config(bg='white')
if __name__ == "__main__":
# if this script is run, make an instance of the left frame from here
# and right right frame from tkB
root = Tk()
Left(root).pack(side=LEFT) # instance of Left from this script
Right(root).pack(side=RIGHT) # instance of Right from secondary script
root.mainloop()
The secondary script (tkB.py):
from Tkinter import *
class Right(Frame):
'''just a frame widget with a black background'''
def __init__(self, parent):
Frame.__init__(self, parent, width=200, height=200)
self.config(bg='black')
if __name__ == "__main__":
# if this script is run, just do this:
root = Tk()
Right(root).pack()
root.mainloop()
Hope that helps.
First, you need to include it in the top. So in the top of ipcam.py write 'import usbcam' (without quotations).

Categories

Resources