Canny·Hough·SIFT로 구성하는 이미지 구조와 특징 검출
Canny, Hough Transform, SIFT의 검출 원리와 성능 특성, OpenCV 기반 에지·기하 구조·특징 정합 파이프라인을 정리한다.
2026-08-14 · 최초 발행 2024-04-29
에지에서 구조와 특징까지 이어지는 처리 흐름
이미지에서 경계선을 찾는 일과 직선·원 같은 형상을 추정하는 일, 서로 다른 장면에서 같은 대상을 식별하는 일은 요구하는 정보가 다르다. Canny Edge Detection, Hough Transform, SIFT는 이 지점을 각각 에지, 기하 구조, 지역 특징으로 나눠 다룬다.
Canny는 노이즈에 강인하면서 얇은 에지를 얻기 위한 에지 검출기다. 가우시안 스무딩, 그래디언트 계산, 비최대 억제(NMS), 히스테리시스 임계 과정을 거친다.
Hough Transform은 에지 포인트에서 직선이나 원의 매개변수를 추정한다. 파라미터 공간의 누산기에 투표를 기록하고, 임계값을 넘는 피크를 구조 후보로 삼는다. 확률적 변형(Probabilistic)은 이 계산을 줄이는 데 사용한다.
SIFT(Scale-Invariant Feature Transform)는 스케일과 회전에 불변인 키포인트 및 128차원 로컬 디스크립터를 만든다. 스케일-스페이스에서 DoG 극값을 찾고, 위치·스케일을 보정한 뒤 방향을 할당하고 디스크립터를 생성한다.
입력 품질과 각 알고리즘의 파라미터
세 알고리즘은 그레이스케일 변환, 노이즈 억제, 대비 보정 같은 입력 전처리 뒤에 에지·기하·특징 단계로 연결할 수 있다. 에지 밀도나 키포인트 수를 단계별로 확인하면 파라미터 자동 튜닝의 피드백으로 쓸 수 있다.
에지가 부족하면 임계값을 다시 설정하고, 조명이 고르지 않으면 CLAHE를 적용할 수 있다. 키포인트가 부족한 경우에는 스케일 범위를 확장한다.
Canny는 가우시안 필터로 고주파 노이즈를 줄인 뒤 Sobel로 그래디언트의 크기와 방향을 계산한다. 비최대 억제가 에지를 얇게 남기고, 상·하한을 쓰는 히스테리시스 임계가 연결성을 유지한다. 임계값 선택에는 민감하므로 자동화 시 Otsu·중앙값 휴리스틱을 활용한다.
Hough Transform에서는 직선의 ρ-θ처럼 정한 파라미터 공간에 투표한다. 누산기 피크를 찾은 후 임계값과 최대 억제(NMS)로 최종 구조를 고른다. HoughLinesP는 계산 비용을 낮추며, 최소 선분 길이와 간극 파라미터가 노이즈 억제에 관여한다.
SIFT는 옥타브와 스케일-스페이스에서 DoG 극값 후보를 추출하고 컨트라스트·엣지 필터링으로 약한 점을 제거한다. 주 방향을 할당해 회전 불변성을 확보하며, 4×4 셀 × 8방향 히스토그램으로 128D 디스크립터를 구성한다. FLANN/BF 매칭과 RANSAC은 정합을 안정화하는 데 사용한다.
검출 대상에 따라 달라지는 적용 방식
도로 차선 검출에서는 ROI를 추출한 뒤 Canny와 HoughLinesP를 적용하고, 차선 모델을 피팅해 곡률과 편차를 산출한다. 조도 변화에는 CLAHE를 적용하고 ROI를 동적으로 조정해 연산량을 줄일 수 있다.
PCB나 부품 정렬 검사에서는 Canny로 외곽을 잡고 Hough로 홀 또는 직선 패턴을 확인한다. 이어 SIFT로 골든 템플릿을 매칭하고 오차 맵을 만든다. 서보 카메라 흔들림에는 SIFT+RANSAC 정합을 적용한다.
문서 스캔 보정은 Canny와 Hough로 문서 가장자리를 찾아 코너를 정렬하고, 퍼스펙티브 보정과 이진화로 이어진다. 그림자와 그라디언트를 줄이기 위해 조명 평탄화를 적용한다.
로봇 픽킹이나 AR 트래킹에서는 SIFT 키포인트를 매칭하고 RANSAC으로 Homography를 추정한 뒤 PnP로 포즈를 계산해 경로 계획이나 오버레이에 연결한다. 텍스처가 빈약한 장면에서는 SIFT에 ORB/AKAZE를 보완 적용한다.
성능 특성과 운영상 제약
CPU 기준 720p 프레임에서 Canny는 1–3 ms, HoughLinesP는 3–8 ms, SIFT는 키포인트 수에 따라 15–40 ms가 걸린다. SIFT 매칭은 조명·스케일 변화에서 ORB 대비 5–15%p의 정합률 향상을 보일 수 있으며, 데이터셋에 의존한다. 자동 임계와 적응형 CLAHE를 활용한 파라미터 자동화는 에지·구조 검출 실패율을 20–40% 줄일 수 있다.
복잡한 배경에서도 구조적 안정성을 확보할 수 있고, 파이프라인을 모듈화하면 유지보수와 기능 확장에 유리하다.
| 지표 | Canny | Hough Transform | SIFT |
|---|---|---|---|
| 성능(속도) | 매우 빠름 | 빠름(확률적 변형 사용 시) | 중간~느림(키포인트 수 의존) |
| 확장성(해상도↑) | 선형 증가, 부담 낮음 | 선형~초선형(누산기 해상도 의존) | 선형~초선형(스케일·키포인트 수 증가) |
| 일관성(조명/스케일) | 조명 민감, 스케일 비불변 | 조명 민감, 스케일 비불변 | 조명·스케일·회전에 비교적 강인 |
| 안정성(노이즈) | 가우시안 스무딩 전제 | 에지 품질 의존, 파라미터 민감 | 비교적 강인, 텍스처 빈약 시 취약 |
| 운영 편의 | 파라미터 2–3개, 튜닝 용이 | 파라미터 다수(임계/간극/길이) | opencv-contrib 필요, 계산량/메모리 부담 |
검출 결과를 되돌려 조정하는 흐름
OpenCV와 Python으로 구현하기
전제조건은 다음과 같다.
- Python 3.8+, OpenCV 4.8+ 권장
- pip install opencv-python opencv-contrib-python
- SIFT는 OpenCV contrib 모듈 필요. 특허 만료로 상업적 사용 가능(최신 정책 확인 필요).
Canny에 자동 임계 휴리스틱을 적용하는 예제다.
# Python 3.8+, opencv-python 4.8+
import cv2 as cv
import numpy as np
img = cv.imread("input.jpg", cv.IMREAD_GRAYSCALE)
assert img is not None, "이미지 로드 실패"
# 대비 보정(선택)
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
g = clahe.apply(img)
# 블러 및 자동 임계
blur = cv.GaussianBlur(g, (5,5), 1.2)
med = np.median(blur)
low = int(max(0, 0.66 * med))
high = int(min(255, 1.33 * med))
edges = cv.Canny(blur, low, high, L2gradient=True)
cv.imwrite("edges.png", edges)
다음 코드는 Canny 결과에서 확률적 허프 변환으로 직선을 찾는다.
# 전제: edges는 Canny 결과
import cv2 as cv
import numpy as np
edges = cv.imread("edges.png", cv.IMREAD_GRAYSCALE)
lines = cv.HoughLinesP(edges, rho=1, theta=np.pi/180, threshold=60,
minLineLength=40, maxLineGap=10)
color = cv.cvtColor(edges, cv.COLOR_GRAY2BGR)
if lines is not None:
for x1, y1, x2, y2 in lines[:, 0]:
cv.line(color, (x1, y1), (x2, y2), (0, 255, 0), 2)
else:
print("라인 없음: threshold/minLineLength 조정 필요")
cv.imwrite("hough_lines.png", color)
SIFT 특징 매칭과 RANSAC 정합은 아래처럼 구성한다.
# opencv-contrib-python 필요
import cv2 as cv
img1 = cv.imread("template.jpg", cv.IMREAD_GRAYSCALE) # 템플릿
img2 = cv.imread("scene.jpg", cv.IMREAD_GRAYSCALE) # 장면
assert img1 is not None and img2 is not None
sift = cv.SIFT_create(nfeatures=2000, contrastThreshold=0.04, edgeThreshold=10)
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
if des1 is None or des2 is None or len(kp1) < 8 or len(kp2) < 8:
raise RuntimeError("키포인트 부족: 조명 보정/스케일 범위 확장 필요")
index_params = dict(algorithm=1, trees=5) # FLANN KD-Tree
search_params = dict(checks=64)
flann = cv.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
# Lowe ratio test
good = [m for m, n in matches if m.distance < 0.75 * n.distance]
if len(good) >= 8:
src = cv.KeyPoint_convert([kp1[m.queryIdx] for m in good])
dst = cv.KeyPoint_convert([kp2[m.trainIdx] for m in good])
H, mask = cv.findHomography(src, dst, cv.RANSAC, 3.0)
inliers = int(mask.sum()) if mask is not None else 0
print(f"정합 Inliers: {inliers}/{len(good)}")
else:
print("유효 매칭 부족: ratio/검출 파라미터 조정 필요")
운영 환경에서 조정할 항목
ROI를 줄이고 이미지 피라미드 레벨을 제한하거나 Hough 누산 해상도를 완화하면 연산량을 낮출 수 있다. SIFT를 ORB/AKAZE로 바꾸면 속도는 향상될 수 있지만 강건성이 낮아지는 트레이드오프가 있다.
프레임별 적응형 임계값은 에지 밀도 목표에 맞출 수 있고, 키포인트 목표치를 기준으로 nfeatures를 동적으로 조정할 수 있다. 조명 불균일에는 Retinex/Illumination compensation, 모션 블러에는 디블러나 짧은 노출 설정을 적용한다. 재현성과 테스트를 위해 시드를 고정하고 데이터셋 기반 그리드 서치, 운영 환경별 프로파일링(CPU vs GPU)을 수행한다.
Canny, Hough, SIFT를 연결하면 에지에서 기하 구조와 로컬 특징으로 이어지는 파이프라인을 구성할 수 있다. ROI 설계, 적응형 임계, RANSAC 기반 정합은 조명·스케일 변화와 연산량 사이의 균형을 다룰 때의 기준이 된다.