'What is the fastest Method to display an Image in a Frame?

I use 2 different Threads to Capture and to Display a Webcamstream.

I checked the FPS in the Capture and in the Display Function.

While the Capture Function can deliver full FPS, the Display Function is slower and getting slower with higher Resolutions.

Is there a faster Way to display the (now synced)Webcamstream in a TKinter Frame?

My goal would be getting synced 20 FPS in Display (and later in Record) after Resizing.

My Testprogramm:

global myfps
myfps = 0
global my_width
my_width = 640
global my_height
my_height = 480

def fakefunc():
    _=7
        
def display_start():
    print('display_start activated')
    gv.test_wiedergabe = 'True'
    mts = multi_thread_stream(ready)

def display_stop():
    print('display_stop activated')
    gv.test_wiedergabe = 'False'
    
def aufnahme_start():
    aufnahme = 'True'
        
class multi_thread_stream:
    def __init__(self, ready=None):
        self.ready = ready
        self.cap = cv2.VideoCapture(0)
        self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 2)
        
        self.AIM_FPS = 1/25
        self.AIM_FPS_MS = int(self.AIM_FPS * 1000)
        
        self.cap.set(3, my_width)
        self.cap.set(4, my_height)
        
        self.frame = {}
        self.fps = 0
        self.wiedergabe = 'False'
        #Create the Threads
        self.t1 = threading.Thread(target=self.capture_stream)
        self.t2 = threading.Thread(target=self.display_image)
        self.t1.name = 'capture_thread'
        self.t2.name = 'display_thread'
        #die starts in eine funktion packen, für start und stop ?
        self.t1.start()
        self.t2.start()
        #self.t1.join()
        #self.t2.join()
        self.aufnahme = 'False'
        frames_per_second = 20
        basepathinc = r'C:/Videotool/Videos'
        isExist = os.path.exists(basepathinc)
        print('path exists :',isExist)
        print('video fullpath :',basepathinc)
        if not isExist:
            os.makedirs(basepathinc)
            print(f'path {basepathinc} created')
        os.chdir(basepathinc)
    
 
    def set_res(key):
        global my_width
        global my_height
        match key:
            case 480:
                my_width = 640
                my_height = 480
                print('480p selected')
            case 720:
                my_width = 1280
                my_height = 720
                print('720p selected')
            case 1080:
                my_width = 1920
                my_height = 1080
                print('1080p selected')
            case 4:
                my_width = 3840
                my_height = 2160
                print('4k selected')
            case _:
                print('wrong selection')

    def capture_stream(self):
        capture_counter = 1
        old_seconds = 0
        seconds = 0
        while True:
            self.ret, self.frame = self.cap.read()
            self.ready.set()
            now = datetime.now()
            seconds = int(now.strftime("%S"))
            #print(f'Frame: {capture_counter} in Second :{seconds}')
            
            if seconds > old_seconds:
                print(f'Captured Frames: {capture_counter-1} in Second : {old_seconds}')
                capture_counter = 2
            else:
                capture_counter += 1
            
            old_seconds = seconds
            time.sleep(self.AIM_FPS)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
    
    def display_image(self):
        self.start_time = time.time()
        self.framerate_trigger = 1  
        self.fps_counter = 0
        display_counter = 0
        old_seconds = 0
        seconds = 0
        while True:
            # Display the resulting frame
            if gv.test_wiedergabe == 'True':
                self.ready.wait()
                #_________________________________
                now = datetime.now()
                seconds = int(now.strftime("%S"))
                if seconds > old_seconds:
                    print(f'Displayed Frames: {display_counter-1} in Second : {old_seconds}')
                    display_counter = 2
                else:
                    display_counter += 1
            
                old_seconds = seconds
                #_________________________________
                framexd=cv2.cvtColor(self.frame,cv2.COLOR_BGR2RGB)
                self.ready.clear()
                self.fps_counter+=1
                if (time.time() - self.start_time) > self.framerate_trigger :
                    #print("FPS: ", self.fps_counter / (time.time() - self.start_time))
                    self.fps = round(int(self.fps_counter / (time.time() - self.start_time)))
                    self.fps_counter = 0
                    self.start_time = time.time()
                global myfps
                myfps = self.fps
                stream_width = self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)
                stream_height = self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
                global_cam_display_fps = int(self.cap.get(cv2.CAP_PROP_FPS))
                #print('FPS:',self.fps)
                fps_x_string           = str(self.fps)
                font                   = cv2.FONT_HERSHEY_TRIPLEX
                fontScale              = 1
                fontColor              = (255,0,0)
                thickness              = 2
                lineType               = 2
                frame_wfps = cv2.putText(framexd,fps_x_string, (0,30), font, fontScale,fontColor,thickness,cv2.LINE_AA)
            
                framexd=Image.fromarray(framexd)
            
                #framexd=Image.fromarray(framexd)
                framexd=ImageTk.PhotoImage(framexd)
                videolabel.configure(image=framexd)
                videolabel.image=framexd
                infolabel.configure(text = f'myFPS: {myfps} /n Auflösung: {stream_width}x{stream_height}')
                #Aufnahme________________________________________________________________________________________
                """
                if self.aufnahme == 'True':
                    self.out.write(frame_wtext_startup)
                else:
                    self.out.release()
                """
                #________________________________________________________________________________________________
              
                
                    
                if cv2.waitKey(1) & 0xFF == ord('q'):
                    break;
            else:
                self.cap.release()
                #self.t1._stop() funkst erst wenn der thread daemonisch ist! also thread daemonisch öffnen
                #self.t2._stop()
                break
    
    
    def __del__(self):
        # When everything done, release the capture
        self.cap.release()
        cv2.destroyAllWindows()


if __name__ == "__main__":    
    print('start.py was started directly')
    
    ready = threading.Event()
    ready = ready
    
    root = Tk()
    root.title('Test Videoverarbeitung')
    root.geometry('800x600')
    
    helv36 = font.Font(family='Helvetica', size=12, weight=font.BOLD)
    
    mainframe = Frame(root)
    mainframe.place(relheight = 1, relwidth = 1)
    
    videoframe = Frame(mainframe)
    videoframe.place(x = 0, y = 0, relheight = 1, relwidth = 0.8)
    
    videolabel = Label(videoframe, text = "Videoframe")
    videolabel.place(relx = 0.5, rely = 0.5, relwidth = 1, relheight = 1, anchor="center")
    
    infolabel = Label(videoframe, text = "Videoframe")
    infolabel.place(relx = 0, rely = 0.8, relwidth = 1, relheight = 0.2)
    
    controlframe = Frame(mainframe)
    controlframe.place(relx = 0.8, y = 0, relheight = 1, relwidth = 0.2)
    
    button1 = Button(controlframe, width=50, text = "Wiedergabe", command=lambda:display_start(), bd = 2, relief = "groove", overrelief = "sunken", font = helv36)
    button1.place(relx = 0, rely = 0, relheight=0.2, relwidth = 1)
    
    button2 = Button(controlframe, width=50, text = "Wiedergabe Stop", command=lambda:display_stop(), bd = 2, relief = "groove", overrelief = "sunken", font = helv36)
    button2.place(relx = 0, rely =0.2, relheight=0.2, relwidth = 1)
    
    button3 = Button(controlframe, width=50, text = "Aufnahme", command=lambda:aufnahme_start(), bd = 2, relief = "groove", overrelief = "sunken", font = helv36)
    button3.place(relx = 0, rely =0.4, relheight=0.2, relwidth = 1)
    
    button3 = Button(controlframe, width=50, text = "Aufnahme Stop (oF)", command=lambda:fakefunc(), bd = 2, relief = "groove", overrelief = "sunken", font = helv36)
    button3.place(relx = 0, rely =0.6, relheight=0.2, relwidth = 1)
    
    checkbutton1 = Checkbutton(controlframe, text = '480p',command=lambda:multi_thread_stream.set_res(480))
    checkbutton1.place(relx = 0, rely = 0.8)
    
    checkbutton1 = Checkbutton(controlframe, text = '720p',command=lambda:multi_thread_stream.set_res(720))
    checkbutton1.place(relx = 0, rely = 0.84)
    
    checkbutton1 = Checkbutton(controlframe, text = '1080p',command=lambda:multi_thread_stream.set_res(1080))
    checkbutton1.place(relx = 0, rely = 0.88)
    
    checkbutton1 = Checkbutton(controlframe, text = '4k',command=lambda:multi_thread_stream.set_res(4))
    checkbutton1.place(relx = 0, rely = 0.92)

    root.mainloop()
    
else: 
    _=7

I am through many Threads about this, but didnt find a Solution.

I created this as my minimal Code Example, to prevent the "use Threads" Suggestion. But to cut out the Part I would like to improve:

framexd=cv2.cvtColor(self.frame,cv2.COLOR_BGR2RGB)
self.ready.clear()
framexd=Image.fromarray(framexd)
framexd=ImageTk.PhotoImage(framexd)
videolabel.configure(image=framexd)
videolabel.image=framexd

This Part is working up to 50% slower then the Capture. I would like to speed it up with other Ideas like Reducing Resolution and the Use of better Hardware.

Next Time I will avoid the bizarre Capitalizations in my Code.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source