[OpenCV] 직선 검출 – 허프 변환(Hough transform)

이번 글에서는 허프 변환을 사용하여 직선 검출을 해보겠습니다.

허프 변환은 영상에서 직선을 검출하는데 자주 사용되는 기법입니다.
우선 2차원 영상에서 y = ax + b는 아래와 같이 표현됩니다. a는 기울기, b는 y절편 입니다.

위 그림에서 점(x1, y1)와 점(x2, y2)는 y = ax + b그래프 위에 있습니다.
이러한 그래프에서 이 수식을 a와 b에 대한 그래프로 바꾸면 b = -xa + y가 됩니다.
점(x1, y1)와 점(x2, y2)까지 표현하면 아래와 같습니다.

여기서 교차점(a1, b1)은 점(x1, y1)와 점(x2, y2)를 일직선으로 이어주는 값입니다. 직선의 방정식을 찾으려면 이러한 교차점을 모두 찾아야합니다. 하지만 위의 y = ax + b방정식에서는 y축과 평행한 직선은 찾을 수 없습니다. a의 값이 무한대가 되어야 하기 때문에, 허프면환을 구현할 때에는 아래와 같은 극좌표계 형식의 직선 방정식을 사용합니다.

x cosθ + y sinθ =  ρ

이것 또한 변환을 하면 아래와 같이 교차점(ρ1, θ1)이 생깁니다.

정확한 그림은 아닙니다..

이 점 (ρ1, θ1)이 직선을 나타내는 파라미터 입니다.

OpenCV에서는 허프 변환 직선 검출을 위해 HoughLines 함수를 제공합니다.

/** 
@param image 8비트 단일 입력 영상
@param lines 직선 정보 출력 벡터
@param rho 축적 배열에서 ρ값의 해상도
@param theta 축적 배열에서 θ값의 해상도
@param threshold 임계값
@param srn 멀티스케일 허프변환에서 rho 해상도를 나누는 값
@param stn 멀티스케일 허프 변환에서 theta 해상도를 나누는 값
@param min_theta 검출할 직선의 최소 theta 값
@param max_theta 검출할 직선의 최대 theta값
 */
void HoughLines( InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn = 0, double stn = 0, double min_theta = 0, double max_theta = CV_PI );

샘플 코드는 아래와 같습니다.

Mat src = imread("building.jpg");
if (src.empty())
{
	cerr << "image load fail" << endl;
	return;
}

Mat srcGray;
cvtColor(src, srcGray, COLOR_BGR2GRAY);

Mat edge;
Canny(srcGray, edge, 150, 150);

vector<Vec2f> lines;
HoughLines(edge, lines, 1, CV_PI / 180, 250);

Mat dst;
cvtColor(edge, dst, COLOR_GRAY2BGR);

for (size_t i = 0 ; i < lines.size() ; i++)
{
	float r = lines[i][0];
	float t = lines[i][1];
	double cos_t = cos(t);
	double sin_t = sin(t);
	double x0 = r * cos_t;
	double y0 = r * sin_t;
	double alpha = 1000;

	Point pt1(cvRound(x0 + alpha * (-sin_t)), cvRound(y0 + alpha * cos_t));
	Point pt2(cvRound(x0 - alpha * (-sin_t)), cvRound(y0 - alpha * cos_t));

	cout << "(" << pt1.x << ", " << pt1.y << ") to (" << pt2.x << ", " << pt2.y << ")" << endl;

	line(dst, pt1, pt2, Scalar(0, 0, 255), 2, LINE_AA);
}

imshow("src", src);
imshow("dst", dst);

waitKey();
destroyAllWindows();

실행 결과는 아래와 같습니다.