KANT에서 진행하는 PA트랙을 진행한지도 벌써 2달이 되었다. 지금은 팀프로젝트 2개를 동시에 진행하는 중인데, 첫번째 프로젝트의 기간이 얼마남지않아 주말인 오늘도 교육장에 나와 프로젝트를 준비하고 있다....(놀러가고싶다....)
아무튼 오늘 작업한 내용은 아래와 같다.
"""
RealSense 카메라로 파란색 물체를 실시간 추적하는 프로그램
- 컬러 영상에서 파란색 영역을 찾아 파란 박스로 표시합니다.
- 깊이(Depth) 영상으로 카메라에서 물체까지의 거리(m)를 박스 위에 표시합니다.
- 화면 중심점(초록 점)과 물체 중심점(빨간 점, 좌표 표시)을 함께 그립니다.
실행: ../.venv/bin/python blue_detect_realsense.py
종료: 영상 창을 클릭한 뒤 q 또는 ESC
"""
import cv2 # 영상 처리 (OpenCV)
import numpy as np # 배열 계산
import pyrealsense2 as rs # Intel RealSense 카메라 제어
# 기준값 설정(색깔을 바꿔야하면 이부분만 변경하면 바로 적용가능)
# 코드는 파란색을 기준으로 작성됨.
# 파란색으로 인정할 HSV 범위 [색상(H), 채도(S), 밝기(V)]
# OpenCV에서 H는 0~179 (일반 색상표 각도 ÷ 2) → 파랑은 약 100~130
LOWER_BLUE = np.array([90, 80, 40]) # 하한
UPPER_BLUE = np.array([135, 255, 255]) # 상한
MIN_AREA = 500 # 이 넓이(픽셀 수)보다 작은 영역은 노이즈로 보고 무시
W, H, FPS = 640, 480, 30 # 영상 크기(가로, 세로)와 초당 프레임 수
CX, CY = W // 2, H // 2 # 화면 중심점 좌표 (320, 240)
# ──────────────────────────────────────────────
# RealSense 카메라 시작
# ──────────────────────────────────────────────
pipeline = rs.pipeline() # 카메라에서 프레임을 받아오는 통로
config = rs.config() # 어떤 영상을 받을지 설정
config.enable_stream(rs.stream.color, W, H, rs.format.bgr8, FPS) # 컬러 영상 (OpenCV와 같은 BGR 순서)
config.enable_stream(rs.stream.depth, W, H, rs.format.z16, FPS) # 깊이 영상 (픽셀마다 거리 값)
try:
profile = pipeline.start(config)
except RuntimeError as e: # 카메라가 연결 안 됐거나 다른 프로그램이 사용 중일 때
raise SystemExit(f"RealSense를 열 수 없습니다: {e}")
# 깊이 값은 정수 단위로 들어옴 → depth_scale을 곱하면 미터(m)가 됨 (D435는 보통 0.001)
depth_scale = profile.get_device().first_depth_sensor().get_depth_scale()
# 컬러 카메라와 깊이 카메라는 위치가 조금 달라서 화면이 어긋남
# → 깊이 영상을 컬러 영상 좌표에 맞춰(정렬) 같은 (x, y)가 같은 지점을 가리키게 함
align = rs.align(rs.stream.color)
# 마스크 노이즈 제거에 쓸 5x5 크기의 필터
kernel = np.ones((5, 5), np.uint8)
def object_distance(depth, mask, x, y, w, h):
"""박스 안 물체까지의 거리(m)를 구한다. 구할 수 없으면 None.
박스 안에서 '파란색이면서 깊이 값이 있는(0이 아닌)' 픽셀만 골라
그 거리들의 중앙값을 사용한다. (배경이나 측정 실패 픽셀의 영향을 줄이기 위함)
"""
roi_depth = depth[y:y + h, x:x + w] # 박스 영역의 깊이 값
roi_mask = mask[y:y + h, x:x + w] > 0 # 박스 영역에서 파란색인 픽셀 (True/False)
values = roi_depth[roi_mask & (roi_depth > 0)] # 파란색이면서 깊이가 측정된 픽셀만
if values.size == 0: # 쓸 수 있는 픽셀이 하나도 없으면 (너무 가깝거나 반사 등)
return None
return float(np.median(values)) * depth_scale # 중앙값을 미터로 변환
# ──────────────────────────────────────────────
# 메인 루프: 프레임을 계속 받아서 처리하고 화면에 표시
# ──────────────────────────────────────────────
try:
while True:
# 1) 카메라에서 컬러 + 깊이 프레임을 받아 정렬
frames = align.process(pipeline.wait_for_frames())
color_frame = frames.get_color_frame()
depth_frame = frames.get_depth_frame()
if not color_frame or not depth_frame: # 한쪽이라도 빠지면 이번 프레임은 건너뜀
continue
frame = np.asanyarray(color_frame.get_data()).copy() # 컬러 영상 (그림을 그릴 것이라 복사본 사용)
depth = np.asanyarray(depth_frame.get_data()) # 깊이 영상
# 2) 파란색 영역만 흰색(255)으로 남긴 마스크 만들기
blurred = cv2.GaussianBlur(frame, (7, 7), 0) # 살짝 흐리게 해서 잡티 줄이기
hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV) # BGR → HSV (색 범위로 고르기 쉬움)
mask = cv2.inRange(hsv, LOWER_BLUE, UPPER_BLUE) # 범위 안 = 흰색, 밖 = 검은색
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 작은 흰 점(노이즈) 지우기
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 물체 안의 작은 구멍 메우기
# 3) 화면 중심점 표시 (초록 점, 반지름 3px)
# ※ OpenCV 색상은 (B, G, R) 순서: (0, 255, 0) = 초록
cv2.circle(frame, (CX, CY), 3, (0, 255, 0), -1) # -1 = 속을 채운 원
# 4) 마스크에서 파란 덩어리의 외곽선을 찾아 하나씩 처리
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
if cv2.contourArea(c) < MIN_AREA: # 너무 작은 덩어리는 노이즈로 보고 건너뜀
continue
# 4-1) 물체를 감싸는 박스와 거리 표시 (파란색)
x, y, w, h = cv2.boundingRect(c) # 박스의 왼쪽 위 좌표(x, y)와 가로(w), 세로(h)
dist = object_distance(depth, mask, x, y, w, h)
label = f"Blue {dist:.2f} m" if dist is not None else "Blue --" # 거리를 못 구하면 --
cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)
cv2.putText(frame, label, (x, max(y - 8, 20)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)
# 4-2) 물체 중심점 표시 (빨간 점 + 좌표)
# moments로 외곽선의 무게중심을 계산: x = m10/m00, y = m01/m00
m = cv2.moments(c)
tx, ty = int(m["m10"] / m["m00"]), int(m["m01"] / m["m00"])
cv2.circle(frame, (tx, ty), 6, (0, 0, 255), -1)
cv2.putText(frame, f"Target ({tx}, {ty})", (tx + 10, ty + 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
# 5) 화면 3개 띄우기
result = cv2.bitwise_and(frame, frame, mask=mask) # 원본에서 파란 부분만 남긴 영상
cv2.imshow("Blue Detection", frame) # 박스, 거리, 중심점이 그려진 영상
cv2.imshow("Mask", mask) # 흑백 마스크 (흰색 = 파란색으로 인식된 곳)
cv2.imshow("Result", result) # 파란 부분만 보이는 영상
# 6) q 또는 ESC(27)를 누르면 종료
if cv2.waitKey(1) & 0xFF in (ord("q"), 27):
break
finally:
# 오류가 나거나 종료할 때도 항상 카메라를 멈추고 창을 닫음
pipeline.stop()
cv2.destroyAllWindows()
프로젝트에 사용하는 depth camera인 D435를 통해 영상을 받아와 파란색 물체를 감지하고 그 물체를 실시간으로 추적하는 프로그램이다.
비전처리에는 가장 많이 사용하는툴인 OpenCV를 사용하였다.
코드의 구성을 대략적으로 살펴보면 다음과 같다.
대략적인 코드의 흐름은 이렇게 흘러가는데 최종적인 목표는 검출된 목표를 계속 중심좌표로 맞추기위해 모터를 움직이는 명령을 보내주어야 하기 때문에 아직 갈길이 멀다고 볼 수 있다.
인식률이 얼마나 잘 나오는지 확인을 하기 위하여 실험을 진행할 필요가 있었다.
그래서 여러 상황을 가정한 예시 이미지를 클로드를 이용하여 생성하였다.(빛이 강하게 들어오는 경우, 빛이 부족한 경우, 이미지에 낀 노이즈 etc...)


위의 이미지는 vscode내에서 생성된 실험용 이미지를 띄운것이고, 아래는 실제 realsense가 보는 시점의 영상이다. 실험에서는 인식할 파란색의 범위를 굉장히 넓게 잡았기 때문에 하늘색, 파란색, 심지어는 청록색과 비슷한 색도 모두 파란색으로 인식하는것을 볼 수 있다.
실제 시연에서는 어떤 조건이 있을지 모르지만 타겟이 될 물체의 색상을 기준으로 하여 최적화를 계속해나가야할 것으로 보인다.또한 인식범위가 넓은편이 아니기에 40cm를 넘어가면 인식을 하지 못했다.