[w6d5]허프 변환 검출

24618 단어 opencvopencv

허프 변환 직선 검출

image space: (x,y)
parameter space: (a,b)
먼저 직선을 지나는 점을 검출하기에 위해서는 공간을 정의해야한다. y=ax+b 에서는 (x,y)를 인자로 사용하고 a와 b는 기울기와 y절편으로 직선의 형태를 결정짓는다. 이와 비교해서 b=-ax+y에서는 (a,b)를 인자로 사용하고 (x,y)는 직선의 형태를 결정짓는 인자로 사용한다먼저 직선을 지나는 점을 검출하기에 위해서는 공간을 정의해야한다. y=ax+b 에서는 (x,y)를 인자로 사용하고 a와 b는 기울기와 y절편으로 직선의 형태를 결정짓는다. 이와 비교해서 b=-ax+y에서는 (a,b)를 인자로 사용하고 (x,y)는 직선의 형태를 결정짓는 인자로 사용한다출.
(x,y) 공간에서 두 점 (x1,y1),(x2,y2)을 지나는 직선을 가y=a1 x+b1라고 한다면,
(a,b) 공간에서 b=-x1 a+y, b=-x2 a +y 직선은 (a1,b1)에서 만나게 된다는 것을 알 수 있다.

다만 (a,b) 공간을 사용하는 경우에는 수직선들을 포함할 수 없다는 단점이 있으며, 많은 방정식을 표현하기 위해서는 기울기 값이 (-inf,inf)의 범위로 매우 넓은 범위이다. 이를 개선하기 위해 Hesse normal form을 이용할 수 있으며 식은 아래와 같다.


Hesse normal form을 이용하여 한 점을 지나는 여러 직선들을 표현하는 방식은 아래의 이미지에서 확인할 수 있다.

직선 검출을 위해서 허프 변환을 적용하면, rho, theta 공간에서 삼각함수와 유사한 형태로 나오게 된다. 아래는 검출 시 결과를 보여주는 이미지이다. 전체가 0으로 설정된 (rho, theta) 행렬을 먼저 만들고, 각 점을 지나가는 곡선이 지나가는 픽셀 값을 1씩 높여 주어 만들 수 있다. 밝기가 밝은 점은 많은 곡선이 지나간다는 의미이며, 즉 많은 점들을 공통으로 지나는 직선을 의미하게 된다.

해당 이미지를 더 자세하게 확인하기 위해 canny를 적용 후 각 점을 지나는 직선을 Hesse normal form을 사용한 공간에서 여러 색으로 표현하면 아래와 같은 모양이 될 것이다.

한 직선 위의 수많은 점들은 오른쪽 그림의 여러 색의 곡선으로 표현된다. 각 곡선은 그 점을 지나는 직선을 표현하며 색들이 교차하는 지점이 모든 여러 곡선들, 즉 여러 점들을 지나는 공통의 직선을 나타내게 된다. 위의 그림에서는 두 개의 지점에서 생기며 직선이 두개 검출된 것으로 볼 수 있다.

void cv::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 
	) 		

OpenCV에서는 직선 검출에 두가지 허프 변환 직선 검출 함수가 존재한다. Input image는 gray scale edge image를 넣어주어야 하며, cv::Canny 함수를 적용한 뒤에 사용하거나 Sobel 필터를 이용한 결과를 이용할 수 있다. cv::HoughLines 함수에서는 lines에 직선의 파라미터 (rho, theta)를 저장할 출력벡터(std::vector<cv::Vec2f>)를 지정하게 된다. rho, theta는 해당 공간에서 간격을 의미하며, threshold은 축적 배열에서 직선으로 판단할 임계값을 나타낸다. min_theta와 max_theta를 이용해 각도 영역을 제한할 수 있다.

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

cv::HoughLinesP 함수는 각도 제한 부분이 없어진 대신, 결과 값을 직선의 두 끝 지점으로 받아 이미지를 표현하기에 편리하다. lines는 std::vector<cv::Vec4i>를 인자로 받으며 lines[0],lines[1],lines[2],lines[3]은 각각 x1,y1,x2,y2에 해당한다. minLineLength는 최소 직선 길이를, maxLineGap은 직선이 어느 정도 간격을 두고 떨어져 있어도 하나의 직선으로 볼 것인지를 결정한다.

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

int hough_thresh=160,hough_minlinelength=50,hough_maxlinegap=50;
double hough_maxlinegap_double;

void on_change(int, void*);

int main()
{
    cv::VideoCapture cap("../resources/test_video.mp4");

    if (!cap.isOpened()){
        std::cerr << "video load failed!" << std::endl;
        return -1;
    }

    //trackbar
    cv::namedWindow("roi_hough");
    cv::createTrackbar("threshold: ","roi_hough",&hough_thresh,1000);
    cv::createTrackbar("min_line_length: ","roi_hough",&hough_minlinelength,500);
    cv::createTrackbar("max line gap: ","roi_hough",&hough_maxlinegap,100,on_change);

    cv::Mat frame,frame_canny,roi_canny,roi_hough;
    std::vector<cv::Vec4i> lines;
    cv::Mat mask(cap.get(cv::CAP_PROP_FRAME_HEIGHT),cap.get(cv::CAP_PROP_FRAME_WIDTH),CV_8UC1,cv::Scalar(0));
    cv::Mat frame_hough = mask.clone();
    roi_hough = mask.clone();

    std::vector<cv::Point> mask_pts(5);

	mask_pts[0] = cv::Point(400, 340);
    mask_pts[1] = cv::Point(680, 340);
	mask_pts[2] = cv::Point(1200, 720);
    mask_pts[3] = cv::Point(0, 720);
    mask_pts[4] = cv::Point(0, 480);

    cv::fillPoly(mask,mask_pts,cv::Scalar(255));
    
    while(true){
        cap >> frame;

        if (frame.empty()){
            std::cout << "empty frame" << std::endl;
            break;
        }

        cv::Canny(frame,frame_canny,50,150);
        frame_canny.copyTo(roi_canny,mask);
        cv::HoughLinesP(frame_canny,lines,0.5,CV_PI/180,hough_thresh,hough_minlinelength,hough_maxlinegap_double);
        frame_hough.setTo(0);
        roi_hough.setTo(0);
        for (auto& line : lines){
            cv::line(frame_hough,cv::Point(line[0],line[1]),cv::Point(line[2],line[3]),cv::Scalar(255),2);
        }
        frame_hough.copyTo(roi_hough,mask);

        cv::imshow("frame",frame);
        cv::imshow("roi_canny",roi_canny);
        cv::imshow("roi_hough",roi_hough);
        
        if(cv::waitKey(30)==27) break;
    }
    cap.release();
    cv::destroyAllWindows();

    return 0;
}

void on_change(int, void*){
    hough_maxlinegap_double = 0.1*hough_maxlinegap;
    // TrackBar는 정수단위만 되는데, 조금 더 세부적으로 조정하기 위해 0.1 단위로 바꿔줌.
}


허프 변환 원 검출

원을 검출할 수 있는데, 2차원 공간에서 그래디언트 방향으로 직선을 그려 축적 이미지를 만들게 되면, 원의 중심에서는 값이 크게 나타나게 된다. 해당 중심을 기준으로 반지름을 늘려가면서 적절한 반지름을 검출하는 방식으로 원을 검출할 수 있다.

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

OpenCV에서는 cv::HoughCircles를 이용하여 원을 검출할 수 있다. 입력영상을 edge 영상이 아닌 일반 영상으로 주어야 하는 점을 주의해야한다. method는 HOUGH_GRADIENT와 HOUGH_GRADIENT_ALT가 있는데, 두 방식에 따라 param이 사용되는 방식이 다르다. dp는 입력영상과 축적 배열의 크기 비율로 높은 값을 설정하면 축적 배열의 크기가 줄어들어 연산양이 줄어든다. HOUGH_GRADIENT 방식에서 param1은 Canny edge 검출기의 높은 임계값, Param2는 축적 배열의 임계값이다. HOUGH_GRADIENT_ALT 방식에서는 Scharr필터를 이용하기 때문에 Param1 값을 더 높게 설정해주어야하며 Param2 값은 원의 perfectness(1.0에 가까운 값)를 의미한다.

https://ko.wikipedia.org/wiki/%ED%97%88%ED%94%84_%EB%B3%80%ED%99%98
https://en.wikipedia.org/wiki/Hough_transform
https://en.wikipedia.org/wiki/Circle_Hough_Transform

좋은 웹페이지 즐겨찾기