- 영상 분석의 목표 중 하나는 이런 객체를 식별하고 추출함에 있음.
- 객체 감지/인식 애플리케이션에서 첫 번째 단계는 관심 객체가 위치했음을 보여주는 영상을 만듬.
- 이진 맵을 어떻게 얻었는지는 관계가 없음.
- 다음 단계는 1과 0으로 구성한 집단에 포함된 객체를 추출.
- 다음과 같이 5장에서 다뤘던 이진 형태의 물소 영상을 들 수 있음.
- 소스
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
int main() {
cv::Mat image= cv::imread("binary.bmp");
cv::namedWindow("Image");
cv::imshow("Image", image);
cv::Mat element5(5, 5, CV_8U, cv::Scalar(1));
// 필터 효과를 더 두드러지게 5x5 구조 요소를 사용
// 영상 닫힘과 영상 열림 (열림, 닫힘 연산자)
cv::morphologyEx(image,image,cv::MORPH_CLOSE,element5);
cv::morphologyEx(image,image,cv::MORPH_OPEN,element5);
cv::imwrite("binaryGroup.bmp",image);
cv::namedWindow("Closed and Opened Image");
cv::imshow("Closed and Opened Image",image);
cv::waitKey(0);
return 0;
}
- 결과
- 열림과 닫힘 형태학 필터의 애플리케이션에 의한 간단한 경계화 작업으로 위 영상을 얻음.
- 이번 예제는 위와 같은 영상 내에서 객체를 어떻게 추출하는지 보여줌.
- 더 구체적으로는 연결 컴포넌트를 추출.
- 즉, 모양은 이진 영상 내에 연결된 화소 집합으로 만듬.
- 영상 내에서 연결 컴포넌트의 외곽선을 추출하는 함수로 cv::findContours 함수를 사용.
- Example
#include <iostream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
int main()
{
cv::Mat image= cv::imread("binaryGroup.bmp",0);
// 입력 영상은 이진 영상, 결과는 외곽선의 벡터로 각 외곽선은 cv::Point로 표현
// 이것은 결과 파라미터가 std::vectors의 std::vector로 정의된 이유
// 연결 구성요소의 외곽선 가져오기
std::vector<std::vector<cv::Point>> contours;
cv::findContours(image,
contours, // 외곽선 벡터
CV_RETR_EXTERNAL, // 외부 외곽선 검색
CV_CHAIN_APPROX_NONE); // 각 외곽선의 모든 화소 탐색
// 지정된 플래그 - 첫 번째는 외부 외곽선이 필요함을 나타내며 객체의 구멍을 무시
// 두 번째 플래그는 외곽선의 형태를 지정 - 현재 옵션으로 벡터는 외곽선 내의 모든 화소 목록
// CV_CHAIN_APPROX_SIMPLE 플래그로 하면 마지막 점이 수평 또는 수직, 대각선 외곽선에 포함됨
// 다른 플래그는 간결한 표현을 얻기 위해 외곽선의 정교하게 연결된 근사치를 제공
// 이전 영상으로 9개 외곽선을 contours.size()로 얻음
// drawContours() 함수는 영상 내의 각 외곽선을 그릴 수 있는 함수
// 하얀 영상 내 검은 외곽선 그리기
cv::Mat result(image.size(), CV_8U, cv::Scalar(255));
cv::drawContours(result, contours,
-1, // 모든 외곽선 그리기
cv::Scalar(0), // 검게
2); // 두께를 2로
// 세 번째 파라미터가 음수라면 모든 외곽선이 그려짐
// 반면 그려져야 하는 외곽선의 첨자를 지정할 수 있음
cv::namedWindow("Contours");
cv::imshow("Contours", result);
cv::waitKey(0);
return 0;
}
- Result
- 예제 분석
- 컴포넌트를 찾을 때까지 영상을 체계적으로 조회하는 과정을 구성된 간단한 알고리즘으로 외곽선을 추출.
- 컴포넌트 내의 출발점부터 그 외곽선을 따라가면서 외곽선 경계에 있는 화소를 표시.
- 외곽선이 완료되면 새로운 컴포넌트를 찾을 때까지 마지막 위치부터 다시 조회.
- 식별한 연결 컴포넌트를 개별로 분석할 수 있음.
- 일부 사전 지식을 관심 객체 크기를 예상하는 데 사용할 수 있다면 컴포넌트의 일부를 제거할 수 있음.
- 즉, 컴포넌트의 둘레에 대한 최소와 최대값을 사용할 수 있다.
- 외곽선 벡터를 반복으로 조회하면서 잘못된 컴포넌트를 제거.
- 소스
#include <iostream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
int main()
{
cv::Mat image= cv::imread("binaryGroup.bmp", 0);
std::vector<std::vector<cv::Point>> contours;
cv::findContours(image,
contours, // 외곽선 벡터
CV_RETR_EXTERNAL, // 외부 외곽선 검색
CV_CHAIN_APPROX_NONE); // 각 외곽선의 모든 화소 탐색
// 컴포넌트의 둘레에 대한 최소와 최대값 사용
// 외곽선 벡터를 반복으로 조회하면서 잘못된 컴포넌트를 제거
// 너무 짧거나 너무 긴 외곽선 제거
int cmin= 100; // 최소 외곽선 길이
int cmax= 1000; // 최대 외곽선 길이
std::vector<std::vector<cv::Point>>::const_iterator itc= contours.begin();
while (itc!=contours.end()) {
if (itc->size() < cmin || itc->size() > cmax)
itc= contours.erase(itc);
else
++itc;
}
// 원 영상 내 외곽선 그리기
cv::Mat original= cv::imread("group.jpg");
cv::drawContours(original,contours,
-1, // 모든 외곽선 그리기
cv::Scalar(255,255,255), // 검게
2); // 두께를 2로
cv::namedWindow("Contours on Animals");
cv::imshow("Contours on Animals",original);
cv::waitKey(0);
return 0;
}
- 결과
- std::vector는 O(N)이므로 반복문에서는 각 삭제 연산을 더 효율적으로 만들 수 있음.
- 원 영상에 남아 있는 외곽선을 그리고 위 그림과 같은 결과를 얻음.
- 부연 설명
- cv::findContours 함수로 이진 맵 내의 모든 폐쇄 외곽선을 포함할 수 있고, 컴포넌트 내의 구멍 형태를 포함할 수 있음.
- 이는 다른 플래그를 함수에 지정해 호출함으로 써 이루어짐.
- 소스
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
int main()
{
cv::Mat image= cv::imread("binaryGroup.bmp", 0);
cv::Mat result(image.size(), CV_8U, cv::Scalar(255));
std::vector<std::vector<cv::Point>> contours;
// 연결 구성요소의 외곽선 가져오기
cv::findContours(image,
contours, // 외곽선 벡터
CV_RETR_LIST, // 외부와 내부 외곽선 탐색
CV_CHAIN_APPROX_NONE); // 각 외곽선의 모든 화소 탐색
// 하얀영상에 검은 외곽선 그리기
result.setTo(cv::Scalar(255));
cv::drawContours(result, contours,
-1, // 모든 외곽선 그리기
cv::Scalar(0), // 검게
2); // 두께를 2로
cv::namedWindow("All Contours");
cv::imshow("All Contours",result);
cv::waitKey(0);
return 0;
}
- 결과
- 여분의 외곽선은 배경인 숲에서 추가된 것.
- 또한 계층 구조로 구성한 외곽선을 가질 수 있음.
- 주 컴포넌트가 부모라면 구멍은 그 컴포넌트의 자식이고, 구멍 내에 컴포넌트가 있다면 이전 자식의 자식이 됨.
- 계층 구조는 다음과 같이 CV_RETR_TREE 플래그를 사용해 얻게됨.
std::vector<cv::Vec4i> hierarchy;
cv::findContours (image,
contours, // 외곽선 벡터
hierarch, // 계층 구조 표현
CV_RETR_TREE // 나무 형태의 모든 외곽선 검색
CV_CHAIN_APPROX_NONE); // 각 외곽선의 모든 화소
- 이번 경우 각 외곽선은 4개 정수의 동일한 첨자인 해당 계층 구조 요소를 갖게됨.
- 첫 두 정수는 동일한 수준의 다음과 이전 외곽선에 대한 첨자.
- 다음 두 정수는 첫 번째 자식과 자신 외곽선의 부모에 대한 첨자.
- 음수 첨자는 외곽선 목록의 마지막을 가리킴.
- CV_RETR_CCOMP 플래그는 동일하지만 2레벨의 계층 구조로 제한.
- 참고문헌 : OpenCV 2 Computer Vision Application Programming Cookbook