학습 목표

  • 컴퓨터 비전이 영상에서 어떤 정보를 만들고 로봇이 이를 어떻게 사용하는지 설명할 수 있다.
  • 영상 좌표와 카메라·로봇·지도 좌표의 차이를 구분할 수 있다.
  • OpenCV로 색상 분할, 형태 분석, 특징 추적과 기하 변환을 구현할 수 있다.
  • ROS 2 Image, CameraInfo, cv_bridge, image_transport, vision_msgs의 역할을 설명할 수 있다.
  • 객체 검출·분할·추적 결과를 거리와 3차원 위치로 바꾸어 로봇 행동에 연결할 수 있다.
  • 조명·Blur·가림·지연·오검출을 시험하고 안전한 실패 동작을 설계할 수 있다.

1. 컴퓨터 비전은 사진을 보는 일이 아니다

카메라는 픽셀 배열을 보냅니다. 그 픽셀이 사과인지, 사람인지, 바닥인지 카메라 자체는 모릅니다. 컴퓨터 비전은 픽셀에서 로봇이 사용할 수 있는 구조와 의미를 계산하는 과정입니다.

빛 → Lens와 Sensor → Pixel 배열 → 보정·전처리 → 특징·영역·객체 → 위치·속도·신뢰도 → 로봇 판단과 제어

단계 입력 출력 예시 로봇 활용
영상 형성 BGR/RGB Image 원시 관측
전처리 Image 보정·Noise 감소 영상 다음 단계 안정화
저수준 Vision Pixel Edge·Corner·Flow 이동·정합
중간 수준 Vision 특징 Contour·Plane·Track 위치와 형태
의미 Vision Image Class·Box·Mask 사람·사물 인식
3D 해석 Pixel+Depth/Geometry Camera Frame의 3D Point 접근·파지·회피

같은 검출 정확도라도 500 ms 늦으면 움직이는 로봇에는 쓸 수 없습니다. 로봇 비전에서는 정확도뿐 아니라 지연, 갱신률, 좌표계, 불확실성, 실패 시 행동까지 성능입니다.


2. 픽셀과 영상 좌표계

영상의 원점은 보통 왼쪽 위입니다. u는 오른쪽, v는 아래쪽으로 증가합니다. 배열은 흔히 [v, u], 즉 [행, 열] 순서로 접근합니다.

(0,0) ─────────────→ u
  │
  │       ● (u,v)
  │
  ▼ v

OpenCV는 기본적으로 BGR 채널 순서를 사용합니다. 웹이나 많은 AI Model은 RGB를 기대합니다. 이 차이를 놓치면 빨강과 파랑이 바뀌며 색 기반 Algorithm과 Model 입력이 틀어집니다.

형식 의미 대표 용도
BGR/RGB 색 채널 3개 표시·일반 영상 처리
Gray 밝기 1개 Edge·Corner·Optical Flow
HSV 색상·채도·밝기 조명 변화에 비교적 강한 색 분할
Depth 픽셀별 거리 3차원 위치와 장애물
import cv2

image = cv2.imread('apple.jpg')
if image is None:
    raise FileNotFoundError('apple.jpg를 읽지 못했습니다')

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
print('shape:', image.shape, 'dtype:', image.dtype)
print('gray min/max:', int(gray.min()), int(gray.max()))

3. 해상도, Sampling과 지연의 Trade-off

1920×1080 RGB 8-bit 영상 한 장은 약 6 MB입니다. 30 FPS Raw 영상은 Header를 제외해도 약 180 MB/s입니다. 해상도를 높이면 작은 물체를 보기 쉽지만 전송·복사·추론 시간이 늘어납니다.

변경 장점 비용과 위험
해상도 증가 작은 특징과 먼 물체 대역폭·메모리·지연 증가
FPS 증가 빠른 움직임 추적 처리량·발열 증가
JPEG 압축 Network 대역폭 감소 손실·압축 지연·Edge Artifact
ROI Crop 관심 영역 처리 속도 증가 ROI 밖 물체를 놓침
Resize 일정한 Model 입력 작은 물체 정보 손실

평균 FPS만 보지 말고 Capture-to-Decision 지연의 p50·p95·p99를 측정하십시오. Queue가 쌓이면 FPS는 30으로 보여도 로봇은 2초 전 영상을 처리할 수 있습니다. 실시간 제어는 오래된 Frame을 모두 처리하기보다 최신 Frame을 선택하는 정책이 필요합니다.


4. 전처리는 정보를 살리는 범위에서 한다

Blur는 Noise를 줄이지만 작은 Edge도 지웁니다. Histogram Equalization은 대비를 높이지만 Noise도 강화할 수 있습니다. Morphology는 작은 구멍과 점을 정리하지만 물체 모양을 바꿉니다.

import cv2

image = cv2.imread('apple.jpg')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

# 빨강은 HSV Hue 양 끝에 걸쳐 있어 두 구간을 합친다.
mask1 = cv2.inRange(hsv, (0, 90, 50), (10, 255, 255))
mask2 = cv2.inRange(hsv, (170, 90, 50), (179, 255, 255))
mask = cv2.bitwise_or(mask1, mask2)

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL,
                               cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
    area = cv2.contourArea(contour)
    if area < 500:
        continue
    x, y, w, h = cv2.boundingRect(contour)
    cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2)

Threshold는 Camera의 Auto Exposure와 White Balance가 바뀌면 무너질 수 있습니다. 현장에서는 낮·밤·역광·LED Flicker·그림자 조건으로 데이터셋을 나누어 시험해야 합니다.


5. Edge, Contour와 형태 특징

Edge는 밝기가 급격히 변하는 위치이고 Contour는 연결된 경계입니다. 사각형 부품, 차선, 원형 Marker처럼 규칙 기반 대상은 Deep Learning 없이도 빠르고 설명 가능한 Pipeline을 만들 수 있습니다.

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 1.2)
edges = cv2.Canny(blurred, 60, 160)

contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL,
                               cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
    perimeter = cv2.arcLength(c, True)
    polygon = cv2.approxPolyDP(c, 0.02 * perimeter, True)
    if len(polygon) == 4 and cv2.contourArea(c) > 1000:
        print('사각형 후보:', polygon.reshape(-1, 2).tolist())

Contour 면적, 둘레, 원형도 4πA/P², 종횡비와 Convexity를 함께 쓰면 단순 색상보다 안정적입니다. 하지만 손으로 정한 규칙은 가림과 복잡한 배경에 약하므로 적용 범위를 명확히 해야 합니다.


6. 특징점과 Optical Flow

Corner와 Feature는 주변 Pattern이 독특해 다음 Frame에서 다시 찾기 쉬운 점입니다. Optical Flow는 연속 영상에서 픽셀이 어떻게 움직였는지 추정합니다. Visual Odometry, 흔들림 보정, 물체 추적과 충돌 예측에 사용됩니다.

prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
curr_gray = cv2.cvtColor(curr_frame, cv2.COLOR_BGR2GRAY)

points = cv2.goodFeaturesToTrack(
    prev_gray, maxCorners=200, qualityLevel=0.01, minDistance=10)
next_points, status, error = cv2.calcOpticalFlowPyrLK(
    prev_gray, curr_gray, points, None)

good_prev = points[status.ravel() == 1]
good_next = next_points[status.ravel() == 1]
print('추적 성공 특징 수:', len(good_next))

Motion Blur, 반복 무늬, Texture가 없는 벽에서는 특징이 사라집니다. 추적점 수와 재투영 오차를 품질 지표로 내보내고, 품질이 낮으면 속도를 줄이거나 다른 센서로 전환해야 합니다.


7. Camera Calibration과 왜곡 보정

3차원 점이 영상에 맺히는 기본 Pin-hole Model은 다음과 같습니다.

s [u v 1]ᵀ = K [R | t] [X Y Z 1]ᵀ

K는 Focal Length와 Principal Point를 담은 Intrinsic Matrix입니다. Lens Distortion 계수는 직선이 휘는 Radial·Tangential Distortion을 설명합니다. R, t는 Camera와 Robot 또는 두 Camera 사이 Extrinsic입니다.

보정판은 화면 중앙뿐 아니라 네 모서리, 여러 거리와 기울기에서 촬영해야 합니다. Reprojection Error 평균 하나만 보지 말고 영상별·영역별 오차를 확인하십시오. 초점·해상도·렌즈를 바꾸면 Intrinsic을 다시 보정해야 합니다.

camera_matrix = camera_info.k.reshape(3, 3)
distortion = camera_info.d
undistorted = cv2.undistort(image, camera_matrix, distortion)

8. 픽셀을 3차원 점으로 바꾸기

픽셀 (u,v)와 Depth Z, Intrinsic (fx,fy,cx,cy)가 있으면 Camera Frame의 점을 계산할 수 있습니다.

X = (u − cx) × Z / fx
Y = (v − cy) × Z / fy
Z = depth(u,v)
def deproject(u, v, depth_m, fx, fy, cx, cy):
    x = (u - cx) * depth_m / fx
    y = (v - cy) * depth_m / fy
    return x, y, depth_m

point_camera = deproject(412, 268, 0.73, 615.0, 615.0, 320.0, 240.0)
print('camera frame [m]:', point_camera)

Depth 영상이 RGB와 정렬됐는지, 단위가 mm인지 m인지 반드시 확인해야 합니다. 한 픽셀 Depth는 구멍과 Noise에 약하므로 Bounding Box 중앙 주변의 유효값 Median을 쓰는 편이 안전합니다. 얻은 점은 측정 Timestamp의 TF로 camera_link → base_link 또는 Tool Frame으로 변환합니다.


9. 객체 검출, 분할과 추적의 차이

작업 출력 적합한 질문
Classification Image 전체 Class 이 사진에 무엇이 있는가?
Object Detection Class+Score+Bounding Box 무엇이 어디에 있는가?
Semantic Segmentation 픽셀별 Class 바닥·벽·사람 영역은 어디인가?
Instance Segmentation 객체별 Mask 겹친 사과 각각의 모양은?
Pose Estimation Keypoint 또는 6D Pose 물체가 어떤 자세인가?
Tracking 시간에 걸친 ID와 궤적 같은 물체가 어디로 움직이는가?

Confidence Score는 실제 확률과 같다고 단정할 수 없습니다. Dataset 밖 환경에서는 높은 점수로 틀릴 수 있으므로 Calibration, Class별 Precision·Recall, 거리·조명별 성능을 측정해야 합니다.

검출기는 Frame마다 Box를 만들고 Tracker는 시간 정보를 이용해 ID와 속도를 유지합니다. 가림 뒤 ID Switch, 새 객체 생성, 오래된 Track 제거 정책이 로봇 행동에 직접 영향을 줍니다.


10. ROS 2 영상 메시지와 전송

sensor_msgs/Image는 크기, Encoding, Row Step, Byte Data와 Header를 담습니다. CameraInfo는 Intrinsic·Distortion·Projection 정보를 담습니다. 두 메시지의 Timestamp와 Frame이 서로 대응해야 합니다.

ros2 topic list | grep image
ros2 topic info /camera/image_raw --verbose
ros2 interface show sensor_msgs/msg/Image
ros2 interface show sensor_msgs/msg/CameraInfo
ros2 topic hz /camera/image_raw
ros2 topic delay /camera/image_raw
ros2 run rqt_image_view rqt_image_view

image_transport는 같은 기본 Image Topic을 Raw 또는 압축 Plugin으로 전달합니다. 원격 모니터링은 압축이 유리할 수 있지만 Robot 내부 Low-latency 처리에서는 압축·해제 비용까지 측정해야 합니다.


11. cv_bridge ROS 2 색상 검출 Node

import cv2
import numpy as np
import rclpy
from cv_bridge import CvBridge
from rclpy.node import Node
from rclpy.qos import qos_profile_sensor_data
from sensor_msgs.msg import Image
from geometry_msgs.msg import PointStamped

class RedObjectDetector(Node):
    def __init__(self):
        super().__init__('red_object_detector')
        self.bridge = CvBridge()
        self.subscription = self.create_subscription(
            Image, '/camera/image_raw', self.on_image,
            qos_profile_sensor_data)
        self.debug_pub = self.create_publisher(Image, '/vision/debug', 10)
        self.center_pub = self.create_publisher(
            PointStamped, '/vision/red_center', 10)

    def on_image(self, msg):
        frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        low = cv2.inRange(hsv, (0, 100, 60), (10, 255, 255))
        high = cv2.inRange(hsv, (170, 100, 60), (179, 255, 255))
        mask = cv2.bitwise_or(low, high)
        contours, _ = cv2.findContours(
            mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        if contours:
            contour = max(contours, key=cv2.contourArea)
            if cv2.contourArea(contour) > 500:
                x, y, w, h = cv2.boundingRect(contour)
                center = PointStamped()
                center.header = msg.header  # 측정 시각과 Frame 보존
                center.point.x = float(x + w / 2)  # pixel u
                center.point.y = float(y + h / 2)  # pixel v
                center.point.z = 0.0
                self.center_pub.publish(center)
                cv2.rectangle(frame, (x, y), (x+w, y+h), (0,255,0), 2)

        debug = self.bridge.cv2_to_imgmsg(frame, encoding='bgr8')
        debug.header = msg.header
        self.debug_pub.publish(debug)

def main():
    rclpy.init()
    node = RedObjectDetector()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

이 예제의 PointStamped는 픽셀 좌표를 임시로 담습니다. 실제 시스템에서는 이름이 의미를 드러내는 사용자 정의 Message 또는 vision_msgs를 사용하고, Meter 단위 3D Point와 Pixel을 혼동하지 않아야 합니다.


12. 검출 결과를 표준 메시지로 표현한다

vision_msgs/Detection2D는 Header, 객체 후보, 2D Bounding Box와 추적 ID를 표현합니다. 원본 영상은 Detection Message에 포함되지 않으므로 Header를 맞추고 필요하면 Exact 또는 Approximate Time Synchronizer로 결합해야 합니다.

ros2 interface show vision_msgs/msg/Detection2D
ros2 interface show vision_msgs/msg/Detection2DArray
ros2 interface show vision_msgs/msg/Detection3D

검출 결과에는 최소한 다음 계약이 필요합니다.

  • 원본 영상과 같은 header.stamp
  • 결과가 표현된 frame_id
  • Class ID와 Label Version
  • Confidence Score의 의미
  • Bounding Box 또는 Mask 좌표 규약
  • Track ID의 생성·유지·폐기 규칙
  • Model 이름·Version·입력 크기

13. 로봇 행동으로 연결할 때

카메라 중앙에서 물체가 오른쪽에 있다고 로봇이 항상 오른쪽으로 움직여야 하는 것은 아닙니다. Camera 장착 방향, Lens 왜곡, Depth, Tool Pose와 장애물 상태를 함께 고려해야 합니다.

검출 → 신뢰도·신선도 검사 → 3D 위치 계산 → TF 변환 → 도달 가능성·충돌 검사 → 속도 제한 → 행동 실행 → 결과 재관측

사과를 집는 로봇 팔

  1. 사과 Instance Mask와 Box를 검출합니다.
  2. Mask 내부 Depth Median으로 Camera Frame 3D 중심을 구합니다.
  3. 측정 시각의 TF로 Base Frame에 변환합니다.
  4. Grasp 후보 Pose와 접근 방향을 계산합니다.
  5. 충돌 없는 Pre-grasp 경로를 계획합니다.
  6. 저속으로 접근하고 Force/Torque 또는 Gripper Current로 접촉을 확인합니다.
  7. 들어 올린 뒤 다시 관측해 파지를 검증합니다.

Vision 결과만 믿고 고속으로 팔을 움직이면 안 됩니다. 사람 감지, Workspace 제한, 충돌 검사와 Hardware 정지는 독립적으로 유지해야 합니다.


14. 성능 평가는 정확도 하나가 아니다

작업 주요 지표 로봇에서 추가로 볼 것
Classification Accuracy, F1 Class별 치명도
Detection Precision, Recall, mAP 거리별 Recall, 지연
Segmentation IoU, Dice 경계 오차와 작은 물체
Depth MAE, RMSE 거리별 Bias와 빈 Pixel
Tracking IDF1, ID Switch Track 생성·소멸 지연
Pose Translation·Rotation Error Grasp 성공률

False Positive와 False Negative의 비용은 같지 않습니다. 사람을 놓치는 False Negative는 상자를 사람으로 잘못 보는 것보다 위험할 수 있습니다. Threshold는 전체 Accuracy가 아니라 임무 위험을 기준으로 선택해야 합니다.

Dataset은 장소·시간·조명·Camera·속도 단위로 분리하십시오. 연속 영상의 이웃 Frame을 Train과 Test에 무작위로 섞으면 거의 같은 장면을 외운 결과가 되어 성능이 부풀려집니다.


15. 현장 실패와 안전한 대응

실패 조건 관측되는 증상 대응
역광·저조도 Contrast 감소·Noise 증가 Exposure 제한, 조명, 감속
Motion Blur Edge와 특징 소실 Shutter 단축, 조명 증가, 감속
Lens 오염 흐림·가짜 특징 선명도 감시, 청소 경고
가림 Detection 소실·ID Switch Tracker, 다중 Camera, 정지
Model 외 입력 높은 Confidence 오분류 OOD 감시, 보수적 행동
오래된 Frame 위치가 실제보다 뒤처짐 Queue 제한, Timestamp 검사
TF 오류 물체 3D 위치가 튐 측정 시각 TF 검사
GPU 과열 FPS 저하·지연 증가 온도 감시와 Degraded Mode

Watchdog은 Topic 존재 여부만 보면 부족합니다. Timestamp Age, Frame Rate, 처리 지연, 이미지 밝기·선명도, 검출 수, Confidence 분포와 연속 실패 횟수를 함께 감시해야 합니다.


16. 실전 검증 절차

  1. Camera 해상도·FPS·Exposure·Encoding을 고정하고 기록합니다.
  2. Intrinsic과 Camera-to-Robot Extrinsic을 보정합니다.
  3. 원본 영상과 Ground Truth를 함께 Bag에 기록합니다.
  4. 거리·각도·조명·속도·가림별 Test Set을 분리합니다.
  5. 정확도와 End-to-end 지연을 동시에 측정합니다.
  6. ROS Header·QoS·TF와 Queue 크기를 점검합니다.
  7. 실패를 주입하고 감속·정지·복구 동작을 검증합니다.
  8. Model·Calibration·Parameter Version을 결과에 기록합니다.
ros2 bag record \
  /camera/image_raw /camera/camera_info \
  /vision/detections /tf /tf_static /diagnostics

ros2 bag info vision_test
ros2 bag play vision_test --clock

ROBOT GLOSSARY

용어 정리

전체 용어 찾아보기 →
Computer Vision컴퓨터 비전
Image와 영상에서 Geometry·움직임·객체 의미를 계산해 기계가 사용할 정보로 만드는 기술입니다.
Color Space색 공간
색을 BGR·RGB·HSV처럼 여러 성분과 좌표로 표현하는 방법입니다.
Thresholding임계값 분할
Pixel 값이 정한 범위에 드는지에 따라 관심 영역과 배경을 나누는 처리입니다.
Contour윤곽선
같은 영역의 경계를 따라 연결된 점들의 집합으로 물체 형태 분석에 사용합니다.
Optical Flow광학 흐름
연속 영상 사이에서 Pixel이나 특징점이 이동한 방향과 크기를 추정한 값입니다.
Camera Intrinsic카메라 내부 파라미터
Focal Length·Principal Point·왜곡처럼 Camera 내부 영상 형성 특성을 나타내는 값입니다.
Reprojection Error재투영 오차
추정한 Camera Model로 점을 영상에 투영한 위치와 실제 관측 위치의 차이입니다.
Object Detection객체 검출
영상에서 객체의 Class와 위치를 Bounding Box 등의 형태로 찾는 작업입니다.
Instance Segmentation인스턴스 분할
같은 Class라도 객체별로 구분하여 각 객체가 차지하는 Pixel Mask를 찾는 작업입니다.
Tracking추적
시간에 걸쳐 같은 객체의 ID와 위치·속도 변화를 이어서 추정하는 과정입니다.
cv_bridgeROS-OpenCV 영상 변환 도구
ROS sensor_msgs/Image와 OpenCV 영상 배열 사이를 변환하는 ROS Package입니다.
image_transport영상 전송 계층
ROS Image Topic을 Raw 또는 압축 Plugin 방식으로 발행하고 구독하게 하는 전송 추상화입니다.
vision_msgs비전 표준 메시지
분류·2D/3D 검출·Bounding Box 등 Vision 결과를 표현하는 ROS 2 Message Package입니다.

연습 문제

  1. 1280×720 BGR 30 FPS Raw 영상의 이론 대역폭을 계산하세요.
  2. BGR을 RGB로 바꾸지 않았을 때 색 분류가 어떻게 틀리는지 실험하세요.
  3. HSV 빨강 Threshold를 세 조명에서 비교하고 실패 범위를 기록하세요.
  4. Morphology Kernel 크기에 따른 작은 객체 Recall 변화를 측정하세요.
  5. Canny 임계값과 Blur 크기가 Edge에 미치는 영향을 비교하세요.
  6. Optical Flow 추적점 수와 재투영 오차를 품질 Topic으로 발행하세요.
  7. 보정판 자세에 따른 Reprojection Error를 비교하세요.
  8. RGB-D 영상의 Box 중앙값과 Mask 내부 Median Depth를 비교하세요.
  9. ImageCameraInfo의 Timestamp가 맞지 않을 때 생기는 오류를 설명하세요.
  10. cv_bridge Node에 처리 시간과 입력 Frame Age Log를 추가하세요.
  11. 사람 검출의 Confidence Threshold별 Precision·Recall 표를 만드세요.
  12. Camera 가림·정지·지연 상황의 안전 상태 전이를 설계하세요.

참고 자료


17. 확인 퀴즈 10문항

답을 선택한 뒤 정답 확인을 누르세요. 정답과 해설은 제출 후에 표시됩니다.

1. OpenCV의 기본 Color 채널 순서는?

2. 영상 배열의 픽셀을 접근하는 일반적인 순서는?

3. 빨강 HSV Threshold가 두 구간인 이유는?

4. Calibration의 Intrinsic에 해당하는 것은?

5. 픽셀 하나를 3차원 점으로 바꾸는 데 추가로 필요한 핵심 값은?

6. Instance Segmentation의 출력은?

7. ROS 2에서 Image와 OpenCV 배열을 연결하는 대표 Package는?

8. Queue가 계속 쌓이는 영상 Pipeline의 가장 큰 문제는?

9. 검출 Confidence가 높으면 반드시 맞는가?

10. Vision 품질이 안전 기준 아래로 내려갔을 때 올바른 행동은?