मैंने हाल ही में एक प्रोग्राम बनाया है जो ओपनसीवी के साथ 2 आईपी कैमरों से बहु वीडियो स्रोत प्रदर्शित करता है। लेकिन मैंने अपने आवेदन के लिए यूआई बनाने का फैसला किया, और अब, यह मेरे लिए इतना स्पष्ट नहीं है कि मैं इसे मल्टी थ्रेडिंग विधि का उपयोग करके कैसे कार्यान्वित कर सकता हूं।

यहाँ वह कोड है जिसका उपयोग मैंने TKinter GUI में केवल एक कैमरा प्रदर्शित करने के लिए किया था:

import tkinter
import cv2
import PIL.Image, PIL.ImageTk
import time

class App:
    def __init__(self, window, window_title, video_source=0):
        self.window = window
        self.window.title(window_title)
        self.video_source = video_source
        
        # open video source (by default this will try to open the computer webcam)
        self.vid = MyVideoCapture(self.video_source)
        
        # Create a canvas that can fit the above video source size
        self.canvas = tkinter.Canvas(window, width = self.vid.width, height = self.vid.height)
        self.canvas.pack()
         
        # Button that lets the user take a snapshot
        self.btn_snapshot=tkinter.Button(window, text="Snapshot", width=50, command=self.snapshot)
        self.btn_snapshot.pack(anchor=tkinter.CENTER, expand=True)
         
        # After it is called once, the update method will be automatically called every delay milliseconds
        self.delay = 15
        self.update()
         
        self.window.mainloop()
     
    def snapshot(self):
        # Get a frame from the video source
        ret, frame = self.vid.get_frame()
        
        if ret:
            cv2.imwrite("frame-" + time.strftime("%d-%m-%Y-%H-%M-%S") + ".jpg", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
    
    def update(self):
        # Get a frame from the video source
        ret, frame = self.vid.get_frame()
        
        if ret:
            self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(frame))
            self.canvas.create_image(0, 0, image = self.photo, anchor = tkinter.NW)
        
        self.window.after(self.delay, self.update)
    
     
class MyVideoCapture:
    def __init__(self, video_source=0):
        # Open the video source
        self.vid = cv2.VideoCapture(video_source)
        if not self.vid.isOpened():
            raise ValueError("Unable to open video source", video_source)
    
        # Get video source width and height
        self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
        self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
    
    def get_frame(self):
        if self.vid.isOpened():
            ret, frame = self.vid.read()
            if ret:
                # Return a boolean success flag and the current frame converted to BGR
                return (ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            else:
                return (ret, None)
        else:
            return (ret, None)
    
    # Release the video source when the object is destroyed
    def __del__(self):
        if self.vid.isOpened():
            self.vid.release()
 
 # Create a window and pass it to the Application object
App(tkinter.Tk(), "Tkinter and OpenCV")

और यहां मेरा पिछला आवेदन था जो विभिन्न धागे में बहु वीडियो स्रोत प्रदर्शित करता है:

from threading import Thread
import cv2
import time

class VideoWriterWidget(object):
    def __init__(self, video_file_name, src=0):
        # Create a VideoCapture object
        self.frame_name = str(src)
        self.video_file = video_file_name
        self.video_file_name = video_file_name + '.avi'
        self.capture = cv2.VideoCapture(src)

        # Default resolutions of the frame are obtained (system dependent)
        self.frame_width = int(self.capture.get(3))
        self.frame_height = int(self.capture.get(4))

        # Set up codec and output video settings
        self.codec = cv2.VideoWriter_fourcc('M','J','P','G')
        self.output_video = cv2.VideoWriter(self.video_file_name, self.codec, 30, (self.frame_width, self.frame_height))

        # Start the thread to read frames from the video stream
        self.thread = Thread(target=self.update, args=())
        self.thread.daemon = True
        self.thread.start()

        # Start another thread to show/save frames
        self.start_recording()
        print('initialized {}'.format(self.video_file))

    def update(self):
        # Read the next frame from the stream in a different thread
        while True:
            if self.capture.isOpened():
                (self.status, self.frame) = self.capture.read()

    def show_frame(self):
        # Display frames in main program
        if self.status:
            cv2.imshow(self.frame_name, self.frame)

        # Press Q on keyboard to stop recording
        key = cv2.waitKey(1)
        if key == ord('q'):
            self.capture.release()
            self.output_video.release()
            cv2.destroyAllWindows()
            exit(1)

    def save_frame(self):
        # Save obtained frame into video output file
        self.output_video.write(self.frame)

    def start_recording(self):
        # Create another thread to show/save frames
        def start_recording_thread():
            while True:
                try:
                    self.show_frame()
                    self.save_frame()
                except AttributeError:
                    pass
        self.recording_thread = Thread(target=start_recording_thread, args=())
        self.recording_thread.daemon = True
        self.recording_thread.start()

if __name__ == '__main__':
    src1 = 'Your link1'
    video_writer_widget1 = VideoWriterWidget('Camera 1', src1)
    src2 = 'Your link2'
    video_writer_widget2 = VideoWriterWidget('Camera 2', src2)
    src3 = 'Your link3'
    video_writer_widget3 = VideoWriterWidget('Camera 3', src3)

    # Since each video player is in its own thread, we need to keep the main thread alive.
    # Keep spinning using time.sleep() so the background threads keep running
    # Threads are set to daemon=True so they will automatically die 
    # when the main thread dies
    while True:
        time.sleep(5)

क्या कोई मेरी मदद कर सकता है कि मैं अपने नए एप्लिकेशन में थ्रेडिंग के साथ टिंकर का उपयोग करके अपने पिछले कोड (मल्टी कैमरे प्रदर्शित करें) का उपयोग कैसे कर सकता हूं?

0
Mahdi Amirsardari 25 जिंदा 2021, 00:14
1
कई धाराओं के लिए root.after का उपयोग क्यों न करें - threads का उपयोग किए बिना। या कैमरों को सूची में रखें और सूची में कई कैमरों के साथ काम करने के लिए for-लूप के साथ एक root.after का उपयोग करें।
 – 
furas
25 जिंदा 2021, 00:25
प्रत्येक फ्रेम के लिए आप कैनवास पर नई छवि बनाते हैं - create_image() - लेकिन यह कैनवास से पिछली छवि को नहीं हटाता है इसलिए यह अधिक मेमोरी का उपयोग कर सकता है। create_image आपको छवि की आईडी देता है - image_id = create_image(...) जिसका उपयोग आप पिछली छवि को हटाने के लिए कर सकते हैं। या आप छवि को PhotoImage जैसे self.photo["image"] = PIL.Image.... में बदल सकते हैं और फिर आपको इसे कैनवास पर बदलने की आवश्यकता नहीं है।
 – 
furas
25 जिंदा 2021, 00:30
थ्रेडिंग में एक बड़ी समस्या हो सकती है - टिंकर (कई अन्य जीयूआई की तरह) थ्रेड्स में विजेट्स का उपयोग करना पसंद नहीं करता है और आपको थ्रेड से मुख्य थ्रेड तक फ्रेम भेजना होगा - यानी। queue का उपयोग करना - और इसे root.after का उपयोग यह जांचने के लिए करना होगा कि कतार में नया फ्रेम है या नहीं, इसे कतार से प्राप्त करें और मुख्य थ्रेड में विजेट अपडेट करें। इसे और अधिक काम की आवश्यकता होगी, फिर सभी सीधे मुख्य धागे में करें। यदि आप छवि पर कुछ प्रसंस्करण करना चाहते हैं तो थ्रेड उपयोगी हो सकता है - चेहरों का पता लगाएं, गति का पता लगाएं, आदि।
 – 
furas
25 जिंदा 2021, 00:35
हो सकता है कि आप एकल कैमरा, एकल कैनवास के साथ विजेट बनाने के लिए Frame का भी उपयोग करें और बाद में प्रोग्राम में कई स्ट्रीम रखने के लिए App में इसे कई बार उपयोग करें।
 – 
furas
25 जिंदा 2021, 00:41

1 उत्तर

सबसे बढ़िया उत्तर

tkinter (कई अन्य GUI की तरह) थ्रेड्स में विजेट्स का उपयोग करना पसंद नहीं करते हैं, इसलिए पहले मैं थ्रेड के बिना सभी को मुख्य प्रक्रिया में चलाने की कोशिश करूंगा।

उदाहरण के लिए मैंने विजेट बनाने के लिए tkinter.Frame के आधार पर अधिकांश कोड को कक्षा में स्थानांतरित कर दिया जिसे मैं अलग-अलग स्ट्रीम के साथ कई बार इस्तेमाल कर सकता हूं। क्योंकि मेरे पास केवल एक कैमरा है (और सिस्टम एक ही कैमरे का कई बार उपयोग नहीं कर सकता) इसलिए मुझे इसका परीक्षण करने के लिए कुछ बाहरी स्ट्रीम/फ़ाइल मिली। क्योंकि स्ट्रीम बहुत बड़ी छवि भेजती है इसलिए मैं आकार बदलकर 400, 300 कर देता हूं

कोड तेजी से काम करता है जब उसे छवि का आकार बदलने की आवश्यकता नहीं होती है।
जब इसे छवि का आकार बदलना होता है तो कभी-कभी इसमें समस्या होती है लेकिन फिर भी यह ठीक है।

enter image description here


import tkinter
import cv2
import PIL.Image, PIL.ImageTk
import time

# widgets with canvas and camera

class tkCamera(tkinter.Frame):

    def __init__(self, window, video_source=0):
        super().__init__(window)
        
        self.window = window
        
        #self.window.title(window_title)
        self.video_source = video_source
        self.vid = MyVideoCapture(self.video_source)

        self.canvas = tkinter.Canvas(window, width=self.vid.width, height=self.vid.height)
        self.canvas.pack()
         
        # Button that lets the user take a snapshot
        self.btn_snapshot = tkinter.Button(window, text="Snapshot", width=50, command=self.snapshot)
        self.btn_snapshot.pack(anchor=tkinter.CENTER, expand=True)
         
        # After it is called once, the update method will be automatically called every delay milliseconds
        self.delay = 15
        self.update_widget()

    def snapshot(self):
        # Get a frame from the video source
        ret, frame = self.vid.get_frame()
        
        if ret:
            cv2.imwrite("frame-" + time.strftime("%d-%m-%Y-%H-%M-%S") + ".jpg", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
    
    def update_widget(self):
        # Get a frame from the video source
        ret, frame = self.vid.get_frame()
        
        if ret:
            self.image = PIL.Image.fromarray(frame)
            self.photo = PIL.ImageTk.PhotoImage(image=self.image)
            self.canvas.create_image(0, 0, image = self.photo, anchor = tkinter.NW)
        
        self.window.after(self.delay, self.update_widget)


class App:

    def __init__(self, window, window_title, video_source1=0, video_source2=0):
        self.window = window

        self.window.title(window_title)
        
        # open video source (by default this will try to open the computer webcam)
        self.vid1 = tkCamera(window, video_source1)
        self.vid1.pack()
        
        self.vid2 = tkCamera(window, video_source2)
        self.vid2.pack()
        
        # Create a canvas that can fit the above video source size
         
        self.window.mainloop()
     
    
     
class MyVideoCapture:
    def __init__(self, video_source=0):
        # Open the video source
        self.vid = cv2.VideoCapture(video_source)
        if not self.vid.isOpened():
            raise ValueError("Unable to open video source", video_source)
    
        # Get video source width and height
        self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
        self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
    
        self.width = 400
        self.height = 300
    
    def get_frame(self):
        if self.vid.isOpened():
            ret, frame = self.vid.read()
            if ret:
                frame = cv2.resize(frame, (400, 300))
                # Return a boolean success flag and the current frame converted to BGR
                return (ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            else:
                return (ret, None)
        else:
            return (ret, None)
    
    # Release the video source when the object is destroyed
    def __del__(self):
        if self.vid.isOpened():
            self.vid.release()
 
# Create a window and pass it to the Application object
App(tkinter.Tk(), "Tkinter and OpenCV", 0, 'https://imageserver.webcamera.pl/rec/krupowki-srodek/latest.mp4')

यदि आप फ्रेम को संसाधित करने की योजना बना रहे हैं - अर्थात। गति या चेहरों का पता लगाएं - फिर get_frame का कोड अलग किए गए विज्ञापन में चल सकता है। थ्रेड हर समय फ़्रेम को संसाधित करेगा और self.frame को असाइन करेगा और get_frame() को केवल वर्तमान self.frame लौटाना चाहिए।

ब्लॉग pyImageSearch पर इसी तरह के विचार देखें पायथन और ओपनसीवी के साथ वेबकैम एफपीएस बढ़ाना .

शायद आप भी इस्तेमाल कर सकते हैं

 from imutils.video import WebcamVideoStream

संपादित करें:

संस्करण अभी भी बिना थ्रेडिंग के लेकिन स्रोतों की सूची के साथ ताकि यह कई कैमरों को प्रदर्शित कर सके। लेकिन 2 से अधिक स्रोतों के लिए इसे प्रदर्शित करने में समस्या है - इसलिए इसे threads का उपयोग करने की आवश्यकता होगी।

BTW: tkinter के विजेट और विंडो में पहले से ही update() विधि है, इसलिए मैंने इसका नाम बदलकर update_frame() कर दिया।

snapshot में मैंने pilow.image.save() का उपयोग किया है, इसलिए मुझे नया फ्रेम पढ़ने और BGR में बदलने की जरूरत नहीं है - और स्ट्रीम बंद होने पर मैं स्नैपशॉट ले सकता हूं। Button केवल कैनवास पर छवि को बदलना बंद कर देता है लेकिन थ्रेड में स्ट्रीम से फ़्रेम पढ़ना बंद नहीं करता है - इसलिए अन्य फ़ंक्शन अभी भी स्ट्रीम को संसाधित या रिकॉर्ड कर सकता है।


import tkinter
import cv2
import PIL.Image, PIL.ImageTk
import time

class MyVideoCapture:

    def __init__(self, video_source=0, width=None, height=None):
    
        # Open the video source
        self.vid = cv2.VideoCapture(video_source)
        if not self.vid.isOpened():
            raise ValueError("Unable to open video source", video_source)

        self.width = width
        self.height = height
    
        # Get video source width and height
        if not self.width:
            self.width = int(self.vid.get(cv2.CAP_PROP_FRAME_WIDTH))    # convert float to int
        if not self.height:
            self.height = int(self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT))  # convert float to int

        self.ret = False
        self.frame = None

    def process(self):
        ret = False
        frame = None
        
        if self.vid.isOpened():
            ret, frame = self.vid.read()
            if ret:
                frame = cv2.resize(frame, (self.width, self.height))
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        
        self.ret = ret
        self.frame = frame
        
    def get_frame(self):
        self.process()  # later run in thread
        return self.ret, self.frame
    
    # Release the video source when the object is destroyed
    def __del__(self):
        if self.vid.isOpened():
            self.vid.release()
 
 
class tkCamera(tkinter.Frame):

    def __init__(self, window, video_source=0, width=None, height=None):
        super().__init__(window)
        
        self.window = window
        
        #self.window.title(window_title)
        self.video_source = video_source
        self.vid = MyVideoCapture(self.video_source, width, height)

        self.canvas = tkinter.Canvas(window, width=self.vid.width, height=self.vid.height)
        self.canvas.pack()
         
        # Button that lets the user take a snapshot
        self.btn_snapshot = tkinter.Button(window, text="Snapshot", width=50, command=self.snapshot)
        self.btn_snapshot.pack(anchor='center', expand=True)
         
        # After it is called once, the update method will be automatically called every delay milliseconds
        self.delay = 15
        self.update_widget()

    def snapshot(self):
        # Get a frame from the video source
        ret, frame = self.vid.get_frame()
        
        if ret:
            cv2.imwrite("frame-" + time.strftime("%d-%m-%Y-%H-%M-%S") + ".jpg", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
    
    def update_widget(self):
        # Get a frame from the video source
        ret, frame = self.vid.get_frame()
        
        if ret:
            self.image = PIL.Image.fromarray(frame)
            self.photo = PIL.ImageTk.PhotoImage(image=self.image)
            self.canvas.create_image(0, 0, image = self.photo, anchor = tkinter.NW)
        
        self.window.after(self.delay, self.update_widget)


class App:

    def __init__(self, window, window_title, video_sources):
        self.window = window

        self.window.title(window_title)
        
        self.vids = []
        
        for source in video_sources:
            vid = tkCamera(window, source, 400, 300)
            vid.pack()
            self.vids.append(vid)
        
        # Create a canvas that can fit the above video source size
         
        self.window.mainloop()
    
if __name__ == '__main__':     

    sources = [
        0, 
        #'https://imageserver.webcamera.pl/rec/krupowki-srodek/latest.mp4',
        #'https://imageserver.webcamera.pl/rec/skolnity/latest.mp4',
        'https://imageserver.webcamera.pl/rec/krakow4/latest.mp4',
    ]
    
        
    # Create a window and pass it to the Application object
    App(tkinter.Tk(), "Tkinter and OpenCV", sources)

संपादित करें

वर्शन जो फ़्रेम को पढ़ने और संसाधित करने के लिए threads का उपयोग करता है। मैं इसे संसाधित करने के लिए time(1/fps) जोड़ता हूं, जब इसकी आवश्यकता होती है, इसलिए यह सुचारू रूप से काम करता है। देरी के लिए 15 यह कभी-कभी जम जाता है।

मैं उन स्रोतों का उपयोग करता हूं जिनमें केवल 24 सेकंड होते हैं इसलिए कुछ सेकंड के बाद वे रुक जाते हैं।

enter image description here


import tkinter
import cv2
import PIL.Image, PIL.ImageTk
import time
import threading

class MyVideoCapture:

    def __init__(self, video_source=0, width=None, height=None, fps=None):
    
        self.video_source = video_source
        self.width = width
        self.height = height
        self.fps = fps
        
        # Open the video source
        self.vid = cv2.VideoCapture(video_source)
        if not self.vid.isOpened():
            raise ValueError("[MyVideoCapture] Unable to open video source", video_source)

        # Get video source width and height
        if not self.width:
            self.width = int(self.vid.get(cv2.CAP_PROP_FRAME_WIDTH))    # convert float to int
        if not self.height:
            self.height = int(self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT))  # convert float to int
        if not self.fps:
            self.fps = int(self.vid.get(cv2.CAP_PROP_FPS))  # convert float to int

        # default value at start        
        self.ret = False
        self.frame = None

        # start thread
        self.running = True
        self.thread = threading.Thread(target=self.process)
        self.thread.start()
        
    def process(self):
        while self.running:
            ret, frame = self.vid.read()
            
            if ret:
                # process image
                frame = cv2.resize(frame, (self.width, self.height))
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            else:
                print('[MyVideoCapture] stream end:', self.video_source)
                # TODO: reopen stream
                self.running = False
                break
                
            # assign new frame
            self.ret = ret
            self.frame = frame
            
            # sleep for next frame
            time.sleep(1/self.fps)
        
    def get_frame(self):
        return self.ret, self.frame
    
    # Release the video source when the object is destroyed
    def __del__(self):
        # stop thread
        if self.running:
            self.running = False
            self.thread.join()

        # relase stream
        if self.vid.isOpened():
            self.vid.release()
            
 
class tkCamera(tkinter.Frame):

    def __init__(self, window, text="", video_source=0, width=None, height=None):
        super().__init__(window)
        
        self.window = window
        
        #self.window.title(window_title)
        self.video_source = video_source
        self.vid = MyVideoCapture(self.video_source, width, height)

        self.label = tkinter.Label(self, text=text)
        self.label.pack()
        
        self.canvas = tkinter.Canvas(self, width=self.vid.width, height=self.vid.height)
        self.canvas.pack()

        # Button that lets the user take a snapshot
        self.btn_snapshot = tkinter.Button(self, text="Start", command=self.start)
        self.btn_snapshot.pack(anchor='center', side='left')
        
        self.btn_snapshot = tkinter.Button(self, text="Stop", command=self.stop)
        self.btn_snapshot.pack(anchor='center', side='left')
         
        # Button that lets the user take a snapshot
        self.btn_snapshot = tkinter.Button(self, text="Snapshot", command=self.snapshot)
        self.btn_snapshot.pack(anchor='center', side='left')
         
        # After it is called once, the update method will be automatically called every delay milliseconds
        # calculate delay using `FPS`
        self.delay = int(1000/self.vid.fps)

        print('[tkCamera] source:', self.video_source)
        print('[tkCamera] fps:', self.vid.fps, 'delay:', self.delay)
        
        self.image = None
        
        self.running = True
        self.update_frame()

    def start(self):
        if not self.running:
            self.running = True
            self.update_frame()

    def stop(self):
        if self.running:
           self.running = False
    
    def snapshot(self):
        # Get a frame from the video source
        #ret, frame = self.vid.get_frame()
        #if ret:
        #    cv2.imwrite(time.strftime("frame-%d-%m-%Y-%H-%M-%S.jpg"), cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR))
        
        # Save current frame in widget - not get new one from camera - so it can save correct image when it stoped
        if self.image:
            self.image.save(time.strftime("frame-%d-%m-%Y-%H-%M-%S.jpg"))
            
    def update_frame(self):
        # widgets in tkinter already have method `update()` so I have to use different name -

        # Get a frame from the video source
        ret, frame = self.vid.get_frame()
        
        if ret:
            self.image = PIL.Image.fromarray(frame)
            self.photo = PIL.ImageTk.PhotoImage(image=self.image)
            self.canvas.create_image(0, 0, image=self.photo, anchor='nw')
        
        if self.running:
            self.window.after(self.delay, self.update_frame)


class App:

    def __init__(self, window, window_title, video_sources):
        self.window = window

        self.window.title(window_title)
        
        self.vids = []

        columns = 2
        for number, source in enumerate(video_sources):
            text, stream = source
            vid = tkCamera(self.window, text, stream, 400, 300)
            x = number % columns
            y = number // columns
            vid.grid(row=y, column=x)
            self.vids.append(vid)
        
        self.window.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.window.mainloop()
    
    def on_closing(self, event=None):
        print('[App] stoping threads')
        for source in self.vids:
            source.vid.running = False
        print('[App] exit')
        self.window.destroy()

if __name__ == '__main__':     

    sources = [
        ('me', 0), 
        ('Zakopane, Poland', 'https://imageserver.webcamera.pl/rec/krupowki-srodek/latest.mp4'),
        ('Kraków, Poland', 'https://imageserver.webcamera.pl/rec/krakow4/latest.mp4'),
        ('Warszawa, Poland', 'https://imageserver.webcamera.pl/rec/warszawa/latest.mp4'),
        #('Baltic See, Poland', 'https://imageserver.webcamera.pl/rec/chlopy/latest.mp4'),
        #('Mountains, Poland', 'https://imageserver.webcamera.pl/rec/skolnity/latest.mp4'),
    ]
        
    # Create a window and pass it to the Application object
    App(tkinter.Tk(), "Tkinter and OpenCV", sources)

संपादित करें

संस्करण जो वीडियो रिकॉर्ड कर सकता है।

cv2 को सही ढंग से सहेजने के लिए बीजीआर रंग के साथ फ्रेम की जरूरत है इसलिए मुझे फ्रेम को आरजीबी में बदलने से पहले इसे सहेजना पड़ा।

मैंने अधिकांश कोड को MyVideoCapture में स्थानांतरित कर दिया है ताकि इसे tkinter के बिना भी उपयोग किया जा सके। मैं छवि को cv2 array या pillow.image के रूप में प्राप्त करने के लिए MyVideoCapture में विकल्प भी जोड़ता हूं - इसलिए अब यह thread के अंदर pillow में परिवर्तित हो जाता है, इसलिए मुख्य धागे को नहीं करना पड़ता है कर दो।


import tkinter
import cv2
import PIL.Image, PIL.ImageTk
import time
import threading

class MyVideoCapture:

    def __init__(self, video_source=0, width=None, height=None, fps=None):
    
        self.video_source = video_source
        self.width = width
        self.height = height
        self.fps = fps
        
        # Open the video source
        self.vid = cv2.VideoCapture(video_source)
        if not self.vid.isOpened():
            raise ValueError("[MyVideoCapture] Unable to open video source", video_source)

        # Get video source width and height
        if not self.width:
            self.width = int(self.vid.get(cv2.CAP_PROP_FRAME_WIDTH))    # convert float to int
        if not self.height:
            self.height = int(self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT))  # convert float to int
        if not self.fps:
            self.fps = int(self.vid.get(cv2.CAP_PROP_FPS))  # convert float to int

        # default value at start        
        self.ret = False
        self.frame = None
        
        self.convert_color = cv2.COLOR_BGR2RGB
        #self.convert_color = cv2.COLOR_BGR2GRAY
        self.convert_pillow = True
        
        # default values for recording        
        self.recording = False
        self.recording_filename = 'output.mp4'
        self.recording_writer = None
        
        # start thread
        self.running = True
        self.thread = threading.Thread(target=self.process)
        self.thread.start()
        
    def start_recording(self, filename=None):
        if self.recording:
            print('[MyVideoCapture] already recording:', self.recording_filename)
        else:
            # VideoWriter constructors
            #.mp4 = codec id 2
            if filename:
                self.recording_filename = filename
            else:
                self.recording_filename = time.strftime("%Y.%m.%d %H.%M.%S", time.localtime()) + ".avi"
            #fourcc = cv2.VideoWriter_fourcc(*'I420') # .avi
            #fourcc = cv2.VideoWriter_fourcc(*'MP4V') # .avi
            fourcc = cv2.VideoWriter_fourcc(*'MP42') # .avi
            #fourcc = cv2.VideoWriter_fourcc(*'AVC1') # error libx264
            #fourcc = cv2.VideoWriter_fourcc(*'H264') # error libx264
            #fourcc = cv2.VideoWriter_fourcc(*'WRAW') # error --- no information ---
            #fourcc = cv2.VideoWriter_fourcc(*'MPEG') # .avi 30fps
            #fourcc = cv2.VideoWriter_fourcc(*'MJPG') # .avi
            #fourcc = cv2.VideoWriter_fourcc(*'XVID') # .avi
            #fourcc = cv2.VideoWriter_fourcc(*'H265') # error 
            self.recording_writer = cv2.VideoWriter(self.recording_filename, fourcc, self.fps, (self.width, self.height))
            self.recording = True
            print('[MyVideoCapture] started recording:', self.recording_filename)
                   
    def stop_recording(self):
        if not self.recording:
            print('[MyVideoCapture] not recording')
        else:
            self.recording = False
            self.recording_writer.release() 
            print('[MyVideoCapture] stop recording:', self.recording_filename)
               
    def record(self, frame):
        # write frame to file         
        if self.recording_writer and self.recording_writer.isOpened():
            self.recording_writer.write(frame)
 
     
    def process(self):
        while self.running:
            ret, frame = self.vid.read()
            
            if ret:
                # process image
                frame = cv2.resize(frame, (self.width, self.height))

                # it has to record before converting colors
                if self.recording:
                    self.record(frame)
                    
                if self.convert_pillow:
                    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    frame = PIL.Image.fromarray(frame)
            else:
                print('[MyVideoCapture] stream end:', self.video_source)
                # TODO: reopen stream
                self.running = False
                if self.recording:
                    self.stop_recording()
                break
                
            # assign new frame
            self.ret = ret
            self.frame = frame

            # sleep for next frame
            time.sleep(1/self.fps)
        
    def get_frame(self):
        return self.ret, self.frame
    
    # Release the video source when the object is destroyed
    def __del__(self):
        # stop thread
        if self.running:
            self.running = False
            self.thread.join()

        # relase stream
        if self.vid.isOpened():
            self.vid.release()
            
 
class tkCamera(tkinter.Frame):

    def __init__(self, window, text="", video_source=0, width=None, height=None):
        super().__init__(window)
        
        self.window = window
        
        #self.window.title(window_title)
        self.video_source = video_source
        self.vid = MyVideoCapture(self.video_source, width, height)

        self.label = tkinter.Label(self, text=text)
        self.label.pack()
        
        self.canvas = tkinter.Canvas(self, width=self.vid.width, height=self.vid.height)
        self.canvas.pack()

        # Button that lets the user take a snapshot
        self.btn_snapshot = tkinter.Button(self, text="Start", command=self.start)
        self.btn_snapshot.pack(anchor='center', side='left')
        
        self.btn_snapshot = tkinter.Button(self, text="Stop", command=self.stop)
        self.btn_snapshot.pack(anchor='center', side='left')
         
        # Button that lets the user take a snapshot
        self.btn_snapshot = tkinter.Button(self, text="Snapshot", command=self.snapshot)
        self.btn_snapshot.pack(anchor='center', side='left')
         
        # After it is called once, the update method will be automatically called every delay milliseconds
        # calculate delay using `FPS`
        self.delay = int(1000/self.vid.fps)

        print('[tkCamera] source:', self.video_source)
        print('[tkCamera] fps:', self.vid.fps, 'delay:', self.delay)
        
        self.image = None
        
        self.running = True
        self.update_frame()

    def start(self):
        #if not self.running:
        #    self.running = True
        #    self.update_frame()
        self.vid.start_recording()

    def stop(self):
        #if self.running:
        #   self.running = False
        self.vid.stop_recording()
    
    def snapshot(self):
        # Get a frame from the video source
        #ret, frame = self.vid.get_frame()
        #if ret:
        #    cv2.imwrite(time.strftime("frame-%d-%m-%Y-%H-%M-%S.jpg"), cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR))
        
        # Save current frame in widget - not get new one from camera - so it can save correct image when it stoped
        if self.image:
            self.image.save(time.strftime("frame-%d-%m-%Y-%H-%M-%S.jpg"))
            
    def update_frame(self):
        # widgets in tkinter already have method `update()` so I have to use different name -

        # Get a frame from the video source
        ret, frame = self.vid.get_frame()
        
        if ret:
            #self.image = PIL.Image.fromarray(frame)
            self.image = frame
            self.photo = PIL.ImageTk.PhotoImage(image=self.image)
            self.canvas.create_image(0, 0, image=self.photo, anchor='nw')
        
        if self.running:
            self.window.after(self.delay, self.update_frame)


class App:

    def __init__(self, window, window_title, video_sources):
        self.window = window

        self.window.title(window_title)
        
        self.vids = []

        columns = 2
        for number, source in enumerate(video_sources):
            text, stream = source
            vid = tkCamera(self.window, text, stream, 400, 300)
            x = number % columns
            y = number // columns
            vid.grid(row=y, column=x)
            self.vids.append(vid)
        
        self.window.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.window.mainloop()
    
    def on_closing(self, event=None):
        print('[App] stoping threads')
        for source in self.vids:
            source.vid.running = False
        print('[App] exit')
        self.window.destroy()

if __name__ == '__main__':     

    sources = [
        ('me', 0), 
        ('Zakopane, Poland', 'https://imageserver.webcamera.pl/rec/krupowki-srodek/latest.mp4'),
        ('Kraków, Poland', 'https://imageserver.webcamera.pl/rec/krakow4/latest.mp4'),
        ('Warszawa, Poland', 'https://imageserver.webcamera.pl/rec/warszawa/latest.mp4'),
        #('Baltic See, Poland', 'https://imageserver.webcamera.pl/rec/chlopy/latest.mp4'),
        #('Mountains, Poland', 'https://imageserver.webcamera.pl/rec/skolnity/latest.mp4'),
    ]
        
    # Create a window and pass it to the Application object
    App(tkinter.Tk(), "Tkinter and OpenCV", sources)

संपादित करें:

मैंने संस्करण बनाया जो स्रोत का चयन कर सकता है - इसलिए यह रिकॉर्ड किए गए वीडियो प्रदर्शित कर सकता है।

यह कोड अराजक है। डायलॉग विंडो अलग वर्ग में हो सकती है।

मैं यहाँ कोड नहीं डाल सकता क्योंकि उत्तर में ३०००० वर्णों की सीमा है।

मैंने इसे GitHub पर डाला: python-cv2-streams-viewer

enter image description here


3
furas 26 जिंदा 2021, 18:18
आपके उत्तर के लिए टीएनएक्स, लेकिन मेरी समस्या यह है कि धागे का उपयोग करके कैमरे कैसे चलाएं, क्योंकि मैं 2 से अधिक कैमरे चलाऊंगा। और इसे प्रदर्शित करने में बहुत देरी हो सकती है। क्या आप धागे का उपयोग करके अपना उत्तर अपडेट कर सकते हैं?
 – 
Mahdi Amirsardari
25 जिंदा 2021, 15:34
दूसरे संस्करण में आपको केवल थ्रेड में चलाना होगा process() - मैंने बाद में कोड डाला।
 – 
furas
25 जिंदा 2021, 16:05
टीएनएक्स बहुत! तुमने सच में मुझे बचा लिया। मेरा एक और सवाल था, अगर मैं फ्रेम रिकॉर्ड करना चाहता हूं और उनमें से प्रत्येक को फिर से खेलना चाहता हूं .. यह कैसे संभव हो सकता है?
 – 
Mahdi Amirsardari
25 जिंदा 2021, 19:11
मेरे पास पुराना उदाहरण है जो फ़ाइल में रिकॉर्ड करने के लिए cv2 का उपयोग करता है लेकिन यह एकल कैमरे के साथ काम करता है और threading का उपयोग नहीं करता है - furas/python-examples - cv2 - रिकॉर्ड-फ़ाइल। लेकिन मुझे लगता है कि आप update_frame में tkCamera (कुछ if record:, if create_new_file: के साथ) में फ्रेम को सेव करेंगे। अंत में इसे MyVideoCapture पर ले जाएं। बाद में मैं ऐसा करने की कोशिश करूंगा - और यही कारण है कि मैं अंतिम कोड में Start, Stop में बटन जोड़ता हूं।
 – 
furas
25 जिंदा 2021, 19:29
मैंने कोड जोड़ा जो फ़ाइल में वीडियो रिकॉर्ड करता है। मैंने इस कोड को MyVideoCapture के अंदर रखा है ताकि इसे tkinter के बिना भी इस्तेमाल किया जा सके - यानी। इसका उपयोग अन्य जीयूआई - पीईक्यूटी, पीईजीटीके, वेब पेज, या यहां तक ​​​​कि जीयूआई के बिना भी किया जा सकता है।
 – 
furas
25 जिंदा 2021, 22:56