단계 6 — 카메라 영상과 OpenCV

차시 3차시 · 선수 학습 단계 5

이번 단계 목표 ESP32-CAM 영상을 노트북에 표시하고, 색공간 변환의 의미를 이해한다.

1. 학습 목표

2. 이론

2-1. MJPEG 스트리밍

MJPEG(Motion JPEG): 영상의 각 프레임을 JPEG 이미지로 압축해 연속 전송하는 방식.

ESP32 ──[JPEG_1]──[JPEG_2]──[JPEG_3]──...──→ 노트북
        매초 약 20장

브라우저에서 http://[ESP32_IP]/stream에 접속하면 영상이 보입니다.

⚠️ 본 ESP32-CAM 펌웨어는 동시에 1개 클라이언트만 지원. 브라우저와 Python을 동시에 못 씁니다.

2-2. 영상 = 숫자 행렬

OpenCV에서 영상은 NumPy 배열입니다.

shape: (240, 320, 3)
       높이 너비 채널(BGR)
📝 OpenCV는 BGR 순서입니다 (RGB가 아닙니다!).

2-3. 색공간: BGR vs HSV

색공간채널장점단점
BGR파랑·초록·빨강직관적조명 변하면 모든 채널이 동시에 변함
HSV색상·채도·명도조명에 강함 (H가 안 변함)직관 약함

차선 인식에서는 HSV가 압도적으로 유리합니다. 흰색 차선을 "밝기 200 이상, 채도 30 이하"로 정의하면 조명이 어두워져도 잘 잡힙니다.

HSV 채널 의미

2-4. 본 차량의 영상 처리 흐름

ESP32 영상 (BGR 320x240)
    ↓ cv2.flip(frame, 1)        # 좌우 미러링 (카메라 설치 방향 보정)
    ↓ cv2.cvtColor(BGR2HSV)
    ↓ ROI 잘라내기 (하단 50%)
    ↓ HSV 마스크 적용
    ↓ 윤곽선 검출
    → 차선 중심점 좌표

3. 핵심 OpenCV 함수

3-1. 영상 표시

import cv2
img = cv2.imread('test.jpg')
cv2.imshow('Window Name', img)
cv2.waitKey(0)  # 키 입력 대기
cv2.destroyAllWindows()

3-2. 비디오 스트림 읽기

cap = cv2.VideoCapture('http://192.168.137.74/stream')
while True:
    ret, frame = cap.read()
    if not ret:
        break
    cv2.imshow('ESP32-CAM', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cap.release()

3-3. BGR → HSV 변환

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

3-4. 색상 마스크 (특정 색만 추출)

import numpy as np
lower = np.array([0, 0, 200])      # 흰색 하한 (H, S, V)
upper = np.array([180, 30, 255])   # 흰색 상한
mask = cv2.inRange(hsv, lower, upper)
# mask는 흑백 영상: 흰색=조건 만족, 검정=불만족

3-5. 마스크 적용

result = cv2.bitwise_and(frame, frame, mask=mask)
# result는 마스크 흰 부분만 원본, 나머지는 검정

4. 활동

활동 5-A: 첫 영상 표시 ⭐ Easy

학생용 프로그램 단계6_영상표시.py를 실행:

  1. ESP32_IP를 본인 차량 IP로 수정
  2. 실행하면 영상이 창에 뜨는가?
  3. q를 눌러 종료

관찰 기록: 영상의 해상도는? FPS는? (초당 몇 장?)

활동 5-B: 색공간 변환 시각화 ⭐⭐ Standard

영상을 4분할로 표시하는 프로그램을 작성하시오.

┌──────────┬──────────┐
│ 원본(BGR)│  H 채널  │
├──────────┼──────────┤
│  S 채널  │  V 채널  │
└──────────┴──────────┘

힌트:

hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)

top    = np.hstack([frame, cv2.cvtColor(h, cv2.COLOR_GRAY2BGR)])
bottom = np.hstack([cv2.cvtColor(s, cv2.COLOR_GRAY2BGR),
                    cv2.cvtColor(v, cv2.COLOR_GRAY2BGR)])
combined = np.vstack([top, bottom])
cv2.imshow('Channels', combined)

관찰 질문

활동 5-C: 색상 추출 미니 도전 ⭐⭐⭐ Challenge

다음 색을 추출하는 마스크를 만들고, 마스크가 잘 작동함을 확인하시오.

도전추출 대상
1본인 손바닥 (살색)
2빨간 색종이
3형광 노란색 마커
H 하한H 상한S 하한V 하한
살색
빨강
형광노랑
💡 빨강은 H가 0과 180 양쪽에 걸쳐 있어 두 마스크를 합쳐야 합니다.

5. 핵심 개념 정리

개념설명
MJPEG프레임을 JPEG로 압축해 연속 전송
ROIRegion of Interest, 관심 영역
BGROpenCV의 기본 색 순서 (파랑-초록-빨강)
HSV색상-채도-명도 색공간 (조명에 강함)
마스크조건을 만족하는 픽셀만 흰색으로 표시한 흑백 영상
cv2.inRangeHSV 범위로 마스크 생성
cv2.bitwise_and마스크와 원본을 결합

6. 자기평가

항목12345
ESP32 영상을 노트북에 띄울 수 있다
HSV의 의미를 설명할 수 있다
cv2.inRange로 색상 마스크를 만들 수 있다
동료와 마스크 결과를 비교·논의했다