What's a faster alternative to PIL.ImageTk.PhotoImage?

Viewed 570

I'm trying to display a video, and using the examples/solutions to the question on how to play a video on Tkinter don't seemt to yeild smooth and fast rendering.

For example:

frame = self.vid_cap.read()
frame = cv2.resize(frame, (696, 486))
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

self.latest_img_ = Image.fromarray(frame)
self.latest_img_ = ImageTk.PhotoImage(image=self.latest_img_)

self.canvas.create_image(0, 0, anchor="nw", image=self.latest_img_)

The above code works, but video is too slow, even when using something like self.canvas.after(1, self.video_loop_). After some benchmarking, it turned out that the following line:

self.latest_img_ = ImageTk.PhotoImage(image=self.latest_img_)

Takes 9ms ~ 100ms regardless when I run the code on my quad-core mac or 12-core rig, while the entire code above it (from capturing, and until converting the numpy array to an image) takes 0ms to 1ms only.

What could be a faster alternative to ImageTk.PhotoImage?

1 Answers

You should keep a reference of the canvas object and modify it instead of creating new objects every time. To do that you can first create a placeholder by passing image=None. Below is a minimum sample:

import tkinter as tk
import cv2
from PIL import Image, ImageTk


class GUI(tk.Tk):
    def __init__(self):
        super().__init__()
        self.canvas = tk.Canvas(self)
        self.canvas.pack(fill="both", expand=1)
        self.streaming_image = self.canvas.create_image(0, 0, anchor="nw", image=None)

    def show_frame(self):
        _, frame = self.vid_cap.read()
        frame = cv2.resize(frame, (696, 486))
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        img = Image.fromarray(frame)
        self.latest_img = ImageTk.PhotoImage(image=img)
        self.canvas.itemconfig(self.streaming_image, image=self.latest_img)
        self.after(10, self.show_frame)

    def start_video(self, path=None):
        self.vid_cap = cv2.VideoCapture(path)
        self.show_frame()


root = GUI()
root.start_video("your_path_here")
root.mainloop()
Related