Favicon

Polygon detection

Peponi5/27/20255m

C#
NugetPackageOpenCvSharp4

1. Introduction

Polygon detection은 이미지 내에서 다각형 모양의 영역을 추출하는 것이다. 삼각형, 사각형 등 다양한 형태의 다각형을 검출할 수 있으며, 주로 다음과 같은 단계를 거쳐 수행한다.

  1. Preprocessing
    색상 공간 변환, 노이즈 제거 등을 수행하여 프로세싱에 알맞게 이미지를 조정한다.

  2. Edge detection
    이미지의 엣지를 찾아낸다. 주로 Canny, Sobel 등의 알고리즘이 사용되며 이미지 특성에 따라 Thresholding 처리를 하기도 한다.

  3. Contour detection
    검출된 엣지를 연결하여 contour를 만들어낸다.

  4. Filtering
    검출된 contour 중 다각형이 될 수 있을 객체를 추려낸다. Contour의 면적, 길이 등을 이용하여 수행한다.

  5. Polygon approximation
    이 단계에서 세밀하게 표현되어 있는 contour를 line segment로 이루어진 다각형으로 근사한다.

    TIP

    Line segment가 만나는 지점이 다각형의 corner가 된다. 이를 이용하여 corner detection을 수행할 수 있다.

  6. Post processing
    Polygon들에 대한 추가 작업을 수행한다. 특정 크기, 형태 등을 이용하여 원하는 객체만 최종 결과로 출력한다.

OpenCV에서는 polygon detection을 수행할 수 있도록 Cv2.ApproxPolyDP() 메서드를 제공한다. 이 메서드는 Ramer–Douglas–Peucker algorithm에 기반하여 주어진 contour 또는 curve를 polygon 형태로 근사하며 다음과 같은 연산을 수행한다.

  1. 직선 생성
    주어진 line segment의 시작점과 끝점을 찾아 직선을 생성한다.

  2. 가장 멀리 떨어진 점 찾기
    Line segment의 point 중 직선과 가장 멀리 떨어져 있는 point (PP) 를 찾는다.

  3. 임계값 (ϵ\epsilon) 비교
    PP와 직선 사이의 거리 (DD) 를 ϵ\epsilon과 비교한다.

    • D<=ϵD <= \epsilon
      Line segment의 중간 점이 근사화에 기여하지 않는 것으로 판단하여 재귀 호출이 중단된다.
    • D>ϵD > \epsilon
      PP를 다각형의 corner로 포함한다. 이 점을 기준으로 line segment를 두 개로 분할하여 1 ~ 3 과정을 재귀적으로 반복한다.

더 이상 나눌 수 있는 line segment가 없어 연산이 종료되면 선택된 PP들이 근사된 다각형의 corner가 된다.

이 문서에서는 Cv2.ApproxPolyDP() 메서드를 이용하여 polygon detection을 수행하는 방법을 알아본다.

실습에 사용할 이미지는 다음과 같다.

polygons

2. Example

canny edgeCanny edge
resultResult
private void ApproxPolyDP(Mat image)
{
    using var grayscale = image.CvtColor(ColorConversionCodes.BGR2GRAY);
    using var canny = grayscale.Canny(50, 150);
 
    // Contour detection : 가장 바깥쪽 외곽선만 가져오고, 필요한 최소한의 포인트만 구함
    canny.FindContours(out var contours, out var hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
 
    using var detected = image.Clone();
 
    // 면적이 너무 작은 외곽선은 객체가 아닐 가능성이 높음
    var contourThreshold = 10;
 
    foreach (var contour in contours)
    {
        if (Cv2.ContourArea(contour) < contourThreshold)
            continue;
 
        // 다각형 계산 : epsilon 값은 보통 외곽선 길이의 5% 이하로 설정 (허용 오차)
        var polygon = Cv2.ApproxPolyDP(contour, 0.03 * Cv2.ArcLength(contour, true), true);
 
        // 도형 외곽선 그리기
        detected.DrawContours([polygon], 0, Scalar.White, 2);
 
        // 꼭지점의 수를 도형 안에 표현
        detected.PutText($"{polygon.Length}", GetCenterApprox(polygon), HersheyFonts.HersheySimplex, 1, Scalar.Black, 2);
    }
 
    Cv2.ImShow("Canny", canny);
    Cv2.ImShow("ApproxPolyDP", detected);
 
    OpenCvSharp.Point GetCenterApprox(OpenCvSharp.Point[] polygon)
    {
        double sumX = 0, sumY = 0;
 
        foreach (var corner in polygon)
        {
            sumX += corner.X;
            sumY += corner.Y;
        }
 
        return new((int)(sumX / polygon.Length), (int)(sumY / polygon.Length));
    }
}

3. 참조 자료