본문 바로가기

OpenCV

OpenCV 윤곽선 탐지 Python, C++

반응형

 

윤곽선 검출을 사용하면 객체의 경계를 검출하고 이미지에서 쉽게 위치를 파악할 수 있습니다. 이는 이미지 전경 추출, 단순 이미지 분할, 검출 및 인식과 같은 여러 흥미로운 응용 분야의 첫 단계가 되는 경우가 많습니다.

 

그러면 OpenCV를 사용하여 윤곽선과 윤곽선 감지에 대해 알아보고, 이를 사용하여 다양한 애플리케이션을 만드는 방법을 직접 확인해 보겠습니다.

 

 

 

Table of Contents

 

  1. 컴퓨터 비전에서의 윤곽선 적용
  2. 윤곽선이란 무엇인가요?
  3. OpenCV를 사용하여 윤곽선을 찾고 그리는 단계입니다.
  4. OpenCV를 사용하여 윤곽선 찾기 및 그리기

- CHAIN_APPROX_NONE을 사용하여 윤곽선 그리기

- CHAIN_APPROX_SIMPLE을 사용하여 윤곽선을 그립니다 .

 

5. 윤곽선 계층 구조

- 부모-자식 관계 

- 등고선 관계 표현

- 다양한 윤곽선 검색 기술

6.요약

 

컴퓨터 비전에서의 윤곽선 적용

 

윤곽선을 동작 감지 또는 분할에 활용하는 정말 멋진 애플리케이션들이 개발되었습니다. 몇 가지 예를 들면 다음과 같습니다.

 

  • 동작 감지 : 감시 영상에서 동작 감지 기술은 실내외 보안 환경, 교통 통제, 스포츠 활동 중 행동 감지, 무인 물체 감지, 심지어 비디오 압축까지 다양한 분야에 활용됩니다. 아래 그림에서 비디오 스트림에서 사람의 움직임을 감지하는 것이 감시 애플리케이션에서 어떻게 유용하게 활용될 수 있는지 살펴보겠습니다. 이미지 왼쪽에 정지해 있는 사람들은 감지되지 않고, 움직이는 사람들만 포착됩니다.이 접근 방식에 대한 자세한 내용은 이 논문을 참조하십시오.

 

 

OpenCV를 이용한 윤곽선 검출 응용. 윤곽선 검출을 이용한 이동 객체(사람) 검출. 움직이는 물체 감지의 예입니다. 움직이는 사람을 식별합니다. 왼쪽에 가만히 서 있는 사람은 감지되지 않습니다.

 

 

 

입력 비디오의 프레임 시리즈 - (a)는 배경 프레임, (b)는 무인 객체가 있는 프레임입니다. (c)와 (d)는 무인 객체가 식별되고 표시된 프레임입니다.

 

인용된 논문의 이미지 - 무인 객체가 있는 배경 프레임과 무인 객체가 없는 배경 프레임 - 무인 객체 식별 및 표시

 

  • 배경/전경 분할 : 이미지의 배경을 다른 배경 이미지로 바꾸려면 이미지-전경 추출(이미지 분할과 유사)을 수행해야 합니다. 윤곽선을 사용하는 것은 분할을 수행하는 한 가지 방법입니다. 자세한 내용은 이 게시물 을 참조하세요 . 다음 이미지는 이러한 적용 사례의 간단한 예를 보여줍니다.

 

윤곽선 감지를 사용하여 이미지 전경을 추출하고 배경을 변경합니다.

 

 

 

간단한 이미지 전경 추출과 윤곽선 감지를 사용하여 이미지에 새로운 배경을 추가하는 예입니다.

 

윤곽선이란 무엇입니까?

 

객체 경계에 있는 모든 점을 연결하면 윤곽선이 생성됩니다. 일반적으로 특정 윤곽선은 색상과 명도가 같은 경계 픽셀을 의미합니다. OpenCV를 사용하면 이미지에서 윤곽선을 쉽게 찾고 그릴 수 있습니다. OpenCV는 두 가지 간단한 함수를 제공합니다.

 

findContours()

drawContours()

 

또한 윤곽선 감지를 위한 두 가지 알고리즘이 있습니다.

 

CHAIN_APPROX_SIMPLE

CHAIN_APPROX_NONE

 

아래 예제에서 이러한 내용을 자세히 살펴보겠습니다. 다음 그림은 이러한 알고리즘이 간단한 물체의 윤곽을 어떻게 감지하는지 보여줍니다.

 

비교 이미지. 왼쪽 이미지는 원본 입력 이미지입니다. 오른쪽 이미지는 감지된 윤곽선을 입력 이미지 위에 겹쳐서 보여줍니다.

 

 

비교 이미지, 입력 이미지 및 윤곽선이 중첩된 출력 이미지입니다.

 

이제 윤곽선에 대한 소개를 받았으니 윤곽선을 감지하는 데 필요한 단계에 대해 알아보겠습니다.

 

OpenCV에서 윤곽선 감지 및 그리기 단계

 

OpenCV를 사용하면 이 작업이 매우 간단합니다. 다음 단계를 따르세요.

 

1. 이미지를 읽고 Grayscale 형식으로 변환합니다.

 

이미지를 읽고 흑백 이미지로 변환합니다. 이미지를 흑백 이미지로 변환하는 것은 다음 단계를 위한 이미지를 준비하는 데 매우 중요합니다. 이미지를 단일 채널 흑백 이미지로 변환하는 것은 임계값 설정에 중요하며, 이는 윤곽선 감지 알고리즘이 제대로 작동하는 데 필수적입니다.

 

2. 이진 임계값 적용

 

윤곽선을 찾을 때는 먼저 회색조 이미지에 이진 임계값 설정이나 캐니 에지 검출을 적용해야 합니다. 여기서는 이진 임계값 설정을 적용합니다.

 

이 기능은 이미지를 흑백으로 변환하여 관심 객체를 강조함으로써 윤곽선 감지 알고리즘의 작업을 용이하게 합니다. 임계값을 설정하면 이미지에서 객체의 경계가 완전히 흰색으로 바뀌고 모든 픽셀의 명도가 동일하게 됩니다. 이제 알고리즘은 이러한 흰색 픽셀에서 객체의 경계를 감지할 수 있습니다.

 

참고 : 값이 0인 검은색 픽셀은 배경 픽셀로 인식되어 무시됩니다.

 

이 시점에서 한 가지 의문이 생길 수 있습니다. 회색조(임계값) 이미지 대신 R(빨간색), G(녹색), B(파란색)와 같은 단일 채널을 사용하면 어떨까요? 이 경우 윤곽선 검출 알고리즘이 제대로 작동하지 않습니다. 앞서 설명했듯이, 이 알고리즘은 윤곽선을 검출하기 위해 경계선과 유사한 명암의 픽셀을 찾습니다. 이진 이미지는 단일(RGB) 색상 채널 이미지보다 이러한 정보를 훨씬 더 잘 제공합니다. 이 블로그의 후반부에서는 회색조 및 임계값 이미지 대신 단일 R, G, B 채널만 사용했을 때의 결과 이미지를 보여드리겠습니다.

 

3. 등고선 찾기

 

이 기능을 사용하여 findContours()이미지의 윤곽을 감지합니다.

 

4. 원본 RGB 이미지에 윤곽선을 그립니다.

 

윤곽선이 식별되면 해당 drawContours()기능을 사용하여 원본 RGB 이미지에 윤곽선을 오버레이합니다.

 

위의 단계는 우리가 코딩을 시작하면 훨씬 더 의미가 있고, 더 명확해질 것입니다.

 

OpenCV를 사용하여 윤곽선 찾기 및 그리기

 

먼저 OpenCV를 가져와서 입력 이미지를 읽습니다.

 

파이썬:

 

import cv2

# read the image

image = cv2.imread('input/image_1.jpg')

 

이미지가 현재 프로젝트 디렉터리의 입력 폴더에 있다고 가정합니다. 다음 단계는 이미지를 회색조 이미지(단일 채널 형식)로 변환하는 것입니다.

 

참고: 모든 C++ 코드는 main() 함수 내에 포함되어 있습니다.

 

C++:

 

#include<opencv2/opencv.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
int main() {
   // read the image
   Mat image = imread("input/image_1.jpg");

 

다음으로, 해당 cvtColor()함수를 사용하여 원본 RGB 이미지를 회색조 이미지로 변환합니다.

 

파이썬:

 

# convert the image to grayscale format

img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

 

C++:

 

// convert the image to grayscale format

Mat img_gray;

cvtColor(image, img_gray, COLOR_BGR2GRAY);

 

이제 threshold()함수를 사용하여 이미지에 이진 임계값을 적용합니다. 150보다 큰 값을 갖는 픽셀은 255(흰색)로 설정됩니다. 결과 이미지의 나머지 모든 픽셀은 0(검은색)으로 설정됩니다. 임계값 150은 조정 가능한 매개변수이므로, 원하는 대로 조정하여 실험해 볼 수 있습니다.

 

imshow()임계값을 설정한 후, 아래에 표시된 함수를 사용하여 이진 이미지를 시각화합니다 .

 

파이썬:

 

# apply binary thresholding
ret, thresh = cv2.threshold(img_gray, 150, 255, cv2.THRESH_BINARY)
# visualize the binary image
cv2.imshow('Binary image', thresh)
cv2.waitKey(0)
cv2.imwrite('image_thres1.jpg', thresh)
cv2.destroyAllWindows()

 

C++:

 

// apply binary thresholding
Mat thresh;
threshold(img_gray, thresh, 150, 255, THRESH_BINARY);
imshow("Binary mage", thresh);
waitKey(0);
imwrite("image_thres1.jpg", thresh);
destroyAllWindows();

 

아래 이미지를 확인해 보세요! 원본 RGB 이미지의 이진 표현입니다. 펜, 태블릿, 그리고 휴대폰의 테두리가 모두 흰색인 것을 명확하게 볼 수 있습니다. 윤곽선 알고리즘은 이들을 객체로 간주하고, 흰색 객체의 테두리 주변의 윤곽점을 찾습니다.

 

휴대폰 뒷면을 포함하여 배경이 완전히 검은색인 점에 주목하세요. 이러한 영역은 알고리즘에서 무시됩니다. 알고리즘은 각 객체 주변의 흰색 픽셀을 유사 강도 픽셀로 간주하고, 유사도 측정을 기반으로 윤곽선을 형성하기 위해 이들을 결합합니다.

 

입력 이미지에 임계값 함수를 적용한 후의 결과 이진 이미지입니다.

 

 

임계값 함수를 적용한 후의 결과 이진 이미지입니다.

 

CHAIN_APPROX_NONE을 사용하여 윤곽선 그리기

 

이제 CHAIN_APPROX_NONE 메서드를 사용하여 윤곽선을 찾아 그리고자 합니다. 

 

findContours() 함수부터 시작합니다. 아래와 같이 세 개의 필수 인수가 있습니다. 선택적 인수에 대해서는 여기 문서 페이지를 참조하십시오.

 

  • image: 이전 단계에서 얻은 이진 입력 이미지.
  • mode: 윤곽선 검색 모드입니다. 이 모드는 로 제공되었는데 RETR_TREE, 이는 알고리즘이 이진 이미지에서 가능한 모든 윤곽선을 검색한다는 것을 의미합니다. 더 많은 윤곽선 검색 모드가 있으며, 이에 대해서도 설명하겠습니다. 이러한 옵션에 대한 자세한 내용은 여기에서 확인할 수 있습니다 .
  • method: 윤곽선 근사 방법을 정의합니다. 이 예제에서는 를 사용합니다 CHAIN_APPROX_NONE. 보다 약간 느리지만 CHAIN_APPROX_SIMPLE, 여기서는 이 방법을 사용하여 모든 윤곽선 점을 저장합니다.

 

여기서 강조할 점은 ' mode검색될 윤곽선의 유형을 나타내는 것'과 ' method윤곽선 내의 어떤 점이 저장되는지를 나타내는 것'입니다. 아래에서 두 가지 모두 더 자세히 살펴보겠습니다.

 

같은 이미지에 대해 다양한 방법으로 얻은 결과를 시각화하고 이해하는 것은 쉽습니다.

 

따라서 아래 코드 샘플에서는 원본 이미지의 복사본을 만든 다음 방법을 보여줍니다(원본을 편집하지 않고).

 

다음으로 drawContours() 함수를 사용하여 RGB 이미지에 등고선을 오버레이합니다. 이 함수는 네 개의 필수 인수와 여러 개의 선택적 인수를 가집니다. 아래의 첫 네 인수는 필수입니다. 선택적 인수에 대해서는 여기 문서 페이지를 참조하십시오.

 

  • image: 윤곽선을 그리려는 입력 RGB 이미지입니다.
  • contours: 윤곽선 findContours() 함수에서 얻은 윤곽선을 나타냅니다.
  • contourIdx: 윤곽선 점의 픽셀 좌표가 획득된 윤곽선에 나열됩니다. 이 인수를 사용하면 이 목록에서 인덱스 위치를 지정하여 그릴 윤곽선을 정확하게 나타낼 수 있습니다. 음수 값을 지정하면 모든 윤곽선이 그려집니다.
  • color: 이것은 그리려는 윤곽점의 색상을 나타냅니다. 여기서는 녹색으로 점을 그립니다.
  • thickness: 윤곽점의 두께입니다.

 

파이썬:

 

# detect the contours on the binary image using cv2.CHAIN_APPROX_NONE
contours, hierarchy = cv2.findContours(image=thresh, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
                                      
# draw contours on the original image
image_copy = image.copy()
cv2.drawContours(image=image_copy, contours=contours, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
                
# see the results
cv2.imshow('None approximation', image_copy)
cv2.waitKey(0)
cv2.imwrite('contours_none_image1.jpg', image_copy)
cv2.destroyAllWindows()

 

C++:

 

// detect the contours on the binary image using cv2.CHAIN_APPROX_NONE
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(thresh, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);
// draw contours on the original image
Mat image_copy = image.clone();
drawContours(image_copy, contours, -1, Scalar(0, 255, 0), 2);
imshow("None approximation", image_copy);
waitKey(0);
imwrite("contours_none_image1.jpg", image_copy);
destroyAllWindows();

 

위 코드를 실행하면 아래 이미지가 생성되어 표시됩니다. 또한 이미지를 디스크에 저장합니다.

 

CHAIN_APPROX_NONE을 사용하여 감지한 윤곽을 입력 이미지에 중첩합니다.

 

 

입력 이미지에 CHAIN_APPROX_NONE을 중첩하여 감지한 윤곽선입니다.

 

다음 그림은 원본 이미지(왼쪽)와 윤곽선이 겹쳐진 원본 이미지(오른쪽)를 보여줍니다.

 

 

 

윤곽선이 감지되어 중첩된 출력 이미지와 입력 이미지를 비교합니다. 원본 이미지와 윤곽선이 그려진 이미지입니다.

 

위 그림에서 볼 수 있듯이, 알고리즘이 생성한 윤곽선은 각 객체의 경계를 식별하는 데 매우 효과적입니다. 하지만 휴대폰을 자세히 살펴보면 하나 이상의 윤곽선이 포함되어 있음을 알 수 있습니다. 카메라 렌즈와 조명과 관련된 원형 영역에는 별도의 윤곽선이 지정되어 있습니다. 또한 휴대폰 가장자리 부분을 따라 '보조' 윤곽선도 있습니다.

 

윤곽선 알고리즘의 정확도와 품질은 제공되는 이진 이미지의 품질에 크게 좌우된다는 점을 명심하세요(이전 섹션의 이진 이미지를 다시 살펴보면 이러한 이차 윤곽선과 관련된 선을 볼 수 있습니다). 일부 애플리케이션은 고품질 윤곽선을 요구합니다. 이러한 경우, 이진 이미지를 생성할 때 다양한 임계값을 적용하여 실험하고 결과 윤곽선이 개선되는지 확인하세요.

 

윤곽선 생성 전에 이진 맵에서 원치 않는 윤곽선을 제거하는 데 사용할 수 있는 다른 방법들이 있습니다. 또한, 여기서 설명할 윤곽선 알고리즘과 관련된 고급 기능들을 사용할 수도 있습니다.

 

단일 채널 사용: 빨간색, 녹색 또는 파란색

 

이해를 돕기 위해, 윤곽선을 검출할 때 빨간색, 초록색, 파란색 채널을 각각 사용했을 때의 결과는 다음과 같습니다. 이 부분은 이전 윤곽선 검출 단계에서 다루었습니다. 다음은 위와 동일한 이미지에 대한 Python 및 C++ 코드입니다.

 

파이썬:

 

import cv2
 
# read the image
image = cv2.imread('input/image_1.jpg')
 
# B, G, R channel splitting
blue, green, red = cv2.split(image)
 
# detect contours using blue channel and without thresholding
contours1, hierarchy1 = cv2.findContours(image=blue, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
 
# draw contours on the original image
image_contour_blue = image.copy()
cv2.drawContours(image=image_contour_blue, contours=contours1, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
# see the results
cv2.imshow('Contour detection using blue channels only', image_contour_blue)
cv2.waitKey(0)
cv2.imwrite('blue_channel.jpg', image_contour_blue)
cv2.destroyAllWindows()
 
# detect contours using green channel and without thresholding
contours2, hierarchy2 = cv2.findContours(image=green, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
# draw contours on the original image
image_contour_green = image.copy()
cv2.drawContours(image=image_contour_green, contours=contours2, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
# see the results
cv2.imshow('Contour detection using green channels only', image_contour_green)
cv2.waitKey(0)
cv2.imwrite('green_channel.jpg', image_contour_green)
cv2.destroyAllWindows()
 
# detect contours using red channel and without thresholding
contours3, hierarchy3 = cv2.findContours(image=red, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
# draw contours on the original image
image_contour_red = image.copy()
cv2.drawContours(image=image_contour_red, contours=contours3, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
# see the results
cv2.imshow('Contour detection using red channels only', image_contour_red)
cv2.waitKey(0)
cv2.imwrite('red_channel.jpg', image_contour_red)
cv2.destroyAllWindows()

 

C++:

 

#include<opencv2/opencv.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
int main() {
   // read the image
   Mat image = imread("input/image_1.jpg");
 
   // B, G, R channel splitting
   Mat channels[3];
   split(image, channels);
 
   // detect contours using blue channel and without thresholding
   vector<vector<Point>> contours1;
   vector<Vec4i> hierarchy1;
   findContours(channels[0], contours1, hierarchy1, RETR_TREE, CHAIN_APPROX_NONE);
   // draw contours on the original image
   Mat image_contour_blue = image.clone();
   drawContours(image_contour_blue, contours1, -1, Scalar(0, 255, 0), 2);
   imshow("Contour detection using blue channels only", image_contour_blue);
   waitKey(0);
   imwrite("blue_channel.jpg", image_contour_blue);
   destroyAllWindows();
 
   // detect contours using green channel and without thresholding
   vector<vector<Point>> contours2;
   vector<Vec4i> hierarchy2;
   findContours(channels[1], contours2, hierarchy2, RETR_TREE, CHAIN_APPROX_NONE);
   // draw contours on the original image
   Mat image_contour_green = image.clone();
   drawContours(image_contour_green, contours2, -1, Scalar(0, 255, 0), 2);
   imshow("Contour detection using green channels only", image_contour_green);
   waitKey(0);
   imwrite("green_channel.jpg", image_contour_green);
   destroyAllWindows();
 
   // detect contours using red channel and without thresholding
   vector<vector<Point>> contours3;
   vector<Vec4i> hierarchy3;
   findContours(channels[2], contours3, hierarchy3, RETR_TREE, CHAIN_APPROX_NONE);
   // draw contours on the original image
   Mat image_contour_red = image.clone();
   drawContours(image_contour_red, contours3, -1, Scalar(0, 255, 0), 2);
   imshow("Contour detection using red channels only", image_contour_red);
   waitKey(0);
   imwrite("red_channel.jpg", image_contour_red);
   destroyAllWindows();
}

 

다음 그림은 세 개의 개별 색상 채널에 대한 윤곽선 감지 결과를 보여줍니다.

 

 

 

회색조 임계값이 적용된 이미지 대신 단일 채널(빨강, 녹색 또는 파랑 채널)만 사용했을 때 감지된 윤곽선을 보여주는 비교 이미지입니다.

 

회색조의 임계값이 적용된 이미지 대신 파란색, 녹색, 빨간색 단일 채널을 사용하면 윤곽선이 감지됩니다.

 

위 이미지 에서 윤곽선 감지 알고리즘이 윤곽선을 제대로 찾지 못하는 것을 확인할 수 있습니다. 이는 객체의 경계를 제대로 감지하지 못하고, 픽셀 간의 명암 차이도 제대로 정의되지 않았기 때문입니다. 이러한 이유로 윤곽선 감지에는 회색조와 이진 임계값 이미지를 사용하는 것이 좋습니다.

 

CHAIN_APPROX_SIMPLE을 사용하여 윤곽선 그리기

 

이제 CHAIN_APPROX_SIMPLE 알고리즘이 어떻게 작동하는지, 그리고 CHAIN_APPROX_NONE 알고리즘과 어떤 점이 다른지 알아봅시다.

 

코드는 다음과 같습니다.

 

파이썬:

 

"""
Now let's try with `cv2.CHAIN_APPROX_SIMPLE`
"""
# detect the contours on the binary image using cv2.ChAIN_APPROX_SIMPLE
contours1, hierarchy1 = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# draw contours on the original image for `CHAIN_APPROX_SIMPLE`
image_copy1 = image.copy()
cv2.drawContours(image_copy1, contours1, -1, (0, 255, 0), 2, cv2.LINE_AA)
# see the results
cv2.imshow('Simple approximation', image_copy1)
cv2.waitKey(0)
cv2.imwrite('contours_simple_image1.jpg', image_copy1)
cv2.destroyAllWindows()

 

C++:

 

// Now let us try with CHAIN_APPROX_SIMPLE`
// detect the contours on the binary image using cv2.CHAIN_APPROX_NONE
vector<vector<Point>> contours1;
vector<Vec4i> hierarchy1;
findContours(thresh, contours1, hierarchy1, RETR_TREE, CHAIN_APPROX_SIMPLE);
// draw contours on the original image
Mat image_copy1 = image.clone();
drawContours(image_copy1, contours1, -1, Scalar(0, 255, 0), 2);
imshow("Simple approximation", image_copy1);
waitKey(0);
imwrite("contours_simple_image1.jpg", image_copy1);
destroyAllWindows();

 

여기서 유일한 차이점은 findContours() 메서드의 방법을 CHAIN_APPROX_NONE 대신 CHAIN_APPROX_SIMPLE로 지정한다는 점입니다.

 

이 CHAIN_APPROX_SIMPLE 알고리즘은 윤곽선을 따라 수평, 수직, 대각선 세그먼트를 압축하고 끝점만 남깁니다. 즉, 직선 경로에 있는 모든 점은 제거되고 끝점만 남게 됩니다. 예를 들어, 직사각형을 따라가는 윤곽선을 생각해 보겠습니다. 네 개의 꼭짓점을 제외한 모든 윤곽선 점이 제거됩니다. 이 방법은 CHAIN_APPROX_NONE 알고리즘이 모든 점을 저장하지 않고 메모리를 덜 사용하므로 실행 시간이 더 짧기 때문에 더 빠릅니다.

 

다음 이미지는 결과를 보여줍니다.

 

이 이미지는 입력 이미지에 CHAIN_APPROX_SIMPLE 방법을 사용하여 감지한 윤곽을 중첩하여 보여줍니다.

 

 

입력 이미지에 CHAIN_APPROX_SIMPLE을 중첩하여 감지한 윤곽선입니다.

 

주의 깊게 관찰하면 CHAIN_APPROX_NONE과 CHAIN_APPROX_SIMPLE의 출력 결과에는 거의 차이가 없습니다. 

 

그럼, 그 이유는 무엇일까요?

 

이것은 drawContours() 함수 덕분입니다. CHAIN_APPROX_SIMPLE 메서드는 일반적으로 더 적은 수의 점을 생성하지만, drawContours() 함수는 인접한 점을 자동으로 연결하여, 해당 점이 컨투어 목록에 포함되지 않더라도 결합합니다.

 

그러면 CHAIN_APPROX_SIMPLE 알고리즘이 실제로 작동하는지 어떻게 확인할 수 있을까?

 

  • 가장 간단한 방법은 OpenCV를 사용하여 윤곽점을 수동으로 반복하고 감지된 윤곽 좌표에 원을 그리는 것입니다.
  • 또한, 우리는 알고리즘의 결과를 시각화하는 데 도움이 되는 다른 이미지를 사용합니다.

 

 

CHAIN_APPROX_SIMPLE 윤곽선 감지 알고리즘을 보여주는 새로운 이미지입니다.

 

다음 코드는 위 이미지를 사용하여 알고리즘의 효과를 시각화합니다 . 두 개의 추가 for 루프와 몇 가지 변수 이름을 CHAIN_APPROX_SIMPLE제외하고는 이전 코드 예제와 거의 동일합니다 .

 

  • 첫 번째 for 루프는 contours 목록에 존재하는 각 등고선 영역을 순회합니다. 
  • 두 번째는 해당 영역의 각 좌표를 반복합니다.
  • 그런 다음 OpenCV의 circle() 함수를 사용하여 각 좌표점에 녹색 원을 그립니다.
  • 마지막으로 결과를 시각화하여 디스크에 저장합니다.

 

파이썬:

 

# to actually visualize the effect of `CHAIN_APPROX_SIMPLE`, we need a proper image
image1 = cv2.imread('input/image_2.jpg')
img_gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
 
ret, thresh1 = cv2.threshold(img_gray1, 150, 255, cv2.THRESH_BINARY)
contours2, hierarchy2 = cv2.findContours(thresh1, cv2.RETR_TREE,
                                               cv2.CHAIN_APPROX_SIMPLE)
image_copy2 = image1.copy()
cv2.drawContours(image_copy2, contours2, -1, (0, 255, 0), 2, cv2.LINE_AA)
cv2.imshow('SIMPLE Approximation contours', image_copy2)
cv2.waitKey(0)
image_copy3 = image1.copy()
for i, contour in enumerate(contours2): # loop over one contour area
   for j, contour_point in enumerate(contour): # loop over the points
       # draw a circle on the current contour coordinate
       cv2.circle(image_copy3, ((contour_point[0][0], contour_point[0][1])), 2, (0, 255, 0), 2, cv2.LINE_AA)
# see the results
cv2.imshow('CHAIN_APPROX_SIMPLE Point only', image_copy3)
cv2.waitKey(0)
cv2.imwrite('contour_point_simple.jpg', image_copy3)
cv2.destroyAllWindows()

 

C++:

 

// using a proper image for visualizing CHAIN_APPROX_SIMPLE
Mat image1 = imread("input/image_2.jpg");
Mat img_gray1;
cvtColor(image1, img_gray1, COLOR_BGR2GRAY);
Mat thresh1;
threshold(img_gray1, thresh1, 150, 255, THRESH_BINARY);
vector<vector<Point>> contours2;
vector<Vec4i> hierarchy2;
findContours(thresh1, contours2, hierarchy2, RETR_TREE, CHAIN_APPROX_NONE);
Mat image_copy2 = image1.clone();
drawContours(image_copy2, contours2, -1, Scalar(0, 255, 0), 2);
imshow("None approximation", image_copy2);
waitKey(0);
imwrite("contours_none_image1.jpg", image_copy2);
destroyAllWindows();
Mat image_copy3 = image1.clone();
for(int i=0; i<contours2.size(); i=i+1){
   for (int j=0; j<contours2[i].size(); j=j+1){
       circle(image_copy3, (contours2[i][0], contours2[i][1]), 2, Scalar(0, 255, 0), 2);
       }
   }
   imshow("CHAIN_APPROX_SIMPLE Point only", image_copy3);
   waitKey(0);
   imwrite("contour_point_simple.jpg", image_copy3);
   destroyAllWindows();

 

위 코드를 실행하면 다음과 같은 결과가 생성됩니다.

 

 

CHAIN_APPROX_SIMPLE 알고리즘을 사용하여 검출된 윤곽선을 중첩한 입력 이미지와 출력 이미지의 비교 이미지입니다. 책의 네 모서리에 있는 네 개의 윤곽선 점에 주목하는 것이 중요합니다. 책의 수직 및 수평 직선은 완전히 무시됩니다.

 

윤곽선 감지에 CHAIN_APPROX_SIMPLE을 사용할 때 책의 네 모서리에 윤곽선이 네 개만 있는 것을 확인할 수 있습니다. 책의 수직 및 수평 직선은 완전히 무시됩니다.

 

위 그림의 오른쪽에 있는 출력 이미지를 살펴보세요 . 책의 세로와 가로 면에는 모서리에 네 개의 점만 있는 것을 알 수 있습니다. 또한 글자와 새가 선분이 아닌 개별 점으로 표시되어 있는 것을 확인할 수 있습니다.

 

윤곽선 계층 구조

 

계층 구조는 윤곽선 간의 부모-자식 관계를 나타냅니다. 각 윤곽선 검색 모드가 이미지의 윤곽선 감지에 어떤 영향을 미치고 계층적 결과를 생성하는지 살펴보겠습니다.

 

부모-자녀 관계

 

윤곽선 감지 알고리즘을 통해 이미지에서 감지된 객체는 다음과 같습니다.

 

  • 이미지 내에 흩어져 있는 단일 개체들(첫 번째 예시처럼), 또는
  • 서로 안에 들어 있는 개체와 도형들 

 

대부분의 경우, 하나의 모양이 여러 모양을 포함하는 경우, 바깥쪽 모양이 안쪽 모양의 부모라고 안전하게 결론 내릴 수 있습니다.

 

다음 그림을 살펴보세요. 여기에는 윤곽선 계층을 보여주는 데 도움이 되는 몇 가지 간단한 모양이 포함되어 있습니다.

 

 

 

윤곽선 계층을 시각화하고 논의하기 위한 직선과 간단한 직사각형 모양의 샘플 이미지입니다.

 

간단한 선과 도형이 있는 이미지입니다. 이 이미지를 사용하여 윤곽선 계층 구조에 대해 자세히 알아보겠습니다.

 

이제 아래 그림을 보세요. 그림 10의 각 도형과 관련된 윤곽선이 표시되어 있습니다. 그림 11의 각 숫자는 의미를 가지고 있습니다.

 

  • 모든 개별 숫자, 즉 1, 2, 3, 4는 윤곽선 계층 구조와 부모-자식 관계에 따라 별도의 객체입니다.
  • 3a는 3의 자식이라고 할 수 있습니다. 3a는 윤곽선 3의 내부 부분을 나타냅니다.
  • 윤곽선 1, 2, 4는 모두 연관된 자식 도형이 없는 부모 도형이므로 번호 매기기는 임의적입니다. 즉, 윤곽선 2는 1로, 그 반대로 레이블이 지정될 수 있습니다.

 

 

위 입력 이미지의 선과 모양에는 부모-자식 관계를 보여주기 위해 번호가 매겨져 있습니다.

 

다양한 모양 사이의 부모-자식 관계를 보여주는 숫자입니다.

 

등고선 관계 표현

 

함수가 두 가지 출력, 즉 윤곽선 목록과 계층 구조를 반환하는 것을 보셨습니다 findContours() . 이제 윤곽선 계층 구조 출력을 자세히 살펴보겠습니다.

 

윤곽선 계층 구조는 배열로 표현되며, 이 배열에는 네 개의 값 배열이 포함됩니다. 다음과 같이 표현됩니다.

 

[ Next, Previous, First_Child, Parent]

 

그러면 이 모든 값들은 무엇을 의미할까요?

 

Next: 이미지에서 동일한 계층적 수준에 있는 다음 윤곽선을 나타냅니다. 따라서,

 

  • 윤곽선 1의 경우, 동일한 계층적 수준에서 다음 윤곽선은 2입니다. 여기서는 Next2가 됩니다.
  • 따라서 윤곽선 3은 자신과 동일한 계층 구조에 윤곽선이 없습니다. 따라서 Next값은 -1이 됩니다.

 

Previous: 동일한 계층 구조에 있는 이전 윤곽선을 나타냅니다. 즉, 윤곽선 1의 값은 항상 Previous-1입니다.

 

First_Child: 현재 고려 중인 윤곽선의 첫 번째 자식 윤곽선을 나타냅니다.

 

  • 컨투어 1과 2에는 자식이 전혀 없습니다. 따라서 이들의 First_Child 인덱스 값은 -1이 됩니다.
    그러나 컨투어 3에는 자식이 있습니다. 따라서 컨투어 3의 First_Child 위치 값은 3a의 인덱스 위치가 됩니다.

 

Parent: 현재 윤곽선에 대한 부모 윤곽선의 인덱스 위치를 나타냅니다.

 

  • 윤곽선 1과 2는 명백히 Parent윤곽선이 없습니다.
  • 등고선 3a의 경우 Parent등고선 3이 됩니다.
  • 윤곽선 4의 경우 부모는 윤곽선 3a입니다.

 

위의 설명은 일리가 있지만, 이러한 계층적 배열을 실제로 어떻게 시각화할 수 있을까요? 가장 좋은 방법은 다음과 같습니다.

 

  • 이전 이미지와 같이 선과 모양이 있는 간단한 이미지를 사용하세요.
  • 다양한 검색 모드를 사용하여 윤곽선과 계층을 감지합니다.
  • 그런 다음 값을 인쇄하여 시각화합니다.

 

다양한 윤곽선 검색 기술

 

지금까지 우리는 RETR_TREE 윤곽선을 찾고 그리기 위해 하나의 특정 검색 기술을 사용했지만, OpenCV에는 윤곽선 검색 기술이 세 가지 더 있습니다. 즉, RETR_LIST, RETR_EXTERNAL입니다 RETR_CCOMP.

 

그러면 이제 그림 10의 이미지를 사용하여 윤곽선을 얻기 위한 관련 코드와 함께 이 네 가지 방법을 각각 살펴보겠습니다.

 

다음 코드는 디스크에서 이미지를 읽고, 이를 회색조로 변환하고, 이진 임계값을 적용합니다.

 

파이썬:

 

"""
Contour detection and drawing using different extraction modes to complement
the understanding of hierarchies
"""
image2 = cv2.imread('input/custom_colors.jpg')
img_gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
ret, thresh2 = cv2.threshold(img_gray2, 150, 255, cv2.THRESH_BINARY)

 

C++:

 

/*
Contour detection and drawing using different extraction modes to complement the understanding of hierarchies
*/
Mat image2 = imread("input/custom_colors.jpg");
Mat img_gray2;
cvtColor(image2, img_gray2, COLOR_BGR2GRAY);
Mat thresh2;
threshold(img_gray2, thresh2, 150, 255, THRESH_BINARY);

 

 

RETR_LIST

 

윤곽선 RETR_LIST검색 방식은 추출된 윤곽선 사이에 부모-자식 관계를 생성하지 않습니다. 따라서 감지된 모든 윤곽선 영역에 대해 First_Child 및 Parent 인덱스 위치 값은 항상 -1입니다.

 

모든 윤곽선에는 위에서 설명한 대로 해당 Previous윤곽선 이 있습니다 Next.

 

이 방법이 코드에서 어떻게 RETR_LIST구현되는지 살펴보세요.

 

파이썬:

 

contours3, hierarchy3 = cv2.findContours(thresh2, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
image_copy4 = image2.copy()
cv2.drawContours(image_copy4, contours3, -1, (0, 255, 0), 2, cv2.LINE_AA)
# see the results
cv2.imshow('LIST', image_copy4)
print(f"LIST: {hierarchy3}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_list.jpg', image_copy4)
cv2.destroyAllWindows()

 

C++:

 

vector&lt;vector&lt;Point>> contours3;
vector&lt;Vec4i> hierarchy3;
findContours(thresh2, contours3, hierarchy3, RETR_LIST, CHAIN_APPROX_NONE);
Mat image_copy4 = image2.clone();
drawContours(image_copy4, contours3, -1, Scalar(0, 255, 0), 2);
imshow("LIST", image_copy4);
waitKey(0);
imwrite("contours_retr_list.jpg", image_copy4);
destroyAllWindows();

 

위 코드를 실행하면 다음과 같은 출력이 생성됩니다.

 

LIST: [[[ 1 -1 -1 -1]

[ 2 0 -1 -1]

[ 3 1 -1 -1]

[ 4 2 -1 -1]

[-1 3 -1 -1]]]

 

예상대로 감지된 모든 윤곽선 영역의 3번째와 4번째 인덱스 위치가 -1인 것을 확실히 볼 수 있습니다.

 

RETR_EXTERNAL

 

윤곽선 RETR_EXTERNAL검색 방법은 정말 흥미로운 방법입니다. 부모 윤곽선만 감지하고 자식 윤곽선은 무시합니다. 따라서 3a와 4와 같은 모든 내부 윤곽선에는 점이 그려지지 않습니다.

 

파이썬:

 

contours4, hierarchy4 = cv2.findContours(thresh2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
image_copy5 = image2.copy()
cv2.drawContours(image_copy5, contours4, -1, (0, 255, 0), 2, cv2.LINE_AA)
# see the results
cv2.imshow('EXTERNAL', image_copy5)
print(f"EXTERNAL: {hierarchy4}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_external.jpg', image_copy5)
cv2.destroyAllWindows()

 

C++ :

 

vector<vector<Point>> contours4;
vector<Vec4i> hierarchy4;
findContours(thresh2, contours4, hierarchy4, RETR_EXTERNAL, CHAIN_APPROX_NONE);
Mat image_copy5 = image2.clone();
drawContours(image_copy5, contours4, -1, Scalar(0, 255, 0), 2);
imshow("EXTERNAL", image_copy5);
waitKey(0);
imwrite("contours_retr_external.jpg", image_copy4);
destroyAllWindows();

 

위 코드는 다음과 같은 출력을 생성합니다.

 

EXTERNAL: [[[ 1 -1 -1 -1]

[ 2 0 -1 -1]

[-1 1 -1 -1]]]

 

 

 

RETR_EXTERNAL 모드의 출력입니다. 감지된 윤곽선을 그려 시각화합니다.

RETR_EXTERNAL 모드를 사용하여 윤곽선을 감지하고 그렸습니다.

 

위의 출력 이미지는 윤곽선 1, 2, 3에 그려진 점만 보여줍니다. 윤곽선 3a와 4는 자식 윤곽선이므로 생략되었습니다.

 

RETR_CCOMP

 

RETR_EXTERNAL과 달리, RETR_CCOMP는 이미지의 모든 윤곽선을 추출합니다. 또한 이미지의 모든 도형 또는 객체에 2단계 계층 구조를 적용합니다.

 

이는 다음을 의미합니다.

 

  • 모든 외부 윤곽선에는 계층 레벨 1이 있습니다.
  • 모든 내부 윤곽선에는 계층 레벨 2가 있습니다.

 

하지만 계층 구조 레벨 2의 다른 윤곽선 내부에 윤곽선이 있다면 어떨까요? 윤곽선 3a 뒤에 윤곽선 4가 있는 것처럼요.

 

그런 경우에는:

 

  • 다시 말해서, 윤곽선 4는 계층 수준 1을 갖습니다.
  • 윤곽선 4 내부에 윤곽선이 있는 경우 계층 수준 2를 갖습니다.

 

다음 이미지에서는 위에서 설명한 대로 계층 구조 수준에 따라 윤곽선에 번호가 매겨졌습니다.

 

 

 

RETR_CCOMP 검색 방법에 대한 윤곽선의 계층 수준에 번호가 매겨진 이미지입니다.

RETR_CCOMP 검색 방법을 사용할 때 윤곽선의 다양한 계층 수준을 보여주는 이미지입니다.

 

위 이미지는 레벨 1과 레벨 2를 각각 HL-1 또는 HL-2 로 나타낸 계층 구조를 보여줍니다 . 이제 코드와 출력 계층 구조 배열도 살펴보겠습니다.

 

파이썬:

 

contours5, hierarchy5 = cv2.findContours(thresh2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
image_copy6 = image2.copy()
cv2.drawContours(image_copy6, contours5, -1, (0, 255, 0), 2, cv2.LINE_AA)
 
# see the results
cv2.imshow('CCOMP', image_copy6)
print(f"CCOMP: {hierarchy5}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_ccomp.jpg', image_copy6)
cv2.destroyAllWindows()

 

C++:

 

vector<vector<Point>> contours5;
vector<Vec4i> hierarchy5;
findContours(thresh2, contours5, hierarchy5, RETR_CCOMP, CHAIN_APPROX_NONE);
Mat image_copy6 = image2.clone();
drawContours(image_copy6, contours5, -1, Scalar(0, 255, 0), 2);
imshow("EXTERNAL", image_copy6);
// cout << "EXTERNAL:" << hierarchy5;
waitKey(0);
imwrite("contours_retr_ccomp.jpg", image_copy6);
destroyAllWindows();

 

위 코드를 실행하면 다음과 같은 출력이 생성됩니다.

 

CCOMP: [[[ 1 -1 -1 -1]

[ 3 0 2 -1]

[-1 -1 -1 1]

[ 4 1 -1 -1]

[-1 3 -1 -1]]]

 

여기서 모든 윤곽이 탐지됨에 따라, 윤곽 검색 방법에 따라 모든 다음, 이전, 첫 번째 자식 및 부모 관계가 유지됨을 확인할 수 있습니다. 예상대로 첫 번째 윤곽 영역의 이전 값은 -1입니다. 또한 부모가 없는 윤곽들도 -1 값을 가집니다.

 

RETR_TREE

 

RETR_CCOMP와 마찬가지로 RETR_TREE도 모든 윤곽선을 검색합니다. 또한 레벨이 1이나 2로 제한되지 않는 완전한 계층 구조를 생성합니다. 각 윤곽선은 자신이 속한 레벨과 해당 상하위 관계에 따라 자체적인 계층 구조를 가질 수 있습니다.

 

 

 

RETR_TREE 검색 방법에 대한 등고선의 계층 수준에 번호가 매겨진 이미지입니다.

RETR_TREE 윤곽선 검색 모드를 사용할 때의 계층 수준.

 

위 그림에서 다음 사실이 분명해집니다.

 

  • 등고선 1, 2, 3은 같은 수준, 즉 수준 0에 있습니다.
  • 윤곽선 3a는 윤곽선 3의 자식이므로 계층 수준 1에 존재합니다.
  • 등고선 4는 새로운 등고선 영역이므로 계층 수준은 2입니다.

 

다음 코드는 RETR_TREE 모드를 사용하여 윤곽선을 검색합니다.

 

파이썬:

 

contours6, hierarchy6 = cv2.findContours(thresh2, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
image_copy7 = image2.copy()
cv2.drawContours(image_copy7, contours6, -1, (0, 255, 0), 2, cv2.LINE_AA)
# see the results
cv2.imshow('TREE', image_copy7)
print(f"TREE: {hierarchy6}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_tree.jpg', image_copy7)
cv2.destroyAllWindows()

 

C++:

 

vector<vector<Point>> contours6;
vector<Vec4i> hierarchy6;
findContours(thresh2, contours6, hierarchy6, RETR_TREE, CHAIN_APPROX_NONE);
Mat image_copy7 = image2.clone();
drawContours(image_copy7, contours6, -1, Scalar(0, 255, 0), 2);
imshow("EXTERNAL", image_copy7);
// cout << "EXTERNAL:" << hierarchy6;
waitKey(0);
imwrite("contours_retr_tree.jpg", image_copy7);
destroyAllWindows();

 

위 코드를 실행하면 다음과 같은 출력이 생성됩니다.

 

TREE: [[[ 3 -1 1 -1]

[-1 -1 2 0]

[-1 -1 -1 1]

[ 4 0 -1 -1]

[-1 3 -1 -1]]]

 

마지막으로, RETR_TREE 모드를 사용할 때 모든 윤곽선이 그려진 전체 이미지를 살펴보겠습니다.

 

 

 

RETR_TREE 검색 모드를 사용할 때 입력 이미지에 중첩된 윤곽선을 보여주는 이미지입니다.

RETR_TREE 검색 모드를 사용할 때 윤곽선을 감지합니다.

 

모든 윤곽선이 예상대로 그려졌고 윤곽선 영역이 명확하게 보입니다. 또한 윤곽선 3과 3a는 윤곽선 경계와 영역이 다르므로 서로 다른 두 개의 윤곽선이라고 추론할 수 있습니다. 동시에, 윤곽선 3a가 윤곽선 3의 자식이라는 것도 매우 분명합니다.

 

이제 OpenCV에서 사용할 수 있는 모든 윤곽 알고리즘과 해당 입력 매개변수 및 구성에 대해 알았으니, 직접 실험하여 어떻게 작동하는지 확인해 보세요.

 

다양한 윤곽선 검색 방법의 런타임 비교

 

윤곽선 검색 방법을 아는 것만으로는 충분하지 않습니다. 각 방법의 상대적인 처리 시간도 알아야 합니다. 다음 표는 위에서 설명한 각 방법의 실행 시간을 비교한 것입니다.

 

 

Contour Retrieval Method Time Take (in seconds)
RETR_LIST 0.000382
RETR_EXTERNAL 0.000554
RETR_CCOMP 0.001845
RETR_TREE 0.005594

 

다양한 방법의 추론 속도 비교

 

위의 표에서 몇 가지 흥미로운 결론이 도출되었습니다.

 

  • RETR_LIST와 RETR_EXTERNAL은 실행 시간이 가장 짧습니다. RETR_LIST는 계층 구조를 정의하지 않으며, RETR_EXTERNAL은 부모 윤곽선만 검색하기 때문입니다.
  • RETR_CCOMP는 실행 시간이 두 번째로 깁니다. 모든 윤곽선을 검색하고 2단계 계층 구조를 정의합니다. 
  • RETR_TREE는 모든 윤곽선을 검색하고 각 부모-자식 관계에 대해 독립적인 계층 구조 수준을 정의하기 때문에 실행 시간이 가장 오래 걸립니다. 

 

위의 시간이 크게 중요하지 않아 보일 수 있지만, 상당한 양의 윤곽선 처리가 필요한 애플리케이션의 경우 이러한 차이점을 인지하는 것이 중요합니다. 또한, 이 처리 시간은 추출하는 윤곽선과 정의하는 계층 수준에 따라 어느 정도 달라질 수 있다는 점도 유의해야 합니다.

 

제한 사항

 

지금까지 살펴본 모든 예시는 흥미롭고 결과도 고무적이었습니다. 하지만 윤곽선 알고리즘이 의미 있고 유용한 결과를 제공하지 못하는 경우도 있습니다. 그러한 예시도 살펴보겠습니다.

 

  • 이미지 속 물체가 배경과 강한 대비를 이루는 경우, 각 물체와 연관된 윤곽선을 명확하게 식별할 수 있습니다. 하지만 아래 그림 16과 같은 이미지가 있다면 어떨까요? 밝은 물체(강아지)뿐만 아니라 관심 물체(강아지)와 같은 값(밝기)으로 어수선한 배경도 있습니다. 오른쪽 이미지의 윤곽선은 완전하지 않습니다. 또한, 배경 영역에 원치 않는 윤곽선이 여러 개 눈에 띕니다.

 

 

실패 사례를 시각화한 비교 이미지. 흰색 강아지와 배경이 있는 이미지에는 많은 모서리와 어수선함이 있어 배경의 어수선함으로 인해 여러 개의 윤곽선 또는 잘못된 윤곽선이 감지됩니다.

 

왼쪽 - 흰색 강아지와 여러 다른 모서리 및 배경색이 있는 입력 이미지. 오른쪽 - 윤곽선 감지 결과를 겹쳐서 표시한 이미지. 윤곽선이 불완전하고 배경이 어수선하여 여러 개의 윤곽선 또는 잘못된 윤곽선이 감지되는 모습을 살펴보세요.

 

  • 이미지에서 객체의 배경이 선으로 가득 차 있는 경우에도 윤곽선 감지가 실패할 수 있습니다.

 

학습을 더욱 발전시키세요

 

이 글에서 흥미로운 내용을 배우고 지식을 넓히고 싶다면 OpenCV에서 제공하는 컴퓨터 비전 1 과정을 추천합니다. OpenCV와 컴퓨터 비전을 시작하기에 좋은 과정으로, 실습 위주로 구성되어 있어 OpenCV를 시작하고 빠르게 익히기에 완벽합니다. 가장 좋은 점은 Python이나 C++ 중 원하는 언어로 수강할 수 있다는 것입니다. 더 자세한 내용은 여기 과정 페이지를 참조하세요.

 

요약

 

윤곽선 검출부터 시작하여 OpenCV에서 구현하는 방법을 배웠습니다. 애플리케이션에서 이동성 검출 및 분할에 윤곽선을 사용하는 방법을 살펴보았습니다. 다음으로, 네 가지 검색 모드와 두 가지 윤곽선 근사 방법을 시연했습니다. 윤곽선을 그리는 방법도 배웠습니다. 마지막으로 윤곽선 계층 구조와 다양한 윤곽선 검색 모드가 이미지 윤곽선 그리기에 미치는 영향을 논의했습니다.

 

주요 내용:

 

  • OpenCV의 윤곽선 감지 알고리즘은 이미지의 배경이 어둡고 관심 객체가 명확하게 정의된 경우 매우 잘 작동합니다.
  • 하지만 입력 이미지의 배경이 복잡하거나 관심 대상과 같은 픽셀 강도를 갖는 경우 알고리즘의 성능은 그다지 좋지 않습니다.

 

모든 코드가 여기에 있으니, 이제 다양한 이미지로 실험해 보세요! 다양한 모양의 이미지를 시도하고, 임계값을 다양하게 조정해 보세요. 또한, 중첩된 윤곽선이 포함된 테스트 이미지를 사용하여 다양한 검색 모드를 탐색해 보세요. 이렇게 하면 객체 간의 계층적 관계를 완벽하게 파악할 수 있습니다.

 

 

반응형

더욱 좋은 정보를 제공하겠습니다.~ ^^