1. 허프변환 직선검출 

 1) 2차원 영상 좌표에서 직선의 방정식을 파라미터 공간으로 변환하여 직선을 찾는 알고리즘. 

 2) 축적배열 : a,b 파라미터 스페이스 표현하는 배열로 직선 성분과 관련된 원소값을 1씩 증가시키는 배열.

   (accumulation arrray , voting algorithm)

 3) 직선의 방정식 y = ax + b 사용시 문제점 : y축과 평행한 수직선 표현 못함 -> 극좌표계 직선의 방정식을 사용

xcos(th) + ysin(th) = p

기울기 m = -(cos(th)/sin(th))

y절편 = (p/sin(th))

 

y = - (cos(th)/sin(th)) * x  +  p/sin(th)

--> xcos(th) + ysin(th) = p

p(rho) 는 원점에서  직선까지 수선의 길이.

th   는 수선의 각도 

 

4) 허프 변환 직선 검출함수

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

img: 그레이스케일 에지 영상 (보통 캐니영상을 입력영상으로 제공)

lines: 직선의 파라미터(rho, theta) 저장 할 출력 벡터 .   vector<Vec2f>.   or Mat . 그러나 보통  vector<Vec2f>형태를 넣는다.

 

rho: 축적배열에서 rho 값의 간격. 픽셀단위. (e.g  1.0 -> 1픽셀 간격)  //원점에서 직선까지 수선의 길이

-> 이 값이 커지면 속도는 증가하고 정확도는 내려감

 

theta : 축적 배열에서 theta 값의 간격. 라디안 단위. (.e.g) CV_PI/180  ->  1도 간격  // y축과 직선이 이루는 각도

 

//theta는 rho와 함께 축적 배열의 해상도를 결정하는 인자.   연속된 값으로 구현하는 rho/theta 파라미터를 축적배열로 표현.  이때 rho/ theta값 간격을 어느정도 촘촘하게 할수 있는지 설정하는 파라미터.

1보다 크거나 180도 보다 클 시 듬성듬성 rho,. theta간격이 떨어져 연산속도는 빠르지만 ,  검출하려는 직선의 정확도는 떨어진다.

rho와 theta 값을 기준 값인 1, 180도 보다 작게 설정 시 연산시간은 증가하나 직선을 찾는 정확도는 높다.

threshold: 축적 배열에서 직선으로 판단할 임계값

 

srn :  멀티스케일 허프 변환에서 rho 해상도를 나누는 값.

         srn에 양의 실수를 지정하면 rho 해상도와 rho/srn 해상도를 각각 이용하여 멀티스케일 허프 변환을 수행.

          srn과 stn이 모두 0이면 일반 허프 변환을 수행

stn: 멀티 스케일 허프변환에서 theta 해상도를 나누는 값.

**srn과 stn의 기본값을 0으로 설정 시 멀티스케일 허프변환 적용 안됨

 

min_theta: 검출할 직선의 최소 theta 값

max_theta: 검출할 직선의 최대 theta값.

 

5) 확률적 허프 변환에 의한 선분 검출함수.

void HoughLinesP(InputArray image, OutputArray lines,
double rho, double theta, int threshold, double minLineLength = 0, double maxLineGap = 0);

P: Probablity 확률의 약자

img: 그레이스케일 에지 영상  // 보통 캐니 엣지 영상

lines: 선분의 시작, 끝 좌표 (x1, y1, x2, y2) 저장할 출력 벡터. vector<Vec4i>

rho: 축적 배열에서 rho값의 해상도. 픽셀 단위. (e.g) 1.0 -> 1픽셀 간격

theta: 축적 배열에서 theta 값의 간격. 라디안 단위. (e.g) CV_PI / 180  -> 1도 간격

theshold: 축적 배열에서 직선으로 판단할 임계값

minLineLength: 검출할 선분의 최소 길이

maxLineGap: 직선으로 간주할 최대 에지 점 간격  / /두개의 선분이 끊어 져 나온 경우 어느 간격까지 선으로 허용할 것인지의 gap 값.

 

HougLlines 영상에 존재하는 직선을 찾는 함수 

HoughLinesP 영상에 존재하는 직선의 성분을 찾는함수 (직선이 어느 위치에서 시작하고 어느 위치에서 끝나는지 , 시작과 끝점 반환)

 

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

using namespace std;
using namespace cv;

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

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

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

	TickMeter tm;  // 시간 클래스
	tm.start();  //시간 측정 시작

	vector<Vec2f> lines1;
	HoughLines(src_edge, lines1, 1, CV_PI / 180, 250); // lines1 (rho값과 theta값) 이란 변수로 검출된 직선의 정보를 받음
	//rho 1: rho값의 해상도 1픽셀 단위로 rho값 변경   theta 1: theta 해상도  1도 간격으로 직선의 각도를 변경하면서 검출
	//축적배열에서 그 누적된 값이 250 보다 큰 경우에 한해서 직선으로 검출

	//threshold값으로  최적의 직선들 검출 하는 법 알아야함.

	tm.stop();  //시간측정끝 (hough lines 함수 사용에 걸린 시간)

	cout << "HoughLines(): " << tm.getTimeMilli() << "ms." << endl;

	Mat dst1;
	cvtColor(src_edge, dst1, COLOR_GRAY2BGR);

	//lines1 로 검출 된 직선들 의 rho와 theta값 이용해서 직선을 그리는 시퀀스
	for (size_t i = 0; i < lines1.size(); i++) {
		float r = lines1[i][0], t = lines1[i][1];
		double cos_t = cos(t), sin_t = sin(t);
		double x0 = r * cos_t, y0 = r * sin_t;
		double alpha = 1000;  // p1, p2 값을 구하기 위한 상수값.

		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(dst1, pt1, pt2, Scalar(0, 0, 255), 1, LINE_AA);
	}

	tm.reset();
	tm.start();

	//직선의 시작과 끝점 찾는 함수 
	vector<Vec4i> lines2;
	HoughLinesP(src_edge, lines2, 1, CV_PI / 180, 160, 50, 5);
	//  해상도값: rho 1 ,  theta 1도      160: 축적배열에서의 threshold 값.    50: 최소 직선 성분    5: 직선성분이 5 이내로 끊어질시 하나의 직선으로 간주              
	tm.stop();

	cout << "HoughLinesP(): " << tm.getTimeMilli() << "ms." << endl;

	Mat dst2;
	cvtColor(src_edge, dst2, COLOR_GRAY2BGR);

	for (size_t i = 0; i < lines2.size(); i++) {
		Vec4i l = lines2[i];
		//시작점에서 끝점까지 빨강으로 직선 그리기
		line(dst2, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 2, LINE_AA);
	}

	imshow("src", src);
	imshow("dst1", dst1); // 직선을 찾음( houghlines)
	imshow("dst2", dst2);  //직선 성분 찾음 (houghlinesP)
	waitKey();
}

2.허프변환 원검출

1) 허프 변환을 응용해 원을 검출 가능

 

2) 속도 향상 위해 Hough gradient method 사용

 - 입력 영상과 동일한 2차원 평면 공간에서 축적 영상 생성

 - 에지 픽셀에서 그래디언트 계산

 - 그래디언트 방향에 따라 직선을 그리면서 값 누적.

 - 원의 중심을 먼저 찾고 적절한 반지름 검출

 - 단점: 여러 개의 동심원을 검출 못함(가장 작은 원 하나만 검출)

 

void HoughCircles(InputArray image, OutputArray circles, int method, double dp,
double minDist, double param1 = 100, double param2 = 100, int minRadius = 0, int maxRadius = 0);

image: 입력 영상(에지 영상 아닌 일반영상)

circles: (cx, cy, r) 정보. Mat(CV_32FC3) 또는 vector<Vec3f>

method : 원 검출 방법을 지정하며,  HOUGH_GRADIENT 또는 HOUGH_GRADIENT_ALT 지정. (HOUGH_GRADIENT 성능보다 ALT 성능이 더 좋음 . 4.3버전부터 지원)

 

dp: 입력 영상과 축적 배열의 크기 비율.  1이면 동일 크기, 2이면 입력 영상의 가로와 세로크기의 1/2 크기의 축적 배열 사용

 

minDist: 검출된 원 중심절들의 최소 거리

param1: Canny 에지 검출기의 높은 임계값. HOUGH_GRADIENT_ALT (300~ 400 임계값 추천)  방법은 Sharr 필터를 사용한다. 이에,

Sobel 필터를 사용하는 HOUGH_GRADIENT (100~ 200 임계값 추천)  방법 보다 큰 임계값을 지정함

// 낮은 임계값은 높은 임계값의 1/2 로 자동 지정됨

param2: HOUGH_GRADIENT 방법에서는 축적 배열 임계값.

HOUGH_GRADIENT_ALT 방법에선 원의 "prefectness"값 (1에 가까울 수록  완벽한 원)   0.8~1.0 사이에서 지정하는게 좋음

 

min/max Radius: 검출할 원의 최소, 최대 반지름

coins.jpg
0.16MB
dial.jpg
0.04MB

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

using namespace std;
using namespace cv;

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

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

	Mat dst1, dst2;
	cvtColor(src, dst1, COLOR_GRAY2BGR);
	cvtColor(src, dst2, COLOR_GRAY2BGR);

	TickMeter tm;

	// HOUGH_GRADIENT

	Mat blr;
	GaussianBlur(src, blr, Size(), 1.0);
	// blr 전처리 한 이유: hough circle 함수가 각 엣지 픽셀에 민감. 원이 있음에도 미세한 차이로 원 검출 어려울 수 있음. 
	// blur 수행시 주어진 이미지가 부드러워짐에 따라 원하는 원의 엣지 픽셀을 검출 할 수 있음
	tm.start();

	vector<Vec3f> circles1; // float 값 3개, 정보: x, y , r   
	HoughCircles(blr, circles1, HOUGH_GRADIENT, 1, 10, 150, 45, 10, 50);
	//minDist 10 으로 10 이상 떨어진 원들 검출.
	// //150 : 캐니엣지 사용시 스레드홀드가 큰 값이 150  min 값은 75 로 자동 설정됨
	//30: 축적배열에서 30보다 큰 좌표 검출
	// 10, 50 : 검출할 원의 최소/최대 반지름 크기.    모든 원 검출 하고자 할 경우 마지막 2개 인자 입력하지 말기
	tm.stop();
	cout << "HOUGH_GRADIENT:     " << tm.getTimeMilli() << "ms." << endl;

	for (size_t i = 0; i < circles1.size(); i++) { //원그리기
		Vec3i c = circles1[i];
		circle(dst1, Point(c[0], c[1]), c[2], Scalar(0, 0, 255), 2, LINE_AA);
	}

	// HOUGH_GRADIENT_ALT

	tm.reset();
	tm.start();

	vector<Vec3f> circles2;
	HoughCircles(src, circles2, HOUGH_GRADIENT_ALT, 1.5, 10, 300, 0.9, 10, 50);
	//blur 영상 입력 할 필요 없음
	// 축적배열 1.5 설정, 잡음을 제거하는게 목적으로 축적배열 1.5~2.0 지정을 권장하며, 이에 따라 원을 잘 검출 할 수 있음

	//샤르 필터를 사용하기에 임계값을 150보다 큰 값을 지정해야 ㄹ함수내부에서 그레디언트 계산이 정확히 계산됨
	// minRadius 1에 가까울수록 원에 가까운 걸 검출
	tm.stop();
	cout << "HOUGH_GRADIENT_ALT: " << tm.getTimeMilli() << "ms." << endl;

	for (size_t i = 0; i < circles2.size(); i++) { // 원 그리기
		Vec3i c = circles2[i];  // circle 함수에 들어갈 원소가 정수형이라 circle에 에러 방지 목적으로 타입을 float -> int 로 변환시킴
		circle(dst2, Point(c[0], c[1]), c[2], Scalar(0, 0, 255), 2, LINE_AA);
	}

	imshow("src", src);
	imshow("dst1", dst1);
	imshow("dst2", dst2);
	waitKey();
}

dst: HoughGradient

dst2: HoughGradient_ALT  : 연산속도도 빠르고 좀 더 검출이 잘 됨

 

 

 

HoughCircles(blr, circles1, HOUGH_GRADIENT, 1, 10, 150, 30, 10, 50);  // 중첩되는 문제 있어서 th 조절

-->HoughCircles(blr, circles1, HOUGH_GRADIENT, 1, 10, 150, 45, 10, 50);  로 변경하여 설정

 

 

참고할 만한 사이트

https://docs.opencv.org/3.4/d9/db0/tutorial_hough_lines.html

'프로그래머스 > OPENCV' 카테고리의 다른 글

컬러영상 처리 기초 (이진화 게시글과 swap됨 )  (0) 2022.12.08
코너 검출 기법  (2) 2022.12.08
캐니 에지 검출기  (0) 2022.12.07
에지검출과 소벨필터(2)  (0) 2022.12.07
에지검출과 소벨필터(1)  (0) 2022.12.07

+ Recent posts