How to get accumulation array on Hough transform in OpenCV



허프 변환(Hough transform)은 주로 영상에서 직선을 검출할 때 사용됩니다. OpenCV에서는 HoughLines() 함수를 이용하여 직선을 나타내는 파라미터(rho, theta)를 얻을 수 있습니다. 직선을 검출하는 것만이 목적이라면 HoughLines() 함수가 반환하는 rho, theta 값만 이용하면 되지만, 간혹 HoughLines() 함수 내부에서 축적 배열이 어떻게 생성되는지 궁금해하는 분들이 있습니다. 그런 분들을 위해 OpenCV 소스 코드를 변경하여 축적 배열을 받아오는 방법에 대해 설명하겠습니다. 참고로 이 포스팅에서는 OpenCV 4.0.0 버전을 기준으로 설명합니다.


HoughLines() 함수는 <OpenCV-SRC>\modules\imgproc\include\opencv2\imgproc.hpp 파일에 선언되어 있고, <OpenCV-SRC>\modules\imgproc\src\hough.cpp 파일에 정의되어 있습니다. 그러므로 이 두 개의 파일을 수정하여 HoughLines() 함수를 변경할 수 있습니다. OpenCV에서 제공하는 HoughLines() 함수 선언은 다음과 같습니다.

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 );

이 함수 선언에 인자를 하나 추가하여 축적 배열을 받아오는 용도로 사용하겠습니다. 그래서 함수 선언을 아래와 같이 변경합니다.

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,
                 OutputArray accum = noArray() );

HoughLines() 함수에 accum 인자를 새로 추가했지만 디폴트 인자를 지정해두었기 때문에 기존 방식의 HoughLines() 함수와 완전히 호환됩니다. 축적 배열을 받고 싶으면 accum에 Mat 객체를 지정하고, 축적 배열이 필요없다면 accum을 지정하지 않으면 됩니다.


그럼 이제 해야할 일은 HoughLines() 함수 안으로 들어가서 함수 내부에서 만들어서 사용하던 축적 배열을 accum 인자로 복사해주면 됩니다. 아주 간단하죠. 다만 허프 변환에 의한 직선 검출이 실제로는 HoughLines() 함수 안에서 다시 호출하는 HoughLinesStandard()라는 이름의 함수에서 수행되고 있기 때문에 건드려야할 부분이 조금 많다는 점이 귀찮을 뿐입니다. 수정해야할 부분을 패치 파일 형식으로 나타내면 아래와 같습니다.


그리고 실제 텍스트 문서로 된 패치 파일도 첨부합니다.   hough.cpp.patch  imgproc.hpp.patch


OpenCV 소스 코드를 변경했으니 실제로 사용하려면 OpenCV를 다시 빌드해야 합니다. OpenCV 소스 코드를 빌드하는 방법은 4.0.0 기준으로 설명해둔 것이 (아직) 없으니 일단 3.3.0 버전 빌드하는 방법을 보시고 따라하시면 될 것 같습니다. http://kkokkal.tistory.com/1315


빌드가 잘 되었고 DLL 파일이 정상적으로 만들어졌다면 이제 수정된 HoughLines() 함수를 사용하는 방법을 설명하겠습니다. 설명은 길게할 것이 없고, 아래 소스 코드를 참고하세요. :-)


#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace std;

int main(void)
{
	Mat src = imread("building.jpg", IMREAD_GRAYSCALE);

	Mat src_edge;
	Canny(src, src_edge, 50, 200);

	vector<Vec2f> lines;
	Mat accum;
	HoughLines(src_edge, lines, 2, CV_PI / 360, 450, 0, 0, 0, CV_PI, accum);

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

	for (size_t i = 0; i < lines.size(); i++) {
		float r = lines[i][0], t = lines[i][1];
		double cos_t = cos(t), sin_t = sin(t);
		double x0 = r * cos_t, 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));
		line(dst, pt1, pt2, Scalar(0, 0, 255), 2, LINE_AA);
	}

	Mat accum_norm;
	normalize(accum, accum_norm, 0, 255, NORM_MINMAX, CV_8U);

	imshow("dst", dst);
	imshow("accum_norm", accum_norm);

	waitKey(0);
	return 0;
}

위 소스 코드에서 HoughLines() 함수 맨 마지막 인자로 accum을 지정하였고, 이 행렬은 실수행 행렬입니다. 그러므로 이를 영상 형태로 화면에 나타내려면 normalize() 함수를 이용하여 픽셀 값 범위를 0~255 사이로 변경하고, 타입도 CV_8UC1으로 변경해야 합니다. 그렇게 만들어진 결과가 accum_norm 영상입니다.


실제로 위 코드를 실행하면 아래와 같은 창이 나타납니다.


참 쉽죠? 참고로 accum 행렬은 가로 방향이 rho, 세로 방향이 theta 입니다.


Posted by kkokkal
: