인공지능을 좋아하는 곧미남

Simultaneously Multi Camera Capture using Multi Thread 본문

code_study/opencv

Simultaneously Multi Camera Capture using Multi Thread

곧미남 2022. 2. 9. 17:54

오늘은 다수의 카메라를 동시에 켜고 녹화하고 끄는 방법을 알아보겠습니다.

 

오늘의 내용은 아래와 같습니다.

 

- INDEX -

- Multi Camera Capture 하는 방법 /w python, opencv

 

- Multi Camera Capture 코드 및 설명


- Multi Camera Capture 하는 방법 및 주의할 점. /w python, opencv, HW

1. Python의 threading API에서 Thread Class를 사용하여 병렬처리가 가능하도록 쓰레드를 만듭니다.

import threading

threading.Thread
  • python 내장 함수 threading에 관해서 알아보겠습니다.

파이썬은 기본적으로 Single Thread에서 실행됩니다. 즉, 하나의 메인 Thread가 파이썬 코드를 실행시키는 것과 같습니다. 왜 Multi Camera Capture를 실행하기 위해 Thread가 필요한지 알아보겠습니다.

 

우선, opencv에서 Cam을 실행하려면 While문을 통해 계속 cam을 동기화해야합니다. 그래서 일반적으로 하나의 Cam을 실행할땐 그 Loop를 벗어나야함으로 다른 Cam을 실행할 수 없습니다. 

 

이때 사용하는 것이 Multi Thread입니다. 내가 실행하고 싶은 코드의 수만큼 subthread를 생성하여 동시에 코드를 실행시킬 수 있습니다.

 

threading 모듈의 Thread() 메서드를 호출하여 subthread객체를 얻고 start() 메서드를 호출하여 실행하도록합니다.

 

저는 threading.Thread 로부터 파생된 파생클래스를 작성하여 사용하는 방식을 선택했습니다. 이때 der run() 이라는 메서드를 오버라이딩하여 thread의 행동을 정의합니다. 아래 코드는 threading 내부에 정의된 메서드인 start(), run()에 대한 코드를 가져와보았습니다. 한번 읽어보시면 어떤 내용인지 파악 가능하실겁니다.

    def start(self):
        """Start the thread's activity.

        It must be called at most once per thread object. It arranges for the
        object's run() method to be invoked in a separate thread of control.

        This method will raise a RuntimeError if called more than once on the
        same thread object.

        """
        if not self._initialized:
            raise RuntimeError("thread.__init__() not called")

        if self._started.is_set():
            raise RuntimeError("threads can only be started once")
        with _active_limbo_lock:
            _limbo[self] = self
        try:
            _start_new_thread(self._bootstrap, ())
        except Exception:
            with _active_limbo_lock:
                del _limbo[self]
            raise
        self._started.wait()

    def run(self):
        """Method representing the thread's activity.

        You may override this method in a subclass. The standard run() method
        invokes the callable object passed to the object's constructor as the
        target argument, if any, with sequential and keyword arguments taken
        from the args and kwargs arguments, respectively.

        """
        try:
            if self._target:
                self._target(*self._args, **self._kwargs)
        finally:
            # Avoid a refcycle if the thread is running a function with
            # an argument that has a member that points to the thread.
            del self._target, self._args, self._kwargs

2. python and opencv version -> 상위 버전은 안될 가능성이 있습니다. 제가 안되었습니다.

  • python에서 사용한 패키지와 버젼은 아래와 같습니다.

     1) python 3.6 -> datetime, threading, os

     2) opencv-contrib-python     4.1.2.30                 pypi_0    pypi
     3) opencv-python             4.1.2.30                 pypi_0    pypi
     4) opencv-python-headless    4.1.2.30                 pypi_0    pypi

 

3. 카메라는 USB 3.0 Port에 모두 연결할 수 있도록 해야한다. 그렇지 않으면 대역폭 문제로 인해 실행되지 않습니다.

 

4. USB 허브 사용 시 주의할 점. USB HUB 3.0 4Port를 사용해서 메인보드에 곧장 연결하지 않고 PC 하나의 Port에 연결하면 HW ID가 1개로 중복되어 버려 이러한 문제가 있습니다.


- Multi Camera Capture 코드

"""
4. r키 입력시 record 변수만 true로 바꾸고, videowriter 초기화 로직 추가

5. waitkey(20) → waitkey(1)로 변경
  멀티스레딩이고, 카메라 3대면 속도가 느려서 바꿔봤습니다. PC 성능에 맞게 설정하면 됩니다.
"""

import datetime
import cv2
import threading
import os

base_path = os.path.dirname(os.path.abspath("__file__"))
base_path = base_path+"\\video"
if not os.path.exists(base_path):
    os.makedirs(base_path)

fourcc = cv2.VideoWriter_fourcc(*'XVID')
record = False

class camThread(threading.Thread):
    def __init__(self, previewName, camID):
        super(camThread, self).__init__()
        self.previewName = previewName
        self.camID = camID

    def run(self):
        print(f"Starting {self.previewName}")
        camPreview(self.previewName, self.camID)


def camPreview(previewName, camID):
    global record
    global base_path
    global fourcc

    cv2.namedWindow(previewName)
    cam = cv2.VideoCapture(camID)
    video = -1 # 객체를 담을 수 있는 변수 선언(-1로 초기화한거임.)
    if cam.isOpened():
        rval, frame = cam.read()
    else:
        rval = False

    while rval:
        cv2.imshow(previewName, frame)
        rval, frame = cam.read()
        key = cv2.waitKey(1)

        now = datetime.datetime.now().strftime("%d_%H-%M-%S")

        # record 상태이고 video가 초기화되어 현재 진행되고 있는 video가 없을때!
        # loop 돌면서 video에 영상이 저장된다.
        if (record == True and video == -1):
            #recode -> video parameters: save name, video format code, fps, (frame height, frame width)
            video = cv2.VideoWriter(os.path.join(base_path,previewName+"_"+str(now)+".avi"), fourcc, 10.0, (frame.shape[1], frame.shape[0]))

        # video 객체에 영상이 저장된 정보가 있고 지금 record가 끝났으니 release()하여 file 저장하고 녹화를 중단한다.
        # 그리고 다시 video를 -1로 초기화하여 다시 record가 on 상태일때 video가 초기화된 데이터로 input되게 -1로 초기화함.
        if (record == False and video != -1):
            video.release()
            video = -1

        if key == 27: # ESC
            break

        elif key == 114: # r
            print("Start Record")
            record = True

        elif key == 101: # e
            #end recode
            print("End Record")
            record = False

        elif key == 99: # c
            #capture
            print("Capture")
            cv2.imwrite(os.path.join(base_path,previewName+"_"+str(now)+".png"), frame)
            print(os.path.join(base_path,previewName+str(now)+".png"))

        if record == True:
            print("Recording...")
            if video != -1:
                video.write(frame)


    cam.release()
    cv2.destroyWindow(previewName)

if __name__ == "__main__":
    # 3개의 cam의 Thread 인스턴스를 생성한다.
    thread_1 = camThread("front", 0)
    thread_2 = camThread("side", 1)
    thread_3 = camThread("bottom", 2)

    # 생성된 스레드 인스턴스를 실행한다.
    """
    It arranges for the object's run() method to be invoked in a separate thread of control.
    """
    thread_1.start()
    thread_2.start()
    thread_3.start()

    print("Active threads", threading.activeCount())

코드를 3번의 수정에 거쳐 At the same time Multi Cam record and save code 작성을 완료했습니다.

 

이 코드의 핵심은 3가지입니다.

 

1. threading.Thread class 사용하여 Multi Thread 구현

 

2. Record Flag를 지역 변수로 사용하기 위해 전역변수를 전역화하기 (global variable) 

 

3. video 객체를 중복되지 않도록 Cam Loop문 이전에 None값을 가진 객체로 초기화하고 Record On일 경우 video 객체 생성하여 영상을 녹화(write())하고 Record Off일 경우 video의 저장을 release()한다.


- Video Resolution 변경 코드.

# video의 Resolution을 변경할 수 있는 코드 작성 cv2.CAP
w = cv2.CAP_PROP_FRAME_WIDTH
h = cv2.CAP_PROP_FRAME_HEIGHT
cam = cv2.VideoCapture(camID, cv2.CAP_DSHOW)
cam.set(w, 960)
cam.set(h, 1280)

 

GitHub: https://github.com/sangheonEN/ComputerVision_w.DeepLearning/blob/main/video_task/multi_cam_capture_record.py

Comments