로봇이 '본다'는 것
라인 센서와 색상 센서는 로봇에게 아주 단순하지만 강력한 눈을 달아 주었다. 검은색인지 흰색인지, 빨간색인지 파란색인지 판단할 수만 있어도 로봇은 선을 따라가고 표식을 구분할 수 있다. 그런데 카메라는 그보다 훨씬 많은 정보를 한 번에 던져 준다. 화면 한 장은 수십만 개, 많게는 수백만 개의 작은 점으로 이루어져 있고, 각 점에는 색과 밝기 정보가 들어 있다.
이 말은 카메라가 훨씬 똑똑한 센서라는 뜻이기도 하지만, 동시에 훨씬 다루기 까다로운 센서라는 뜻이기도 하다. 거리 센서 하나는 숫자 하나를 돌려준다. 예를 들어 23cm처럼 말이다. 하지만 카메라는 "여기 640×480개의 픽셀이 있으니 네가 알아서 해석해 봐"라고 말한다. 로봇 입장에서는 갑자기 조용한 방에서 시장 한복판으로 나온 셈이다.
그래서 카메라 기반 로봇의 첫 목표는 거창한 인공지능이 아니다. 먼저 영상을 안정적으로 가져오고, 그 영상에서 로봇 제어에 쓸 수 있는 작은 정보를 뽑아내는 것이다. 예를 들어 "초록색 공의 중심이 화면 왼쪽에 있다", "물체가 너무 가까워졌다", "라인이 화면 중앙에서 오른쪽으로 치우쳤다" 같은 정보면 충분하다. 이 장에서는 라즈베리파이에 카메라를 연결하고, OpenCV로 프레임을 읽은 뒤, 색상 추적을 이용해 간단한 이동 명령을 만드는 출발점까지 가 본다.

CSI 카메라 모듈과 USB 웹캠
라즈베리파이에서 가장 많이 쓰는 카메라 연결 방식은 크게 두 가지다. 하나는 라즈베리파이 전용 카메라 모듈을 CSI 포트에 연결하는 방식이고, 다른 하나는 일반 USB 웹캠을 USB 포트에 꽂는 방식이다.
CSI는 Camera Serial Interface의 줄임말이다. 라즈베리파이 보드에 있는 얇은 리본 케이블용 커넥터를 떠올리면 된다. 라즈베리파이 카메라 모듈은 이 포트를 통해 보드와 직접 연결된다. 공식 카메라 모듈이나 호환 모듈은 라즈베리파이의 카메라 소프트웨어 스택과 잘 맞물려 동작하며, rpicam-hello, rpicam-still, rpicam-vid 같은 도구로 테스트할 수 있다.1
USB 웹캠은 우리가 PC에 꽂아 쓰는 그 카메라다. 대부분의 일반 웹캠은 UVC, 즉 USB Video Class 규격을 사용한다. 쉽게 말해 운영체제가 "아, 이건 카메라구나" 하고 비교적 쉽게 알아볼 수 있도록 약속된 방식이다. 라즈베리파이에서도 /dev/video0 같은 V4L2 장치로 잡히는 경우가 많고, OpenCV의 VideoCapture(0)로 프레임을 읽기 쉽다.2
| 구분 | CSI 카메라 모듈 | USB 웹캠 |
|---|---|---|
| 연결 방식 | 라즈베리파이 CSI/MIPI 커넥터에 리본 케이블 연결 | USB 포트에 연결 |
| 장점 | 라즈베리파이와 통합이 좋고, 품질과 지연시간 면에서 유리한 경우가 많음 | 연결이 쉽고 제품 선택 폭이 넓으며 케이블 배치와 교체가 편함 |
| 주의점 | 리본 케이블 방향과 커넥터 파손에 주의해야 함 | USB 대역폭, 전원 부족, 드라이버 호환성 문제가 생길 수 있음 |
| 확인 도구 | rpicam-hello, rpicam-still, rpicam-vid | lsusb, v4l2-ctl, ffmpeg, OpenCV |
| 추천 상황 | 고정 장착, 낮은 지연, 라즈베리파이 중심 영상 처리 | 빠른 실험, 긴 케이블, 여러 대의 카메라, 손쉬운 교체 |
로봇에서는 카메라의 "성능"만큼 "장착하기 쉬운가"도 중요하다. CSI 카메라는 얇고 가벼워서 로봇 앞쪽이나 팬틸트 구조에 올리기 좋다. 하지만 리본 케이블이 짧거나 꺾임에 약할 수 있다. USB 웹캠은 케이블이 튼튼하고 위치를 바꾸기 쉽다. 대신 웹캠 자체가 무겁거나 크면 작은 로봇의 무게중심을 흐트러뜨릴 수 있다.
ESP32-CAM 같은 보드도 선택지가 될 수 있다. ESP32-CAM은 작은 보드 안에 Wi-Fi와 카메라가 함께 들어 있어 영상 스트리밍 로봇을 간단히 만들 수 있다. 실제로 ESP32-CAM 기반 자동차 로봇 예제에서는 웹 페이지에 카메라 영상을 띄우고 전진, 후진, 좌회전, 우회전 버튼으로 로봇을 조종하는 구조를 사용한다.3 다만 이 장에서는 영상 처리를 직접 해 보기 위해 라즈베리파이를 중심으로 설명한다. ESP32-CAM은 "무선 카메라 노드", 라즈베리파이는 "영상을 분석하는 두뇌"로 나누어 쓰는 식으로 확장할 수 있다.
카메라를 연결하기 전에 확인할 것
카메라는 센서이면서 동시에 꽤 많은 데이터를 계속 쏟아내는 장치다. 그래서 연결하기 전에 몇 가지를 먼저 챙기는 편이 좋다.
CSI 카메라 모듈을 연결할 때는 반드시 라즈베리파이 전원을 끈 상태에서 작업한다. 리본 케이블은 얇고 커넥터의 잠금 플라스틱도 약하다. 힘으로 밀어 넣기보다 커넥터 잠금 장치를 살짝 열고, 케이블을 끝까지 넣은 뒤, 다시 잠그는 순서로 작업한다. 카메라가 인식되지 않을 때 가장 흔한 원인은 케이블이 반대로 꽂혔거나, 끝까지 들어가지 않았거나, 잠금 장치가 제대로 닫히지 않은 경우다.
USB 웹캠은 간단히 꽂으면 되지만 전원을 가볍게 보면 안 된다. 라즈베리파이는 본체, USB 장치, Wi-Fi, 모터 제어 회로까지 함께 쓰는 경우가 많다. 여기에 웹캠까지 추가하면 전류가 더 필요하다. 화면 오른쪽 위에 저전압 경고가 뜨거나, 카메라가 중간에 끊기거나, 영상이 멈춘다면 전원 어댑터와 케이블부터 의심하자. 특히 로봇에서는 모터가 출발하는 순간 전압이 출렁이기 때문에, 보드 전원과 모터 전원 분리, 공통 GND, 충분한 전류 여유가 다시 중요해진다.
카메라 기반 로봇은 CPU도 바쁘다. 영상 처리, 웹 스트리밍, 무선 통신, 모터 명령이 한 보드에서 동시에 돌아가면 라즈베리파이가 뜨거워질 수 있다. OpenCV처럼 계속 프레임을 처리하는 작업은 CPU 부하가 크므로, 라즈베리파이 4나 5를 쓴다면 방열판이나 팬을 준비하는 편이 안정적이다.4
라즈베리파이 카메라 모듈 확인하기
먼저 라즈베리파이 OS가 최신 상태인지 확인하자. 아래 명령은 패키지 목록을 갱신하고, 카메라 테스트와 OpenCV 실습에 필요한 도구를 설치한다.
sudo apt update
sudo apt install -y rpicam-apps v4l-utils python3-opencv라즈베리파이 OS Bookworm 계열에서는 카메라 명령 이름이 rpicam-* 형태다. 예전 문서나 예제에서는 libcamera-hello, libcamera-still 같은 이름을 볼 수 있는데, 최신 환경에서는 rpicam-hello, rpicam-still을 먼저 시도하면 된다.
CSI 카메라 모듈이 제대로 연결되었는지 확인하려면 다음 명령을 실행한다.
rpicam-hello -t 0모니터가 연결된 데스크톱 환경이라면 카메라 미리보기 창이 열린다. -t 0은 종료하지 않고 계속 실행하라는 뜻이다. 중지하려면 Ctrl+C를 누른다.
사진 한 장을 저장해 보고 싶다면 다음처럼 실행한다.
rpicam-still -o camera_test.jpg짧은 영상을 저장할 수도 있다.
rpicam-vid -t 5000 -o camera_test.h264여기서 -t 5000은 5000밀리초, 즉 5초 동안 촬영하라는 뜻이다. 이 단계에서 영상이 잘 나온다면 하드웨어 연결은 대체로 성공이다. 아직 OpenCV로 무언가를 인식하지 않았더라도, 로봇의 눈이 뜨인 셈이다.
만약 SSH로 접속한 상태라 미리보기 창을 볼 수 없다면 사진 저장 명령부터 테스트하는 것이 좋다. 저장된 파일은 scp로 PC에 복사하거나, 라즈베리파이의 파일 공유 기능을 이용해 확인할 수 있다.
USB 웹캠 확인하기
USB 웹캠은 먼저 운영체제가 장치를 인식했는지 확인한다.
lsusb목록에 웹캠 이름이나 제조사명이 보이면 USB 장치로는 인식된 것이다. 다음으로 V4L2 장치 목록을 확인한다. V4L2는 Video4Linux2의 줄임말로, 리눅스에서 카메라 같은 영상 장치를 다루는 표준 인터페이스라고 생각하면 된다.
v4l2-ctl --list-devices출력 예시는 환경마다 다르지만, 대략 다음처럼 /dev/video0 또는 /dev/video1이 보인다.
HD Webcam: HD Webcam
/dev/video0
/dev/video1웹캠이 지원하는 해상도, 프레임레이트, 영상 포맷도 확인할 수 있다.
v4l2-ctl --device=/dev/video0 --list-formats-ext여기서 MJPG, YUYV 같은 포맷 이름이 보일 수 있다. MJPEG는 각 프레임을 JPEG처럼 압축해서 보내는 방식이다. USB 대역폭은 덜 쓰지만, 압축을 푸는 데 CPU가 필요하다. YUYV는 압축이 적거나 없는 형태라 CPU 부담은 상대적으로 낮을 수 있지만, 데이터량이 커질 수 있다. 어느 쪽이 더 좋은지는 웹캠, 라즈베리파이 모델, 해상도에 따라 달라진다.
사진 한 장을 빠르게 찍어 보고 싶다면 ffmpeg를 사용할 수 있다.
sudo apt install -y ffmpeg
ffmpeg -f v4l2 -video_size 640x480 -i /dev/video0 -frames:v 1 usb_test.jpg이제 USB 웹캠이 /dev/video0으로 정상 동작한다는 사실을 확인했다. 다음 단계는 Python에서 이 프레임을 읽는 것이다.
OpenCV로 프레임 읽기
OpenCV는 영상 처리와 컴퓨터 비전에서 가장 널리 쓰이는 라이브러리 중 하나다. 여기서 컴퓨터 비전이란 컴퓨터가 이미지나 영상을 보고 의미 있는 정보를 뽑아내는 기술을 말한다. 이 장에서는 거창한 인식 모델을 돌리기보다, 카메라 프레임을 읽고 화면에 표시하는 가장 기본 흐름부터 시작한다.
USB 웹캠은 다음 코드로 바로 테스트할 수 있다. 파일 이름을 camera_preview.py로 저장하자.
import cv2
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
cap.set(cv2.CAP_PROP_FPS, 30)
if not cap.isOpened():
print("카메라를 열 수 없습니다. 장치 번호를 확인하세요.")
raise SystemExit
while True:
ok, frame = cap.read()
if not ok:
print("프레임을 읽지 못했습니다.")
break
cv2.imshow("camera", frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()실행은 다음처럼 한다.
python3 camera_preview.py창이 열리고 영상이 보이면 성공이다. q를 누르면 종료된다. 만약 영상이 나오지 않으면 cv2.VideoCapture(1)처럼 번호를 바꾸어 보자. 여러 개의 /dev/video* 장치가 잡히는 웹캠도 있기 때문이다. USB 웹캠을 OpenCV에서 테스트할 때 장치 번호 확인, 저전압 문제, 호환성 문제는 자주 만나는 원인이다.5
CSI 카메라 모듈은 환경에 따라 접근 방식이 조금 다르다. 최신 라즈베리파이 OS에서는 rpicam-* 도구로 카메라가 잘 동작하는지 먼저 확인하고, Python에서는 Picamera2 라이브러리로 프레임을 받아 OpenCV에 넘기는 방식이 많이 쓰인다. 일부 환경에서는 GStreamer 파이프라인을 이용해 cv2.VideoCapture()로 CSI 카메라를 열 수도 있지만, OpenCV 빌드 옵션에 따라 동작하지 않을 수 있다. 초보 단계에서는 "CSI 카메라는 rpicam으로 확인, USB 웹캠은 V4L2와 VideoCapture로 확인"이라는 흐름을 먼저 익히는 편이 덜 헷갈린다.
중요한 것은 카메라 종류가 아니라 프레임을 얻은 다음의 처리 과정이다. OpenCV 입장에서는 CSI 카메라에서 온 프레임이든 USB 웹캠에서 온 프레임이든 결국 하나의 이미지 배열이다. 이 배열을 줄이고, 색공간을 바꾸고, 필요한 부분을 골라내면 로봇 제어에 쓸 수 있는 정보가 나온다.
해상도와 프레임레이트는 욕심내지 않는다
카메라를 처음 연결하면 누구나 높은 해상도를 쓰고 싶어진다. 1920×1080 영상은 보기 좋고, 멀리 있는 물체도 더 선명하게 보인다. 하지만 이동 로봇에서는 "잘 보이는 영상"보다 "늦지 않는 영상"이 더 중요할 때가 많다.
예를 들어 로봇이 초록색 공을 따라간다고 하자. 1초 전의 고해상도 화면을 보고 방향을 결정하면 로봇은 이미 늦었다. 공은 움직였고, 로봇도 움직였고, 화면 속 정보는 과거가 되었다. 반대로 320×240 해상도라도 20~30fps로 안정적으로 들어오고 처리 지연이 작다면 로봇은 훨씬 민첩하게 반응할 수 있다.
영상 처리 지연은 여러 단계에서 쌓인다. 카메라가 빛을 받아 프레임을 만드는 시간, 라즈베리파이로 전송되는 시간, 압축을 푸는 시간, OpenCV가 처리하는 시간, 웹으로 전송하는 시간, 최종적으로 모터 명령이 나가는 시간까지 모두 더해진다. 사람이 보기에는 "조금 버벅인다" 정도지만, 움직이는 로봇에게는 벽에 부딪히느냐 멈추느냐의 차이가 될 수 있다.
처음 실습할 때는 다음 기준으로 시작하면 좋다.
색상 추적, 라인트레이싱 보조, 간단한 장애물 판단은 320×240 또는 640×480에서 시작한다.
프레임레이트는 무리하게 60fps를 노리기보다 15~30fps를 안정적으로 유지한다.
조명이 어두우면 해상도를 올리기보다 조명을 개선하고, 카메라 흔들림을 줄인다.
알고리즘이 느리면 전체 화면을 처리하지 말고 필요한 영역만 잘라서 처리한다.
특히 조명은 생각보다 중요하다. 사람 눈은 어두운 방에서도 꽤 잘 적응하지만, 카메라는 노출 시간을 늘리면서 영상이 흐려질 수 있다. 로봇이 움직이면 흔들림이 더 커진다. 작은 LED 조명 하나를 추가하는 것만으로도 인식 안정성이 크게 좋아질 때가 많다.
영상 전처리의 기본 흐름
카메라 프레임을 그대로 로봇 제어에 쓰기는 어렵다. 프레임에는 바닥, 벽, 그림자, 책상 다리, 사람 손, 전선 등 온갖 정보가 들어 있다. 로봇에게 필요한 것은 그중 아주 작은 일부다. 예를 들어 초록색 공을 따라가는 로봇이라면 "초록색으로 보이는 영역의 중심 좌표"만 있으면 된다.
OpenCV에서 자주 쓰는 기본 흐름은 다음과 같다.
프레임 크기를 줄여 처리량을 낮춘다.
BGR 이미지를 HSV 같은 색공간으로 변환한다.
원하는 색 범위만 마스크로 골라낸다.
작은 잡음을 줄이고, 가장 큰 윤곽선을 찾는다.
윤곽선의 중심점과 면적을 계산해 로봇 명령으로 바꾼다.
여기서 색공간이라는 말이 낯설 수 있다. OpenCV가 카메라에서 읽은 이미지는 보통 BGR 순서로 색을 표현한다. 파랑, 초록, 빨강 값을 섞어 색을 나타내는 방식이다. 하지만 특정 색을 추적할 때는 HSV가 더 편하다. HSV는 색상(Hue), 채도(Saturation), 명도(Value)로 색을 표현한다. "초록색 계열"처럼 색상 범위를 정하기가 BGR보다 쉽다.
마스크는 원하는 부분만 흰색으로, 나머지는 검은색으로 만든 흑백 이미지다. 로봇에게 "이 흰색 영역만 봐"라고 알려주는 필터라고 생각하면 된다.
색상 추적으로 물체 중심 찾기
이번 예제에서는 초록색 물체를 추적해 보자. 초록색 공, 초록색 포스트잇, 초록색 병뚜껑처럼 주변과 잘 구분되는 물체면 된다. 아래 코드는 USB 웹캠 기준으로 작성했지만, frame만 얻을 수 있다면 CSI 카메라에서도 같은 처리 과정을 사용할 수 있다.
파일 이름을 color_tracking.py로 저장한다.
import cv2
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
if not cap.isOpened():
print("카메라를 열 수 없습니다.")
raise SystemExit
# 초록색 HSV 범위. 조명과 물체 색에 따라 조정해야 한다.
lower_green = (35, 60, 60)
upper_green = (85, 255, 255)
while True:
ok, frame = cap.read()
if not ok:
break
# 처리 속도를 위해 프레임 크기를 줄인다.
frame = cv2.resize(frame, (320, 240))
height, width = frame.shape[:2]
center_x = width // 2
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower_green, upper_green)
# 작은 점 잡음을 줄인다.
mask = cv2.erode(mask, None, iterations=1)
mask = cv2.dilate(mask, None, iterations=2)
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
command = "STOP"
if contours:
largest = max(contours, key=cv2.contourArea)
area = cv2.contourArea(largest)
if area > 300:
M = cv2.moments(largest)
if M["m00"] != 0:
obj_x = int(M["m10"] / M["m00"])
obj_y = int(M["m01"] / M["m00"])
error = obj_x - center_x
cv2.circle(frame, (obj_x, obj_y), 6, (0, 0, 255), -1)
cv2.line(frame, (center_x, 0), (center_x, height), (255, 255, 255), 1)
dead_zone = 30
if area > 12000:
command = "STOP"
elif error < -dead_zone:
command = "LEFT"
elif error > dead_zone:
command = "RIGHT"
else:
command = "FORWARD"
cv2.putText(frame, f"error: {error}, area: {int(area)}", (10, 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
cv2.putText(frame, f"CMD: {command}", (10, 220),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow("frame", frame)
cv2.imshow("mask", mask)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()이 코드는 아직 모터를 움직이지 않는다. 대신 화면에 LEFT, RIGHT, FORWARD, STOP 같은 명령을 표시한다. 이 단계가 중요하다. 카메라 인식이 안정적인지 확인하기 전에 모터부터 연결하면, 로봇이 책상에서 뛰어내리거나 벽을 향해 돌진할 수 있다. 먼저 모니터 화면에서 명령이 의도대로 바뀌는지 확인하자.
코드의 핵심은 error = obj_x - center_x다. 물체 중심이 화면 중앙보다 왼쪽에 있으면 오차가 음수이고, 오른쪽에 있으면 양수다. 이 오차를 이용해 로봇이 어느 방향으로 회전할지 결정한다. 물체가 화면 중앙 근처에 있으면 전진하고, 물체 면적이 너무 커지면 가까이 왔다고 보고 멈춘다.
물론 이 방식은 아주 단순하다. 물체가 멀어도 크게 보이는 경우가 있고, 조명이 바뀌면 색 범위가 흔들릴 수 있다. 그래도 이 예제에는 카메라 기반 제어의 핵심 구조가 모두 들어 있다. "영상 입력 → 정보 추출 → 오차 계산 → 명령 생성"이라는 흐름이다. 뒤에서 제어를 배울 때 이 오차를 더 부드럽게 다루면 P 제어, PID 제어로 자연스럽게 이어진다.
화면 중심 오차를 로봇 명령으로 바꾸기
카메라 영상에서 중심점을 찾았다고 해서 로봇이 바로 똑똑해지는 것은 아니다. 중심점을 로봇의 행동으로 바꾸는 규칙이 필요하다. 가장 단순한 규칙은 다음과 같다.
| 조건 | 의미 | 명령 |
|---|---|---|
| 물체가 보이지 않음 | 추적 대상 없음 | 정지 또는 천천히 회전하며 탐색 |
| 물체가 화면 왼쪽 | 로봇이 왼쪽으로 방향을 맞춰야 함 | 왼쪽 회전 |
| 물체가 화면 오른쪽 | 로봇이 오른쪽으로 방향을 맞춰야 함 | 오른쪽 회전 |
| 물체가 중앙이고 적당히 멂 | 방향이 맞고 접근 가능 | 전진 |
| 물체 면적이 너무 큼 | 너무 가까움 | 정지 |
이런 규칙 기반 제어는 단순하지만 강력하다. 초보 로봇에게는 복잡한 인공지능보다 이런 명확한 규칙이 더 안정적일 때가 많다. 특히 작은 바퀴 로봇은 카메라 지연, 바닥 미끄러짐, 모터 편차가 모두 영향을 주기 때문에 처음부터 복잡한 알고리즘을 얹으면 원인을 찾기 어렵다.
실제 로봇에 연결할 때는 라즈베리파이가 직접 모터 드라이버를 제어할 수도 있고, 아두이노나 ESP32에 명령을 보내 모터 제어를 맡길 수도 있다. 예를 들어 라즈베리파이는 카메라와 OpenCV를 담당하고, ESP32는 모터 PWM과 엔코더 처리를 담당하는 식이다. 이 구조는 뒤의 "라즈베리파이와 마이크로컨트롤러 연결" 장에서 더 자세히 다룬다.
여기서는 한 가지 원칙만 기억하자. 카메라는 판단을 느리게 만들 수 있고, 모터는 빠르게 사고를 낼 수 있다. 따라서 카메라 기반 로봇에는 항상 정지 조건이 있어야 한다. 프레임을 일정 시간 이상 읽지 못하면 정지, 명령이 일정 시간 이상 갱신되지 않으면 정지, 물체를 잃어버리면 정지 또는 저속 탐색처럼 안전한 기본값을 정해 두어야 한다.
웹 브라우저로 영상 보기
로봇에 카메라를 달면 자연스럽게 스마트폰이나 노트북에서 로봇 시야를 보고 싶어진다. 가장 단순한 방식 중 하나가 MJPEG 스트리밍이다. MJPEG는 JPEG 이미지를 연속으로 보내 마치 영상처럼 보이게 하는 방식이다. 압축 효율은 최신 동영상 코덱보다 떨어지지만, 구조가 단순하고 웹 브라우저에서 다루기 쉽다.
아래는 Flask를 이용한 아주 간단한 MJPEG 스트리밍 예제다. 먼저 Flask가 없다면 설치한다.
sudo apt install -y python3-flask파일 이름을 mjpeg_stream.py로 저장한다.
import cv2
from flask import Flask, Response
app = Flask(__name__)
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
def generate_frames():
while True:
ok, frame = cap.read()
if not ok:
continue
frame = cv2.resize(frame, (640, 480))
ok, buffer = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, 70])
if not ok:
continue
jpg = buffer.tobytes()
yield (b"--framern"
b"Content-Type: image/jpegrnrn" + jpg + b"rn")
@app.route("/")
def index():
return '<html><body><h3>Robot Camera</h3><img src="/video"></body></html>'
@app.route("/video")
def video():
return Response(generate_frames(), mimetype="multipart/x-mixed-replace; boundary=frame")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)실행 후 같은 네트워크의 스마트폰이나 PC 브라우저에서 다음 주소로 접속한다.
http://라즈베리파이_IP주소:5000영상이 보이면 성공이다. 여기에 전진, 후진, 좌회전, 우회전 버튼을 추가하면 웹 조종 로봇의 기본 형태가 된다. 다만 영상 스트리밍과 로봇 제어를 한 웹 페이지에서 함께 사용할 때는 지연을 반드시 고려해야 한다. 스마트폰 화면에 보이는 장면은 현재가 아니라 아주 조금 과거다. 네트워크 상태가 나쁘거나 해상도가 높으면 그 과거는 더 멀어진다.
그래서 원격 조종 로봇은 속도를 낮추고, 버튼을 누르고 있을 때만 움직이며, 버튼에서 손을 떼면 즉시 정지하도록 만드는 것이 안전하다. ESP32-CAM 웹 로봇 예제에서도 버튼을 누르는 동안 움직이고, 버튼을 놓으면 정지하는 방식이 자주 사용된다.3
카메라 기반 로봇에서 자주 생기는 문제
카메라가 들어오면 로봇 문제는 조금 더 복잡해진다. 센서값 하나가 이상할 때보다 원인이 많기 때문이다. 그래도 증상을 나누어 보면 대부분 차근차근 해결할 수 있다.
카메라가 아예 인식되지 않으면 케이블 방향,
/dev/video*장치 번호,lsusb,v4l2-ctl --list-devices부터 확인한다.영상이 중간에 끊기면 전원 부족, USB 케이블 품질, 과열, 너무 높은 해상도와 프레임레이트를 의심한다.
영상은 나오지만 처리가 느리면 해상도를 낮추고, 프레임을 축소하고, 전체 화면 대신 관심 영역만 처리한다.
색상 추적이 흔들리면 조명을 일정하게 만들고, HSV 범위를 다시 조정하고, 그림자와 반사가 적은 물체를 사용한다.
로봇이 영상과 반대로 움직이면 카메라 장착 방향, 좌우 반전, 모터 배선, 명령 매핑을 확인한다.
주행 중 화면이 심하게 흔들리면 카메라 브래킷을 단단히 고정하고, 필요하면 고무 패드로 진동을 줄인다.
특히 마지막 항목은 실제 로봇에서 중요하다. 책상 위에서 손으로 카메라를 들고 테스트할 때는 영상이 깨끗하다. 하지만 로봇에 올려 주행시키면 모터 진동, 바퀴 흔들림, 바닥 요철 때문에 영상이 흔들린다. 카메라 모듈을 글루건으로 대충 붙이면 처음에는 편하지만, 나중에 인식 오류의 원인이 된다. 카메라는 로봇의 눈이다. 눈이 흔들리면 판단도 흔들린다.
카메라는 많은 것을 보지만, 로봇은 필요한 것만 써야 한다
카메라를 달면 로봇이 갑자기 세상을 다 이해할 것처럼 느껴진다. 하지만 실제 로봇 제작에서는 반대로 생각하는 편이 좋다. 카메라는 너무 많은 것을 보여준다. 그래서 우리는 그중 필요한 것만 버리지 않고 남기는 방법을 배워야 한다.
이 장에서는 라즈베리파이 카메라 모듈과 USB 웹캠의 차이를 비교했고, 카메라가 정상 인식되는지 확인했으며, OpenCV로 프레임을 읽고 색상 물체의 중심점을 찾았다. 그리고 그 중심점과 화면 중앙의 오차를 이용해 왼쪽 회전, 오른쪽 회전, 전진, 정지 같은 단순한 명령을 만들었다.
이 정도만으로도 카메라 기반 로봇의 문은 열린다. 초록색 공을 따라가는 로봇, 색상 표식을 보고 방향을 바꾸는 로봇, 스마트폰으로 영상을 보며 조종하는 감시 로봇을 만들 수 있다. 이후 통신 장에서는 이런 영상 처리 결과와 제어 명령을 보드 사이에서 주고받는 방법을 다룬다. 그리고 프로젝트 장에서는 카메라가 달린 이동 로봇으로 다시 돌아와, 지금 배운 프레임 읽기와 색상 추적을 실제 주행 제어에 연결해 볼 것이다.
참고
1Camera software - Raspberry Pi Documentation
2Working with USB webcams on your Raspberry Pi
3ESP32-CAM Remote Controlled Car Robot Web Server
4Configuring Raspberry Pi for OpenCV: Camera & Cooling Workflow
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.