contours.bmp
0.15MB

1. 객체 단위 분석

- (흰색)객체를 분할해 특징 분석  

- 객체 위치 및 크기정보, ROI 추출, 모양 분석 등

- 종류:  레이블링 ,  외곽선 검출

 

2. 레이블링(Connected component labeling)

- 서로 연결되어 있는 객체 픽셀에 고유한 번호를 지정(레이블 맵)

- 영상안에 객체를 영역 기반으로 모양을 분석한다.

- 레이블맵, 바운딩박스, 픽셀 개수, 무게중심좌표를 반환

 

3. 외곽선 검출(contour tracing)

- 각 객체의 외곽선 좌표 모두 검출

- 외곽선 기반 모양 분석

- 다양한 외곽선 처리 함수에서 활용가능(근사화, 컨벡스헐 등)

 

레이블링기법과 외곽선 검출은 객체를 단위별로 분할해서 분석하는 방법들로 접근법에 따라 둘 중 하나를 선택한다.

영역기반이 적합하면 레이블링, 외곽선 기반으로 모양 분석이 적합하면 외곽선 검출을 선택한다. 

 

4. 레이블링(labeling)

1) 개념

 - 동일 객체에 속한 모든 픽셀에 고유 번호 매기는 작업.

 - 일반적으로 이진 영상에서 수행.

 - Connected component labeling

2) 픽셀의 연결관계

 - 4 이웃 연결관계 (4-neightbor connectivity)

    : 상하좌우, 4개 픽셀만 연결된 것으로 취급. (십자모양)

 - 8 이웃 연결 관계 (8-neightbor connectivity)

    :  상하좌우와 대각선 방향, 8개 픽셀이 모두 연결된 것으로 취급.

 

입력영상: 이진영상 (zero: 배경, non-zero: 객체)  , 일반적으로 CV_8UC1 행렬을 가짐

출력영상: 레이블 맵, label map (2차원 정수행렬, 객체들이 고유번호로 매겨짐), CV_32S  (Int) 타입을 일반적으로 가짐

 

int connectedComponents(InputArray image, OutputArray labels,
int connectivity = 8, int ltype = CV_32S);

image: (입력) 8비트 1채널 영상  // 이진화영상

labels: (출력) 레이블링 결과 행렬. 레이블맵 정수형 행렬 반환 . Mat 객체. 

connectivity : 4 또는 8

ltype: 출력영상타입. CV_32S (int type) 또는  CV_16S (메모리 절약 목적, short 타입) . 대체로 CV_32S 지정함.

반환값:  객체 개수. N을 반환하면 0~(N-1) 사이의 레이블이 존재하며, 0은 배경 의미(실제 객체 개수는 N-1)

실제로 객체 개수에 +1 을 한값을 반환함.

해당 함수 반환값이 10이면 1~9 번까지 객체 존재하며, 여기에 0번(배경)이 추가 됨으로써 10 이됨  

 

int connectedComponentsWithStats(InputArray image, OutputArray labels, 
OutputArray stats, OutputArray centroids,
int connectivity = 8, int ltype = CV_32S);

image: 8비트 1채널 영상

labels: (출력) 레이블링 결과 행렬, 레이블맵 Mat 객체

stats: (출력) 각 객체의 바운딩 박스, 픽셀개수 정보를 담은 행렬(CV_32SC1 [정수형 행렬], N x 5)

centroids: (출력) 각 객체의 무게 중심 위치 정보를 담은 행렬(CV_64FC1, N x 2)

connectivity: 4 또는 8

ltype: 출력영상 타입, CV_32S 또는 CV_16S

반환값: 객체 개수. N을 반환하면 0 ~  N-1 사이의 레이블이 존재하며 0은 배경을 의미( 실제 객체 개수는 N-1)

 

레이블링 사용시 connectedComponentsWithStats 함수를 주로 사용

 

 

책 설명 참고하길.

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

using namespace std;
using namespace cv;
int main() {
	uchar data[] = {
		0,0,1,1,0,0,0,0,
		1,1,1,1,0,0,1,0,
		1,1,1,1,0,0,0,0,
		0,0,0,0,0,1,1,0,
		0,0,0,1,1,1,1,0,
		0,0,1,1,0,0,1,0,
		0,0,1,1,1,1,1,0,
		0,0,0,0,0,0,0,0,
	};

	Mat src(8, 8, CV_8UC1, data);
#if 0
	Mat labels;
	int num_labels = connectedComponents(src, labels);  //union find 로 그룹 매겨서 객체 갯수 파악 가능. bfs 섬구하기로 객체 갯수 파악 가능
	
	cout << "src:\n" << src << endl;
	cout << "number of labels: " << num_labels << endl;
	cout << "labels: \n" << labels << endl;
#else
	Mat labels, stats, centroids;  
	//labels: (출력)레이블링 결과 행렬, 레이블맵 Mat 객체
	//stats : (출력)각 객체의 바운딩 박스, 픽셀개수 정보를 담은 행렬(CV_32SC1[정수형 행렬], N x 5)
	//centroids: (출력) 각 객체의 무게 중심 위치 정보를 담은 행렬
	int num_labels = connectedComponentsWithStats(src, labels, stats, centroids);
	cout << "src:\n" << src << endl;
	cout << "number of labels: " << num_labels << endl;
	cout << "labels: \n" << labels << endl;
	cout << "stats:\n" << stats << endl;
	cout << "centroids: \n" << centroids << endl;


#endif
}

 

레이블링

키보드 영상에서 문자 영역 분할 예제

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

using namespace std;
using namespace cv;

int main()
{
	Mat src = imread("keyboard.bmp", IMREAD_GRAYSCALE);

	if (src.empty()) {
		cerr << "Image laod failed!" << endl;
		return -1;
	}
	Mat src_bin;
	threshold(src, src_bin, 0, 255, THRESH_BINARY | THRESH_OTSU);  //자동으로 이진화 설정

	Mat labels, stats, centroids;
	int cnt = connectedComponentsWithStats(src_bin, labels, stats, centroids);
	cout << cnt << endl;

	Mat dst;
	cvtColor(src, dst, COLOR_GRAY2BGR);
	for (int x = 1; x < cnt; ++x) {
		int* p = stats.ptr<int>(x);
		if (p[4] < 20)continue;  // p[4]란? 레이블링된 부분의 area
		//cout <<"p: " <<*p << endl; //x 좌표
		cout << p[0] << " " << p[1] << " " << p[2] << " " << p[3] << " " << p[4] << endl;
		rectangle(dst, Rect(p[0], p[1], p[2], p[3]), Scalar(0, 255, 255));

		Mat crop = src(Rect(p[0], p[1], p[2], p[3]));
		imshow("crop", crop);
		waitKey();
	}
	imshow("src", src);
	imshow("dst", dst);
	waitKey(); 
}

stats: statistics output for each label, including the background label. Statistics are accessed via stats(label, COLUMN) where COLUMN is one of ConnectedComponentsTypes, selecting the statistic. The data type is CV_32S.

 

https://docs.opencv.org/3.4/d3/dc0/group__imgproc__shape.html

https://docs.opencv.org/3.4/d3/dc0/group__imgproc__shape.html#gac7099124c0390051c6970a987e7dc5c5

 

 

5. 외곽선 검출(contour tracing, boundary tracking) 

 1) 객체의 외곽선 좌표를 모두 추출하는 작업.

 2) 바깥쪽 & 안쪽(홀) 외곽선 

  -> 외곽선의 계층 구조도 표현가능

 

6. 외곽선 좌표 표현하기

 1) 외곽선 점 하나: Point p;

 2) 객체 하나의 외곽선: vector<Point> contour;

 3)  여러 객체의 외곽선: vector<vecor<Point>> contours;

 

7. 외곽선 검출함수.

 void findContours(InputOutputArray image, OutputArrayOfArrays contours,
 OutputArray hierarchy, int mode, int method, Point offset = Point());
 
 
 void findContours(InputOutputArray image, OutpuitArrayOfArrays contours,
 int mode, int method, Point offset = Point());

image: 입력영상, non-zero 픽셀을 객체로 간주함

contours: 검출된 외곽선 정보. vector<vector<Point>> 자료형

hierarchy:

 - 외곽선 계층 정보. vector<Vec4i> 자료형

 - 네개의 정수값이 차례대로  next, prev, child, parent외곽선 인덱스를 가리킴. 해당 외곽선 없으면 -1 지정.

mode: 외곽선 검출모드.

   - RETR_EXTERNAL, RETR_LIST, RETR_CCOMP, RETR_TREE 중 하나를 지정.

 

method: 외곽선 근사화 방법:

 - CHAIN_APPROX_NONE(근사화 없음) 또는 CHAIN_APPROX_SIMPLE(수직선, 수평선, 대각선에 대해 끝점 만 저장) 중 하나를 지정.

offset: 좌표값 이동 옵셋.

 

8. OpenCV 외곽선 그리기 함수.

void drawContours(InputOutputArray image, InputArrayOfArrays contours,
int contourIdx, const Scalar& color, int thickness = 1,
int lineType = LINE_8, InputArray hierarchy = noArray(),
int maxLevel = INT_MAX, Point offset = Point());

image: 입력영상

contours: findcontours함수가 반환한 값을 입력. 외곽선정보, vector<vector<Point>> 자료형

contourIdx: 외곽선 인덱스. contourIdx<0 이면 모두 그림

color: 외곽선 색상

thickness: 외곽선 두께. thickness<0 이면 내부를 채움

lineType: 선 종류, LINE_4, LINE_8, LINE_AA 중 하나.

hierarchy: findContours() 함수에서 구한 외곽선 계층 정보.

 계층 정보를 사용하지 않으면 noArray() 또는 Mat()  지정.

maxLevel: 그리기를 수행할 최대 외곽선 레벨. maxLevel = 0 이면 contourIdx 외곽선만 그림.

offset: 좌표값 이동 옵셋.

 

contours.bmp
0.15MB

 

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

using namespace std;
using namespace cv;

int main()
{
	Mat src = imread("contours.bmp", IMREAD_GRAYSCALE);

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return -1;
	}

	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	//외곽선 검출
	findContours(src, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_NONE);
	// mode: RETR_CCOMP  부모 자식 관계 추출(2단계)
	//근사화 작업 x CHAIN_APPROX_NONE

	//외곽선 그리기
	Mat dst = Mat::zeros(src.rows, src.cols, CV_8UC3);

	//idx가 0 이상이면 루프 돌음
	for (int idx = 0; idx >= 0; idx = hierarchy[idx][0]) {  // [0] 은 next 정보
		Scalar color(rand() & 255, rand() & 255, rand() & 255);
		drawContours(dst, contours, idx, color, 2, LINE_8, hierarchy);
	}

	imshow("src", src);
	imshow("dst", dst);
	waitKey();
}

 

 

contours2

milkdrop.bmp
0.06MB

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

using namespace std;
using namespace cv;

int main()
{
	Mat src = imread("milkdrop.bmp", IMREAD_GRAYSCALE);

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return -1;
	}

	
	Mat src_bin;
	threshold(src, src_bin, 0, 255, THRESH_BINARY | THRESH_OTSU);

	vector<vector<Point>> contours;
	findContours(src_bin, contours, RETR_LIST, CHAIN_APPROX_NONE);

	Mat dst = Mat::zeros(src.rows, src.cols, CV_8UC3);
	for (unsigned i = 0; i < contours.size(); i++) {
		Scalar color(rand() & 255, rand() & 255, rand() & 255);
		drawContours(dst, contours, i, color, 1, LINE_8);
	}

	imshow("src", src);
	imshow("src_bin", src_bin);
	imshow("dst", dst);
	waitKey();
}

'프로그래머스 > OPENCV' 카테고리의 다른 글

참고  (0) 2022.12.09
OpenCV 외곽선 함수와 응용  (1) 2022.12.09
모폴로지 (잡음 제거)  (0) 2022.12.08
지역이진화  (0) 2022.12.08
컬러영상 처리 기초 (이진화 게시글과 swap됨 )  (0) 2022.12.08

+ Recent posts