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: 좌표값 이동 옵셋.
#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
#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 |