Favicon

Line detection

Peponi6/2/20259m

C#
NugetPackageOpenCvSharp4HoughLinesHoughLinesPLineSegmentDetector

1. Introduction

이미지 내의 픽셀 정보를 분석하면 객체에 대한 형태, 구조 정보 등을 얻을 수 있다. Line detection은 그 중 직선 형태의 위치와 정보를 추출하는 것으로 자동차의 차선 인식, 문서 스캔 등 다양한 컴퓨터 비전 분야에 활용될 수 있다. 고전적인 line detection은 주로 다음과 같은 단계를 거쳐 수행한다.

  1. Preprocessing
    색상 공간 변환, 노이즈 제거 등을 수행하여 프로세싱에 알맞게 이미지를 조정한다.
  2. Edge detection
    이미지의 엣지를 찾아낸다. 주로 Canny, Sobel 등의 알고리즘이 사용되며 이미지 특성에 따라 Thresholding 처리를 하기도 한다.
  3. Line detection
    검출된 엣지를 분석하여 y=ax+by = ax + b 형태의 방정식을 충족하는 픽셀의 집합을 찾는다.

OpenCV에서는 line detection을 수행할 수 있도록 Mat.HoughLines(), Mat.HoughLinesP(), LineSegmentDetector와 같은 기능을 제공한다. 이 문서에서는 해당 방법들을 이용하여 line detection을 수행하는 예시와 함께 간략한 결과를 알아본다.

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

Image by liuen123 from Pixabay

2. HoughLines

canny edgeCanny edge
houghLinesHoughLines detected

Hough transform은 픽셀을 parameter space 요소로 변환 후 특정 모양을 찾는 기술로, 직선 검출의 대표적인 방법 중 하나이다. 직선은 이미지에서 여러 개의 픽셀로 구성되는데, 허프 변환은 이를 반전시켜 하나의 픽셀이 여러 개의 직선을 구성하도록 한다. 직선상의 픽셀은 parameter space로 변환될 때 모두 같은 지점을 지나게 됨으로, 이 교차점을 검출하면 이미지 공간에서 직선을 찾을 수 있게 된다.

private void HoughLines(Mat image)
{
    using var grayscale = image.CvtColor(ColorConversionCodes.BGR2GRAY);
    using var canny = grayscale.Canny(250, 500);
 
    // Mat.HoughLines(거리, 각도, threshold)
    // 1픽셀 단위의 거리, 0.25도 단위 각도로 직선 찾기 수행
    var houghLines = canny.HoughLines(1, Math.PI / 180 / 4, 150);
 
    if (houghLines is null)
    {
        MessageBox.Show("Not found");
        return;
    }
 
    using var detected = image.Clone();
 
    foreach (var line in houghLines)
    {
        var x0 = Math.Cos(line.Theta) * line.Rho;
        var y0 = Math.Sin(line.Theta) * line.Rho;
 
        // 직선 계산 : 이미지 모든 영역에 그려지도록 길게 그림
        var point1 = new OpenCvSharp.Point(Math.Round(x0 + 1000 * (-1 * Math.Sin(line.Theta))), Math.Round(y0 + 1000 * Math.Cos(line.Theta)));
        var point2 = new OpenCvSharp.Point(Math.Round(x0 - 1000 * (-1 * Math.Sin(line.Theta))), Math.Round(y0 - 1000 * Math.Cos(line.Theta)));
 
        detected.Line(point1, point2, Scalar.Red);
    }
 
    Cv2.ImShow("Canny edge", canny);
    Cv2.ImShow("HoughLines detected", detected);
}

3. HoughLinesP

canny edgeCanny edge
houghLinesPHoughLinesP detected

Mat.HoughLinesP() 메서드는 엣지 이미지에서 선분을 검출하여 시작점과 끝점을 반환한다. Mat.HoughLines() 메서드가 모든 엣지를 고려하여 연산을 수행하는 반면, Mat.HoughLinesP() 메서드는 일부 픽셀을 샘플링하여 연산을 줄이는 특징을 가지고 있다. 또한 직선의 최소 길이 및 픽셀 간격을 조절할 수 있어 노이즈의 영향을 최소화 하는 데 유용하다.

private void HoughLinesP(Mat image)
{
    using var grayscale = image.CvtColor(ColorConversionCodes.BGR2GRAY);
    using var canny = grayscale.Canny(250, 500);
 
    // Mat.HoughLines(거리, 각도, threshold, 최소 길이, 픽셀 간격)
    // 1픽셀 단위의 거리, 0.25도 단위 각도, 최소 5픽셀 길이, 픽셀 간격 1로 직선 찾기 수행
    // 픽셀 간격 : 주어진 간격 안에 엣지가 존재하면 동일 직선으로 간주
    var houghLinesP = canny.HoughLinesP(1, Math.PI / 180 / 4, 50, 5, 1);
 
    if (houghLinesP is null || houghLinesP.Length == 0)
    {
        MessageBox.Show("Not found");
        return;
    }
 
    using var detected = image.Clone();
 
    foreach (var line in houghLinesP)
    {
        detected.Line(line.P1, line.P2, Scalar.Red);
    }
 
    Cv2.ImShow("HoughLinesP detected", detected);
}

4. LineSegmentDetector

lsd detected

앞선 방법과 달리, LineSegmentDetector (LSD) 는 이미지의 픽셀 정보를 이용하여 직접 선분을 검출한다. 픽셀의 gradient 방향과 동일 방향의 픽셀을 분석하여 선분의 시작점과 끝점을 반환한다. Hough transform에 비해 빠른 속도를 가지고 있으며, 이미지의 스케일이 변하더라도 비교적 안정적으로 직선을 검출하는 특징을 가지고 있다. 다만, 중복 선분이 검출될 수 있고 노이즈에 민감하게 반응할 수 있어 경우에 따라 적절한 전처리, 후처리가 필요하다.

private void LineSegmentDetector(Mat image)
{
    using var grayscale = image.CvtColor(ColorConversionCodes.BGR2GRAY);
 
    // LineSegmentDetector(refine, scale, sigmaScale, quant, angTh, logEps, ...)
    // 각도 해상도 3, 허용 각도 차이 10, 픽셀 밀도 0.8로 직선 찾기 수행
    using var detector = OpenCvSharp.LineSegmentDetector.Create(LineSegmentDetectorModes.RefineAdv, quant: 3, angTh: 10, densityTh: 0.8);
 
    detector.Detect(grayscale, out var lsdLines, out var widths, out var precisions, out var nfas);
 
    if (lsdLines is null || lsdLines.Length == 0)
    {
        MessageBox.Show("Not found");
        return;
    }
 
    using var detected = image.Clone();
 
    detector.DrawSegments(detected, Mat.FromArray(lsdLines));
 
    Cv2.ImShow("LSD detected", detected);
}

LSD는 많은 파라미터를 갖고 있는데, 이에 대한 설명은 다음 내용을 참조한다.

  1. refine (LineSegmentDetectorModes)
    • LineSegmentDetectorModes.RefineNone : No refinement applied
    • LineSegmentDetectorModes.RefineStd : Standard refinement is applied. E.g. breaking arches into smaller straighter line approximations.
    • LineSegmentDetectorModes.RefineAdv : Advanced refinement. Number of false alarms is calculated, lines are refined through increase of precision, decrement in size, etc.
  2. scale
    이미지의 스케일을 줄인 후 연산을 수행한다. 스케일을 줄여 검출하는 경우 적은 수의 긴 선분을 검출하는 경향이 있다. 기본 값은 0.8이다. (80%의 스케일로 연산 수행)
  3. sigmaScale
    가우시안 필터의 표준 편차에 영향을 준다. 실제 값은 sigmaScale/scalesigmaScale / scale이 적용되며, 너무 큰 값을 주는 경우 분리 검출되어야 할 선분이 합쳐질 수도 있다.
  4. quant
    각도 해상도를 의미하며, 값이 작을 수록 해상도가 높아져 선분을 정밀하게 구분할 수 있다.
  5. angTh
    허용 각도 차이를 설정한다. 값이 작을수록 직선에 가까운 선분 단위로 검출되며, 값이 커지면 조금 휘어지더라도 하나의 선분으로 검출될 수 있다.
  6. logEps
    refine 파라미터가 LineSegmentDetectorModes.RefineAdv로 설정되어 있을 때만 유효하다. LSD 연산은 내부적으로 사각형 근사 과정이 있는데, 이 때의 근사 오차를 설정한다. 값이 클수록 근사가 부정확해도 선분으로 인정된다.
  7. densityTh
    값이 클수록 명확한 선분만 검출된다. 노이즈 또는 명확하지 않은 선분을 필터링 하는 데 유용하다.
  8. nBins
    픽셀의 gradient 방향을 히스토그램으로 표현할 때 사용하는 bin 수를 설정한다. 값이 높을 수록 분해능이 높아지지만, 계산량이 늘어나게 된다.

5. 참조 자료