Skip to main content
Summary

딥러닝 시대에도 OpenCV가 필요한 이유

로봇 비전을 처음 배우면 금방 이런 생각이 듭니다. "요즘은 YOLO나 Segment Anything 같은 모델이 물체를 알아서 찾아 주는데, 굳이 OpenCV를 배워야 할까?"

답은 분명합니다. 여전히 필요합니다.

딥러닝 모델은 강력하지만, 로봇 시스템 전체에서 보면 하나의 부품입니다. 카메라에서 이미지를 읽고, 크기를 맞추고, 색상 공간을 바꾸고, 특정 영역만 잘라내고, 모델 결과를 화면에 표시하고, 물체의 중심점을 계산하는 일은 여전히 자주 필요합니다. 딥러닝 모델이 "컵"을 찾아 주더라도, 그 컵의 중심 픽셀이 어디인지, 마스크 영역이 얼마나 큰지, 로봇팔이 접근할 후보 지점이 어디인지는 별도로 계산해야 합니다.

OpenCV는 이때 사용하는 로봇 비전의 기본 공구입니다. 드라이버나 렌치처럼 화려하지는 않지만, 없으면 실험이 멈춥니다. 이번 장에서는 앞으로 로봇팔과 AI 에이전트를 만들 때 계속 등장할 OpenCV의 핵심 기능을 빠르게 복습하겠습니다.

이번 장에서 다룰 흐름은 간단합니다.

  1. 이미지를 읽는다.

  2. 색상 공간을 바꾼다.

  3. 원하는 색상이나 영역만 마스크로 분리한다.

  4. 윤곽선을 찾는다.

  5. 중심점을 계산해 물체의 2D 위치를 얻는다.

이 흐름은 단순해 보이지만, 실제 로봇 프로젝트에서 매우 자주 쓰입니다. 예를 들어 "빨간 컵을 집어 줘"라는 명령을 받은 로봇이 있다고 합시다. LLM은 "빨간 컵을 찾아서 pick 동작을 실행하라"는 계획을 만들 수 있습니다. 하지만 카메라 이미지 속에서 빨간 영역을 분리하고, 컵의 중심 픽셀을 찾고, 그 위치를 로봇 좌표로 넘기는 첫 단계는 OpenCV가 담당할 수 있습니다.

OpenCV 복습

이미지 읽기와 표시하기

OpenCV는 파이썬에서 보통 cv2라는 이름으로 사용합니다. 가장 먼저 해야 할 일은 이미지를 읽는 것입니다.

import cv2

image = cv2.imread("cup.jpg")

if image is None:
    raise FileNotFoundError("이미지를 읽을 수 없습니다. 파일 경로를 확인하세요.")

cv2.imshow("image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.imread()는 파일에서 이미지를 읽어 NumPy 배열로 반환합니다. 지난 장에서 이미지는 결국 숫자의 배열이라고 이야기했습니다. OpenCV에서 읽은 이미지도 마찬가지입니다. image.shape를 출력해 보면 이미지의 높이, 너비, 채널 수를 확인할 수 있습니다.

print(image.shape)

예를 들어 출력이 (480, 640, 3)이라면 높이가 480픽셀, 너비가 640픽셀, 색상 채널이 3개라는 뜻입니다. 여기서 중요한 점은 순서입니다. 이미지 좌표에서 보통 가로 방향은 x, 세로 방향은 y라고 부릅니다. 하지만 배열의 형태는 (height, width, channels)입니다. 즉 배열 인덱스로는 세로가 먼저, 가로가 나중에 옵니다.

height, width, channels = image.shape
print("width:", width)
print("height:", height)

로봇 비전에서는 이 차이를 헷갈리면 안 됩니다. 물체 중심점이 (cx, cy)라고 할 때 cx는 오른쪽으로 증가하는 가로 좌표이고, cy는 아래쪽으로 증가하는 세로 좌표입니다. 이미지의 왼쪽 위가 원점 (0, 0)입니다.

cv2.imshow()는 별도의 창을 띄워 이미지를 보여 줍니다. cv2.waitKey(0)은 키 입력을 기다립니다. 이 코드가 없으면 창이 뜨자마자 닫힐 수 있습니다. 마지막의 cv2.destroyAllWindows()는 OpenCV가 만든 창을 정리합니다.

만약 서버나 노트북 환경처럼 화면 창을 띄우기 어렵다면 파일로 저장해서 확인할 수도 있습니다.

cv2.imwrite("output.jpg", image)

로봇 실험을 하다 보면 GUI가 없는 환경에서 코드를 실행하는 경우도 많습니다. 이때는 imshow()보다 imwrite()로 중간 결과를 저장하는 방식이 더 안전합니다.

카메라에서 프레임 읽기

파일 이미지가 아니라 실제 카메라를 사용하려면 VideoCapture를 사용합니다. 웹캠이나 USB 카메라를 연결한 뒤 다음과 같이 프레임을 읽을 수 있습니다.

import cv2

cap = cv2.VideoCapture(0)

if not cap.isOpened():
    raise RuntimeError("카메라를 열 수 없습니다.")

while True:
    ret, frame = cap.read()

    if not ret:
        print("프레임을 읽지 못했습니다.")
        break

    cv2.imshow("camera", frame)

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

cap.release()
cv2.destroyAllWindows()

cv2.VideoCapture(0)에서 0은 기본 카메라를 의미합니다. 카메라가 여러 대라면 1, 2처럼 번호를 바꿔야 할 수 있습니다.

cap.read()는 두 값을 반환합니다. ret은 프레임을 정상적으로 읽었는지 알려 주는 불리언 값이고, frame은 실제 이미지입니다. 로봇 시스템에서는 카메라가 항상 완벽하게 동작한다고 가정하면 안 됩니다. 케이블이 빠질 수도 있고, 장치 권한 문제가 생길 수도 있고, 순간적으로 프레임을 놓칠 수도 있습니다. 그래서 ret을 반드시 확인하는 습관이 필요합니다.

반복문 안에서 매번 들어오는 frame이 현재 카메라 이미지입니다. 앞으로 이 프레임에 대해 색상 변환, 마스크 생성, 윤곽선 검출을 수행하면 실시간 물체 추적기가 됩니다.

BGR, RGB, HSV 이해하기

OpenCV를 처음 사용할 때 가장 많이 헷갈리는 부분이 색상입니다. 우리는 보통 색상 이미지를 RGB라고 부릅니다. RGB는 Red, Green, Blue의 약자입니다. 그런데 OpenCV는 기본적으로 이미지를 BGR 순서로 다룹니다. 즉 Blue, Green, Red 순서입니다.

image = cv2.imread("cup.jpg")
print(image[0, 0])  # [B, G, R]

이 차이는 작은 실수처럼 보이지만 결과를 크게 바꿉니다. 예를 들어 OpenCV로 읽은 이미지를 Matplotlib으로 바로 표시하면 색이 이상하게 보일 수 있습니다. Matplotlib은 보통 RGB 순서를 기대하는데, OpenCV 이미지는 BGR 순서이기 때문입니다.

import cv2
import matplotlib.pyplot as plt

bgr = cv2.imread("cup.jpg")
rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)

plt.imshow(rgb)
plt.axis("off")
plt.show()

cv2.cvtColor()는 색상 공간을 변환할 때 사용합니다. BGR에서 RGB로 바꾸거나, BGR에서 HSV로 바꿀 수 있습니다.

로봇 비전에서 특히 중요한 색상 공간은 HSV입니다. HSV는 색을 다음 세 요소로 표현합니다.

  • H, Hue: 색상입니다. 빨강, 초록, 파랑처럼 "무슨 색인가"를 나타냅니다.

  • S, Saturation: 채도입니다. 색이 얼마나 선명한지 나타냅니다.

  • V, Value: 명도입니다. 얼마나 밝은지 나타냅니다.

RGB나 BGR은 조명 변화에 꽤 민감합니다. 같은 빨간 컵이라도 밝은 곳에서는 픽셀 값이 (높은 R, 중간 G, 중간 B)처럼 보이고, 어두운 곳에서는 전체 값이 낮아집니다. 반면 HSV에서는 색상에 해당하는 H를 중심으로 조건을 줄 수 있어 색상 기반 분리가 조금 더 직관적입니다.

예를 들어 "빨간 물체만 찾고 싶다"면 BGR 값으로 조건을 만들 수도 있지만, 실제 환경에서는 조명 때문에 금방 흔들립니다. HSV를 사용하면 "색상은 빨강 근처이고, 채도와 명도는 어느 정도 이상인 픽셀"처럼 표현할 수 있습니다.

hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

OpenCV에서 HSV의 범위는 일반적인 색상 이론과 조금 다릅니다. H는 0부터 179까지, S와 V는 0부터 255까지입니다. 빨강은 색상 원의 양 끝에 걸쳐 있기 때문에 보통 두 구간으로 나누어 잡습니다. 이 부분은 뒤에서 코드로 살펴보겠습니다.

마스크로 원하는 영역만 남기기

마스크는 로봇 비전에서 매우 자주 등장하는 개념입니다. 마스크는 원본 이미지와 같은 가로·세로 크기를 가진 흑백 이미지라고 생각하면 됩니다. 관심 있는 픽셀은 흰색, 관심 없는 픽셀은 검은색으로 표시합니다.

OpenCV에서는 보통 흰색을 255, 검은색을 0으로 표현합니다.

255: 통과
0  : 차단

마스크를 만들면 원본 이미지에서 원하는 영역만 남길 수 있습니다. 예를 들어 빨간 컵만 남기고 나머지 배경을 지우고 싶다면, 빨간색 범위에 해당하는 픽셀만 255로 만들면 됩니다.

다음 코드는 HSV 색상 공간에서 빨간색 영역을 분리하는 예입니다.

import cv2
import numpy as np

image = cv2.imread("cup.jpg")
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

lower_red1 = np.array([0, 100, 80])
upper_red1 = np.array([10, 255, 255])

lower_red2 = np.array([170, 100, 80])
upper_red2 = np.array([179, 255, 255])

mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)

mask = mask1 | mask2

result = cv2.bitwise_and(image, image, mask=mask)

cv2.imshow("original", image)
cv2.imshow("mask", mask)
cv2.imshow("red object", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.inRange()는 각 픽셀이 지정한 하한값과 상한값 사이에 들어오는지 검사합니다. 조건에 맞는 픽셀은 255가 되고, 조건에 맞지 않는 픽셀은 0이 됩니다.

여기서 빨강 범위를 두 개로 나눈 이유는 HSV의 H 값이 원형으로 이어지기 때문입니다. 빨강은 0 근처에도 있고 179 근처에도 있습니다. 그래서 [0, 10] 구간과 [170, 179] 구간을 합쳐 빨간 영역으로 사용합니다.

cv2.bitwise_and()는 마스크가 흰색인 부분만 원본 이미지에서 남깁니다. 결과 이미지를 보면 빨간 물체는 보이고, 나머지 부분은 어둡게 사라집니다.

이 방식은 단순하지만 강력합니다. 실험실에서 색이 뚜렷한 블록, 공, 컵을 찾을 때는 딥러닝 모델 없이도 꽤 잘 작동합니다. 특히 로봇 비전 입문 단계에서는 색상 마스크가 훌륭한 출발점입니다.

마스크를 깨끗하게 다듬기

현실의 이미지는 깨끗하지 않습니다. 조명 반사, 그림자, 카메라 노이즈, 배경 색상 때문에 마스크에는 작은 점들이 생기거나 물체 내부에 구멍이 생길 수 있습니다.

예를 들어 빨간 컵을 찾는 마스크를 만들었는데, 컵 주변에 작은 흰 점들이 생겼다고 합시다. 로봇은 그 점들을 작은 물체로 오해할 수 있습니다. 반대로 컵 영역 안에 검은 구멍이 있으면 컵의 면적이나 중심점 계산이 흔들릴 수 있습니다.

이때 사용하는 것이 모폴로지 연산입니다. 이름은 조금 어렵지만 원리는 간단합니다. 흰색 영역을 깎거나, 넓히거나, 작은 구멍을 메우는 이미지 처리입니다.

kernel = np.ones((5, 5), np.uint8)

mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

MORPH_OPEN은 작은 흰색 노이즈를 제거하는 데 좋습니다. MORPH_CLOSE는 흰색 영역 내부의 작은 검은 구멍을 메우는 데 좋습니다. 커널 크기 (5, 5)는 주변 픽셀을 어느 정도 범위로 볼지 정합니다. 너무 작으면 노이즈 제거 효과가 약하고, 너무 크면 실제 물체 모양까지 망가질 수 있습니다.

로봇 실험에서는 이 값을 고정된 정답처럼 외우기보다 카메라 해상도와 물체 크기에 맞게 조정해야 합니다. 작은 물체를 찾는다면 커널도 작게, 큰 물체를 찾는다면 조금 크게 잡을 수 있습니다.

윤곽선 찾기

마스크를 얻었다면 이제 물체의 모양을 찾을 수 있습니다. OpenCV에서는 흰색 영역의 경계를 윤곽선, 즉 contour로 찾습니다.

contours, hierarchy = cv2.findContours(
    mask,
    cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE
)

cv2.findContours()는 마스크에서 흰색 영역의 외곽선을 찾습니다. cv2.RETR_EXTERNAL은 가장 바깥쪽 윤곽선만 찾겠다는 뜻입니다. 컵 내부의 작은 구멍이나 복잡한 내부 구조보다, 물체 하나의 바깥 경계가 중요할 때 자주 사용합니다.

cv2.CHAIN_APPROX_SIMPLE은 윤곽선을 표현할 때 불필요한 중간 점을 줄여 줍니다. 예를 들어 직선의 모든 픽셀을 저장하는 대신 양 끝점 위주로 간단하게 표현합니다. 보통 물체 검출 후 위치를 찾는 용도에서는 이 옵션으로 충분합니다.

윤곽선이 여러 개 나올 수도 있습니다. 빨간 컵 하나만 찾고 싶었지만, 배경에 빨간 스티커가 있거나 조명 반사가 빨갛게 잡히면 여러 영역이 검출됩니다. 이때 가장 큰 윤곽선을 선택하는 방법을 자주 사용합니다.

if len(contours) > 0:
    largest_contour = max(contours, key=cv2.contourArea)
    area = cv2.contourArea(largest_contour)
    print("area:", area)

cv2.contourArea()는 윤곽선이 둘러싼 면적을 계산합니다. 로봇이 잡아야 할 물체가 화면에서 충분히 크게 보인다면, 작은 노이즈보다 면적이 클 가능성이 높습니다. 따라서 가장 큰 윤곽선을 대표 물체로 선택할 수 있습니다.

다만 이 방법도 완벽하지는 않습니다. 배경에 더 큰 빨간 물체가 있으면 로봇은 그쪽을 선택할 수 있습니다. 그래서 실제 시스템에서는 색상, 크기, 위치, 형태, 딥러닝 검출 결과 등을 함께 사용합니다. 중요한 것은 OpenCV가 이런 정보를 계산하는 기본 도구라는 점입니다.

중심점 계산하기

로봇에게 "물체가 있다"는 사실만으로는 충분하지 않습니다. 로봇은 "어디에 있는가"를 알아야 합니다. 이미지에서 가장 기본적인 위치 정보는 2D 중심점입니다.

윤곽선의 중심점은 모멘트(moment)를 사용해 계산할 수 있습니다. 여기서 모멘트는 이미지 영역의 무게중심을 구하기 위한 값이라고 이해하면 됩니다. 물리에서 질량이 분포한 물체의 중심을 구하는 것과 비슷합니다.

M = cv2.moments(largest_contour)

if M["m00"] != 0:
    cx = int(M["m10"] / M["m00"])
    cy = int(M["m01"] / M["m00"])
    print("center:", cx, cy)

m00은 영역의 면적과 관련된 값입니다. 이 값이 0이면 나눗셈을 할 수 없으므로 반드시 확인해야 합니다. cx, cy는 윤곽선 영역의 중심 좌표입니다.

이제 이 중심점을 원본 이미지에 표시해 보겠습니다.

cv2.circle(image, (cx, cy), 5, (0, 255, 0), -1)
cv2.putText(
    image,
    f"({cx}, {cy})",
    (cx + 10, cy),
    cv2.FONT_HERSHEY_SIMPLEX,
    0.6,
    (0, 255, 0),
    2
)

cv2.circle()은 중심점에 원을 그립니다. 색상 (0, 255, 0)은 OpenCV 기준으로 초록색입니다. OpenCV는 BGR 순서이므로 (B, G, R)입니다. -1은 원 내부를 채우라는 뜻입니다.

cv2.putText()는 이미지 위에 글자를 표시합니다. 로봇 비전 실험에서는 중간 결과를 화면에 표시하는 일이 매우 중요합니다. 물체 검출이 틀렸는지, 마스크가 이상한지, 중심점이 흔들리는지 눈으로 확인할 수 있어야 디버깅이 됩니다.

또한 물체 주변에 사각형을 그릴 수도 있습니다.

x, y, w, h = cv2.boundingRect(largest_contour)
cv2.rectangle(image, (x, y), (x + w, y + h), (255, 0, 0), 2)

cv2.boundingRect()는 윤곽선을 감싸는 가장 단순한 직사각형을 반환합니다. 여기서 (x, y)는 사각형의 왼쪽 위 좌표이고, w, h는 너비와 높이입니다.

중심점과 바운딩 박스는 앞으로 객체 검출 모델에서도 계속 등장합니다. YOLO 같은 모델도 결국 이미지 속 물체의 위치를 바운딩 박스로 알려 줍니다. OpenCV로 이 개념을 먼저 익혀 두면 딥러닝 검출 결과를 다룰 때 훨씬 편해집니다.

빨간 물체 추적기 만들기

지금까지 배운 내용을 하나로 합쳐 보겠습니다. 다음 코드는 카메라에서 프레임을 읽고, 빨간색 영역을 마스크로 분리한 뒤, 가장 큰 윤곽선의 중심점을 화면에 표시합니다.

import cv2
import numpy as np

cap = cv2.VideoCapture(0)

if not cap.isOpened():
    raise RuntimeError("카메라를 열 수 없습니다.")

while True:
    ret, frame = cap.read()
    if not ret:
        print("프레임을 읽지 못했습니다.")
        break

    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

    lower_red1 = np.array([0, 100, 80])
    upper_red1 = np.array([10, 255, 255])
    lower_red2 = np.array([170, 100, 80])
    upper_red2 = np.array([179, 255, 255])

    mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
    mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
    mask = mask1 | mask2

    kernel = np.ones((5, 5), np.uint8)
    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
    )

    if len(contours) > 0:
        largest_contour = max(contours, key=cv2.contourArea)
        area = cv2.contourArea(largest_contour)

        if area > 500:
            M = cv2.moments(largest_contour)

            if M["m00"] != 0:
                cx = int(M["m10"] / M["m00"])
                cy = int(M["m01"] / M["m00"])

                x, y, w, h = cv2.boundingRect(largest_contour)

                cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)
                cv2.circle(frame, (cx, cy), 6, (0, 255, 0), -1)
                cv2.putText(
                    frame,
                    f"center: ({cx}, {cy})",
                    (cx + 10, cy),
                    cv2.FONT_HERSHEY_SIMPLEX,
                    0.6,
                    (0, 255, 0),
                    2
                )

    cv2.imshow("frame", frame)
    cv2.imshow("mask", mask)

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

cap.release()
cv2.destroyAllWindows()

이 코드는 작은 로봇 비전 시스템의 축소판입니다. 카메라에서 세상을 읽고, 관심 있는 정보를 분리하고, 위치를 계산하고, 결과를 시각화합니다. 여기서 계산한 (cx, cy)는 아직 이미지 좌표입니다. 로봇팔이 바로 사용할 수 있는 좌표는 아닙니다. 로봇팔은 "이미지의 320, 240 위치"가 아니라 "로봇 기준 좌표계에서 x, y, z가 얼마인지"를 알아야 합니다.

그래서 이후 장에서는 깊이 카메라와 좌표 변환을 다룹니다. 하지만 출발점은 바로 여기입니다. 픽셀 좌표를 안정적으로 찾지 못하면 3D 위치 추정도, 로봇팔 집기도 흔들릴 수밖에 없습니다.

색상 기반 검출의 장점과 한계

색상 기반 검출은 빠르고 이해하기 쉽습니다. 딥러닝 모델을 학습할 필요도 없습니다. 빨간 공, 파란 블록, 초록색 표식처럼 색이 분명한 물체를 찾는 실험에서는 매우 유용합니다. 특히 로봇 시스템을 처음 만들 때는 색상 마스크만으로도 많은 것을 배울 수 있습니다.

하지만 한계도 분명합니다. 조명이 바뀌면 색이 달라집니다. 그림자가 생기면 명도가 낮아지고, 반사가 생기면 채도가 바뀝니다. 배경에 비슷한 색이 있으면 잘못 검출될 수 있습니다. 물체가 여러 개 있으면 어떤 물체가 명령의 대상인지 구분하기 어렵습니다.

그래서 실제 AI 로봇 에이전트에서는 색상 기반 OpenCV 처리와 딥러닝 모델을 함께 사용합니다. 예를 들어 LLM이 "빨간 컵"이라는 목표를 추출하면, 객체 검출 모델이 컵 후보를 찾고, OpenCV가 해당 영역 안에서 빨간색 비율을 계산할 수 있습니다. 또는 세그멘테이션 모델이 물체 마스크를 만들고, OpenCV가 그 마스크의 중심점과 면적을 계산할 수 있습니다.

중요한 것은 둘 중 하나만 고르는 것이 아닙니다. 딥러닝은 의미를 잘 이해하고, OpenCV는 픽셀 수준 계산을 빠르고 명확하게 처리합니다. 로봇 비전에서는 이 둘을 함께 쓰는 감각이 필요합니다.

로봇 에이전트 관점에서 보기

앞 장들에서 우리는 AI 로봇 에이전트를 인식, 계획, 실행, 피드백의 반복 구조로 바라보았습니다. 이번 장의 OpenCV 코드는 그중 인식 모듈의 가장 기본적인 형태입니다.

사용자가 "빨간 컵을 집어 오른쪽에 놓아 줘"라고 말하면, 로봇은 먼저 빨간 컵을 찾아야 합니다. 이때 시스템 내부에서는 다음과 같은 흐름이 만들어질 수 있습니다.

  1. LLM이 명령에서 대상 객체와 조건을 추출한다.

  2. 비전 모듈이 카메라 이미지에서 후보 물체를 찾는다.

  3. OpenCV가 색상 마스크, 윤곽선, 중심점을 계산한다.

  4. 좌표 변환 모듈이 픽셀 위치를 로봇 좌표로 바꾼다.

  5. 로봇팔 실행 모듈이 해당 위치로 이동해 집기 동작을 수행한다.

이번 장에서 다룬 것은 2번과 3번의 기초입니다. 아직은 빨간색만 찾는 단순한 예제지만, 구조는 훨씬 큰 시스템과 같습니다. 이미지 입력을 숫자로 다루고, 필요한 정보를 추출해 다음 모듈로 넘기는 것. 이것이 로봇 비전의 핵심입니다.

작은 실험: 내 책상 위 물체 찾기

직접 실험해 보면 OpenCV의 감각이 훨씬 빨리 잡힙니다. 책상 위에 색이 뚜렷한 물체 하나를 올려놓고 카메라를 켜 보세요. 빨간 컵, 파란 뚜껑, 초록색 블록처럼 배경과 잘 구분되는 물체가 좋습니다.

먼저 HSV 범위를 조금씩 바꾸며 마스크가 어떻게 달라지는지 확인해 봅니다. 채도 S의 하한을 높이면 흐릿한 색이나 회색 배경이 줄어듭니다. 명도 V의 하한을 높이면 어두운 그림자 영역이 줄어듭니다. 반대로 너무 높이면 실제 물체 일부가 사라질 수 있습니다.

그다음 물체를 카메라 앞에서 천천히 움직여 보세요. 중심점 좌표가 따라 움직일 것입니다. 물체를 멀리 가져가면 면적이 작아지고, 가까이 가져오면 면적이 커집니다. 이 면적 변화는 나중에 물체가 너무 멀리 있는지, 너무 가까이 있는지 판단하는 데도 활용할 수 있습니다.

마지막으로 조명을 바꿔 보세요. 형광등 아래, 창가, 그림자 속에서 마스크가 어떻게 흔들리는지 보면 현실 세계의 어려움이 바로 느껴집니다. 로봇 비전은 코드만의 문제가 아닙니다. 카메라 위치, 조명, 배경, 물체 재질이 모두 결과에 영향을 줍니다.

이번 장 정리

OpenCV는 로봇 비전의 기초 체력을 만들어 주는 도구입니다. 딥러닝 모델이 발전해도 카메라 프레임을 읽고, 색상 공간을 변환하고, 마스크를 만들고, 윤곽선을 분석하고, 중심점을 계산하는 작업은 계속 필요합니다.

이번 장에서는 파일과 카메라에서 이미지를 읽는 방법을 살펴보았습니다. OpenCV가 기본적으로 BGR 순서를 사용한다는 점도 확인했습니다. 또한 HSV 색상 공간을 이용해 원하는 색상 영역을 분리하고, 마스크를 다듬은 뒤, 윤곽선과 모멘트로 물체의 중심점을 계산했습니다.

이제 우리는 이미지 속 물체의 2D 위치를 찾을 수 있습니다. 다음 장에서는 한 단계 더 나아가 객체 검출을 다룹니다. 색상만으로 찾는 것이 아니라, "컵", "병", "상자"처럼 의미 있는 물체를 찾는 방법입니다. 그때도 OpenCV는 사라지지 않습니다. 딥러닝 모델이 찾아낸 결과를 표시하고, 잘라내고, 후처리하고, 로봇이 사용할 수 있는 정보로 바꾸는 데 계속 함께할 것입니다.

책 목록

AI 로봇 에이전트 만들기: 비전·로봇팔·학습·LLM으로 자연어 명령을 실행하는 로봇

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기