How to display videos on tkinter using sequence of images - python

I am trying to build a GUI that displays sequence of images as videos. The images are numpy arrays.
The code is working when I try to display one image at a time but it crashes when I try to run them as a sequence.
The code:
from tkinter import *
from scipy.io import loadmat
from PIL import ImageTk, Image
import time
data = loadmat('DepthFrames.mat')['DepthFrames'].squeeze(axis=0)
print(data.shape)
counter = 0
root = Tk()
image = ImageTk.PhotoImage(image = Image.fromarray(data[counter]))
root.title("WUDU VIDEOS LABEL TOOL")
myLabel = Label(root, image = image)
myLabel.grid(row = 0)
def changeImg():
global counter
counter +=1
print(counter)
image = ImageTk.PhotoImage(image = Image.fromarray(data[counter]))
myLabel.configure(image = image)
myLabel.image = image
def playVideo():
for i in range(10):
image = ImageTk.PhotoImage(image = Image.fromarray(data[i]))
myLabel.configure(image = image)
myLabel.image = image
time.sleep(0.03333)
my_Button = Button(text = "Play video",command = playVideo)
my_Button.grid(row = 1)
root.mainloop()

time.sleep blocks the main thread of tkinter. Your code will freeze the GUI until the for loop is completed and the image will be shown as the last image. For more details, see this post.
You need to use the after method. Something like this:
def playVideo(frame=0):
try:
image = ImageTk.PhotoImage(image = Image.fromarray(data[frame]))
except IndexError:
return
myLabel.configure(image = image)
myLabel.image = image
root.after(33, playVideo, frame+1)

Related

Best way if applicable to implement multiprocessing to this python image handler?

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!

My python tkinter buttons are updating too quickly after the wrong comparison is made

I'm trying to make a memory game for fun and as a learning experience, and I've run into the issue where even with something like time.sleep(.5) I still can't get buttons to update correctly with a delay. In fact the second button seems to update to hidden as it's about to show the proper image. I'm assuming the issue lies somewhere in the buttonClicked() function.
I'm trying to figure out how I can make it show one button, then the second, then wait half a second and hide both. And if someone understands why this is happening or where I could look into the issue and read up on my own, that would be helpful.
Thanks.
from re import A
import time
import tkinter as tk
from tkinter import *
from typing_extensions import Self
from PIL import Image, ImageTk
import glob
import os, os.path
import numpy as np
from sqlalchemy import null
#resize images and button
imgButtonWidth = 100
imgButtonHeight = 100
imgButtonSize = (imgButtonWidth,imgButtonHeight)
#Set the height and width of the game by number of items.
width = 6
height = 6
#buttons = [[Button]*width]*height
#Total number of items 36 (0-35)
count = width*height-1
buttonList = []
#Will be a 2d array of [button, id]
answersList = []
clickedCount = 0
imgs = []
hiddenImg = null
# Create frame, set default size of frame and background color.
root = Tk()
root.title('Memory Game')
root.geometry(str(imgButtonWidth * (width+1)) + "x" + str(imgButtonHeight * (height+1)))
root.config(bg='darkblue')
frame = Frame(root, bg='darkblue')
# Fetch images from location and create a list of Image objects, then return.
def getImages():
imgs = []
path = "/home/paul/Programming/Python/MyMiniProjects/Mid/MemoryGame/"
valid_images = [".jpg",".gif",".png",".tga"]
for f in os.listdir(path):
ext = os.path.splitext(f)[1]
if ext.lower() not in valid_images:
continue
imgs.append([Image.open(os.path.join(path,f)).resize(imgButtonSize), f])
return imgs + imgs
#Shuffle images for the game
imgs = getImages()
random.shuffle(imgs)
#Simple image to cover the tiles
hiddenImg = ImageTk.PhotoImage(Image.new('RGB', (imgButtonWidth, imgButtonHeight), (0,0,105)))
#Disable buttons after a match
def disable():
global clickedCount, answersList
clickedCount = 0
for a in answersList:
a[0]["state"] = "disabled"
a[0]["bg"] = "green"
answersList = []
#Hide buttons again
def hide():
global clickedCount, answersList
clickedCount = 0
for a in answersList:
#a[0].config(image = hiddenImg)
a[0]["image"] = hiddenImg
a[0]["state"] = "normal"
a[0]["bg"] = "white"
answersList = []
def wrong():
for a in answersList:
a[0]["bg"] = "red"
def buttonClicked(picture, id, button):
global clickedCount, answersList
print(clickedCount, len(answersList))
#print(button.image, "1", hiddenImg, picture)
if button.image is hiddenImg and clickedCount < 2:
button["image"] = picture
button["state"] = "disabled"
clickedCount += 1
answersList.append([button, id])
if len(answersList) == 2:
#Check id but make sure it's not the same button pressed twice
if answersList[0][1] is answersList[1][1]:#and answersList[0][0] is not answersList[1][0]:
disable()
else:
wrong()
hide()
#Create the actual buttons with their respective image
for h in range(height): #print(buttons[w][::],"\n")
newList = []
for w in range(width):
tempImage = imgs.pop(count)
picture = ImageTk.PhotoImage(tempImage[0])
id = tempImage[1]
button = Button(frame, image=hiddenImg, state=NORMAL, height=imgButtonHeight, width=imgButtonWidth)
#Need to split this up because of how python handles closures
button["command"] = lambda pic_temp=picture, id_temp=id, button_temp = button: buttonClicked(pic_temp, id_temp, button_temp)
button.image = hiddenImg
#buttons[w][h].name = str(w + h)
#buttons[w][h].grid(row=w, column=h, ipadx=random.randint(0,40), ipady=random.randint(0,40), padx=random.randint(0,5), pady=random.randint(0,5))
button.grid(row=h, column=w, padx=1, pady=1)
#Button(frame, image=picture).grid(row=w, column=h, ipadx=random.randint(0,40), ipady=random.randint(0,40), padx=random.randint(0,5), pady=random.randint(0,5))
count -= 1
# buttonList.append(buttons[h][w])
newList.append(button)
buttonList.append(newList)
# for y in range(height):
# for x in range(width):
# print(ButtonList[y][x])
# print("")
frame.pack(expand=True)
root.mainloop()```

Python - tkinter; TypeError: Expected Ptr<cv::UMat> for argument 'src'

I'm trying to build a graphical interface for image processing. I have problem uploading function.
When I try to check if the file / image exists and modify it, I get this error and I don't know how to fix it.
TypeError: Expected Ptr<cv::UMat> for argument 'src'
This is my code:
import cv2
import instructions as instructions
from PIL import Image,ImageTk
from tkinter.filedialog import askopenfile
root = tk.Tk()
logo = Image.open('logo.png')
logo = ImageTk.PhotoImage(logo)
logo_label = tk.Label(image = logo)
logo_label.image = logo
logo_label.grid(column=1,row=0)
def upload():
browse.set("loading...")
file = askopenfile(parent=root,mode="rb",title="Choose an image",filetypes =[("JPG file","*.jpg"),("PNG file","*.png"),("JPEG file","*.jpeg")])
if file:
gray = cv2.cvtColor(file, cv2.COLOR_RGB2GRAY)
gray = cv2.medianBlur(gray,5)
edges = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY, 9, 9)
color = cv2.bilateralFilter(file,9,250,250)
cartoon =cv2.bitwise_and(color,color,mask=edges)
cv2.imshow("Cartoon", cartoon)
intructions = tk.Label(root,text= "Select an image",font = "Raleway")
instructions.grid(columnspan=3,column=0,row=1)
browse = tk.StringVar()
browse_button = tk.Button(root,textvariable = browse,command = lambda:upload(),font = "Raleway",bg="#20bebe",fg ="white",width=15,height =2)
browse.set("Browse")
browse_button.grid(column=1,row=2)
canvas = tk.Canvas(root,width = 600,height = 300)
canvas.grid(columnspan = 3)
root.mainloop()
Thank you!
In line no 20: you are using CvtColor to change the image to grayscale.
gray = cv2.cvtColor(file, cv2.COLOR_RGB2GRAY) you need to pass the file pointer in place of file.
That is why you are getting the error TypeError: Expected Ptr<cv::UMat> for argument 'src'
You need to :
First read the file using img=cv2.imread(file), and then use
CvtColor on the img data using gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).
So delete the content in line 20 and add
img=cv2.imread(file)
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
Edit:
One important thing on line 6 you are importing askopenfile which returns a binary encoded path of the image. This is wrong implementation.
Instead you should import askopenfilename; this returns the path of the image file.
I am sharing the entire updated code here :
import cv2
import tkinter as tk
import instructions as instructions
from PIL import Image,ImageTk
from tkinter.filedialog import askopenfilename
root = tk.Tk()
logo = Image.open('logo.jpg')
logo = ImageTk.PhotoImage(logo)
logo_label = tk.Label(image = logo)
logo_label.image = logo
logo_label.grid(column=1,row=0)
def upload():
browse.set("loading...")
file = askopenfilename(parent=root,title="Choose an image",filetypes =[("JPG file","*.jpg"),("PNG file","*.png"),("JPEG file","*.jpeg")])
print(file)
if file:
img=cv2.imread(file)
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
gray = cv2.medianBlur(gray,5)
edges = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY, 9, 9)
color = cv2.bilateralFilter(img,9,250,250)
cartoon =cv2.bitwise_and(color,color,mask=edges)
cv2.imshow("Cartoon", cartoon)
intructions = tk.Label(root,text= "Select an image",font = "Raleway")
instructions.grid(columnspan=3,column=0,row=1)
browse = tk.StringVar()
browse_button = tk.Button(root,textvariable = browse,command = lambda:upload(),font = "Raleway",bg="#20bebe",fg ="white",width=15,height =2)
browse.set("Browse")
browse_button.grid(column=1,row=2)
canvas = tk.Canvas(root,width = 600,height = 300)
canvas.grid(columnspan = 3)
root.mainloop()
This should work let me know.
Hope this solves your purpose
You need to pass an image to cv2.cvtColor, you are currently passing a string - file.
if file:
src = cv2.imread(file)
gray = cv2.cvtColor(src, cv2.COLOR_RGB2GRAY)
gray = cv2.medianBlur(gray,5)

Resize video frame to fit tkinter window size when the window is resized

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 .

Tkinter display other windows

Is it possible to display a window which has been made by OpenCV using Tkinter? I want to open it using Tkinter so that I can provide more GUI functions. Has this been done before? I checked google and SO itself but did not find anything.
So as kobejohn suggested, I am attaching the code for the camera capture and display.
import cv2
import urllib
import numpy as np
import subprocess
stream=urllib.urlopen('IP Address')
bytes=''
while True:
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:]
i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8),cv2.CV_LOAD_IMAGE_COLOR)
cv2.imshow('i',i)
if cv2.waitKey(1) ==27:
exit(0)
This code is based on the discussion in comments. It doesn't put the opencv window into tkinter. It just takes opencv images and puts them into tkinter.
Prakhar, I don't have an available IP camera so can you try this? I have confirmed that it works with the USB code at the bottom of this answer.
Basically, I just inserted your jpg reading code into a simplified version of this SO question to get the code below. It uses a 2-step conversion: bytes --> opencv image --> tkinter image. There may be a more efficient way to convert directly from the bytes to a tkinter image but you can fix that if performance becomes a problem.
IP Camera
import cv2
import numpy as np
import PIL.Image
import PIL.ImageTk
import Tkinter as tk
import urllib
stream = urllib.urlopen('IP Address')
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:
root.destroy() # this avoids the update event being in limbo
else:
update_image(image_label)
root.after(1, func=lambda: update_all(root, image_label))
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(0, func=lambda: update_all(root, image_label))
root.mainloop()
USB Camera
*edit - I have confirmed that the code below works to take video from a USB camera using opencv and send it to a tkinter window. So hopefully the above code will work for your ip camera.
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)
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()

Categories