I have Tkinter GUI with a canvas that currently shows the image by default and there is a switch that will print Cam On and Cam off when switched back and forth.
What I'm trying to do is to get my webcam to stream instead of that image when I flit the switch on.
Here is the code I have for my GUI:
import tkinter
import tkinter.messagebox
import customtkinter
from PIL import Image,ImageTk
# Setting up theme of GUI
customtkinter.set_appearance_mode("Dark") # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
class App(customtkinter.CTk):
def __init__(self):
super().__init__()
# configure window
self.is_on = True
self.image = ImageTk.PhotoImage(Image.open("../data/Mars.PNG"))
self.title("Cool Blue")
self.geometry(f"{1200}x{635}")
# configure grid layout (4x4)
self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure((2, 3), weight=0)
self.grid_rowconfigure((0, 1, 2, 3), weight=0)
###################################################################
# Create Sidebar for LED, LIghts and Camera controls
###################################################################
self.lights_control = customtkinter.CTkFrame(self)
self.lights_control.grid(row=3, column=0, rowspan = 1, padx=(5, 5), pady=(10, 10), sticky="nsew")
self.lights_control.grid_rowconfigure(1, weight=1)
# Camera
self.camera_switch = customtkinter.CTkSwitch(master=self.lights_control, text="Camera", command=self.camera_switch)
self.camera_switch.grid(row=2, column=1, pady=10, padx=20, )
###################################################################
# Create canvas for RPCam live stream
###################################################################
self.picam_frame = customtkinter.CTkFrame(self)
self.picam_frame.grid(row=0, column=1, rowspan=4, padx=(5, 5), pady=(10, 10), sticky="nsew")
self.picam_frame.grid_rowconfigure(4, weight=1)
self.picam_canvas = customtkinter.CTkCanvas(self.picam_frame, width=1730, height=944, background="gray")
self.picam_canvas.create_image(0, 0, image=self.image, anchor="nw")
self.picam_canvas.pack()
#########################################################################
# Camera Switch
#########################################################################
def camera_switch(self, event=None):
if self.is_on:
print("Cam on")
self.is_on = False
else:
print("Cam off")
self.is_on = True
if __name__ == "__main__":
app = App()
app.mainloop()
Now I did some research and found this code below that will do the trick in its own canvas but I am have not had luck to use the logic from the code below in my own code above.
# Import required Libraries
from tkinter import *
from PIL import Image, ImageTk
import cv2
# Create an instance of TKinter Window or frame
win = Tk()
# Set the size of the window
win.geometry("700x350")
# Create a Label to capture the Video frames
label =Label(win)
label.grid(row=0, column=0)
cap= cv2.VideoCapture(0)
# Define function to show frame
def show_frames():
# Get the latest frame and convert into Image
cv2image= cv2.cvtColor(cap.read()[1],cv2.COLOR_BGR2RGB)
img = Image.fromarray(cv2image)
# Convert image to PhotoImage
imgtk = ImageTk.PhotoImage(image = img)
label.imgtk = imgtk
label.configure(image=imgtk)
# Repeat after an interval to capture continiously
label.after(20, show_frames)
show_frames()
win.mainloop()
So what I really need is help with using the logic from the code below and incorporating it into my own code above.
Can you please help me?
Here is my latest attempt to get this solved. So I was able to merge these two code based on the logic I provided above. And when I start the GUI and switch the camera button on, I can see that camera is starting since the LED light on the camera turns on and I get a windows notification that my program is starting the camera. however, it is not showing on the canvas yet.
So I think I am close but I am not sure what I'm missing.
import tkinter
import tkinter.messagebox
import customtkinter
from PIL import Image,ImageTk
import cv2
# Setting up theme of GUI
customtkinter.set_appearance_mode("Dark") # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
class App(customtkinter.CTk):
def __init__(self):
super().__init__()
# configure window
self.is_on = True
self.image = ImageTk.PhotoImage(Image.open("../data/Mars.PNG"))
self.capture = cv2.VideoCapture(0)
self.title("Cool Blue")
self.geometry(f"{1200}x{635}")
# configure grid layout (4x4)
self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure((2, 3), weight=0)
self.grid_rowconfigure((0, 1, 2, 3), weight=0)
###################################################################
# Create Sidebar for LED, LIghts and Camera controls
###################################################################
self.lights_control = customtkinter.CTkFrame(self)
self.lights_control.grid(row=3, column=0, rowspan = 1, padx=(5, 5), pady=(10, 10), sticky="nsew")
self.lights_control.grid_rowconfigure(1, weight=1)
# Camera
self.camera_switch = customtkinter.CTkSwitch(master=self.lights_control, text="Camera", command=self.camera_switch)
self.camera_switch.grid(row=2, column=1, pady=10, padx=20, )
###################################################################
# Create canvas for RPCam live stream
###################################################################
self.picam_frame = customtkinter.CTkFrame(self)
self.picam_frame.grid(row=0, column=1, rowspan=4, padx=(5, 5), pady=(10, 10), sticky="nsew")
self.picam_frame.grid_rowconfigure(4, weight=1)
# self.picam_canvas = customtkinter.CTkCanvas(self.picam_frame, width=1730, height=944, background="gray")
# self.picam_canvas.create_image(0, 0, image=self.image, anchor="nw")
self.picam_canvas = tkinter.Canvas(self.picam_frame, width=1730, height=944)
#self.picam_canvas.pack
#########################################################################
# Camera Switch
#########################################################################
def camera_switch(self, event=None):
if self.is_on:
self.update_frames()
print("Cam on")
self.is_on = False
else:
#self.close_camera()
self.image
print("Cam off")
self.is_on = True
def update_frames(self):
# Get the current frame from the webcam
_, frame = self.capture.read()
# Convert the frame to a PhotoImage object
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frame = Image.fromarray(frame)
frame = ImageTk.PhotoImage(frame)
# Update the canvas with the new frame
self.picam_canvas.create_image(0, 0, image=frame, anchor="nw")
self.picam_canvas.image = frame
# Schedule the next update
self.after(1000, self.update_frames)
def close_camera(self):
self.capture.release()
if __name__ == "__main__":
app = App()
app.mainloop()
Again, if anyone has any insight I would really appreciate it.
In the code you can take advantage of the variable self.is_on in the update_frame function to finish the loop. Something like this.
def update_frames(self):
# Change the frame by the initial image and breaks the loop
if self.is_on:
self.picam_canvas.create_image(0, 0, image=self.image, anchor="nw")
return
else:
_, frame = self.capture.read()
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frame = Image.fromarray(frame)
frame = ImageTk.PhotoImage(frame)
self.picam_canvas.create_image(0, 0, image=frame, anchor="nw")
self.picam_canvas.image = frame
self.picam_canvas.after(1, self.update_frames)
But this gave an error because when you turn it back on, the video capture didn't start. So I modified some lines in the camera_switch function.
def camera_switch(self, event=None):
if self.is_on:
self.capture = cv2.VideoCapture(0)
print("Cam on")
self.is_on = False
self.update_frames()
else:
self.close_camera()
self.image
print("Cam off")
self.is_on = True
The line self.capture = cv2.VideoCapture(0) was at the beginning but I removed it and put it here so that it initializes the video capture when the button is pressed
Related
Disclaimer: I am only marginally experienced in cv2 and Tkinter , so pardon me if this question is silly.
What am I trying to do?
I am trying to read input from 2 cameras at the same time and display the resulting frames using a Tkinter GUI in real-time.
What is the issue I am facing?
There is a significant lag in the dual video streams when displayed using tkinter.
What have I tried?
I have checked if this issue persists when displaying a single video frame and the issue does not persist.
code :
import numpy as np
import cv2
import tkinter as tk
from PIL import Image, ImageTk
def show_frame_left():
_, frame = cap_left.read()
frame = cv2.flip(frame, 1)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
lmain_left.imgtk = imgtk
lmain_left.configure(image=imgtk)
lmain_left.after(10, show_frame_left) #previously 10
def show_frame_right():
_, frame = cap_right.read()
frame = cv2.flip(frame, 1)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
lmain_right.imgtk = imgtk
lmain_right.configure(image=imgtk)
lmain_right.after(5, show_frame_right) #previously 10
#Set up GUI
window = tk.Tk() #Makes main window
window.bind('<Escape>', lambda e: root.quit())
window.wm_title("Cleron Vsoft")
#Graphics window
image_frame_left = tk.Frame(window, width=600, height=500)
image_frame_left.grid(row=0, column=0, padx=10, pady=2)
#Capture video frames
lmain_left = tk.Label(image_frame_left)
lmain_left.grid(row=0, column=0)
cap_left = cv2.VideoCapture(4) #1(side cam) , 3(top cam),4(int top cam) works
#Slider window (slider controls stage position)
sliderFrame_left = tk.Frame(window, width=600, height=100)
sliderFrame_left.grid(row = 600, column=0, padx=10, pady=2)
show_frame_left() #Display 2
#Graphics window
image_frame_right = tk.Frame(window, width=600, height=500)
image_frame_right.grid(row=0, column=1, padx=10, pady=2)
#Capture video frames
lmain_right = tk.Label(image_frame_right)
lmain_right.grid(row=0, column=0)
cap_right = cv2.VideoCapture(3) #1(side cam) , 3(top cam),4(int top cam) works
#Slider window (slider controls stage position)
sliderFrame_right = tk.Frame(window, width=600, height=100)
sliderFrame_right.grid(row = 600, column=0, padx=10, pady=2)
show_frame_right() #Display 2
window.mainloop() #Starts GUI
error :
youtube link: https://youtu.be/mRVVyHfkXBc
How do I display my dual video feed without lag?
Ok, so i have a problem and tried way too long to fix it. I dont know how to resize an image without it breaking my layout. In case (1) it fills up the whole space thus destroying my layout and i think it is because i call the configure function in a function that is bound to the <Configure> event. In case (2) i can resize the image but i have a huge border around the scaled image which destroys the layout. I even tried to use a Canvas instead of the Label but just drawing the Canvas destroys the layout, because it takes up too much space and shifts everything to the right.
Ideas how to fix it?
from tkinter import *
from PIL import ImageTk, Image
global image_label, image_ref
def main():
global image_label
root = Tk()
root.geometry("600x400")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
content_frame = Frame(root, bg="#FF0000")
content_frame.grid(row=0, column=0, sticky="news")
column_ratio = [10,25,10,45,10]
row_ratio = [10,80,10]
for col_idx in range(5):
content_frame.grid_columnconfigure(col_idx, weight=column_ratio[col_idx])
for row_idx in range(3):
content_frame.grid_rowconfigure(row_idx, weight=row_ratio[row_idx])
image_label = Label(content_frame, bg="#00FF00")
image_label.grid(row=1, column=1, sticky="news")
#image_label.bind("<Configure>", resize_configure) # <--- (1)
second_label = Label(content_frame, bg="#0000FF", text="Label on the right")
second_label.grid(row=1, column=3, sticky="news")
second_label.bind("<Button-1>", resize_click) # <--- (2)
root.mainloop()
def resize(size, label):
global image_ref
image = Image.open("test.png").resize(size, Image.ANTIALIAS)
image_ref = ImageTk.PhotoImage(image)
label.configure(image=image_ref)
def resize_configure(event):
global image_label
size = (event.width, event.height)
resize(size, image_label)
def resize_click(event):
global image_label
image_label.update()
size = (image_label.winfo_width(), image_label.winfo_height())
resize(size, image_label)
if __name__ == "__main__":
main()
Case 1 as an image
Case 2 as an image
How it should look like
You can use place() instead of grid():
import tkinter as tk
from PIL import Image, ImageTk
base_img = Image.open('test.png')
def on_label_resize(event):
lbl = event.widget
img = ImageTk.PhotoImage(base_img.resize((event.width, event.height)))
lbl.config(image=img)
lbl.image = img
def main():
root = tk.Tk()
root.geometry('640x400')
img_lbl = tk.Label(root, image=None, bg='#00ff00')
img_lbl.place(relx=.1, rely=.1, relwidth=.25, relheight=.8)
img_lbl.bind('<Configure>', on_label_resize)
txt_lbl = tk.Label(root, text='Label on the right', bg='#0000ff')
txt_lbl.place(relx=.45, rely=.1, relwidth=.45, relheight=.8)
root.mainloop()
if __name__ == '__main__':
main()
I have a program that resizes the buttons when you resize the window but the image doesn't resize with the buttons.
This is my code:
l = Label(window, image=img).grid(row=0, column=0, rowspan=3, sticky='nesw')
con = Frame(window).grid(row=0, column=1, sticky='nesw')
nextImg = PhotoImage(file='nextImg.png')
lastImg = PhotoImage(file='lastImg.png')
ok = PhotoImage(file="ok".png')
nextCam = Button(con, image=nextImg, command=nxt, background='#2B2B2B').grid(row=0, column=1, sticky='nesw')
lastCam = Button(con, image=lastImg, command=lst, background='#2B2B2B').grid(row=2, column=1, sticky='nesw')
takeImg = Button(con, image=ok, command=ok, background='#2B2B2B').grid(row=1, column=1, sticky='nesw')
I expect the output to look like this:
But what it actually does is:
------------------------Edit-----------------------
This needs to work with more than 2 buttons.
Interesting question. PhotoImage does not have a resize method, but you could use a PIL image to get that. If you don't have PIL you need to pip install pillow to get it.
import tkinter as tk
from PIL import Image, ImageTk
class ImageButton(tk.Button):
"""A button that displays an Image and resizes the image as the Button size changes"""
def __init__(self, master=None, image=None, **kwargs):
super().__init__(master, **kwargs)
if not image: return # no image provided; act as a normal Button
if isinstance(image, str):
self.image = Image.open(image)
elif isinstance(image, Image.Image):
self.image = image
else:
raise TypeError("'image' argument must be a PIL.Image or filename")
self.bind("<Configure>", self._on_configure)
def _on_configure(self, event=None):
size = event.width-4, event.height-4
self.photoimage = ImageTk.PhotoImage(self.image.resize(size))
self.config(image=self.photoimage)
### test / demo code: ###
def main():
root = tk.Tk()
root.geometry('200x200')
win = ImageButton(root, image="ON.gif")
win.pack(fill=tk.BOTH, expand=True)
root.mainloop()
if __name__ == '__main__':
main()
Note that you MUST define the initial size of the window for this to work. If you don't then every resize will trigger it to grow some more.
I've created a class Tela which is basically is my Screen and I'm trying to display the webcam video on a Tkinter GUI. My webcam LED is on but the label "painel" where I want do show my video is grey. Can someone tell me what's wrong with my code. I appreciate.
class Tela:
def __init__(self, janela):
self.janela = janela
self.janela.title("Reconhecimento Facial")
self.janela.config(background="#FFFFFF")
# Open camera
self.cam = cv2.VideoCapture(0)
self.detector = dlib.get_frontal_face_detector()
self.quadro = tkinter.Frame(janela, width=600, height=500)
self.quadro.grid(row=0, column=0, padx=10, pady=2, rowspan=3)
self.painel = tkinter.Label(self.quadro)
self.quadro.grid(row=0, column=0, rowspan=3)
# Methods for screen update
self.delay = 15
self.update()
self.janela.mainloop()
def update(self):
# Get frame
ret, frame = self.cam.read()
faces, confianca, idx = self.detector.run(frame)
for i, face in enumerate(faces):
e, t, d, b = (int(face.left()), int(face.top()), int(face.right()), int(face.bottom()))
cv2.rectangle(frame, (e, t), (d, b), (0, 255, 255), 2)
cv2image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGBA)
image = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=image)
self.painel.imgtk = imgtk
self.painel.configure(image=imgtk)
self.janela.after(self.delay, self.update)
# Creates the window
Tela(tkinter.Tk())
Mistake is in second line
self.painel = tkinter.Label(self.quadro)
self.quadro.grid(row=0, column=0, rowspan=3)
You created Label but you don't put it in window - it has to be self.painel.grid instead of self.quadro.grid
self.painel = tkinter.Label(self.quadro)
self.painel.grid(row=0, column=0, rowspan=3)
I'm writing a program that needs to display a video stream in a Tkinter window. Since there will also be buttons for performing various functions, I'm using grid to organize where everything goes.
The following code, modified from Show webcam sequence TkInter, works fine on my Raspberry Pi:
import Tkinter as tk
import cv2
from PIL import Image, ImageTk
width, height = 800, 600
cap = cv2.VideoCapture(0)
root = tk.Tk()
lmain = tk.Label(root)
lmain.pack()
def show_frame():
_, 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, show_frame)
show_frame()
root.mainloop()
However, combining it with Tkinter doesn't work. (In what follows, I've tried commenting out Display 1, Display 2, and neither.)
import numpy as np
import cv2
import Tkinter as tk
import Image, ImageTk
#Set up GUI
window = tk.Tk() #Makes main window
window.wm_title("Digital Microscope")
window.config(background="#FFFFFF")
#Graphics window
imageFrame = tk.Frame(window, width=600, height=500)
imageFrame.grid(row=0, column=0, padx=10, pady=2)
#Capture video frames
lmain = tk.Label(imageFrame)
cap = cv2.VideoCapture(0)
def show_frame():
_, 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, show_frame)
tk.Label(imageFrame, image=show_frame()).grid(row=0, column=0, padx=10, pady=2) #Display 1
#Slider window (slider controls stage position)
sliderFrame = tk.Frame(window, width=600, height=100)
sliderFrame.grid(row = 600, column=0, padx=10, pady=2)
show_frame() #Display 2
window.mainloop() #Starts GUI
How can I get the video to display in imageFrame?
This should work:
import numpy as np
import cv2
import Tkinter as tk
import Image, ImageTk
#Set up GUI
window = tk.Tk() #Makes main window
window.wm_title("Digital Microscope")
window.config(background="#FFFFFF")
#Graphics window
imageFrame = tk.Frame(window, width=600, height=500)
imageFrame.grid(row=0, column=0, padx=10, pady=2)
#Capture video frames
lmain = tk.Label(imageFrame)
lmain.grid(row=0, column=0)
cap = cv2.VideoCapture(0)
def show_frame():
_, 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, show_frame)
#Slider window (slider controls stage position)
sliderFrame = tk.Frame(window, width=600, height=100)
sliderFrame.grid(row = 600, column=0, padx=10, pady=2)
show_frame() #Display 2
window.mainloop() #Starts GUI
First of all, you have the line tk.Label(imageFrame, image=show_frame()).grid(row=0, column=0, padx=10, pady=2), and since show_frame() doesn't return anything, you've set image to None. Second of all, you need to make sure you lmain.grid(), otherwise lmain won't show.
If you want to have two displays one on top of the other, you could do something like this:
import numpy as np
import cv2
import Tkinter as tk
import Image, ImageTk
#Set up GUI
window = tk.Tk() #Makes main window
window.wm_title("Digital Microscope")
window.config(background="#FFFFFF")
#Graphics window
imageFrame = tk.Frame(window, width=600, height=500)
imageFrame.grid(row=0, column=0, padx=10, pady=2)
#Capture video frames
cap = cv2.VideoCapture(0)
def show_frame():
_, frame = cap.read()
frame = cv2.flip(frame, 1)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
display1.imgtk = imgtk #Shows frame for display 1
display1.configure(image=imgtk)
display2.imgtk = imgtk #Shows frame for display 2
display2.configure(image=imgtk)
window.after(10, show_frame)
display1 = tk.Label(imageFrame)
display1.grid(row=1, column=0, padx=10, pady=2) #Display 1
display2 = tk.Label(imageFrame)
display2.grid(row=0, column=0) #Display 2
#Slider window (slider controls stage position)
sliderFrame = tk.Frame(window, width=600, height=100)
sliderFrame.grid(row = 600, column=0, padx=10, pady=2)
show_frame() #Display
window.mainloop() #Starts GUI
Try this code:
from PIL import Image, ImageTk
import Tkinter as tk
import argparse
import datetime
import cv2
import os
class Application:
def __init__(self, output_path = "./"):
""" Initialize application which uses OpenCV + Tkinter. It displays
a video stream in a Tkinter window and stores current snapshot on disk """
self.vs = cv2.VideoCapture(0) # capture video frames, 0 is your default video camera
self.output_path = output_path # store output path
self.current_image = None # current image from the camera
self.root = tk.Tk() # initialize root window
self.root.title("PyImageSearch PhotoBooth") # 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)
# create a button, that when pressed, will take the current frame and save it to file
btn = tk.Button(self.root, text="Snapshot!", command=self.take_snapshot)
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):
""" Get frame from the video stream and show it in Tkinter """
ok, frame = self.vs.read() # read frame from video stream
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
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.after(30, self.video_loop) # call the same function after 30 milliseconds
def take_snapshot(self):
""" Take snapshot and save it to the file """
ts = datetime.datetime.now() # grab the current timestamp
filename = "{}.jpg".format(ts.strftime("%Y-%m-%d_%H-%M-%S")) # construct filename
p = os.path.join(self.output_path, filename) # construct output path
self.current_image.save(p, "JPEG") # save image as jpeg file
print("[INFO] saved {}".format(filename))
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()