허프 변환 목차
허프 변환
Hough Line Transform
이미지에서 직선을 찾기 위해 사용되는 알고리즘
직선 \(y = mx + b\)을 매개변수식으로 바꾸면 \(r = xcosθ + ysinθ\)
- r: 원점으로부터 직선까지의 수직 거리
- θ: 이 직선과 수직인 직선과 x축 사이의 각도를 시계 반대 방향으로 측정한 값
[ex]
좌표 (300,300) 을 지나는 모든 직선에 대한 r과 θ를 구해서 극좌표계에 그려보면 사인 곡선이 그려짐.
식 \(r = xcosθ + ysinθ\)에 좌표 (300,300) 을 대입하고 θ 를 0에서 180까지 증가시키면서 r값을 구한 결과
좌표 (100,100) , (200,200) , (300,300) 세 점을 지나는 모든 직선에 대한 r과 θ를 구하여 극좌표계에 그려보면 3개의 사인곡선이 그려지는데,
한 점에서 만남
교차점의 r, θ값을 구하여 직선의 방정식으로 변환 후,
그려보면 세 점 (100,100) ,(200,200) , (300,300) 을 지나는 직선임을 확인할 수 있음.
사인 곡선의 교차점을 구하기 위해 2차원 배열 accumulator 에 사인 곡선을 누적함
일정 개수 이상 사인 곡선이 교차한 지점의 r, θ값을 사용하면 직선을 구할 수 있음
이를 이미지화 해보면 수많은 곡선들이 그려짐
하나의 곡선은 하나의 점을 지나는 모든 직선들이다.
곡선들의 교점이 직선일 가능성이 있는 후보점인데 많은 곡선이 겹쳐질수록 실제 에지 위를 지나는 직선일 가능성이 높아짐
HoughLines 함수 원형
OpenCV에서는 Hough Line Transform을 위해 HoughLinesP
함수와 HoughLines
함수를 제공
- 앞에서 설명한 극좌표계에서의 교차점 정보인 r, θ를 리턴
- 모든 점에 대해 계산하기 때문에 결과를 출력하는 데 오래 걸림
픽셀 중 서로 직선 관계를 갖는 픽셀들만 골라내는 것
HoughLines (InputArray image, OutputArray lines, double rho, double theta, int threshold )
lines = cv2.HoughLines(img, rho, theta, threshold, lines, srn=0, stn=0, min_theta, max_theta)
- img: 입력 이미지, 1 채널 바이너리 스케일
- rho: 거리 측정 해상도, 0~1
- theta: 각도, 라디안 단위 (np.pi/0~180)
- threshold: 직선으로 판단할 최소한의 동일 개수, 같은 직선에 몇 개의 점이 등장해야 직선으로 판단할지를 나타내는 최소한의 개수 (작은 값: 정확도 감소, 검출 개수 증가 / 큰 값: 정확도 증가, 검출 개수 감소)
- lines: 검출 결과, N x 1 x 2 배열 (r, Θ)
- srn, stn: 멀티 스케일 허프 변환에 사용, 선 검출에서는 사용 안 함
- min_theta, max_theta: 검출을 위해 사용할 최대, 최소 각도
거리와 각도를 얼마나 세밀하게 계산할 것인지를 rho와 theta 파라미터로 조정
[단점]
모든 점에 대해 수많은 선을 그어서 직선을 찾기 때문에 연산량이 무척 많음
코드
[C++]
# include < opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat img_edge, img_result;
Mat img_src = imread( " test.jpg", IMREAD_GRAYSCALE );
Canny(img_src, img_edge, 50, 150);
cvtColor(img_edge, img_result, COLOR_GRAY2BGR);
vector<Vec2f> lines;
HoughLines(img_edge, lines, 1, CV_PI/180, 150 );
for( size_t i = 0; i < lines.size(); i++ )
{
float rho = lines[i][0], theta = lines[i][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
pt1.x = cvRound(x0 + 1000*(-b));
pt1.y = cvRound(y0 + 1000*(a));
pt2.x = cvRound(x0 - 1000*(-b));
pt2.y = cvRound(y0 - 1000*(a));
line( img_result, pt1, pt2, Scalar(0,0,255), 3);
}
imshow("Source", img_src);
imshow("Standard Hough Line Transform", img_result);
waitKey();
return 0;
}
[Python]
import math
import cv2 as cv
import numpy as np
img_src = cv.imread("test.jpg", cv.IMREAD_GRAYSCALE)
img_edge = cv.Canny(img_src, 50, 150)
img_result = cv.cvtColor(img_edge, cv.COLOR_GRAY2BGR)
img_result_P = np.copy(img_result)
lines = cv.HoughLines(img_edge, 1, np.pi / 180, 150)
if lines is not None:
for i in range(0, len(lines)):
rho = lines[i][0][0]
theta = lines[i][0][1]
a = math.cos(theta)
b = math.sin(theta)
x0 = a * rho
y0 = b * rho
pt1 = (int(x0 + 1000*(-b)), int(y0 + 1000*(a)))
pt2 = (int(x0 - 1000*(-b)), int(y0 - 1000*(a)))
cv.line(img_result, pt1, pt2, (0,0,255), 3)
cv.imshow("Source", img_src)
cv.imshow("Standard Hough Line Transform", img_result)
cv.waitKey()
HoughLinesP 함수 원형
[개선]
위의 HoughLines 함수 단점 개선
모든 점을 고려하지 않고 무작위로 선정한 픽셀에 대해 허프 변환을 수행하고 점차 그 수를 증가시키는 방법
- 선의 시작과 끝 좌표 리턴
- v2.HoughLines()보다 선 검출이 적음
- 엣지를 강하게 하고 threshold를 낮게 지정 필요
확률적 허프 선 변환
lines = cv2.HoughLinesP(img, rho, theta, threshold, lines, minLineLength, maxLineGap)
- minLineLength(optional): 선으로 인정할 최소 길이
- maxLineGap(optional): 선으로 판단할 최대 간격
- lines: 검출된 선 좌표, N x 1 x 4 배열 (x1, y1, x2, y2) 이외의 파라미터는 cv2.HoughLines()와 동일
코드
[C++]
# include < opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat img_edge, img_result;
Mat src = imread( " test.jpg", IMREAD_GRAYSCALE );
Canny(src, img_edge, 50, 150);
cvtColor(img_edge, img_result, COLOR_GRAY2BGR);
vector<Vec4i> linesP;
HoughLinesP(img_edge, linesP, 1, CV_PI/180, 50, 50, 5 );
for( size_t i = 0; i < linesP.size(); i++ )
{
Vec4i l = linesP[i];
line( img_result, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, LINE_ AA);
}
imshow("Source", src);
imshow("Probabilistic Line Transform", img_result);
waitKey();
return 0;
}
[Python]
import math
import cv2 as cv
import numpy as np
img_src = cv.imread("test.jpg", cv.IMREAD_GRAYSCALE)
img_edge = cv.Canny(img_src, 50, 150)
img_result = cv.cvtColor(img_edge, cv.COLOR_GRAY2BGR)
linesP = cv.HoughLinesP(img_edge, 1, np.pi / 180, 50, None, 50, 5)
if linesP is not None:
for i in range(0, len(linesP)):
l = linesP[i][0]
cv.line(img_result, (l[0], l[1]), (l[2], l[3]), (0,0,255), 3, cv.LINE_AA)
cv.imshow("Source", img_src)
cv.imshow("Probabilistic Line Transform", img_result)
cv.waitKey()