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
:

OpenCV 4.0 Beta release and QR code decoder


엊그제 OpenCV 4.0 Beta 버전이 릴리즈되었습니다. OpenCV 4.0 Beta 버전은 아래 링크에서 다운로드 받을 수 있습니다.

https://github.com/opencv/opencv/releases
https://github.com/opencv/opencv_contrib/releases


OpenCV 4.0 Beta 버전의 특징은 https://opencv.org/opencv-4-0-0-beta.html 에 잘 설명되어 있구요.. 아래는 해당 사이트의 설명을 번역한 것입니다.

================================================
OpenCV 4.0 Beta에는 OpenCV 4.0 Alpha보다 29개의 새로운 패치가 포함되었습니다.

* ONNX* (Open Neural Network Exchange) 가져오기 기능이 향상되어 더 많은 토폴로지를 지원합니다.
* OpenCV DNN 샘플 object_detection.py 파일이 올바른 모델 파라미터를 채울 수 있도록 향상되어 사용하기 쉬워졌습니다.
* G-API (Graph API) - 매우 효율적인 영상 처리 파이프 라인 엔진이 opencv_gapi 모듈로 통합되었습니다.
* 무료 QUirc( https://github.com/dlbeer/quirc) 라이브러리에 기반한 빠른 QR 코드 디코더가 통합되었으며, 이를 이용하여 완전한 QR 코드 검출 및 디코딩이 가능합니다. (640x480 해상도에서 20~80FPS).
* AVX2를 사용하는 18개의 함수, 60개 이상의 커널이 가속화되었습니다.
* iGPU용 Kinect Fusion 알고리즘이 가속화되어 고해상도 볼륨(512x512x512)에서 병렬 CPU 버전보다 3배 빨라졌습니다.
================================================


OpenCV 4.0 Alpha 버전에서는 QR 코드 검출 함수만 들어가 있었는데요, Beta 버전에는 검출과 해석 함수 모두 포함되어 있네요. OpenCV 4.0에 들어가는 QR 코드 해석 기능은 QUirc라는 라이브러리를 OpenCV에서 통합한 것입니다.


QR 코드 해석 성능이 어떨까 궁금해서 예제 코드를 만들어 봤습니다.
(2018/11/14 수정: OpenCV 4.0.0 RC 버전에서 QR 코드 관련 API가 변경되어 코드를 새로 업데이트 하였습니다. OpenCV 4.0.0 Beta 버전용 코드는 아래 [더보기]를 클릭하면 볼 수 있습니다.)

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

using namespace cv;
using namespace std;

int main(void)
{
	VideoCapture cap(0);

	if (!cap.isOpened()) {
		cerr <<"Camera open failed!" << endl;
		return -1;
	}

	QRCodeDetector detector;

	Mat frame, gray;
	while (1) {
		cap >> frame;

		if (frame.empty()) {
			cerr << "Frame load failed!" << endl;
			return -1;
		}

		cvtColor(frame, gray, COLOR_BGR2GRAY);

		vector<Point> points;
		if (detector.detect(gray, points)) {
			polylines(frame, points, true, Scalar(0, 255, 255), 2);

			String info = detector.decode(gray, points);
			if (!info.empty()) {
				polylines(frame, points, true, Scalar(0, 0, 255), 2);
				cout << info << endl;
			}
		}

		imshow("frame", frame);
		if (waitKey(1) == 27)
			break;
	}

	return 0;
}


위 소스 코드는 컴퓨터에 연결된 카메라로부터 프레임을 받아와 QR 코드를 검출하고, QR 코드에 포함된 문자열을 콘솔 명령창에 출력합니다. QR 코드에 노란색 박스가 그려지면 검출만 된거고, 빨간색 박스가 그려지면 해석까지 성공한 상태입니다. 실제로 프로그램을 빌드해서 실행해서 QR 코드를 카메라로 비추면.. 음.. 잘 됩니다. 간간히 몇몇 프레임은 QR 코드를 검출만 하고 해석을 못하는 경우가 생기는데, 제 컴퓨터의 카메라 화질이 너무 안 좋아서 그런건지 라이브러리 성능의 문제인지 명확하지가 않네요.


실제 동작 화면을 캡쳐해서 보여드리면요... 프로그램을 실행해서 아래 QR 코드가 있는 모니터 화면을 카메라로 비추면 콘솔창에 문자열이 출력됩니다.

(화질이 진짜 안 좋죠? :-)


그래도 함수 한 두 개로 QR 코드를 해석할 수 있게 되었네요. 참 쉽네요.. :-)






Posted by kkokkal
:

OpenCV 3.4.2 Jpeg library performance (compare to OpenCV 3.4.0)


OpenCV 3.4.2 버전부터 기본 JPEG 라이브러리가 libjpeg-turbo(ver 1.5.3-62)로 변경되었습니다. 그 전에는 Independent JPEG Group의 JPEG 라이브러리(v9.0b)가 사용되었었죠. libjpeg-turbo 라이브러리는 SIMD를 사용하기 때문에 Independent JPEG Group의 라이브러리보다 더 빠르게 동작한다고 알려져 있습니다. 각각의 라이브러리에 대한 자세한 설명은 홈페이지를 참고하세요.


이미 OpenCV 3.4.3 버전이 나온 시점이지만, 3.4.2의 libjpeg-turbo 라이브러리의 성능 향상이 어느 정도인지 확인해보기 위해 간단한 테스트 프로그램을 만들어보았습니다.


int main(void)
{
	Mat src[10];
	TickMeter tm;

	cout << "OpenCV version: " << CV_VERSION << endl;

	tm.start();
	for (int i = 0; i < 10; i++) {
		String filename = format("in\\test%d.jpg", i);

		src[i] = imread(filename);

		if (src[i].empty()) {
			cerr << "Image load failed!" << endl;
			return -1;
		}
	}
	tm.stop();
	cout << "JPEG load took " << tm.getTimeMilli() << "ms." << endl;

	tm.reset();
	tm.start();
	for (int i = 0; i < 10; i++) {
		String filename = format("out\\data%d.jpg", i);

		bool ret = imwrite(filename, src[i]);

		if (!ret) {
			cerr << "Image save failed!" << endl;
			return -1;
		}
	}
	tm.stop();
	cout << "JPEG save took " << tm.getTimeMilli() << "ms." << endl;

	return 0;
}

위 프로그램 소스는 ./in/ 폴더에 있는 test0.jpg ~ test9.jpg 10 장의 JPEG 영상을 불러와서 다시 ./out/ 폴더에 저장하는, 아주 간단한 소스 코드입니다. 시간 측정은 TickMeter 클래스를 사용하였구요...


위 소스 코드를 OpenCV 3.4.0 버전을 이용하여 빌드하고, 또 다시 OpenCV 3.4.2 버전을 이용하여 빌드하였습니다. OpenCV 라이브러리는 OpenCV 홈페이지에서 미리 빌드된 버전을 사용하였구요, JPEG 사진은 DSLR과 스마트폰으로 찍은 12M, 16M 사진을 사용했습니다. 측정된 시간은 아래와 같습니다.


OpenCV 3.4.2 버전을 사용했을 경우가 OpenCV 3.4.0 버전보다는 빠르긴 한데, 생각보다 훨씬 빠르지는 않네요. 위의 결과를 표로 정리하면 다음과 같습니다.


사실 이 결과는 인터넷에 나와 있는 JPEG turbo의 성능 비교와 너무 차이가 나서 뭔가 이상하다는 생각이 듭니다. 아래 링크에서는 최소 2~3배는 빨라졌는데 말이죠..

https://libjpeg-turbo.org/About/Performance

http://www.briancbecker.com/blog/2010/analysis-of-jpeg-decoding-speeds/


왜 이렇게 성능 향상이 적은지에 대해서는 더 고민을 해봐야할 것 같습니다. 뭔가 추가적인 환경 설정을 한다거나, 빌드 옵션이 뭔가 잘못되었는지는 확인을 더 해봐야겠지만, 어쨋든 기본적인 사용법으로 OpenCV 3.4.2를 사용할 때 이정도의 성능 향상이 있다... 라는 정도의 결론이네요. 뭔가 좀 아쉽네요. ^^;


Posted by kkokkal
: