#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
기능: 전체 프로그램의 실행을 조정하는 메인 스크립트입니다.
- 기능 설명:
- 이미지 로드, 모델 초기화, 객체 탐지, 거리 및 시차 계산, 이미지 시각화와 같은 모든 과정을 통합하여 실행합니다.
- 객체의 카테고리와 거리를 터미널에 출력하며, 주석이 추가된 이미지를 화면에 표시합니다.
- 역할: 프로그램의 주 실행 루프를 담당하며, 각 모듈의 기능을 조합하여 전체 파이프라인을 완성합니다.
#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
❌ 에러 발생
- 라벨 불일치:
- labels1과 labels2에서 객체를 탐지할 때, 동일한 라벨을 갖는 객체가 두 이미지에서 일치하지 않는 경우 발생할 수 있습니다. 예를 들어, 왼쪽 이미지에서 label=74.0의 객체가 탐지되었지만 오른쪽 이미지에서 동일한 라벨의 객체가 없는 경우 distances 또는 disparities에서 이 라벨을 찾지 못하게 됩니다.
- 라벨 중복 또는 비일관성:
- YOLO 모델이 객체를 탐지할 때 동일한 객체로 인식되었지만, 라벨 값이 약간 다른 경우가 발생할 수 있습니다. 이로 인해 두 이미지 간에 완벽하게 매칭되지 않는 라벨이 생길 수 있습니다.
- 여러 객체 처리:
- 코드 자체는 여러 객체를 동시에 처리할 수 있지만, 동일한 객체를 두 이미지에서 제대로 매칭하지 못할 때 문제가 발생합니다. 이는 스테레오 비전에서 일반적으로 발생할 수 있는 문제입니다.
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. 실시간 객체 인식 및 거리 계산
👌 실행 가능
'Python > [스테레오 비전]' 카테고리의 다른 글
#5. 스테레오 카메라 알고리즘 제작 (2) | 2024.08.06 |
---|---|
#4. 스테레오 카메라 원리 분석(4) : Census transform와 Rank transform (0) | 2024.08.06 |
#3. 스테레오 카메라 원리 분석(3) : SAD와 SSD (1) | 2024.08.06 |
#2. 스테레오 카메라 원리 분석(2) : 스테레오 정합 (0) | 2024.08.06 |
#1. 스테레오 카메라 원리 분석(1) : 스테레오 비전 (0) | 2024.08.06 |