2013년 1월 26일 토요일

OpenCV #7-3 Example (선을 점집합으로 맞추기)

 - 영상 내의 선을 감지하는 것은 매우 중요하며, 선의 위치와 방향에 대한 정확한 추정치를 얻는 것도 중요.
 - 점집합을 가장 적합하게 맞춘 선을 찾는 방법을 알아봄.

 - 영상 내의 직선을 따라 정렬한 것으로 보이는 점을 찾는 일을 먼저 한다.
 - 이전 절에서 감지했던 선 중 하나를 사용하자.
 - cv::HoughLinesP를 사용해 감지한 선에 lines 함수를 호출한 std::vector를 포함한다고 가정.

  • Example
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#define PI 3.1415926

class LineFinder {
private:
cv::Mat img; // 원 영상
std::vector<cv::Vec4i> lines; // 선을 감지하기 위한 마지막 점을 포함한 벡터
double deltaRho;
double deltaTheta; // 누산기 해상도 파라미터
int minVote; // 선을 고려하기 전에 받아야 하는 최소 투표 개수
double minLength; // 선에 대한 최소 길이
double maxGap; // 선에 따른 최대 허용 간격

public:
LineFinder() : deltaRho(1), deltaTheta(PI/180), minVote(10), minLength(0.), maxGap(0.) {}
// 기본 누적 해상도는 1각도 1화소 
// 간격이 없고 최소 길이도 없음

// 해당 세터 메소드들

// 누적기에 해상도 설정
void setAccResolution(double dRho, double dTheta) {
deltaRho= dRho;
deltaTheta= dTheta;
}

// 투표 최소 개수 설정
void setMinVote(int minv) {
minVote= minv;
}

// 선 길이와 간격 설정
void setLineLengthAndGap(double length, double gap) {
minLength= length;
maxGap= gap;
}

// 허프 선 세그먼트 감지를 수행하는 메소드
// 확률적 허프 변환 적용
std::vector<cv::Vec4i> findLines(cv::Mat& binary) {
lines.clear();
cv::HoughLinesP(binary,lines,deltaRho,deltaTheta,minVote, minLength, maxGap);
return lines;
} // cv::Vec4i 벡터를 반환하고, 감지된 각 세그먼트의 시작과 마지막 점 좌표를 포함.

// 위 메소드에서 감지한 선을 다음 메소드를 사용해서 그림
// 영상에서 감지된 선을 그리기
void drawDetectedLines(cv::Mat &image, cv::Scalar color=cv::Scalar(255,255,255)) {

// 선 그리기
std::vector<cv::Vec4i>::const_iterator it2= lines.begin();

while (it2!=lines.end()) {
cv::Point pt1((*it2)[0],(*it2)[1]);
cv::Point pt2((*it2)[2],(*it2)[3]);
cv::line( image, pt1, pt2, color);
++it2;
}
}
};

int main()
{
cv::Mat image = cv::imread("road.jpg",0);

// 캐니 알고리즘 적용
cv::Mat contours;
cv::Canny(image,contours,125,350);

LineFinder ld;

// 확률적 허프변환 파라미터 설정하기
ld.setLineLengthAndGap(100,20);
ld.setMinVote(80);

std::vector<cv::Vec4i> li= ld.findLines(contours);

// 선의 첫 번째에 속한 것으로 보이는 점 집합을 추출하기 위해 다음과 같이 진행
// 검은 영상에 하얀 선을 그린 후 그 선을 감지하기 위해 사용하는 외곽선의 캐니 영상으로 함께 교차
int n=0; // 0번째 선 선택
cv::Mat oneline(image.size(),CV_8U,cv::Scalar(0)); // 검은 영상
cv::line(oneline, // 하얀 선
cv::Point(li[n][0],li[n][1]),
cv::Point(li[n][2],li[n][3]),
cv::Scalar(255),
5); //특정 두께 5를 갖는 선을 그림
cv::bitwise_and(contours, oneline, oneline);
// 외곽선과 하얀 선 간의 AND
// 결과는 지정한 선과 관련된 점만 포함

// 검은 영상과 하얀 선을 반전시킴
cv::Mat onelineInv;
cv::threshold(oneline,onelineInv,128,255,cv::THRESH_BINARY_INV);
cv::namedWindow("One line");
cv::imshow("One line", onelineInv);

// 이중 반복문으로 집합 내에 있는 점의 좌표를 cv::Points의 std::vector에 삽입
std::vector<cv::Point> points;
// 모든 점 위치를 얻기 위환 화소 조회
for( int y = 0; y < oneline.rows; y++ ) { // y행
uchar* rowPtr = oneline.ptr<uchar>(y);
for( int x = 0; x < oneline.cols; x++ ) { // x열
   // 외곽선에 있다면
if (rowPtr[x]) {
points.push_back(cv::Point(x,y));
}
}
    }

// cv::fitLine을 호출해 가장 적합한 선을 찾음
cv::Vec4f line;
cv::fitLine(cv::Mat(points),line,
CV_DIST_L2, // 거리 유형
0, // L2 거리를 사용하지 않음
0.01,0.01); // 정확도
// 단위 방향 벡터(cv::Vec4f의 첫 두개 값), 
// 선에 놓인 한 점의 좌표(cv::Vec4f의 마지막 두 값) 형태인 선 방정식의 파라미터를 제공
// 마지막 두 파라미터는 선 파라미터에 대한 요구 정확도를 지정
// 함수에서 요구 하는 std::vector 내에 포함된 입력 점은 cv::Mat로 전달

// 선 방정식은 일부 속성계산에 사용
// 올바른 선을 계산하는지 확인하기 위해 영상에 예상 선을 그림
// 200화소 길이와 3화소 두께를 갖는 임의의 검은 세그먼트를 그림
int x0= line[2]; // 선에 놓은 한 점
int y0= line[3];
int x1= x0-200*line[0]; // 200 길이를 갖는 벡터 추가
int y1= y0-200*line[1]; // 단위 벡터 사용
image= cv::imread("road.jpg",0);
cv::line(image,cv::Point(x0,y0),cv::Point(x1,y1),cv::Scalar(0),3);
cv::namedWindow("Estimated line");
cv::imshow("Estimated line",image);

cv::waitKey(0);

return 0;
}


  • Result


  • 예제 분석
 - 선을 점집합으로 맞추는 것은 수학의 고전적인 문제.
 - OpenCV 구현은 각 점에서 선 거리의 합을 최소화해 진행.
 - 여러 거리 함수가 제안됬고, 가장 빠른 옵션은 CV_DIST_L2로 지정해 유클리디안 거리를 사용.
 - 이 선택은 표준 최소 제곱 선 맞추기에 해당.
 - 영외(점이 선에 속하지 않음)에 점집합을 포함했을 때 점에 더 적은 영향을 주는 다른 거리 함수를 선택할 수 있움.
 - 최소화는 M 추정 기술에 기반을 두며, 반복적으로 선으로부터 거리에 반비례하는 가중치와 함께 가중 최소 제곱 문제를 해결.

 - 함수를 사용하면 3D 점집합에 대한 선 맞춤이 가능.
 - 입력은 이번 경우에 cv::Point3i 또는 cv::point3f의 집합이고, 결과는 std::Vec6f 이다.


  • 부연 설명
 - cv::fitEllipse 함수는 2D 점집합에 대한 타원을 맞춤.
 - 내부에 타원이 들어간 회전된 사각형(cv::RotatedRect 인스턴스)을 반환.
 - 이번 경우에는 다음과 같이 쓸 수 있다.

cv::RotatedRect rrect = cv::fitEllipse(cv::Mat(points));
cv::ellipse(image, rrect, cv::Scalar(0));

 - cv::ellipse 함수는 계산된 타원을 그리기 위해 사용하는 함수 중 하나.

  • 참고문헌 : OpenCV 2 Computer Vision Application Programming Cookbook