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: 검출할 원의 최소, 최대 반지름
#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 |