5.26. 선과 선분 찾기

어떤 장면 특징은 연결된 색상 영역이 아니라 방향을 가진 직선 에지입니다. 바닥에 칠해진 선, 두 표면 사이의 이음매, 인쇄된 사각형의 한 변, 출입구의 가장자리 같은 것들입니다. 블롭 검출기에게 이런 것을 찾게 하는 것은 잘못된 질문입니다. 에지는 폭이 한 픽셀이고, 블롭 알고리즘은 색상이 있는 면적을 원하므로, 답은 비어 있거나 노이즈가 많게 돌아옵니다.

방향을 가진 에지에 알맞은 검출기는 Hough 선 변환 입니다. image 모듈은 이를 두 가지 형태로 제공합니다. find_lines()무한 선(모든 선이 이미지 전체를 가로질러 뻗음)을 반환하고, find_line_segments()유한 선분(각 선이 프레임 안에 끝점을 가짐)을 반환합니다. 애플리케이션이 어느 쪽을 필요로 하는지는 관심 있는 에지가 프레임 전체에 걸쳐 연속적인지 아니면 일부 구간에만 걸쳐 있는지에 달려 있습니다.

5.26.1. Hough 변환의 동작 원리

두 검출기는 동일한 핵심 아이디어를 공유하므로 한 번 이해해 두면 도움이 됩니다. image 모듈은 먼저 입력에 Sobel 방식의 에지 필터를 실행하여 각 픽셀이 방향을 가진 에지 위에 있을 가능성을 점수화합니다. 그런 다음 각 에지 픽셀은 자신이 속할 수 있는 모든 선에 투표 합니다. 가장 많은 투표를 모은 선이 이깁니다.

선은 Hough 공간 에서 두 개의 수로 매개변수화됩니다. theta 는 선의 각도(0 – 179도)이고, rho 는 이미지 원점에서 선까지의 수직 거리(부호 있음, 픽셀 단위)입니다. 이미지가 포함하는 모든 선은 (theta, rho) 공간의 한 점입니다. 입력의 각 에지 픽셀은 자신의 위치와 일치하는 모든 (theta, rho) 조합에 한 표씩 기여합니다. 개념적으로는 Hough 공간을 지나는 하나의 곡선입니다. 이런 곡선들이 많이 교차하는 곳에서는 많은 에지 픽셀이 같은 선에 동의한 것이며, 그 교차점이 하나의 검출입니다.

검출기는 투표 총합이 임계값을 넘는 Hough 공간의 국소 최댓값을 반환합니다. 반환되는 각 Line 은 두 가지 표현을 모두 담습니다. 끝점 형식의 x1, y1, x2, y2 (무한 선의 경우 이미지 경계로 잘림), Hough 형식의 theta, rho, 그리고 크기와 투표 수를 각각 나타내는 lengthmagnitude 입니다.

5.26.2. 무한 선

find_lines() 는 Hough 변환을 실행하여 가장 강한 선들을 반환하며, 각 선은 이미지 전체를 가로질러 뻗습니다:

lines = img.find_lines(threshold=1500, theta_margin=25, rho_margin=25)

for l in lines:
    img.draw_line(l, color=(255, 0, 0))

threshold 는 선이 받아들여지기 위한 최소 투표 총합입니다. 투표 총합은 기여한 모든 픽셀의 Sobel 에지 크기를 더한 것이므로, threshold 값이 클수록 통과하려면 더 길거나 강한 에지가 필요합니다. 그래서 적절한 값은 이미지 해상도(해상도가 높을수록 같은 길이의 선이 더 많은 투표를 누적함)뿐만 아니라 장면에도 달려 있어, 특정 애플리케이션에 맞춰 튜닝해야 합니다. 튜닝의 대략적인 출발점은 다음과 같습니다. 깨끗한 이미지의 평범한 선에는 1000, 대비가 약하거나 짧은 선에는 500 이하, 에지 노이즈의 군집을 통해 오검출 선이 생기는 복잡한 장면에는 2000 이상입니다.

theta_marginrho_margin 은 인접한 최댓값들의 병합 을 제어합니다. 하나의 물리적 에지는 실제 (theta, rho) 주변에 높은 투표 수를 가진 작은 군집의 빈(bin)을 만들어내며, 검출기는 반환하기 전에 각 군집을 그 정점으로 합칩니다. theta_margin=25 (도)는 방향이 25도 이내인 정점들을 병합하고, rho_margin=25 (픽셀)는 거리가 25픽셀 이내인 정점들을 병합합니다. 기본값은 합리적입니다. 값을 올리면 더 적고 더 뚜렷한 선이 반환되고, 값을 내리면 더 많고 때로는 중복된 선이 반환됩니다.

x_stridey_stride 는 투표 중에 에지 픽셀을 건너뛰며 진행하는데, find_blobs() 에서 픽셀을 건너뛰는 방식과 같습니다. 기본값 21 은 일반적인 경우에 적합합니다. 이를 올리면 해상도를 희생하는 대신 탐색이 빨라집니다. roi 는 탐색을 프레임의 한 영역으로 제한하여 반환되는 선을 좁히는 동시에 작업량도 줄입니다.

반환되는 각 선은 곧바로 그릴 수 있습니다. Line 객체는 draw_line() 에 그대로 전달되며, 이 메서드는 객체의 앞부분에서 (x1, y1, x2, y2) 끝점 필드를 읽습니다. l.theta 는 도 단위의 각도로, 한 번의 비교로 선을 수평, 수직, 대각선으로 분류합니다. l.magnitude 는 투표 총합으로, 반환된 선들을 강한 것부터 약한 것 순으로 정렬합니다.

5.26.3. 선분

find_lines() 는 프레임 전체에 걸친 에지에 알맞은 검출기이지만, 인쇄된 바코드의 왼쪽 변, 레이블의 위쪽 에지, 자(ruler)의 보이는 면처럼 많은 실제 에지는 이미지의 일부 구간에만 걸쳐 있습니다. find_line_segments() 는 끝점이 프레임 안에 있는 유한 선분을 반환합니다:

segments = img.find_line_segments(merge_distance=5, max_theta_difference=10)

for s in segments:
    img.draw_line(s, color=(0, 255, 0))

선분 검출기는 Hough 공간에서 투표하는 대신 방향을 가진 에지 픽셀을 따라 직접 추적하며, 그 결과는 짧은 직선 구간들의 모음입니다. merge_distance 는 두 개의 동일 직선상 짧은 구간이 여전히 하나의 반환 선분으로 병합될 수 있는 최대 픽셀 간격을 설정합니다. max_theta_difference 는 병합기가 인접한 구간들 사이에서 허용하는 방향 차이를 도 단위로 설정합니다. 너그러운 병합(merge_distance=10, max_theta_difference=15)은 때때로 실제로는 분리된 에지를 이어 붙이는 대가로 적은 수의 긴 선분을 반환합니다. 엄격한 병합(merge_distance=0, max_theta_difference=5)은 많은 짧은 선분을 반환하고 이를 정리하는 일은 Python에서 애플리케이션에 맡깁니다.

결과 객체는 find_lines() 가 반환하는 것과 동일한 Line 타입이며 동일한 속성을 가지므로, 파이프라인은 두 종류의 검출을 모두 동일한 후속 코드 경로로 처리할 수 있습니다. 실질적인 유일한 차이는 선분의 끝점이 이미지 안에서 선의 실제 끝인 반면, 무한 선의 끝점은 선이 이미지 경계와 만나는 지점이라는 점입니다.

5.26.4. 각각을 언제 사용할지

두 메서드 중 어느 것을 선택할지는 결국 하나의 질문으로 귀결됩니다. 애플리케이션이 선이 어디서 끝나는지 신경 쓰는가?

답이 아니오일 때는 find_lines() 가 알맞은 도구입니다. 선을 따라가는 로봇은 선이 어느 방향으로 가는지선이 프레임 하단을 어디서 가로지르는지 를 알아야 합니다. 선 자체는 지평선 너머까지 뻗어 있습니다. 지평선 검출기는 이미지에서 가장 강한 방향성 에지를 원하며, 지평선이 어디서 끝나는지는 알 필요가 없습니다.

답이 예일 때는 find_line_segments() 가 알맞은 도구입니다. 인쇄된 사각형의 네 변을 식별하려면 끝점을 아는 네 개의 선분이 필요합니다. 디스플레이를 가리키는 손가락을 추적한다는 것은 끝점이 손가락의 끝과 밑동인 짧은 선분을 따라가는 것을 뜻합니다. 보이는 긁힌 자국의 길이를 측정하려면 선분이 픽셀로 차지하는 실제 범위가 필요합니다.

두 검출기는 공통된 한계를 공유합니다. 바로 대비 가 필요하다는 것입니다. 두 검출기가 기반으로 하는 Sobel 에지 필터는 밝기 기울기에 반응합니다. 동일하게 밝은 배경에 대비되는 색상 에지(같은 휘도의 초록 벽에 그어진 빨간 선)는 기울기를 만들지 못하므로 검출도 일어나지 않습니다. 실제로 이런 경우가 나타나면, 해결책은 탐색하기 전에 알맞은 대비를 가진 단일 LAB 채널을 그레이스케일 이미지로 추출하는 것입니다. b 채널을 선택한 to_grayscale() 은 휘도 채널만으로는 평평한 곳에서 빨강을 초록과 대비시켜 분리해 내며, 그 채널 이미지를 선 검출기에 넘기면 됩니다.