반응형
아래 유튜브 영상에 본 포스팅을 개선한 내용이 포함되어 있습니다.





최종업데이트 - 2018. 6. 10



OpenCV를 이용하여 카메라로부터 캡쳐되는 영상은 BGR 영상입니다.

RGB가 아니라 BGR이라고 부르는 것은 Blue, Green, Red 순으로 한 픽셀이 구성되기 때문입니다.


코드에서는 HSV 영상으로 변환한 후,  영상에서 원하는 색을 분리하고 있습니다.

HSV 영상에서 Hue 성분은 다음처럼 특정 색의 컬러가 일정한 범위를 갖기 때문에 분리해내기가 쉽습니다.


이미지 출처 : https://www.dreamstime.com/stock-illustration-color-colors-wheel-names-degrees-rgb-hsb-hsv-hue-image78027630




참고 [2]에 따르면  cvtColor 함수를 사용하여 변화하면 0 < Hue < 179,  0 < Saturation < 255, 0 < Value < 255 의 범위를 갖습니다.

위 그림의 값에 0.5를 곱하면 원하는 색의 hue 값입니다.



예제 코드에서는 Scalar 객체로 원하는 색의 값을 B, G , R 순으로 선언한 후,   Hue 값의 범위에 맞추어 마스크 영상을 만들어줍니다.

현재는 빨간색, 파란색, 노란색, 자홍색만 코드에 포함되어 있습니다.



아래 영상은 노란색을 검출해본 결과입니다. 검출된 노란 물체 주변으로 빨간색 사각형이 보입니다.

단순히 구현하기 위해서 형태를 보지 않고 가장 큰 물체를 기준으로 사각형이 그려집니다.





테스트에 사용된 영상입니다.  실제 카메라로 할 경우에는 조명이 너무 어둡거나 너무 밝으면 안좋은 결과가 보일 수 도 있습니다.


test.mp4




#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
int range_count = 0;

Scalar red(0, 0, 255);
Scalar blue(255, 0, 0);
Scalar yellow(0, 255, 255);

Scalar magenta(255, 0, 255);


Mat rgb_color = Mat(1, 1, CV_8UC3, red);
Mat hsv_color;

cvtColor(rgb_color, hsv_color, COLOR_BGR2HSV);


int hue = (int)hsv_color.at<Vec3b>(0, 0)[0];
int saturation = (int)hsv_color.at<Vec3b>(0, 0)[1];
int value = (int)hsv_color.at<Vec3b>(0, 0)[2];


cout << "hue = " << hue << endl;
cout << "saturation = " << saturation << endl;
cout << "value = " << value << endl;


int low_hue = hue - 10;
int high_hue = hue + 10;

int low_hue1 = 0, low_hue2 = 0;
int high_hue1 = 0, high_hue2 = 0;

if (low_hue < 10 ) {
range_count = 2;

high_hue1 = 180;
low_hue1 = low_hue + 180;
high_hue2 = high_hue;
low_hue2 = 0;
}
else if (high_hue > 170) {
range_count = 2;

high_hue1 = low_hue;
low_hue1 = 180;
high_hue2 = high_hue - 180;
low_hue2 = 0;
}
else {
range_count = 1;

low_hue1 = low_hue;
high_hue1 = high_hue;
}


cout << low_hue1 << "  " << high_hue1 << endl;
cout << low_hue2 << "  " << high_hue2 << endl;


VideoCapture cap("test.mp4");
Mat img_frame, img_hsv;


if (!cap.isOpened()) {
cerr << "ERROR! Unable to open camera\n";
return -1;
}


for (;;)
{
// wait for a new frame from camera and store it into 'frame'
cap.read(img_frame);

// check if we succeeded
if (img_frame.empty()) {
cerr << "ERROR! blank frame grabbed\n";
break;
}


//HSV로 변환
cvtColor(img_frame, img_hsv, COLOR_BGR2HSV);


//지정한 HSV 범위를 이용하여 영상을 이진화
Mat img_mask1, img_mask2;
inRange(img_hsv, Scalar(low_hue1, 50, 50), Scalar(high_hue1, 255, 255), img_mask1);
if (range_count == 2) {
inRange(img_hsv, Scalar(low_hue2, 50, 50), Scalar(high_hue2, 255, 255), img_mask2);
img_mask1 |= img_mask2;
}


//morphological opening 작은 점들을 제거
erode(img_mask1, img_mask1, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));
dilate(img_mask1, img_mask1, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));


//morphological closing 영역의 구멍 메우기
dilate(img_mask1, img_mask1, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));
erode(img_mask1, img_mask1, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)));


//라벨링
Mat img_labels, stats, centroids;
int numOfLables = connectedComponentsWithStats(img_mask1, img_labels,
stats, centroids, 8, CV_32S);


//영역박스 그리기
int max = -1, idx = 0;
for (int j = 1; j < numOfLables; j++) {
int area = stats.at<int>(j, CC_STAT_AREA);
if (max < area)
{
max = area;
idx = j;
}
}


int left = stats.at<int>(idx, CC_STAT_LEFT);
int top = stats.at<int>(idx, CC_STAT_TOP);
int width = stats.at<int>(idx, CC_STAT_WIDTH);
int height = stats.at<int>(idx, CC_STAT_HEIGHT);


rectangle(img_frame, Point(left, top), Point(left + width, top + height),
Scalar(0, 0, 255), 1);


imshow("이진화 영상", img_mask1);
imshow("원본 영상", img_frame);


if (waitKey(5) >= 0)
break;
}


return 0;
}




참고


[1] https://stackoverflow.com/questions/32522989/opencv-better-detection-of-red-color


[2] https://docs.opencv.org/3.4.1/df/d9d/tutorial_py_colorspaces.html


[3] https://github.com/opencv/opencv/blob/master/samples/cpp/videocapture_basic.cpp



문제 발생시 지나치지 마시고 댓글 남겨주시면 가능한 빨리 답장드립니다.


제가 쓴 책도 한번 검토해보세요 ^^

+ Recent posts