title: Python Multi camera FPS is so slow despite using threads
body: <p>I tried to dynamically display multi live camera in python tkinter with threads.
  But I have a FPS problem and performance unfortunately bad. The program use to ip camera links dynamically create ui and then frame using in threads. But despite to using threads, performance not satisfactory. How can I improve performance ?</p>
  <pre><code>from functools import partial
  
  from PIL import Image, ImageTk
  import tkinter as tk
  from tkinter import messagebox
  from tkinter import font as tkFont
  import argparse
  import datetime
  import cv2
  import imutils
  import os
  from threading import Thread
  
  
  variable = 1
  threads = []
  camera_urls = ['rtsp://mycamera1.cgi', 'mycamera2.cgi']
  
  video_captures=[]
  video_panels=[]
  video_currentImages=[]
  snaphsot_display_panels=[]
  video_labels=[]
  
  
  class Application:
      def __init__(self, output_path=&quot;./&quot;):
  
          self.output_path = output_path
          self.current_image = None
          self.root = tk.Tk()
          self.root.title(&quot;Kamera&quot;)
          self.root.protocol('WM_DELETE_WINDOW', self.destructor)
  
          for indexofUrl in range(len(camera_urls)):
              print(&quot;[INFO] URL::&quot; + camera_urls[indexofUrl])
              self.vs = cv2.VideoCapture(camera_urls[indexofUrl])
              video_captures.append(self.vs)
              self.panel = tk.Label(self.root, borderwidth=5, relief=&quot;sunken&quot;, bg=&quot;green&quot;)
              self.panel.grid(row=0, column=indexofUrl, padx=20, pady=20)
              video_panels.append(self.panel)
              print(&quot;[INFO] STEP:: 1&quot;)
              mylabel = tk.Label(text=&quot;Kamera &quot; +str(indexofUrl), bg=&quot;black&quot;, fg=&quot;white&quot;, font=(None, 15))
              mylabel.grid(row=1, column=indexofUrl)
              print(&quot;[INFO] STEP:: 2&quot;)
              btn = tk.Button(self.root, text=&quot;Görüntüyü kaydet(Kamera &quot;+str(indexofUrl)+&quot;)&quot;, command=partial(self.take_snapshot,indexofUrl), bg=&quot;green&quot;, fg='white')
              btn.grid(row=2, column=indexofUrl, padx=20, pady=20)
              print(&quot;[INFO] STEP:: 3&quot;)
              self.panel3 = tk.Label(self.root)
              self.panel3.grid(row=3, column=indexofUrl)
              print(&quot;[INFO] STEP:: 4&quot;)
              self.mylabel2 = tk.Label(text=&quot;Snapshot bilgileri:&quot;, bg=&quot;blue&quot;, fg=&quot;white&quot;, font=(None, 15))
              self.mylabel2.grid(row=4, column=indexofUrl)
              video_labels.append(self.mylabel2)
              print(&quot;[INFO] STEP:: 5&quot;)
              self.panel4 = tk.Label(self.root,relief=&quot;sunken&quot;, borderwidth=5, bg=&quot;black&quot;)
              self.panel4.grid(row=5, column=indexofUrl, padx=20, pady=20)
              snaphsot_display_panels.append(self.panel4)
  
              mythread = Thread(target=self.my_video_loop, args=())
              mythread.daemon = True
              mythread.start()
              threads.append(mythread)
          for t in threads:
              t.join()
  
          #self.my_video_loop()
          self.thread2 = Thread(target= self.my_video_loop, args=())
          self.thread2.daemon=True
          self.thread2.start()
  
  
  
      def my_video_loop(self):
          for indexOfVideCaptures in range(len(video_captures)):
              ok, frame= video_captures[indexOfVideCaptures].read()
              frame=self.maintain_aspect_ratio_resize(frame , width=400)
              if ok:
                  if len(video_currentImages) &gt; len(camera_urls):
                      video_currentImages.clear()
                  cv2image = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
                  self.current_image = Image.fromarray(cv2image)
                  video_currentImages.append(self.current_image)
                  imgtk = ImageTk.PhotoImage(image=self.current_image)
                  video_panels[indexOfVideCaptures].imgtk =imgtk
                  video_panels[indexOfVideCaptures].config(image=imgtk)
          self.root.after(30, self.my_video_loop)
  
  
      # Resizes a image and maintains aspect ratio
      def maintain_aspect_ratio_resize(self, image, width=None, height=None, inter=cv2.INTER_AREA):
          # Grab the image size and initialize dimensions
          dim = None
          (h, w) = image.shape[:2]
  
          # Return original image if no need to resize
          if width is None and height is None:
              return image
  
          # We are resizing height if width is none
          if width is None:
              # Calculate the ratio of the height and construct the dimensions
              r = height / float(h)
              dim = (int(w * r), height)
          # We are resizing width if height is none
          else:
              # Calculate the ratio of the 0idth and construct the dimensions
              r = width / float(w)
              dim = (width, int(h * r))
  
          # Return the resized image
          return cv2.resize(image, dim, interpolation=inter)
  
      def take_snapshot(self, camera):
          ts = datetime.datetime.now()  # current timestamp
          filename = &quot;{}.png&quot;.format(ts.strftime(&quot;%Y-%m-%d__%H-%M-%S&quot;))  #filename
          p = os.path.join(self.output_path, filename)  # output path
          if camera &gt;= 0:
              if len(video_currentImages) == 0:
                  self.take_snapshot(camera)
                  # self.root.after(500, self.take_snapshot(camera))
              elif len(video_currentImages) == len(camera_urls):
                  video_currentImages[camera].save(p, &quot;PNG&quot;)  # save image as jpeg file
                  print(&quot;[INFO] saved {}&quot;.format(filename))
                  imgtk3 = ImageTk.PhotoImage(image=video_currentImages[camera])
                  snaphsot_display_panels[camera].imgtk =imgtk3
                  snaphsot_display_panels[camera].config(image=imgtk3)
                  video_labels[camera].config(text=filename.rstrip(&quot;.png&quot;) + f&quot;\n KAMERA {camera}&quot;)
  
      def destructor(self):
          &quot;&quot;&quot; Destroy the root object and release all resources &quot;&quot;&quot;
          print(&quot;[INFO] closing...&quot;)
          self.root.destroy()
          for indexcameras in range(len(video_captures)):
              video_captures[indexcameras].release()  # release camera
          cv2.destroyAllWindows()  # it is not mandatory in this application
  
  
  # construct the argument parse and parse the arguments
  ap = argparse.ArgumentParser()
  ap.add_argument(&quot;-o&quot;, &quot;--output&quot;, default=&quot;./&quot;,
                  help=&quot;path to output directory to store snapshots (default: current folder&quot;)
  args = vars(ap.parse_args())
  
  # start the app
  print(&quot;[INFO] starting...&quot;)
  pba = Application(args[&quot;output&quot;])
  pba.root.mainloop()
  
  
  </code></pre>