12.1 레이블링
12.1.1 레이블링의 이해
레이블링(labeling): 배경과 객체를 구분한 후 각각의 객체를 구분하고 분석하는 작업. 영상 내에 존재하는 객체 픽셀 집합에 고유 번호를 매기는 작업으로 연결. 구성 레이블링이라고도 함
int connectedComponents(InputArray image, OutputArray labels, int connectivity = 8, int ltype = CV_32S);
- image: 입력 영상. CV_8UC1 또는 CV_8SC1
- labels: 출력 레이블 맵 행렬
- connectivity: 연결성. 8 또는 4를 지정할 수 있음
- ltype: 출력 행렬 타입. CV_32S 또는 CV_16S를 지정할 수 있음
- 반환값: 레이블 개수. 반환값이 N이면 0부터 N-1 까지의 레이블 번호가 존재하며, 이 중 0번 레이블은 배경을 나타냄. 실제 객체 개수는 N-1임
void labeling_basic()
{
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, 0, 1, 0, 0, 1, 0,
0, 0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0,
};
Mat src = Mat(8, 8, CV_8UC1, data) * 255;
Mat labels;
int cnt = connectedComponents(src, labels);
cout << "src:\n" << src << endl;
cout << "labels:\n" << labels << endl;
cout << "number of labels: " << cnt << endl;
}
- 3~14행: 그림 12-2에서 예제로 사용했던 입력 영상과 동일한 형태의 그레이스케일 영상 src를 생성.uchar 자료형 배열 data를 픽셀 데이터로 사용하는 임시 Mat 객체를 생성한 후, 모든 원소에 255를 곱한 결과 행렬을 src에 저장
- 16~17행: connectedComponents() 함수를 실행하고, 레이블 맵을 labels 행렬에 저장
- 19~21행: src, labels 행렬과 connectedComponents() 함수가 반환한 정수를 화면에 출력
12.1.2 레이블링 응용
int connectedComponentsWithStats(InputArray image, OutputArray labels, OutputArray stats,
OutputArray centroids, int connectivity = 8, int ltype = CV_32S);
- image: 입력 영상. CV_8UC1 또는 CV_8SC1
- labels: 출력 레이블 맵 행렬
- stats: 각각의 레이블 영역에 대한 통계 정보를 담은 행렬. CV_32S
- centroids: 각각의 레이블 영역의 무게 중심 좌표 정보를 담은 행렬. CV_64F
- connectivity: 연결성. 8 또는 4를 지정할 수 있음
- ltype: 출력 행렬 타입. CV_32S 또는 CV_16S를 지정할 수 있음
- 반환값: 레이블 개수. 반환값이 N이면 0부터 N-1까지의 레이블 번호가 존재하며, 이 중 0번 레이블은 배경을 나타냄 실제 객체 개수는 N-1
void labeling_stats()
{
Mat src = imread("keyboard.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
Mat bin;
threshold(src, bin, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat labels, stats, centroids;
int cnt = connectedComponentsWithStats(bin, labels, stats, centroids);
Mat dst;
cvtColor(src, dst, COLOR_GRAY2BGR);
for (int i = 1; i < cnt; i++) {
int* p = stats.ptr<int>(i);
if (p[4] < 20) continue;
rectangle(dst, Rect(p[0], p[1], p[2], p[3]), Scalar(0, 255, 255));
}
imshow("src", src);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
- 3행: keyboard.bmp 영상을 그레이스케일 형식으로 불러와 src에 저장
- 10~11행: src 영상을 오츠 알고리즘으로 이진화하여 bin에 저장
- 13~14행: bin 영상에 대해 레이블링을 수행하고 각 객체 영역의 통계 정보를 추출
- 16~17행: src 영상을 3채널 컬러 영상 형식으로 변환하여 dst에 저장
- 19행: 배경 영역을 제외하고 흰색 객체 영역에 대해서만 for 반복문을 수행
- 22행: 객체의 픽셀 개수가 20보다 작으면 잡음이라고 간주하고 무시
- 24행: 검출된 객체를 감싸는 바운딩 박스를 노란색으로 그림
12.2 외곽선 검출
12.1.1 외곽선 검출
객체의 외곽선(contour): 객체 영역 픽셀 중에서 배경 영역과 인접한 일련의 픽셀을 의미. 보통 검은색 배경 안에 있는 흰색 객체 영역에서 가장 최외곽에 있는 픽셀을 찾아 외곽선으로 정의. 만약 흰색 객체 영역 안에 검은색 배경 영역인 홀(hole)이 존재한다면 홀을 둘러싸고 있는 객체 픽셀들도 외곽선으로 검출할 수 있음
void findContours(InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy,
int mode, int method, Point offset = Point());
void findContours(InputArray image, OutputArrayOfArrays contours, int mode, int method,
Point offset = Point());
- image: 입력 영상. 8비트 1채널 영상이어야 하고, 0이 아닌 픽셀을 객체로 취급. 만약 mode가 RETR_CCOMP이면 CV_32SC1의 타입의 영상을 지정할 수 있음
- contours: 검출된 외곽선 정보. vector<vector<Point>> 타입의 변수를 지정
- hierarchy: 외곽선 계층 정보. vector<Vec4i> 타입의 변수를 지정
- mode: 외곽선 검출 모드. RetrievalModes 열거형 상수를 지정
- method: 외곽선 근사화 방법. ContourApproximationModes 열거형 상수를 지정
- offset: 외곽선 점 좌표의 오프셋 (이동 변위)
void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color,
int thickenss = 1, int lineType = LINE_8, InputArray hierarchy = noArray(),
int maxLevel = INT_MAX, Point offset = Point());
- image: 외곽선을 그릴 영상
- contours: finContours() 함수로 구한 전체 외곽선 정보. vector<vector<Point>> 타입의 변수를 지정
- contourIdx: 외곽선 번호. 음수를 지정하면 전체 외곽선을 그린다.
- color: 외곽선 색상(또는 밝기)
- thickness: 외곽선 두께.FILLED 또는 -1을 지정하면 외곽선 내부를 채움
- lineType: 외곽선 타입
- hierarchy: 외곽선 계층 정보
- maxLevel: 그릴 외곽선의 최대 레벨. 이 값이 0이면 지정한 번호의 외곽선만 그리고, 1보다 같거나 크면 그에 해당하는 하위 레벨의 외곽선까지 그림
- offset: 추가적으로 지정할 외곽선 점 좌표의 오프셋(이동 변위), 지정한 좌표 크기만큼 외곽선 좌표를 이동하여 그림.
void contours_basic()
{
Mat src = imread("contours.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
vector<vector<Point>> contours;
findContours(src, contours, RETR_LIST, CHAIN_APPROX_NONE);
Mat dst;
cvtColor(src, dst, COLOR_GRAY2BGR);
for (int i = 0; i < contours.size(); i++) {
Scalar c(rand() & 255, rand() & 255, rand() & 255);
drawContours(dst, contours, i, c, 2);
}
imshow("src", src);
imshow("dst", dst);
waitKey(0);
destroyAllWindows();
}
- 3행: contours.bmp 파일을 그레이스케일 형식으로 불러와 src에 저장. contours.bmp는 픽셀값이 0과 255로 구성된 이진 영상
- 10~11행: src 영상으로부터 모든 외곽선을 검출. 외곽선의 계층 정보는 추출하지 않음
- 13~14행: src 영상을 3채널 컬러 영상으로 변환하여 dst에 저장
- 16행: 전체 외곽선 개수만큼 for반복문을 수행
- 16~19행: contours에 저장된 각각의 외곽선을 임의의 색상으로 그림
void contours_hier()
{
Mat src = imread("contours.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours(src, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
Mat dst;
cvtColor(src, dst, COLOR_GRAY2BGR);
for (int idx = 0; idx >= 0; idx = hierarchy[idx][0]) {
Scalar c(rand() & 255, rand() & 255, rand() & 255);
drawContours(dst, contours, idx, c, -1, LINE_8, hierarchy);
}
imshow("src", src);
imshow("dst", dst);
waitKey(0);
destroyAllWindows();
}
- 10~12행: findContours() 함수 호출 시 hierarchy 인자를 전달하여 계층 정보를 받아옴
- 17행: 0번 외곽선부터 시작하여 계층 정보의 다음 외곽선으로 이동하면서 for 반복문을 수행
- 19행: drawContours() 함수에 hierarchy 정보를 전달하여 외곽선을 그리도록 함. 선의 두께를 -1로 지정하였으므로 외곽선 내부를 지정한 색깔로 채움
12.2.2 외곽선 처리 함수
Rect boundingRect(InputArray points);
- points: 입력 점들의 집합. vector<Point> 또는 Mat 타입
- 반환값: 입력 점들이 감싸는 최소 크기의 사각형
RotatedRect minAreaRect(InputArray points);
- points: 입력 점들의 집합. vector<Point> 또는 Mat 타입
- 반환값: 입력 점들을 감싸는 최소 크기의 회전된 사각형
void minEnclosing(InputArray points, Point2f& center, float& radius);
- points: 입력 점들의 집합. vector<Point> 또는 Mat 타입
- center: 원의 중심 좌표
- radius: 원의 반지름
double arcLength(InputArray curve, bool closed);
- curve: 입력 곡선
- closed: 폐곡선 여부
- 반환값: 입력 곡선의 길이
double contourArea(InputArray contour, bool oriented = false);
- contour: 입력 곡선
- oriented: 진행 방향 정보 사용 여부. 이 값이 true이면 곡선의 진행 방향 (시계 방향 또는 반시계 방향)에 따라 면적의 부호가 달라짐. 이 값이 false 이면 면적의 절댓값을 반환
- 반환값: 입력 곡선이 감싸는 면적
void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed);
- curve: (입력) 2차원 점들의 좌표
- approxCurve: (출력) 근사화된 점들의 좌표
- epsilon: 근사화 정밀도 파라마터. 입력 곡선과 근사화된 곡선까지의 최대 거리를 지정
- closed: 폐곡선 여부. true면 폐곡선이고, false이면 폐곡선이 아님
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
using namespace std;
void setLabel(Mat& img, const vector<Point>& pts, const String& label)
{
Rect rc = boundingRect(pts);
rectangle(img, rc, Scalar(0, 0, 255), 1);
putText(img, label, rc.tl(), FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255));
}
int main(int argc, char* argv[])
{
Mat img = imread("polygon.bmp", IMREAD_COLOR);
if (img.empty()) {
cerr << "Image load failed!" << endl;
return -1;
}
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
Mat bin;
threshold(gray, bin, 200, 255, THRESH_BINARY_INV | THRESH_OTSU);
vector<vector<Point>> contours;
findContours(bin, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
for (vector<Point> pts : contours) {
if (contourArea(pts) < 400)
continue;
vector<Point> approx;
approxPolyDP(pts, approx, arcLength(pts, true)*0.02, true);
int vtc = (int)approx.size();
if (vtc == 3) {
setLabel(img, pts, "TRI");
} else if (vtc == 4) {
setLabel(img, pts, "RECT");
} else {
double len = arcLength(pts, true);
double area = contourArea(pts);
double ratio = 4. * CV_PI * area / (len * len);
if (ratio > 0.85) {
setLabel(img, pts, "CIR");
}
}
}
imshow("img", img);
waitKey(0);
return 0;
}
- 7~12행: setLabel() 함수는 img 영상에서 pts 외곽선 주변에 바운딩 박스를 그리고 label 문자열을 출력
- 9행: pts 외곽선을 감싸는 바운딩 박스를 구함
- 10행: 바운딩 박스를 주황색으로 표시
- 11행: 바운딩 박스 좌측 상단에 label 문자열을 출력
- 16행: polygon.bmp 파일을 3채널 컬러 영상 형식으로 불러와 img에 저장
- 23~24행: img 영상을 그레이스케일 형식으로 변환하여 gray에 저장
- 26~27행: gray 영상을 오츠 알고리즘으로 자동 이진화하여 bin에 저장
- 29~30행: bin 영상에서 모든 객체의 바깥쪽 외곽선을 검출
- 32행: 검출된 각 객체의 외곽선 좌표를 pts 변수로 참조하면서 for 반복문을 수행
- 33~34행; 외곽선이 감싸는 면적이 400보다 작으면 잡음으로 간주하여 무시
- 36~37행: pts 외곽선을 근사화하여 approx에 저장
- 41~42행: 근사화된 외곽선의 꼭지점 개수가 3이면 외곽선 주변에 바운딩 박스를 그리고 "TRI" 문자열을 출력
- 43~44행: 근사화된 외곽선의 꼭지점 개수가 4이면 외곽선 주변에 바운딩 박스를 그리고 "RECT" 문자열을 출력
- 45~52행: 객체의 면적 대 길이 비율을 조사하여 원에 가까우면 외곽선 주변에 바운딩 박스를 그리고 " CIR" 문자열을 출력
'3학년 > OpenCV' 카테고리의 다른 글
[OpenCV] 14장 지역 특징점 검출과 매칭 (0) | 2023.07.03 |
---|---|
[OpenCV] 13장 객체 검출 (0) | 2023.07.03 |
[OpenCV] 11장 이진화와 모폴로지 (0) | 2023.07.02 |
[OpenCV] 10장 컬러 영상 처리 (0) | 2023.07.02 |
[OpenCV] 9장 에지 검출과 응용 (0) | 2023.07.01 |