본문 바로가기

Python/[스테레오 비전]

#6. 실시간 영상 분석

728x90
반응형

 

 

실시간 영상 분석 | Notion

#1. YOLO 기반 이미지 스테레오 비전 알고리즘

udangtangtang-cording-oldcast1e.notion.site

 

#1. YOLO 기반 이미지 스테레오 비전 알고리즘

  • ESP32 CAM에서 HTTP를 이용해 두 카메라의 이미지를 받아옴
  • YOLO 기반 스테레오 비전 알고리즘 적용 및 거리 도출
  • 카테고리 및 객체 간 거리 반환

1. model.py

기능: YOLOv5 모델을 로드하는 함수가 포함되어 있습니다.

  • load_model(weights_path): 주어진 경로에서 YOLOv5 모델을 로드합니다. 이 함수는 torch.hub.load()를 사용하여 YOLOv5 모델을 다운로드하고 메모리에 로드합니다.
  • 역할: 모델 로드 및 초기화를 담당하며, 이를 통해 이미지에서 객체를 탐지하는 데 필요한 모델을 준비합니다.

2. image_processing.py

기능: 이미지를 로드하는 함수가 포함되어 있습니다.

  • load_image(image_path): 주어진 경로에서 이미지를 로드합니다. 이 함수는 OpenCV의 cv2.imread()를 사용하여 이미지를 메모리에 로드하고 반환합니다.
  • 역할: 이미지 파일을 불러와 이후의 탐지 및 분석 작업에 사용할 수 있도록 준비합니다.

3. detection.py

기능: 객체 탐지 및 바운더리 박스를 추출하는 함수가 포함되어 있습니다.

  • detect_objects(model, img): 로드된 모델을 사용하여 입력 이미지에서 객체를 탐지합니다. 탐지 결과를 반환합니다.
  • get_bounding_boxes(results): 모델의 탐지 결과에서 객체의 라벨과 바운더리 박스를 추출합니다. 이를 통해 이미지 내에서 탐지된 객체의 위치와 유형을 파악할 수 있습니다.
  • 역할: 이미지에서 객체를 탐지하고, 객체의 위치와 라벨 정보를 추출합니다. 이후 거리 계산 및 시각화에 필요한 정보를 제공합니다.

4. distance_calculation.py

기능: 객체 간 시차를 계산하고, 이를 통해 거리 계산을 수행하는 함수들이 포함되어 있습니다.

  • calculate_disparity(box1, box2, img_width): 두 이미지에서 동일 객체의 바운더리 박스를 비교하여 시차를 계산합니다. 시차는 두 이미지에서 동일 객체의 중심 간 거리 차이로 계산됩니다.
  • calculate_distance(disparity, fl, tantheta, img_width): 계산된 시차를 사용하여 객체와 카메라 간의 거리를 계산합니다.
  • compute_distances_and_disparity(labels1, boxes1, labels2, boxes2, fl, tantheta, img_width): 두 이미지의 바운더리 박스를 비교하여 각각의 시차와 거리를 계산합니다. 이를 통해 각 객체의 시차와 거리 정보를 반환합니다.
  • 역할: 이미지에서 탐지된 객체들 간의 거리와 시차를 계산합니다. 이는 스테레오 비전을 통해 객체와 카메라 간의 물리적 거리를 측정하는 데 사용됩니다.

5. visualization.py

기능: 이미지에 객체의 카테고리와 거리 정보를 주석으로 추가하는 함수가 포함되어 있습니다.

  • annotate_image_with_distances(img, labels, boxes, distances, class_map): 입력 이미지에 객체의 바운더리 박스를 그리고, 객체의 카테고리와 거리 정보를 텍스트로 주석 처리합니다. 주석이 추가된 이미지를 반환합니다.
  • 역할: 탐지된 객체의 위치와 거리 정보를 시각적으로 표시합니다. 이를 통해 사용자가 이미지에서 탐지된 객체의 유형과 거리를 직관적으로 이해할 수 있도록 돕습니다.

6. class_map.py

기능: YOLOv5 모델이 사용하는 클래스 맵을 정의한 파일입니다.

  • CLASS_MAP: YOLOv5 모델이 사용하는 클래스(객체의 유형)와 그에 대응하는 카테고리 이름을 매핑한 딕셔너리입니다.
  • 역할: 모델이 반환하는 객체 라벨을 인간이 이해할 수 있는 카테고리 이름으로 변환합니다. 이를 통해 객체 탐지 결과를 해석하고, 시각적으로 표시할 수 있게 합니다.

7. main.py

기능: 전체 프로그램의 실행을 조정하는 메인 스크립트입니다.

  • 기능 설명:
    • 이미지 로드, 모델 초기화, 객체 탐지, 거리 및 시차 계산, 이미지 시각화와 같은 모든 과정을 통합하여 실행합니다.
    • 객체의 카테고리와 거리를 터미널에 출력하며, 주석이 추가된 이미지를 화면에 표시합니다.
  • 역할: 프로그램의 주 실행 루프를 담당하며, 각 모듈의 기능을 조합하여 전체 파이프라인을 완성합니다.

class_map.py
0.00MB
detection.py
0.00MB
distance_calculation.py
0.00MB
image_processing.py
0.00MB
main.py
0.00MB
model.py
0.00MB
visualization.py
0.00MB

 

#2. 구현 과정

prototypeV1

❌ 에러 발생

Traceback (most recent call last): File "/Users/apple/Desktop/Python/Smarcle/MakersDay/StereoCam/실시간/src/prototype.py", line 83, in <module> main() File "/Users/apple/Desktop/Python/Smarcle/MakersDay/StereoCam/실시간/src/prototype.py", line 72, in main img1_annotated = vis.annotate_image_with_distances(img1, labels1, boxes1, distances, CLASS_MAP) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/apple/Desktop/Python/Smarcle/MakersDay/StereoCam/실시간/src/visualization.py", line 7, in annotate_image_with_distances distance = distances [label]

 

`KeyError: 39.0` 에러는 `distances` 딕셔너리에서 키 `39.0`을 찾을 수 없을 때 발생하는 오류입니다. 이 문제는 객체 탐지 결과에서 `label`이 `distances`에 존재하지 않을 때 발생합니다.

이 문제를 해결하기 위해 다음과 같은 방법을 적용할 수 있습니다:

1. 키 존재 여부 확인: `distances` 딕셔너리에 `label` 키가 존재하는지 확인하고, 만약 존재하지 않으면 기본값(예: "Unknown" 또는 0)을 반환하도록 수정합니다.
2. 예외 처리 추가: 코드에서 예외를 처리하여 프로그램이 중단되지 않고 계속 실행되도록 합니다.

prototypeV2

❌ 에러 발생
  1. 라벨 불일치:
    • labels1과 labels2에서 객체를 탐지할 때, 동일한 라벨을 갖는 객체가 두 이미지에서 일치하지 않는 경우 발생할 수 있습니다. 예를 들어, 왼쪽 이미지에서 label=74.0의 객체가 탐지되었지만 오른쪽 이미지에서 동일한 라벨의 객체가 없는 경우 distances 또는 disparities에서 이 라벨을 찾지 못하게 됩니다.
  2. 라벨 중복 또는 비일관성:
    • YOLO 모델이 객체를 탐지할 때 동일한 객체로 인식되었지만, 라벨 값이 약간 다른 경우가 발생할 수 있습니다. 이로 인해 두 이미지 간에 완벽하게 매칭되지 않는 라벨이 생길 수 있습니다.
  3. 여러 객체 처리:
    • 코드 자체는 여러 객체를 동시에 처리할 수 있지만, 동일한 객체를 두 이미지에서 제대로 매칭하지 못할 때 문제가 발생합니다. 이는 스테레오 비전에서 일반적으로 발생할 수 있는 문제입니다.

prototypeV3~4

import cv2
import requests
import numpy as np
import model as yolo_model
import detection as det
import distance_calculation as dist_calc
import visualization as vis
from class_map import CLASS_MAP
from datetime import datetime
import warnings
import concurrent.futures

# 모든 경고 무시
warnings.filterwarnings("ignore")

def capture_frame_from_esp32(cam_url):
    """
    ESP32 카메라 서버에서 프레임을 캡처하여 반환합니다.
    """
    img_resp = requests.get(cam_url)
    img_arr = np.array(bytearray(img_resp.content), dtype=np.uint8)
    img = cv2.imdecode(img_arr, -1)
    return img

def process_frames(left_cam_url, right_cam_url, model, fl, tantheta, img_width):
    """
    두 개의 ESP32 카메라에서 프레임을 가져와 처리하는 함수
    """
    # ESP32에서 이미지 캡처
    img1 = capture_frame_from_esp32(left_cam_url)
    img2 = capture_frame_from_esp32(right_cam_url)

    if img1 is None or img2 is None:
        print("Failed to capture images from ESP32 cameras.")
        return None, None

    # 객체 탐지
    results1 = det.detect_objects(model, img1)
    results2 = det.detect_objects(model, img2)

    # 바운더리 박스와 라벨 추출
    labels1, boxes1 = det.get_bounding_boxes(results1)
    labels2, boxes2 = det.get_bounding_boxes(results2)

    # 거리 및 시차 계산
    distances, disparities = dist_calc.compute_distances_and_disparity(labels1, boxes1, labels2, boxes2, fl, tantheta, img_width)

    return (img1, labels1, boxes1, distances, disparities)

def main():
    # ESP32 카메라 주소
    left_cam_url = "http://192.168.0.14/capture"
    right_cam_url = "http://192.168.0.13/capture"

    # YOLOv5 모델 로드
    weights_path = '/Users/apple/Desktop/Python/yolov5s.pt'
    model = yolo_model.load_model(weights_path)

    # 스테레오 비전 설정
    fl = 2.043636363636363
    tantheta = 0.5443642625
    img_width = 800  # SVGA 해상도 기준

    with concurrent.futures.ThreadPoolExecutor() as executor:
        while True:
            try:
                # 병렬 처리로 프레임을 처리
                future = executor.submit(process_frames, left_cam_url, right_cam_url, model, fl, tantheta, img_width)
                result = future.result()

                if result is None:
                    continue

                img1, labels1, boxes1, distances, disparities = result

                # 현재 시간 출력
                current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                print(f"Time: {current_time}")

                # 시차 및 거리 출력
                for label in labels1:  # labels1과 distances, disparities의 키가 일치하는지 확인
                    if label in distances:
                        category = CLASS_MAP.get(int(label), "Unknown")
                        distance = distances.get(label, "Unknown")
                        print(f"Category: {category}, Distance: {distance:.2f} meters")

                print("-"*50)

                # 이미지에 주석 추가
                img1_annotated = vis.annotate_image_with_distances(img1, labels1, boxes1, distances, CLASS_MAP)

                # 최종 이미지 파이썬 창에 띄우기
                cv2.imshow("Annotated Image", img1_annotated)

                if cv2.waitKey(1) & 0xFF == ord('q'):
                    break

            except KeyError as e:
                print(f"KeyError: {e} - Skipping this frame.")
                continue
            except Exception as e:
                print(f"An unexpected error occurred: {e}")
                break

    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()
💡 해상도 변경 : QVGA(320x240)

 

해상도를 QVGA(320x240)로 낮추고, 전체적인 처리 속도를 높이기 위한 코드와 전략을 작성해 보겠습니다. 특히, M1 맥북 프로의 성능을 최대로 활용할 수 있도록 코드를 최적화하겠습니다.

1. 해상도 변경

ESP32 CAM의 해상도를 QVGA로 설정하면 프레임 크기가 줄어들어 전송 및 처리 속도가 빨라집니다.

2. M1 맥북 프로에서 처리 속도 최적화

M1 칩은 효율적인 멀티스레딩과 GPU 가속을 지원합니다. 이를 활용하기 위해 다음을 고려할 수 있습니다:

  • ThreadPoolExecutor: 멀티스레딩을 사용하여 병렬로 작업을 처리합니다.
  • OpenCV 최적화: OpenCV의 기본 설정이 M1 칩에 최적화되어 있으므로 특별한 설정 없이도 효율적으로 작동합니다.
  • numpy 최적화: numpy 연산이 대부분 벡터화되어 있어 M1 칩의 SIMD 명령어를 활용할 수 있습니다.

#3. 실시간 객체 인식 및 거리 계산

👌 실행 가능

 

728x90
반응형
댓글