Favicon

Edge detection

Peponi5/15/202511m

C#
NugetPackageOpenCvSharp4Filter2DSobelScharrLaplacianCannyGradient

1. Introduction

사람이 시각을 통해 사물을 인식하는 경우, 객체 형상에 대한 특징 (질감, 모양 등) 을 이용하여 판단하게 된다. 컴퓨터 또한 이미지 프로세싱을 통해 형상에 대한 특징을 추론하여 적절한 객체 인식을 수행할 수 있다. 이 때, 형상에 대한 감지 기법 중 하나로 edge detection을 활용할 수 있다.

Edge는 이미지상 서로 다른 영역의 경계에서 발생하는 밝기, 색상의 변화가 급변하는 지점을 의미한다. Edge detection은 커널을 활용해 컨볼루션 연산을 수행함으로써 미분 값을 근사적으로 계산하며, 이 과정을 통해 객체의 외관에 대한 특징을 찾아낼 수 있다.

Edge detection은 주로 다음과 같은 상황에 활용할 수 있다.

  • 객체 인식, 분류
    감시 카메라와 같은 비전 시스템의 경우 edge를 활용하여 사람, 자동차 등을 인식할 수 있다.
  • ROI 분할
    이미지 영역 중 문서에 해당하는 부분만 추출하는 등 ROI를 세분화하여 분석에 활용할 수 있다.
  • 이미지 압축
    이미지의 디테일한 정보가 필요 없는 경우, edge만 추출하여 저장함으로써 데이터 양을 줄일 수 있다.

이 문서에서는 edge detection을 수행할 수 있는 방법에 대해 간략하게 소개한다.

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

Image from Pixabay

2. Filter2D

edge XEdge X
edge YEdge Y
edge X+YEdge X+Y

Mat.Filter2D() 메서드는 이미지 필터링에 사용되는 메서드로, 커널을 이용한 correlation 연산을 수행한다. 커널을 구성하기에 따라 블러, 샤프닝 등의 효과를 얻을 수 있으며 엣지를 검출하는 것 또한 가능하다.

기초적인 커널의 모양은 다음과 같다.

[111000111]\begin{bmatrix} -1&-1&-1\\ 0&0&0\\ 1&1&1 \end{bmatrix}

Mat.Filter2D()를 이용한 edge detection은 가장 기초적인 방법으로, 후술하는 다른 방법들은 이 과정을 간략하게 수행할 수 있도록 편의 기능을 제공해 주는 것으로 이해할 수 있다. 여기서는 Sobel edge detection을 수행하는 커널을 구성하고 연산을 수행하는 예시를 보여준다.

private void Filter2D(Mat image)
{
    // Sobel example
 
    // Kernel 생성
    float[] weightX = [-1,0,1,
                              -2,0,2,
                              -1,0,1];
    float[] weightY = [-1,-2,-1,
                               0,0,0,
                               1,2,1];
 
    using var kernelX = Mat.FromPixelData(3, 3, MatType.CV_32FC1, weightX);
    using var kernelY = Mat.FromPixelData(3, 3, MatType.CV_32FC1, weightY);
 
    using var edgeX = image.Filter2D(-1, kernelX);
    using var edgeY = image.Filter2D(-1, kernelY);
    using var edgeXYMerged = edgeX + edgeY;
 
    Cv2.ImShow("Filter Edge X", edgeX);
    Cv2.ImShow("Filter Edge Y", edgeY);
    Cv2.ImShow("Filter Edge X+Y", edgeXYMerged);
}

3. Sobel

edge XEdge X
edge YEdge Y
edge X+YEdge X+Y

Sobel edge detectionMat.Filter2D()를 이용하는 대표적인 방법 중 하나로, 수평과 수직 방향의 edge를 따로 계산하여 전체 edge를 구할 수 있다.

Sobel 연산에 사용되는 기본적인 커널의 모양은 다음과 같다.

Kernelx(Verticaledge)=[101202101]Kernel_x \, (Vertical \,\, edge) = \begin{bmatrix} -1&0&1\\-2&0&2\\-1&0&1 \end{bmatrix} Kernely(Horizontaledge)=[121000121]Kernel_y \, (Horizontal \,\, edge) = \begin{bmatrix} -1&-2&-1\\0&0&0\\1&2&1 \end{bmatrix}
private void Sobel(Mat image)
{
    // Sobel(depth, xorder, yorder, ...)
    using var sobelX = image.Sobel(-1, 1, 0);
    using var sobelY = image.Sobel(-1, 0, 1);
    using var sobelXYMerged = sobelX + sobelY;
 
    Cv2.ImShow("Sobel X", sobelX);
    Cv2.ImShow("Sobel Y", sobelY);
    Cv2.ImShow("Sobel X+Y", sobelXYMerged);
}

4. Scharr

edge XEdge X
edge YEdge Y
edge X+YEdge X+Y

Scharr edge detection은 Sobel 커널에 비해 중심부 픽셀의 가중치가 크게 적용된다. 일반적으로 333*3 커널을 사용하거나 대각 엣지를 검출하는 경우 Sobel 연산보다 유리할 수 있다고 알려져 있다. Sobel 연산과 달리, Scharr 연산은 333*3 커널만을 사용한다.

Scharr 연산에 사용되는 커널의 모양은 다음과 같다.

Kernelx(Verticaledge)=[30310010303]Kernel_x \, (Vertical \,\, edge) = \begin{bmatrix} -3&0&3\\-10&0&10\\-3&0&3 \end{bmatrix} Kernely(Horizontaledge)=[31030003103]Kernel_y \, (Horizontal \,\, edge) = \begin{bmatrix} -3&-10&-3\\0&0&0\\3&10&3 \end{bmatrix}
private void Scharr(Mat image)
{
    // Scharr(depth, xorder, yorder, ...)
    using var scharrX = image.Scharr(-1, 1, 0);
    using var scharrY = image.Scharr(-1, 0, 1);
    using var scharrXYMerged = scharrX + scharrY;
 
    Cv2.ImShow("Scharr X", scharrX);
    Cv2.ImShow("Scharr Y", scharrY);
    Cv2.ImShow("Scharr X+Y", scharrXYMerged);
}

5. Laplacian

laplacian

Laplacian edge detection 또한 커널을 이용한 연산을 수행한다. 앞선 방법들과는 달리, Laplacian 연산은 모든 방향의 2차 미분 값을 더하는 방식으로 동작하며 라플라시안이 0이 되는 지점 (zero crossing point) 이 엣지가 된다. 또한 라플라시안 커널의 특성상 엣지의 방향에 대한 정보는 찾기 어렵다.

Laplacian edge detection은 노이즈에 취약한 관계로 가우시안 블러를 통해 노이즈를 제거한 후 수행하는 것이 일반적이다. 이 방법을 LoG (Laplacian of Gaussian) 라고 한다.

Laplacian 연산에 사용되는 일반적인 커널의 모양은 다음과 같다. (OpenCV의 Mat.Laplacian()ksize = 1인 경우 이 커널을 사용한다)

Kernel=[010141010]Kernel = \begin{bmatrix} 0&1&0\\1&-4&1\\0&1&0 \end{bmatrix}

경우에 따라서는 다음과 같이 커널을 구성하기도 한다.

Kernel=[111181111]Kernel = \begin{bmatrix} -1&-1&-1\\-1&8&-1\\-1&-1&-1 \end{bmatrix}
private void Laplacian(Mat image)
{
    using var gaussian = image.GaussianBlur(new OpenCvSharp.Size(3, 3), 0);
 
    //아래 Laplacian() 호출의 ksize가 1인 경우, 다음 주석 과정과 동일함
    //float[] weight = [0,1,0,
    //                         1,-4,1,
    //                         0,1,0];
 
    //using var kernel = Mat.FromPixelData(3, 3, MatType.CV_32FC1, weight);
 
    //using var edge = gaussian.Filter2D(-1, kernel);
 
    //Cv2.ImShow("Laplacian Edge", edge);
 
    // Laplacian(depth, ksize, ...)
    using var laplacian = gaussian.Laplacian(-1, 3);
 
    Cv2.ImShow("Laplacian", laplacian);
}

6. Canny

canny

Canny edge detection은 이미지 프로세싱에서 엣지를 검출하는데 가장 많이 사용하는 방법 중 하나이다. 단계적으로 알고리즘이 구성되어 있으며 노이즈에 강하고 엣지를 얇고 정확하게 찾을 수 있는 특징이 있다. Canny 엣지 검출은 다음과 같은 단계를 거쳐 수행된다.

  1. Gaussian blur
    이미지에서 노이즈를 제거하기 위해 Gaussian blur를 적용한다.
  2. Sobel edge detection
    노이즈가 제거된 이미지에 Sobel 커널을 이용하여 X, Y 방향의 gradient를 구한다.
  3. Non-maximum suppression
    계산된 gradient 방향에 놓인 주변 픽셀과의 gradient 크기를 비교한다. 픽셀의 gradient 크기가 주변보다 작다면 픽셀 값을 0으로 바꾸어준다. 이 과정에서 엣지가 간결하게 정리된다.
  4. Thresholding
    간결해진 엣지 정보를 대상으로 thresholding을 수행한다. 지정한 min보다 작은 경우 엣지에서 제외하며, max보다 큰 경우 확실하게 엣지로 보존한다. min ~ max 사이 값의 경우 확실한 엣지와의 연결 여부를 따져 엣지로 보존한다.

상기 과정을 거쳐 보존된 엣지 정보가 최종 결과로 출력된다. 다음은 canny edge detection을 수행하는 코드이다.

private void Canny(Mat image)
{
    // Grayscale 변환
    // Color 이미지에도 canny edge detection이 적용 가능하지만, 흑백 이미지에서 더 잘 작동하는 것으로 알려져 있다.
    using var grayscale = image.CvtColor(ColorConversionCodes.BGR2GRAY);
 
    // Canny(threshold_min, threshold_max, ...)
    // max값을 넘어서는 pixel은 바로 엣지로 취급
    // min ~ max 사이의 pixel은 확실한 엣지와 연결이 되어 있는 경우 엣지로 취급
    // min보다 낮은 pixel은 엣지에서 제외
    using var canny = grayscale.Canny(100, 200);
 
    Cv2.ImShow("Canny", canny);
}

7. Gradient

gradient

미분을 이용하는 앞선 과정들과는 달리, gradient는 형태 차이 (팽창과 침식의 차이) 를 이용하여 엣지를 찾는 방법이다. Dilation - erosion을 수행하며, 계산 과정에서 객체의 경계 부분만 남게 되어 간단하게 엣지를 검출하는 데 사용된다.

private void Gradient(Mat image)
{
    using var grayscale = image.CvtColor(ColorConversionCodes.BGR2GRAY);
    using var binary = grayscale.Threshold(200, 255, ThresholdTypes.Binary);
    using var kernel = Mat.Ones(3, 3, MatType.CV_8UC1);
 
    using var transformed = binary.MorphologyEx(MorphTypes.Gradient, kernel);
 
    Cv2.ImShow("Gradient", transformed);
}

8. 참조 자료