Display video camera in two tkinter windows

i want to display video camera in two different windows using tkinter. i have tried a lot but it only displays the video in one frame.
Here’s my code. is there any thing i can fixe on it?

#add the necessairy librairy
import tkinter as tk
import threading
import time
import subprocess
from imutils.video import VideoStream
import imutils
import cv2
import argparse
from PIL import Image, ImageTk

#class to construct the second video window
class PhotoBoothApp:
	def __init__(self, vs):
		# store the video stream object and output path,
		self.vs = vs
		# initialize read frame,
		self.frame = None
		# initialize the thread stop event
		self.thread = None
		self.stopEvent_2 = None
		# initialize the root window and image panel
		self.root = tk.Tk()
		self.panel = None    		
		self.but_frame = tk.Frame(self.root)
		# create a button, that when pressed, will take the current
		# frame and save it to file
		btn = tk.Button(self.but_frame, bd = '5',text=" photo ")
		self.but_frame.pack(side="left")
		# start a thread that constantly pools the video sensor for
		# the most recently read frame
		self.stopEvent_2 = 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("Reglage")
		self.root.wm_protocol("WM_DELETE_WINDOW", self.onClose)

	def videoLoop(self):
		# 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_2.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 = tk.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 :
			print("[INFO] caught a RuntimeError")

	def onClose(self):
		# set the stop event 
		print("[INFO] closing...")
		self.stopEvent_2.set()
		#cleanup the camera, 
		self.vs.stop()
		# the quit process to continue
		self.root.quit()

def Run_video_cam2():
	#open the second frame with video is displaying on it 
	pba = PhotoBoothApp(camera)
	pba.root.mainloop() 
	
def fun_Loop():
	#add some variable
	global camera
	global frame
	panel = None
	#open the second video stream in the second window
	camera = VideoStream(0).start()
	try:
		frame = camera.read()
		# keep looping over frames until we are instructed to stop
		while not stopEvent.is_set():
			print ('----------------------------------------------')
			frame = camera.read()
			#cv2.imshow('video frame1', frame)
			# 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(frame, cv2.COLOR_BGR2RGB)
			image = Image.fromarray(image)
			image = ImageTk.PhotoImage(image)

			# if the panel is not None, we need to initialize it
			if panel is None:
				panel = tk.Label(image=image)
				#put the image inside the paneml
				panel.image = image
				panel.pack(side="left", padx=10, pady=10)

			# otherwise, simply update the panel
			else:
				panel.configure(image=image)
				panel.image = image
		#stop the runnig camera
		camera.release()
	except RuntimeError :
		print("+_[INFO] caught a RuntimeError_+")
			
# here's the main window
Mafenetre = tk.Tk()
#set main window title
Mafenetre.title("GUI")
Mafenetre['bg']='white' # couleur de fond
# get screen width and height
wf1= Mafenetre.winfo_screenwidth()
hf1= Mafenetre.winfo_screenheight()
A = str(wf1)
B = str(hf1)
# set the dimensions of the screen 
# and where it is placed

w = 500 # width for the Tk root
h = 500 # height for the Tk root
# get screen width and height
ws = Mafenetre.winfo_screenwidth() # width of the screen
hs = Mafenetre.winfo_screenheight() # height of the screen
# calculate x and y coordinates for the Tk master window
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
# set the dimensions of the screen 
# and where it is placed
Mafenetre.geometry('%dx%d+%d+%d' % (w, h, x, y))
# add test button to the main window
reglage = tk.Button(Mafenetre, bd = '5',text="  PARAM  ", bg='#c42034', fg ='white',font=("Helvetica", 12), command=Run_video_cam2)
reglage.pack(side=tk.TOP, padx=6,pady=35)
thread = None
stopEvent = None
# start a thread that constantly pools the video sensor for
# the most recently read frame
stopEvent = threading.Event()
thread = threading.Thread(target=fun_Loop, args=())
thread.start()
Mafenetre.mainloop()

I didn’t test this code but tkinter should have only one main window created with tk.Tk() and it should run only one mainloop(). If you create second tk.Tk() with second mainloop() then it blocks first window and first mainloop() and this can make problem.

You should create second window with tk.Toplevel() and without second mainloop().

Besides, as I know VideoStream already use Thread to read stream and you could use tk.after() instead of thread to update image in Label. And you could create empty Label at start and you wouldn’t have to test if panel is None.


EDIT:

Here is my version which display video in two windows without using threads

Both windows uses similar classes.

import tkinter as tk
from PIL import Image, ImageTk
from imutils.video import VideoStream
import cv2
import datetime

class MainWindow:

    def __init__(self):    
        self.window = tk.Tk()

        #self.window.title("GUI")
        #self.window['bg'] = 'white'
        
        #width  = 500
        #height = 500

        #screen_width  = self.window.winfo_screenwidth()
        #screen_height = self.window.winfo_screenheight()

        #x = (screen_width - width)//2
        #y = (screen_height - height)//2
        
        #self.window.geometry('{}x{}+{}+{}'.format(width, height, x, y))
        #self.window.geometry('{}x{}'.format(width, height))
        
        self.button = tk.Button(self.window, text='Open Second Window', command=self.open_second_window)
        self.button.pack(side='top')
        
        self.panel = tk.Label(self.window)
        self.panel.pack(side="left")

        self.stream = VideoStream(0)
        self.stream.start()
        
        self.stop = False
        self.window.after(100, self.video_loop)

        self.window.wm_protocol("WM_DELETE_WINDOW", self.on_close)
        self.window.mainloop()

    def video_loop(self):
        frame = self.stream.read()
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        self.image = Image.fromarray(image)
        self.photo = ImageTk.PhotoImage(self.image)  # assigned to class variable `self.photo` so it resolves problem with bug in PhotoImage
        self.panel.configure(image=self.photo)  
        if not self.stop:
            self.window.after(40, self.video_loop)   # 40ms = 25FPS
            #self.window.after(25, self.video_loop)   # 25ms = 40FPS

    def on_close(self):
        self.stop = True
        self.stream.stop()
        self.window.destroy()
        
    def open_second_window(self):
        #open the second frame with video is displaying on it 
        self.pba = PhotoBoothApp(self.stream)

        
#class to construct the second video window
class PhotoBoothApp:

    def __init__(self, stream):
        self.stream = stream

        self.window = tk.Toplevel()

        self.button = tk.Button(self.window, text="Take Photo", command=self.take_photo)
        self.button.pack(side="top")
        
        self.panel = tk.Label(self.window)
        self.panel.pack(side="left")
        
        self.stop = False
        self.window.after(100, self.video_loop)
        
        self.window.wm_protocol("WM_DELETE_WINDOW", self.on_close)

    def video_loop(self):
        frame = self.stream.read()
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        self.image = Image.fromarray(image)
        self.photo = ImageTk.PhotoImage(self.image)  # assigned to class variable `self.photo` so it resolves problem with bug in PhotoImage
        self.panel.configure(image=self.photo)
        if not self.stop:
            self.window.after(40, self.video_loop)   # 40ms = 25FPS
            #self.window.after(25, self.video_loop)   # 25ms = 40FPS

    def on_close(self):   # PEP8: `lower_case_names` for functions/methods/variables
        self.stop = True
        #self.stream.stop()   # don't stop stream
        self.window.destroy()
            
    def take_photo(self):
        name = datetime.datetime.now().strftime('%Y.%m.%d-%H.%M.%S.png')
        self.image.save(name, 'PNG')
        print('saved:', name)
                   
if __name__ == '__main__':
    MainWindow()