I'm new in the Python GUI world. I'm developing a Python project to detect faces in a webcam and take a picture of the face, that part is already set up. I've been searching and testing codes for the GUI part, I tried with PyQt and PySide but I didn't have succed. I found a code from Adrian at PyImageSearch which have a frame for the webcam and a button to capture the image and use Tkinter for the GUI.
from __future__ import print_function
from PIL import Image
from PIL import ImageTk
import Tkinter as tki
import threading
import datetime
import imutils
import cv2
import os
class PhotoBoothApp:
def __init__(self, vs, outputPath):
# store the video stream object and output path, then initialize
# the most recently read frame, thread for reading frames, and
# the thread stop event
self.vs = vs
self.outputPath = outputPath
self.frame = None
self.thread = None
self.stopEvent = None
# initialize the root window and image panel
self.root = tki.Tk()
self.panel = None
# create a button, that when pressed, will take the current
# frame and save it to file
btn = tki.Button(self.root, text="Snapshot!",
command=self.takeSnapshot)
btn.pack(side="bottom", fill="both", expand="yes", padx=10,
pady=10)
# start a thread that constantly pools the video sensor for
# the most recently read frame
self.stopEvent = threading.Event()
self.thread = threading.Thread(target=self.videoLoop, args=())
self.thread.start()
# set a callback to handle when the window is closed
self.root.wm_title("PyImageSearch PhotoBooth")
self.root.wm_protocol("WM_DELETE_WINDOW", self.onClose)
def videoLoop(self):
# DISCLAIMER:
# I'm not a GUI developer, nor do I even pretend to be. This
# try/except statement is a pretty ugly hack to get around
# a RunTime error that Tkinter throws due to threading
try:
# keep looping over frames until we are instructed to stop
while not self.stopEvent.is_set():
# grab the frame from the video stream and resize it to
# have a maximum width of 300 pixels
self.frame = self.vs.read()
self.frame = imutils.resize(self.frame, width=300)
# OpenCV represents images in BGR order; however PIL
# represents images in RGB order, so we need to swap
# the channels, then convert to PIL and ImageTk format
image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
image = Image.fromarray(image)
image = ImageTk.PhotoImage(image)
# if the panel is not None, we need to initialize it
if self.panel is None:
self.panel = tki.Label(image=image)
self.panel.image = image
self.panel.pack(side="left", padx=10, pady=10)
# otherwise, simply update the panel
else:
self.panel.configure(image=image)
self.panel.image = image
except RuntimeError, e:
print("[INFO] caught a RuntimeError")
def takeSnapshot(self):
# grab the current timestamp and use it to construct the
# output path
ts = datetime.datetime.now()
filename = "{}.jpg".format(ts.strftime("%Y-%m-%d_%H-%M-%S"))
p = os.path.sep.join((self.outputPath, filename))
# save the file
cv2.imwrite(p, self.frame.copy())
print("[INFO] saved {}".format(filename))
def onClose(self):
# set the stop event, cleanup the camera, and allow the rest of
# the quit process to continue
print("[INFO] closing...")
self.stopEvent.set()
self.vs.stop()
self.root.quit()
Now the part for the detection face I think it should go in the videoLoop function and I added the face detection code to it so the function looks like this
try:
# keep looping over frames until we are instructed to stop
while not self.stopEvent.is_set():
# grab the frame from the video stream and resize it to
# have a maximum width of 300 pixels
self.frame = self.vs.read()
self.frame = imutils.resize(self.frame, width=300)
# OpenCV represents images in BGR order; however PIL
# represents images in RGB order, so we need to swap
# the channels, then convert to PIL and ImageTk format
detector = cv2.CascadeClassifier("C:\Proyectos\Python\GUI\tkinter-photo-booth\haarcascade_frontalface_default.xml")
image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
# load the cat detector Haar cascade, then detect cat faces
# in the input image
rects = detector.detectMultiScale(image, scaleFactor=1.5,
minNeighbors=5, minSize=(30, 30))
# loop over the cat faces and draw a rectangle surrounding each
for (i, (x, y, w, h)) in rects:
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
image = Image.fromarray(image)
image = ImageTk.PhotoImage(image)
# if the panel is not None, we need to initialize it
if self.panel is None:
self.panel = tki.Label(image=image)
self.panel.image = image
self.panel.pack(side="left", padx=10, pady=10)
# otherwise, simply update the panel
else:
self.panel.configure(image=image)
self.panel.image = image
But when I run the code, the rectangles to frame the face doesn't appear. I don't really know if the code to detect faces should go in the voidLoop function or if it should go in the snapshot function. I've already ask to Adrian in his webpage but I'm searching for extra help. Thanks in advance
Related
Hope you all are well!
Spent the last couple weeks researching image processing for my tkinter application and came up with this script:
import contextlib
import tkinter as tk
from PIL import Image, ImageTk, ImageSequence
import requests
from itertools import cycle
class ImageLabel(tk.Label):
"""
A Label that displays images, and plays them if they are gifs
:im: A PIL Image instance or a string filename
"""
def load(self, url, width, height=None):
request = requests.get(url, stream=True).raw
im = Image.open(request)
if (height != None):
size = (width, height)
else:
size = (width, get_relative_height(im, width))
try:
self.delay = im.info['duration']
except Exception:
self.delay = 100
global frames_complete
frames_complete = False
self.frames_chunk = cycle(process_frames(im, size))
if frames_complete:
self.next_frame()
def next_frame(self):
self.config(image=next(self.frames_chunk))
self.after(self.delay, self.next_frame)
def unload(self):
self.destroy()
def get_relative_height(source, mywidth):
_, height = source.size
wpercent = (mywidth/float(height))
return int((float(height)*float(wpercent)))
def process_frames(im, size): # resize and arrange gifs
frames_chunk = []
mode = analyseImage(im)["mode"]
last_frame = im.convert("RGBA")
for i, frame in enumerate(ImageSequence.Iterator(im)):
frame_image = Image.new("RGBA", frame.size)
if mode == "partial":
frame_image.paste(last_frame)
print(f'Processing frame {i}')
frame_image.paste(frame, (0, 0), frame.convert("RGBA"))
frame_image.thumbnail(size, Image.BICUBIC)
new_frame = ImageTk.PhotoImage(frame_image)
frames_chunk.append(new_frame)
print("appended frame to frames_chunk")
print("frames completed")
global frames_complete
frames_complete = True
return frames_chunk
def analyseImage(im):
"""
Pre-process pass over the image to determine the mode (full or additive).
Necessary as assessing single frames isn't reliable. Need to know the mode
before processing all frames.ll
"""
results = {
"size": im.size,
"mode": "full",
}
with contextlib.suppress(EOFError):
while True:
if im.tile:
tile = im.tile[0]
update_region = tile[1]
update_region_dimensions = update_region[2:]
if update_region_dimensions != im.size:
results["mode"] = "partial"
break
im.seek(im.tell() + 1)
return results
# test:
root = tk.Tk()
lbl = ImageLabel(root)
lbl.pack()
lbl.load("https://www.saic.edu/~anelso13/gif/images/cat14.gif", 300)
root.mainloop()
running this from inside a tkinter app slows the app down and also freezes the GUI until the frames are finished processing.
This class works alright when it's alone, but there are two major issues I've been struggling to solve,
The process_frames function itterates frame by frame and is very slow. in the app I'm working on I instance this class two times and it takes about 10 seconds to process and resize every frame. I ran the function inside a thread but it didn't seem to improve speed whatsoever.
2: The Main tkinter application freezes until both sets of frames process. I've looked at a few resources and tried a few implementations (tkinter: preventing main loop from freezing) and here.
I have a thread running in the program already which does work as expected but using the same method for processing the frames does not work.
Any and all help is greatly appreciated!
I've already succeded to plot a grayscale histogram of a video : for each image of the video, the histogram was updated to correspond to the current image. For this program I used the classic way, with the functions subplots, plot, set_ydata etc. I only had 2 windows : one with the video and one figure with the histogram, and now what I'm trying to do is to have only one window with the video and the histogram on it, and add buttons like "pause", "play" or "restart". With research I saw that Tkinter could be a way to do that, so I started to use it.
I configured all my window (with buttons, displaying the video and the histogram) and video is shown normally, but I can't update my histogram, my program just plot the first histogram (of the first image) and nothing else. I've already tried several things, like the tkinter animation, or to put an ax clear and a draw() in my function calc_hist() (with the function draw() I have an error "draw_wrapper() missing 1 required positional argument: 'renderer'", I didnt find what it corresponded to), but it's not working. Maybe I misused theses functions, so maybe you cand find what's going wrong with my code.
Here's my class App which configure the window and supposed to display the histogram (I delete useless part for my problem like functions and declaration of button to reduce the code) :
import tkinter
import cv2
import PIL.Image, PIL.ImageTk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import matplotlib.pyplot as plt
class App:
def __init__(self, window, window_title, video_source=0):
self.window = window
self.window.title(window_title)
self.video_source = video_source
self.vid = MyVideoCapture(self.video_source)
#Video
self.canvas = tkinter.Canvas(window, width = 640, height = 480)
self.canvas.grid(row=0, column = 0)
#Histogram
self.frame_hist = tkinter.Frame(window)
self.frame_hist.grid(row=0, column = 1)
self.figure = plt.Figure(figsize=(5,4), dpi = 100)
self.ax = self.figure.add_subplot()
self.canvas_hist = FigureCanvasTkAgg(self.figure, self.frame_hist)
self.canvas_hist.get_tk_widget().pack(fill = tkinter.BOTH, side = tkinter.TOP)
self.ax = self.figure.gca()
x = np.linspace(0, 255, 256)
y = np.linspace(10, 100000, 256)
self.canvas_hist, = self.ax.plot(x,y)
self.ax.set_ylabel('Nombre pixel', fontsize = 15)
self.ax.set_xlabel('Valeur pixel', fontsize = 15)
self.ax.set_yscale('log')
self.delay = 15
self.update()
self.window.mainloop()
def update(self):
ret, frame = self.vid.get_frame()
if ret :
self.gris = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
self.smaller_image = cv2.resize(self.gris,(640,480))
self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(self.smaller_image))
self.canvas.create_image(0, 0, image = self.photo, anchor = tkinter.NW)
self.calc_hist(self.gris)
self.window.after(self.delay, self.update)
def calc_hist(self, gris) :
self.histogram = cv2.calcHist([gris], [0], None, [256], [0, 256])
self.canvas_hist.set_ydata(self.histogram)
and here's the second part of my code with the video class to initialize it, I put you the code just in case but I think it's useless to look it, nothing matter to my problem in it :
class MyVideoCapture:
def __init__(self, video_source=0):
# Open the video source
self.vid = cv2.VideoCapture(video_source)
if not self.vid.isOpened():
raise ValueError("Unable to open video source", video_source)
# Get video source width and height
self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
def get_frame(self):
if self.vid.isOpened():
ret, frame = self.vid.read()
if ret:
# Return a boolean success flag and the current frame converted to BGR
return (ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
else:
return (ret, None)
else:
return (ret, None)
# Release the video source when the object is destroyed
def __del__(self):
if self.vid.isOpened():
self.vid.release()
# Create a window and pass it to the Application object
App(tkinter.Tk(), "Tkinter and OpenCV", "output.avi")
And here's my final interface :
When you update the y data, you need to refresh the graph using self.canvas_hist.draw().
However self.canvas_hist (instance of FigureCanvasTkAgg()) is overwritten by the line:
self.canvas_hist, = self.ax.plot(x, y)
So suggest to change the above line to:
self.graph, = self.ax.plot(x, y)
Then add self.canvas_hist.draw() at the end of calc_hist():
def calc_hist(self, gris):
histogram = cv2.calcHist([gris], [0], None, [256], [0, 256])
self.graph.set_ydata(histogram)
self.canvas_hist.draw()
My GUI is a slideshow of images and there is a certain correct image on each page of the slideshow. The correct image is the top-left triangle, heart balloon, the circle, and the hippo. I am completely new to python so I have been struggling with how to create box around the selected image when a user is using the slideshow. This box forms around the correct or the incorrect image, whichever is pressed. To further explain, this includes inserting a box around an image based on where the user clicks but it does not say whether the image selected is the correct image or not. Thank you for your help
import PIL.Image
import PIL.ImageDraw
import tkinter as tk
import PIL.ImageTk
import csv
from PIL import Image
MAX_HEIGHT = 500
# height of the window (dimensions of the image)
class App(tk.Frame):
def __init__(self, imageData, master=None):
tk.Frame.__init__(self, master)
self.clickStatus = tk.StringVar()
self.loadedImages = dict()
self.master.title('Slideshow')
fram = tk.Frame(self)
tk.Button(fram, text="Previous Image", command=self.prev).pack(side=tk.LEFT)
tk.Button(fram, text=" Next Image ", command=self.next).pack(side=tk.LEFT)
tk.Label(fram, textvariable=self.clickStatus, font='Helvetica 18 bold').pack(side=tk.RIGHT)
# inside or outside
fram.pack(side=tk.TOP, fill=tk.BOTH)
self.imageLabel = tk.Label(self)
# drawing the image on the label
self.imageData = imageData
self.currentIndex = 0
# start from 0th image
self.__loadImage__()
self.imageLabel.bind("<Button-1>", self.clicked_evt)
# when you click button, it opens event of clicked_evt
self.imageLabel.pack()
self.pack()
def clicked_evt(self, evt):
x, y = evt.x, evt.y
imgData = self.loadedImages[self.imageData[self.currentIndex]['image_file']]
(l, t), (r,b) = imgData['lt'], imgData['rb']
if t<=y<=b and l<=x<=r:
##self.clickStatus.set('inside')
print('Inside')
else:
##self.clickStatus.set('outside')
print('Outside')
def __loadImage__(self):
if self.imageData[self.currentIndex]['image_file'] not in self.loadedImages:
self.im = PIL.Image.open(self.imageData[self.currentIndex]['image_file'])
ratio = MAX_HEIGHT/self.im.height
# ratio divided by existing height -> to get constant amount
height, width = int(self.im.height*ratio), int(self.im.width * ratio)
# calculate the new h and w and then resize next
self.im = self.im.resize((width, height))
lt = (int(self.imageData[self.currentIndex]['left']*ratio), int(self.imageData[self.currentIndex]['top']*ratio))
rb = (int(self.imageData[self.currentIndex]['right']*ratio), int(self.imageData[self.currentIndex]['bottom']*ratio))
# modifying new ratios with new height and width
#shape = [lt, rb]
# print(shape)
#img1 = PIL.ImageDraw.Draw(self.im)
#img1.rectangle(shape, outline ="red")
if self.im.mode == "1":
self.img = PIL.ImageTk.BitmapImage(self.im, foreground="white")
else:
self.img = PIL.ImageTk.PhotoImage(self.im)
imgData = self.loadedImages.setdefault(self.imageData[self.currentIndex]['image_file'], dict())
imgData['image'] = self.img
imgData['lt'] = lt
imgData['rb'] = rb
# for next and previous so it loads the same image adn don't do calculations again
self.img = self.loadedImages[self.imageData[self.currentIndex]['image_file']]['image']
self.imageLabel.config(image=self.img, width=self.img.width(), height=self.img.height())
def prev(self):
self.currentIndex = (self.currentIndex+len(self.imageData) - 1 ) % len(self.imageData)
self.__loadImage__()
# here if i go to the first one and press back, goes to last, round robbin
def next(self):
self.currentIndex = (self.currentIndex + 1) % len(self.imageData)
self.__loadImage__()
# here if i go to the last one and press next, goes to first, round robbin
def loadData(fname):
with open(fname, mode='r') as f:
reader = csv.DictReader(f)
data = [dict(row) for row in reader]
for row in data:
row['top'], row['bottom'], row['left'], row['right'] = int(row['top']),int(row['bottom']),int(row['left']),int(row['right'])
return data
if __name__ == "__main__":
data = loadData('bounding_box.csv')
app = App(data)
app.mainloop()
As far as I understand you simply want to draw a rectangle around an image when it is clicked (explanation in code comments):
from tkinter import Tk, Canvas
from PIL import Image, ImageTk
# below code is used to create images, you can also load them from a file if you need
mode = 'RGB'
size = (150, 150)
color_lst = ['red', 'green', 'blue', 'yellow']
# best to get all the images for the slide in a single list for further easier workings
img_lst = [Image.new(mode, size, color) for color in color_lst]
# selecting function that will be called when user clicks on image
def select(id_):
canvas.create_rectangle(canvas.bbox(id_), width=5)
# here you will put other code to be executed after
# user clicks on the image like going to the next frame or sth
# additionally you could use this if you want the other rectangles to disappear
# but kinda pointless if you will switch frames and stuff
# for canvas_id in canvas_images:
# if canvas_id == id_:
# continue
# canvas.create_rectangle(canvas.bbox(canvas_id), width=5, outline='white')
root = Tk()
# here create a photoimage list from the above image list
photo_lst = [ImageTk.PhotoImage(image) for image in img_lst]
# create canvas
canvas = Canvas(root, width=400, height=400)
canvas.pack()
# set the coordinates for the four images
coordinates = [(100, 100), (300, 100), (100, 300), (300, 300)]
# create the images and append their id to a list
canvas_images = [
canvas.create_image(pos[0], pos[1], image=photo) for pos, photo in zip(coordinates, photo_lst)
]
# bind the ids from `canvas_images` to being clicked and execute simple drawing method
for c_img in canvas_images:
canvas.tag_bind(
c_img, '<Button-1>', lambda e, i=c_img: select(i)
)
root.mainloop()
I've written a piece of code to play a video using OpenCV on tkinter. Its part of a game I've made and compiled into an application. But I've noticed that when I play the game in a different computers, since the screen sizes are different, the video doesn't fit exactly to screen size like I want it to. The same goes for the background images I used in different pages but I wrote a piece of code to resize the background images to screen size. Here it is:
def resizeimage(self,event) :
width, height = self.winfo_width(), self.winfo_height()
image = self.bg_image.resize((width,height))
self.image1 = ImageTk.PhotoImage(image)
self.bg_label.config(image = self.image1)
I've bound this function to the label that displays the background image like this:
self.bg_image = Image.open("project_pics\\start_pg.png")
bg_image = ImageTk.PhotoImage(self.bg_image)
self.bg_label = Label(self,image=bg_image)
self.bg_label.image = bg_image
self.bg_label.bind('<Configure>',self.resizeimage)
self.bg_label.grid(sticky="nwse")
here, self.bg_image is the image to be displayed as background and self.bg_label is the label that displays the image.
I know that I can implement something similar by resizing the frames, in my code to play the video, but I cant seem to figure out a quick, efficient a way to do so. Here is the code for the video player:
from tkinter import *
from tkinter.ttk import Button
from PIL import Image, ImageTk
import time
import cv2 as cv2
from threading import Thread
from Scripts.music_player import m_player
from Scripts.styles import Styles
# The Video Player
class VideoPlayer :
def __init__(self,parent) :
self.parent = parent
self.play = False
def player(self,vid_file,m_file,nxt_func):
def get_frame():
ret,frame = vid.read()
if ret and self.play :
return(ret,cv2.cvtColor(frame,cv2.COLOR_BGR2RGB))
else :
return(ret,None)
def update() :
ret,frame = get_frame()
if ret and self.play :
img = Image.fromarray(frame)
photo = ImageTk.PhotoImage(image=img)
photo.image=img
self.canvas.itemconfig(self.vid_frame,image=photo)
self.canvas.image=photo
self.parent.after(delay,lambda : update())
else :
time.sleep(0.01)
# stopping vid_music and starting game music
m_player.music_control(m_file,True,-1,0)
m_player.music_control("project_media\\signal.ogg",False,-1,0)
nxt_func()
def skip() :
self.play = False
self.parent.clear()
self.play = True
# starting music
m_player.music_control("project_media\\signal.ogg",True,-1,0)
m_player.music_control(m_file,False,-1,0)
vid = cv2.VideoCapture(vid_file)
width = vid.get(cv2.CAP_PROP_FRAME_WIDTH)
height = vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.canvas = Canvas(self.parent, width = width, height = height)
self.canvas.place(relx=0.5,rely=0.5,anchor=CENTER)
self.vid_frame = self.canvas.create_image(0, 0, anchor = NW)
# Skip button
if vid_file != "project_media\\glitch.mp4" :
skip_thread = Thread(target=skip)
skip = Button(self.parent,text="Skip",command=skip_thread.start,style="skip.TButton")
skip.place(relx=0.88,rely=0.04)
delay = 5
update()
My question is this. How could I efficiently resize my frames to fit screen size without slowing down execution?. Also, the function I'm using right now to resize my background images also seems to be slowing down execution. So I can see something like a glitch on the screen every time I change pages. So is there any other way I can resize my background images. Sorry if the code is a bit messy. I'm a beginner and this is the first game I've made .
I'm trying to create a GUI for playing a video that fills up the entire screen, while the button for Snapshot is still visible at the bottom.
Right now, What i manage to do is just set the app window itself to fullscreen, resulting a small sized video playing at the top and a huge "snapshot" button at the button.
Is there a way to make the video fill up the entire screen?
thanks!
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('Cat Walking.mp4') # 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.root.attributes("-fullscreen",True)
#self.oot.wm_state('zoomed')
self.panel.config(image=imgtk) # show the image
self.root.after(1, 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()
It's not a hard task if you don't care about execution time! We knew that resizing of an image isn't a rocket science for common user, but under the hood it takes some time to resize each frame. And if you really wonder about time and options - there're many options to play around from numpy/scipy to skimage/skvideo.
But let's try to do something with your code "as is" so we have two options to play with: cv2 and Image. For testing I grabbed 20 secs of "Keyboard Cat" video from youtube (480p) and resize each frame upto 1080p, and GUI looks like this (fullscreen 1920x1080):
Resize Methods / timeit elapsed time of showing frames:
cv2.resize() / ~81.377 s.
Image.resize() / ~82.98 s.
As you see - no big difference between theese two so here's a code (only Application class and video_loop changed):
#imports
try:
import tkinter as tk
except:
import Tkinter as tk
from PIL import Image, ImageTk
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('KeyCat.mp4') # 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.root.attributes("-fullscreen", True)
# getting size to resize! 30 - space for button
self.size = (self.root.winfo_screenwidth(), self.root.winfo_screenheight() - 30)
self.panel = tk.Label(self.root) # initialize image panel
self.panel.pack(fill='both', expand=True)
# create a button, that when pressed, will take the current frame and save it to file
self.btn = tk.Button(self.root, text="Snapshot!", command=self.take_snapshot)
self.btn.pack(fill='x', expand=True)
# 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
cv2image = cv2.resize(cv2image, self.size, interpolation=cv2.INTER_NEAREST)
self.current_image = Image.fromarray(cv2image) #.resize(self.size, resample=Image.NEAREST) # convert image for PIL
self.panel.imgtk = ImageTk.PhotoImage(image=self.current_image)
self.panel.config(image=self.panel.imgtk) # show the image
self.root.after(1, self.video_loop) # call the same function after 30 milliseconds
But you knew - do such a things "on fly" isn't a good idea, so lets try to resize all frames first and then do all stuff(only Application class and video_loop method changed, resize_video method added):
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('KeyCat.mp4') # capture video frames, 0 is your default video camera
...
# init frames
self.frames = self.resize_video()
self.video_loop()
def resize_video(self):
temp = list()
try:
temp_count_const = cv2.CAP_PROP_FRAME_COUNT
except AttributeError:
temp_count_const = cv2.cv.CV_CAP_PROP_FRAME_COUNT
frames_count = self.vs.get(temp_count_const)
while self.vs.isOpened():
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
cv2image = cv2.resize(cv2image, self.size, interpolation=cv2.INTER_NEAREST)
cv2image = Image.fromarray(cv2image) # convert image for PIL
temp.append(cv2image)
# simple progress print w/o sys import
print('%d/%d\t%d%%' % (len(temp), frames_count, ((len(temp)/frames_count)*100)))
else:
return temp
def video_loop(self):
""" Get frame from the video stream and show it in Tkinter """
if len(self.frames) != 0:
self.current_image = self.frames.pop(0)
self.panel.imgtk = ImageTk.PhotoImage(self.current_image)
self.panel.config(image=self.panel.imgtk)
self.root.after(1, self.video_loop) # call the same function after 30 milliseconds
timeit elapsed time of showing pre-resized frames: ~78.78 s.
As you see - resizing isn't a main problem of your script, but a good option!