- 영상 분석의 목표 중 하나는 이런 객체를 식별하고 추출함에 있음.
- 객체 감지/인식 애플리케이션에서 첫 번째 단계는 관심 객체가 위치했음을 보여주는 영상을 만듬.
- 이진 맵을 어떻게 얻었는지는 관계가 없음.
- 다음 단계는 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






 
















