Computer >> 컴퓨터 >  >> 프로그래밍 >> C++

C++와 OpenCV를 활용한 실시간 색상 추적 구현 방법

색상 추적(Color Tracking)은 색상 감지(Color Detection)와 매우 유사한 기술입니다. 두 기술의 차이점은 추적에는 객체의 위치 정보가 추가로 필요하다는 점입니다. 즉, 감지된 객체의 면적을 계산하고 해당 영역의 현재 좌표를 지속적으로 파악한 뒤, OpenCV의 line() 함수를 사용해 객체가 움직인 경로를 화면에 시각적으로 표시하게 됩니다.

이 글에서는 C++과 OpenCV를 사용하여 실시간 카메라 영상에서 특정 색상을 추적하는 전체 과정을 예제 코드와 함께 살펴봅니다.

핵심 동작 원리

예제 코드를 살펴보기 전에, 색상 추적이 처리되는 주요 단계를 정리하면 다음과 같습니다.

  1. BGR → HSV 변환: 조명 변화에 강한 HSV 색 공간으로 변환하여 색상 기반 필터링이 용이하도록 합니다.
  2. inRange() 마스킹: 트랙바로 조절한 H(색조), S(채도), V(명도) 범위 안에 있는 픽셀만 흰색으로 남기고 나머지는 검은색으로 만듭니다.
  3. 모폴로지 연산(erode/dilate): 침식과 팽창을 조합하여 전경의 작은 노이즈를 제거하고 객체 내부의 작은 구멍을 메웁니다.
  4. moments()로 무게중심 계산: 감지된 영역의 m00(면적), m10, m01 값을 이용해 객체의 중심 좌표를 구합니다.
  5. line()으로 경로 그리기: 이전 프레임의 좌표와 현재 좌표를 연결하는 빨간색 선을 그려 이동 궤적을 표시합니다.

예제 코드

#include<iostream>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
int main(int argc, char** argv) {
    VideoCapture video_load(0);//기본 카메라에서 비디오 캡처//
    namedWindow("Adjust");//이미지를 표시할 윈도우 선언//
    int Hue_Low = 0;//색조(hue)의 하한값//
    int Hue_high = 22;//색조(hue)의 상한값//
    int Sat_Low = 99;//채도(saturation)의 하한값//
    int Sat_high = 255;//채도(saturation)의 상한값//
    int Val_Low = 0;//명도(value)의 하한값//
    int Val_high = 255;//명도(value)의 상한값//
    createTrackbar("LowH", "Adjust", &Hue_Low, 179);//최소 샹조 트랙바//
    createTrackbar("HighH", "Adjust", &Hue_high, 179);//최대 샹조 트랙바//
    createTrackbar("LowS", "Adjust", &Sat_Low, 255);//최소 채도 트랙바//
    createTrackbar("HighS", "Adjust", &Sat_high, 255);//최대 채도 트랙바//
    createTrackbar("LowV", "Adjust", &Val_Low, 255);//최소 명도 트랙바//
    createTrackbar("HighV", "Adjust", &Val_high, 255);//최대 명도 트랙바//
    int Horizontal_Last = -1;//초기 수평 위치//
    int vertical_Last = -1;//초기 수직 위치//
    Mat temp;//비디오 스트림의 프레임을 저장할 행렬 선언//
    video_load.read(temp);//비디오 스트림에서 프레임 로드//
    Mat track_motion = Mat::zeros(temp.size(), CV_8UC3);//감지용 검정 행렬 생성//
    while (true) {
        Mat actual_Image;//실제 이미지를 저장할 행렬 선언//
        bool temp_load = video_load.read(actual_Image);//비디오 프레임을 행렬에 로드//
        Mat converted_to_HSV;//변환된 이미지를 저장할 행렬 선언//
        cvtColor(actual_Image, converted_to_HSV, COLOR_BGR2HSV);//BGR 이미지를 HSV로 변환//
        Mat adjusted_frame;//감지된 색상을 저장할 행렬 선언//
        inRange(converted_to_HSV, Scalar(Hue_Low, Sat_Low, Val_Low),
        Scalar(Hue_high, Sat_high, Val_high), adjusted_frame);//트랙바 값 범위 적용//
        erode(adjusted_frame, adjusted_frame, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));//전경에서 작은 객체를 제거하는 모폴로지 열기//
        dilate(adjusted_frame, adjusted_frame, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));//전경에서 작은 객체를 제거하는 모폴로지 열기//
        dilate(adjusted_frame, adjusted_frame, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));//전경의 작은 구멍을 채우는 모폴로지 닫기//
        erode(adjusted_frame, adjusted_frame, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));//전경의 작은 구멍을 채우는 모폴로지 닫기//
        Moments detecting_object = moments(adjusted_frame);//감지된 색상 프레임으로부터 모멘트 객체 생성//
        double vertical_moment = detecting_object.m01;//수직 위치 값 가져오기//
        double horizontal_moment = detecting_object.m10;//수평 위치 값 가져오기//
        double tracking_area = detecting_object.m00;//객체의 면적 가져오기//
        if (tracking_area > 10000){ //객체의 면적이 10000픽셀보다 클 때//
            int posX = horizontal_moment / tracking_area;//객체의 수평 위치 계산//
            int posY = vertical_moment / tracking_area;//객체의 수직 위치 계산//
            if (Horizontal_Last >= 0 && vertical_Last >= 0 && posX >= 0 && posY >= 0){ //감지된 객체가 움직일 때//
                line(track_motion, Point(posX, posY), Point(Horizontal_Last, vertical_Last), Scalar(0, 0, 255), 2);//감지된 객체의 이동 경로에 빨간색 선 그리기//
            }
            Horizontal_Last = posX;//새로운 수평 위치 갱신//
            vertical_Last = posY;//새로운 수직 위치 값 갱신//
        }
        imshow("Detected_Object", adjusted_frame);//감지된 객체 표시//
        actual_Image = actual_Image + track_motion;//원본 비디오 프레임에 연속적인 선 그리기//
        imshow("Actual", actual_Image);//원본 비디오 표시//
        cout << "position of the object is:" << Horizontal_Last << "," << vertical_Last << endl;//추적된 좌표 값 출력//
        if(waitKey(30) == 27){ //ESC 키를 누르면 루프 종료//
            cout << "esc key is pressed by user" << endl;
            break;
        }
    }
    return 0;
}

코드 설명

먼저 VideoCapture 객체로 기본 카메라(장치 번호 0)에서 영상을 받아옵니다. 이후 createTrackbar()를 통해 HSV 각 성분의 최솟값과 최댓값을 실시간으로 조절할 수 있는 슬라이더 6개를 생성합니다. 트랙바를 활용하면 코드를 다시 컴파일하지 않고도 추적하려는 색상 범위를 손쉽게 튜닝할 수 있습니다.

무한 루프 안에서는 매 프레임마다 BGR 이미지를 HSV로 변환한 뒤 inRange()로 색상 마스크를 생성합니다. 여기에 침식(erode)과 팽창(dilate)을 교차 적용하는 모폴로지 열기/닫기 연산을 수행하여 작은 노이즈를 걸러냅니다.

moments() 함수가 반환하는 값 중 m00은 감지 영역의 면적, m10m01은 각각 x축·y축 방향의 모멘트 합입니다. 면적이 10000픽셀보다 클 때만 m10/m00, m01/m00으로 무게중심 좌표를 계산하는데, 이는 너무 작은 영역이 노이즈로 오인되는 것을 방지하기 위함입니다.

마지막으로 이전 좌표와 현재 좌표를 line() 함수로 연결하여 빨간색 궤적을 그리고, imshow()로 결과를 화면에 출력합니다. ESC 키(ASCII 코드 27)를 누르면 루프가 종료됩니다.

실행 결과

프로그램을 실행하면 카메라 영상에서 설정한 색상 범위에 해당하는 객체가 흰색 마스크로 감지되고, 원본 영상 위에 객체가 지나간 자리마다 빨간색 궤적 선이 그려지는 것을 확인할 수 있습니다. 콘솔 창에는 객체의 현재 x, y 좌표가 실시간으로 출력됩니다.

C++와 OpenCV를 활용한 실시간 색상 추적 구현 방법


C++와 OpenCV를 활용한 실시간 색상 추적 구현 방법