본문 바로가기
opencv

contour / labelling

by 꼰대코더 2023. 12. 7.

contour  는 모양의 외곽에 위치한 포인트들을 연결하는 커브를 의미한다. OpenCV 에서의 contour 는 크게 두가지 함수가 있다.

  • findContours()    : 외곽선상의 포인트 좌표 수집
  • drawContours()  :  수집된 좌표를 연결한 선긋기

수집된 포인트 좌표로 구할 수 있는 것:

무게중심 moments()
외곽선 안쪽의 면적 contourArea()
외곽선의 길이 arcLength()
간략한 외곽선 approxPolyDP()
울퉁불퉁 정도 convexHull()
울퉁불퉁 여부 isContourConvex()
외곽선을 내부에 포함한 사각형 boundingRect()
회전을 고려한 외곽선을 내부에 포함한 사각형
사각형의 4코너 포인트
minAreaRect()

boxPoints()
외곽선을 내부에 포함한 원 minEnclosingCircle()
회전을 고려한 외곽선을 내부에 포함한 사각의 타원형
타원형의 중심을 지나는 라인
fitEllipse()


 

findContours

cv::Mat img = 바이너리 이미지 (물체=255, 바탕=0)
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;

cv::findContours(img, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

// 각 외곽포인트들을 연결해서 선긋기
 cv::RNG rng;
Mat drawing = Mat::zeros( img.size(), CV_8UC3 );
for( size_t i = 0; i< contours.size(); i++ )
{
    cv::Scalar color = cv::Scalar( rng.uniform(0, 256), rng.uniform(0,256), rng.uniform(0,256) );
    cv::drawContours( drawing, contours, (int)i, color, 2, LINE_8, hierarchy, 0 );
}

hierarchy 는 4번째 파라미터에 따라 쓰임새가 있지만 너무 복잡하기 때문에 대부분 단순한 외부 외곽선만 찾기(=CV_RETR_EXTERNAL) 를 사용한다. 만약 내부에 존재하는 물체의 외곽선도 찾길 원할 경우 자료를 찾아보길 바란다.

마지막 CV_CHAIN_APPROX_SIMPLE 는 빠르게 대충의 포인트만 찾아내는 옵션으로 대부분의 경우 사용한다.

 

결과인 contours 는 이중배열로 아래와 같은 구조이다.
사각형: contours[0] = [( x0, y0), ( x1, y1), ( x2, y2), ( x3, y3)]

삼각형: contours[1] = [( x0, y0), ( x1, y1), ( x2, y2)]

원       : contours[2] = [( x0, y0), ( x1, y1), ( x2, y2), ( x3, y3), .......]

마름모: contours[3] = [( x0, y0), ( x1, y1), ( x2, y2), ( x3, y3)]

화살표: contours[4] = [( x0, y0), ( x1, y1), ( x2, y2), ( x3, y3) , .......]

각 contour 로 위에서 제시한 함수들로 원하는 기능의 값을 구할 수 있다.

 

labelling 은 독립된 물체(=255) 영역에 같은 라벨(0, 1, 2, 3...) 을 지정하여 물체를 특정할 수 있는 기능이다. 

findContours 와 비슷하게 오브젝트 찾기에 유용하다. findContours 가 외곽선의 윤곽을 리턴하는것에 비해 labelling은 단순한 면적정보를 리턴하기에 간단한 정보만 필요시에는 유용하게 쓸수 있다.

그리고 findContours 에서는 오브젝트내의 오브젝트를 찾기위해서는 그에 맞는 파라미터와 결과를 hierarchy와 함께 분석해야 하는 번거로움이 있으나 labelling은 각각 리턴하기 때문에 간단하게 처리 할 수 있다.

cv::Mat image = 바이너리 이미지(물체=255, 바탕=0)

cv::Mat LabelImg;
cv::Mat stats;
cv::Mat centroids;

// labelling 
int nLab = cv::connectedComponentsWithStats(image, LabelImg, stats, centroids);

// 디버깅용 라벨 칼라지정
std::vector<cv::Vec3b> colors(nLab);
colors[0] = cv::Vec3b(0, 0, 0);
for (int i = 1; i < nLab; ++i) {
    colors[i] = cv::Vec3b((rand() & 255), (rand() & 255), (rand() & 255));
}

// 디버깅용 이미지에 칼러 입히기
cv::Mat Dst(src.size(), CV_8UC3);
for (int i = 0; i < Dst.rows; ++i) {
    int *lb = LabelImg.ptr<int>(i);
    cv::Vec3b *pix = Dst.ptr<cv::Vec3b>(i);
    for (int j = 0; j < Dst.cols; ++j) {
        pix[j] = colors[lb[j]];
    }
}

// 라벨외곽 좌표와 사각형 
for (int i = 1; i < nLab; ++i) {
    int *param = stats.ptr<int>(i);

    int x = param[cv::ConnectedComponentsTypes::CC_STAT_LEFT];
    int y = param[cv::ConnectedComponentsTypes::CC_STAT_TOP];
    int height = param[cv::ConnectedComponentsTypes::CC_STAT_HEIGHT];
    int width = param[cv::ConnectedComponentsTypes::CC_STAT_WIDTH];

    cv::rectangle(Dst, cv::Rect(x, y, width, height), cv::Scalar(0, 255, 0), 2);
}

// 중력 중심
for (int i = 1; i < nLab; ++i) {
    double *param = centroids.ptr<double>(i);
    int x = static_cast<int>(param[0]);
    int y = static_cast<int>(param[1]);

    cv::circle(Dst,cv::Point(x, y), 3, cv::Scalar(0, 0, 255), -1);
}

// 면적
for (int i = 1; i < nLab; ++i) {
    int *param = stats.ptr<int>(i);
    std::cout << "area "<< i <<" = " << param[cv::ConnectedComponentsTypes::CC_STAT_AREA] << std::endl;

    // 좌상위치에 번호 출력
    int x = param[cv::ConnectedComponentsTypes::CC_STAT_LEFT];
    int y = param[cv::ConnectedComponentsTypes::CC_STAT_TOP];
    std::stringstream num;
    num << i;
    cv::putText(Dst, num.str(), cv::Point(x+5, y+20), cv::FONT_HERSHEY_COMPLEX, 0.7, cv::Scalar(0, 255, 255), 2);
}

 

'opencv' 카테고리의 다른 글

공백 메우기  (2) 2023.12.02
b/w 이미지의 칼럼(수직방향)별 검정(=0)픽셀수 카운트  (0) 2023.12.02
cv::Mat 이모저모  (1) 2023.11.29
이미지 회전  (0) 2023.11.27
스캔문서의 기울기 알아보기 - 2  (0) 2023.11.27