Test on the correspondences of feature points
특징점 대응 시험
교점의 cross ratio 값을 구하고, 그 값과 가장 가까운 cross ratio 값을 가지는 점을 패턴에서 찾아 대응시킨다.
Try #1. one-to-all
입력 영상에서 검출한 직선들로부터 생기는 각 교점에서 수평 방향으로 다음 세 개의 교점, 수직 방향으로 다음 세 개의 교점을
지나는 직선에 대한 cross ratio (x,y)값을
구한다. 이상적으로, 1에서 구한 cross ratio 값과 일치하는 cross ratio 값을 가지는 패턴의 격자점이 입력 영상의 해당
교차점과 실제로 대응하는 점이라고 볼 수 있다.
직선 검출에 오차나 오류가 적을 경우, 아래 테스트 결과에서 보듯 입력 영상의 교차점에 대해 실제 패턴의 직선을 1대 1로
즉각적으로 찾는다. 즉, 입력 영상의 한 점에서의 수평 방향 cross ratio 값에 대해 패턴의 모든 수평선들의 cross
ratio 값을 일일이 대조하여 가장 근접한 값을 가지는 직선을 대응시키는 방식이다. (아래 오른쪽 사진은 같은 방식으로 수직
방향 cross ratio 값을 가지고 대응되는 직선을 찾는 경우임.) (point-to-line)
수평선 위의 점들에 대한 cross ratio 값만 비교한 결과
수선 위의 점들에 대한 cross ratio 값만 비교한 결과
입력 영상에서 하나의 교차점의 x방향 cross ratio 값과 같은 cross ratio 값을 가지는 세로선을
실제 패턴에서 찾고, y방향 cross
ratio 값에 대해서 가로선을 찾으면, 패턴 위에 그 세롯선과 가로선이 교차하는 점 하나가 나온다. 입력 이미지 상의 한 점에 대해 패턴의 모든 직선을 (가로선의 개수+세로선의 개수) 번 비교하여 대응점을 연결하는 것이다. (point-to-point)
(패턴 인식이 성공적인 경우)
(잘못된 대응점 연결이 발생한 경우)
source code:
void matchXY ( vector<CvPoint2D32f> &p, vector<CvPoint2D32f> &CRimage, int numIx, int numIy, vector<CvPoint3D32f> &world, vector<CvPoint2D32f> &CRworld, int numPx, int numPy, IplImage* iplMatch, CvPoint2D32f scale )
{
for( int i = 0; i < numIx; i++ ) // points in x-direction on the input image
{
// check if x-component of the point is valid
if( -1 == CRimage[i*numIy+0].x )
{
cout << endl << "could not make matching in x-direction" << endl;
continue;
}
// CvScalar generateRandomColor(unsigned char thR, unsigned char thG, unsigned char thB) defined in "matching.h"
CvScalar colorMatch = generateRandomColor(50,50,50);
for( int j = 0; j < numIy; j++ ) // points in y-direction on the input image
{
// check if y-component of the point is valid
if( -1 == CRimage[i*numIy+j].y )
{
cout << endl << "could not make matching in y-direction" << endl;
continue;
}
// to find the x-index of the corresponding point
int indexPx = 0;
float errX_min = fabs( CRimage[i*numIy+j].x- CRworld[indexPx*numPy+0].x );
// search points in x-direction on the real pattern
for( int wx = 0; wx < numPx; wx++ )
{
float errX = CRimage[i*numIy+j].x - CRworld[wx*numPy+0].x;
if ( fabs(errX) < errX_min )
{
errX_min = fabs(errX);
indexPx = wx;
}
}
// to find the y-index of the corresponding point
int indexPy = 0;
float errY_min = fabs( CRimage[i*numIy+j].y - CRworld[0*numPy+indexPy].y );
// search points in y-direction on the real pattern
for( int wy = 0; wy < numPy; wy++ )
{
float errY = CRimage[i*numIy+j].y - CRworld[0*numPy+wy].y;
if ( fabs(errY) < errY_min )
{
errY_min = fabs(errY);
indexPy = wy;
}
}
// cout << endl << i << ", " << j << " point in the input frame is matched with "
// << indexPy << "-th point in the real pattern" << endl;
// draw the line to connect "world" point and "image" point
CvPoint pointImage = cvPoint(cvRound(p[i*numIy+j].x), cvRound(IMG_HEIGHT + p[i*numIy+j].y));
CvPoint pointPattern = cvPoint(cvRound(world[indexPx*numPy+indexPy].x*scale.x), cvRound(world[indexPx*numPy+indexPy].y*scale.y));
그러므로 현재는 (1) 입력 영상에서 한 직선 위에 있는 것으로 추산된 일련의 점들에서의 cross ratio 값들의 수치적
경향을 고려하지 않고 있으며, (2) 입력 영상에 실제 패턴의 어느 부분(위치나 범위)이 잡힌 것인지를 판단하지 않고 무조건 전체
패턴의 모든 격자점들에 대해서 cross ratio 값을 비교하고 있다.
swPark_2000rti 439쪽: In the initial identification process, we first extract and identify vertical and horizontal lines of the pattern by comparing their cross-ratios, and then we compute the intersections of the lines. Theoretically with this method, we can identify feature points in every frame automatically, but several situations cause problems in the real experiments.
박승우_1999전자공학회지 94쪽: 초기 인식과정에서는 패턴 상의 교점을 인식하기 위해 패턴의 제작과정에서 설명한 것처럼 영상에서 구해진 가로선과 세로선의 Cross-ratio를 패턴의 가로선과 셀로선이 가지는 Cross-ratio와 비교함으로써 몇번째 선인지를 인식하게 된다. 이러한 방법을 이용해 영상으로부터 자동으로 특징점을 찾고 인식할 수 있지만, 실제 적용 상에서는 몇 가지 제한점이 따르게 된다.
0. NMS (Non Maximum Suppression)을 적용한 Hough transform에 의한 Line 찾기
OpenCV 라이브러리의 HoughLines2() 함수는 전에 기술한 바( http://leeway.tistory.com/801 )와 같이 실제 패턴에서는 하나의 직선 위에 놓인 점들에 대해 이미지 프레임에서 검출된 edges을 가지고 여러 개의 직선을 찾는 결과를 보인다. 이는 HoughLines2()
함수가 출력하는, 직선을 정의하는 두 파라미터 rho와 theta에 대해 ( x*cos(theta) + y*sin(theta) = rho ) 계산된 값들이 서로 비슷하게 나오는 경우에 최적값을 선별하는 과정을 거치지 않고 모든 값들을 그대로 내보내기 때문이다. 그래서 OpenCV의 이 함수를 이용하지 않고, 따로 Hough transform을 이용하여 선을 찾는 함수를 만들되 여기에 NMS (Non Maximum Suppression)를 적용하도록 해 보았다. 하지만 이 함수를 실시간 비디오 카메라 입력에 대해 매 프레임마다 실행시키면 속도가 매우 느려져 쓸 수 없었다. 그래서, 속도 면에서 월등한 성능을 보이는 OpenCV의 HoughLines2()
함수를 그대로 따 오고 대신 여기에 NMS 부분을 추가하여 수정한 함수를 매 입력 프레임마다 호출하는 방법을 택하였고, 실시간 처리가 가능해졌다. (->소스코드)
산출된 수직선들을 이미지 프레임의 왼쪽에서부터 오른쪽으로 나타난 순서대로 번호를 매기고 (아래 그림의 붉은색 번호), 수평선들을 위로부터 아래로 나타난 순서대로 번호를 매긴다 (아래 그림의 푸른색 번호). 이 과정에서 수직선의 경우 x절편, 수평선의 경우 y절편의 값을 기준으로 하여 계산하였다.
아래 코드에서 "line.x0"가 "line" 직선의 x절편임
// rearrange lines from left to right
void indexLinesY ( CvSeq* lines, IplImage* image )
{
// retain the values of "rho" & "theta" of found lines
int numLines = lines->total;
// line_param line[numLines]; 이렇게 하면 나중에 이 변수를 밖으로 빼낼 때 (컴파일 에러는 안 나지만) 문제가 됨.
line_param *line = new line_param[numLines];
for( int n = 0; n < numLines; n++ )
{
float* newline = (float*)cvGetSeqElem(lines,n);
line[n].rho = newline[0];
line[n].theta = newline[1];
}
// rearrange "line" array in geometrical order
float temp_rho, temp_theta;
for( int n = 0; n < numLines-1; n++ )
{
for ( int k = n+1; k < numLines; k++ )
{
float x0_here = line[n].rho / cos(line[n].theta);
float x0_next = line[k].rho / cos(line[k].theta);
if( x0_here > x0_next ) {
temp_rho = line[n].rho; temp_theta = line[n].theta;
line[n].rho = line[k].rho; line[n].theta = line[k].theta;
line[k].rho = temp_rho; line[k].theta = temp_theta;
}
}
}
// calculate the other parameters of the rearranged lines
for( int n = 0; n < numLines; n++ )
{
line[n].a = cos(line[n].theta);
line[n].b = sin(line[n].theta);
line[n].x0 = line[n].rho / line[n].a;
line[n].y0 = line[n].rho / line[n].b;
void indexLinesY( CvSeq* lines, IplImage* image ) 함수를 line_param*
indexLinesY( CvSeq* lines, IplImage* image )라고 바꾸어 structure로 선언한
line_param 형태의 배열을 출력하도록 하고, 이 출력값을 교점을 구하는 함수의 입력으로 하면
line_param
line[numLines];
이렇게 함수 안에서 선언했던 부분이 함수 밖으로 출력되어 다른 함수의 입력이 될 때 입력값이
제대로 들어가지 않는다. 다음과 같이 바꾸어 주어야 함.
이미지 프레임에서 찾은 수평선들을 보면 제일 위쪽의 직선이 0번이 아니라 4번부터 순번이 매겨져 있다. 프레임 바깥에 (위쪽에) 세 개의 직선이 더 있다는 뜻인데...
수직성 상의 edges 검출 영상
수평선 상의 edges 검출 영상
수직선들을 왼쪽부터 오른쪽으로, 수평선들을 위에서 아래로 정열한 결과
왼쪽 두 개는
line detection에 입력으로 쓰인 영상이고, 마지막 것은 이로부터 순서대로 정열한 직선을 규정하는 매개변수 출력값이다. 0번부터 3번 수평선의 y절편 값이 음수로 나타나고 있다.
2. 교점의 순서 매기기
격자 무늬의 직선들의 교점(intersections)을 과정1에서 계산한 직선의 순번을 이용하여 indexing한다. 빨간 세로선 0번과 파란 가로선 0번의 교점은 00번, 이런 식으로.
// index intersection points of lines in X and Y
CvPoint* indexIntersections ( line_param* lineX, line_param* lineY, int numLinesX, int numLinesY, IplImage* image )
// find intersections of lines, "linesX" & "linesY", and draw them in "image"
{
int numPoints = (numLinesX+1) * (numLinesY+1);
CvPoint *p = new CvPoint[numPoints]; // the intersection point of lineX[i] and lineY[j]
char txt[100]; // text to represent the index number of an intersection
입력 영상 input을 단일 채널 temp로 바꾸어 1차 DoG 필터링을 하여 검출된 edges를 양 방향 세기 비교와 NMS를 통해 수평 방향과 수직 방향으로 나눈 영상 detected edges를 입력으로 하여 Hough transform에 NMS를 적용하여 line detection을 한 결과를 input 창에 그리고, 이미지 프레임 좌표를 기준으로 검출된 직선들에 순서를 매겨 이로부터 교차점의 위치와 순번을 계산하여 input 창에 표시한다.
현재 상태의 문제점: (1) 패턴과 카메라 모두 정지하여 입력 영상(상좌)이 고정된 경우에, DoG 필터링한 결과(중)는 비교적 안정적이지만 수평, 수직 방향 세기 비교와 NMS를 통해 각 방향에 대해 뽑은 edges를 표시한 영상(하)은 프레임이 들어올 때마다 변화가 있다. 그래서 이 두 영상을 입력으로 하여 직선 찾기를 한 결과(상좌 빨간색 선들)와 이로부터 계산한 교차점들의 위치 및 순번(상좌 연두색 동그라미와 하늘색 숫자)도 불안정하다. (2) 또한 패턴과의 거리에 대해 카메라 렌즈의 초점이 맞지 않으면 결과가 좋지 않다.
/* Test: feature points identification in implementing a virtual studio
1) grid pattern design with cross ratios
2) lines detection by Hough transform with Non Maximum Suppression,
modifying cvHoughLines2() function in OpenCV library
#include <OpenCV/OpenCV.h> // framework on Mac
//#include <cv.h>
//#include <highgui.h>
//#include <cxmisc.h>
#include <iostream>
#include <vector>
using namespace std;
//#include "nms.h" // Non Maximum Suppression to extract vertical and horizontal edges separately
//#include "nmshough.h" // Hough transform with Non Maximum Suppression to detect lines
struct line_param // structure to contain parameters to define a line
{
// eqn of a line: a*x + b*y = rho, when a = cos(theta) & b = sin(theta)
float rho, theta;
float a, b;
float x0, y0; // x-intercept, y-intercepts
};
// rearrange vertical lines from left to right
void indexLinesY (vector<line_param>& line, CvSeq* lines, IplImage* image )
{
int numLines = lines->total; // total number of detected lines
line.resize(numLines); // define the size of "line" vector as "numLines"
char txt[100]; // text to represent the index number of an ordered line
// get "rho" and "theta" values of lines detected by Hough transform and NMS
for( int n = 0; n < numLines; n++ )
{
float* newline = (float*)cvGetSeqElem(lines,n);
line[n].rho = newline[0];
line[n].theta = newline[1];
}
// rearrange "line" array in geometrical order, that is, by values of x-intercept in the image frame coordinate
float temp_rho, temp_theta;
for( int n = 0; n < numLines-1; n++ )
{
for ( int k = n+1; k < numLines; k++ )
{
float x0_here = line[n].rho / cos(line[n].theta);
float x0_next = line[k].rho / cos(line[k].theta);
// rearrange horizontal lines from up to bottom
void indexLinesX (vector<line_param>& line, CvSeq* lines, IplImage* image )
{
int numLines = lines->total; // total number of detected lines
line.resize(numLines); // define the size of "line" vector as "numLines"
char txt[100]; // text to represent the index number of an ordered line
// get "rho" and "theta" values of lines detected by Hough transform and NMS
for( int n = 0; n < numLines; n++ )
{
float* newline = (float*)cvGetSeqElem(lines,n);
line[n].rho = newline[0];
line[n].theta = newline[1];
}
// rearrange "line" array in geometrical order, that is, by values of y-intercept in the image frame coordinate
float temp_rho, temp_theta;
for( int n = 0; n < numLines-1; n++ )
{
for ( int k = n+1; k < numLines; k++ )
{
float y0_here = line[n].rho / sin(line[n].theta);
float y0_next = line[k].rho / sin(line[k].theta);
// index intersection points of lines in X and Y
void indexIntersections (vector<CvPoint>& p, vector<line_param>& lineX, vector<line_param>& lineY, IplImage* image )
{ // find "p", intersections of lines, "linesX" & "linesY", and draw them in "image"
int numLinesX = lineX.size(), numLinesY = lineY.size(); // total number of detected lines
int numPoints = numLinesX * numLinesY; // total number of intersection of the lines
p.resize(numPoints); // define the size of "p" vector as "numPoints"
char txt[100]; // text to represent the index number of an intersection point
// calculate intersection points of vertical and horizontal lines
for( int i = 0; i < numLinesX; i++ )
{
for( int j = 0; j < numLinesY; j++ )
{
int indexP = numLinesY * i + j; // index number of intersection points in geometrical order
float Px = ( lineX[i].rho*lineY[j].b - lineY[j].rho*lineX[i].b ) / ( lineX[i].a*lineY[j].b - lineX[i].b*lineY[j].a ) ;
float Py = ( lineX[i].rho - lineX[i].a*Px ) / lineX[i].b ;
p[indexP].x = cvRound(Px);
p[indexP].y = cvRound(Py);
// display the points in an image
cvCircle( image, p[indexP], 3, CV_RGB(0,255,50) /* , <#int line_type#>, <#int shift#> */ );
sprintf(txt, "%d-%d", i, j); cvPutText(image, txt, p[indexP], &cvFont(0.7), CV_RGB(50,255,250));
}
}
return;
}
int main()
{
IplImage* iplInput = 0; // input image
IplImage* iplGray = 0; // grey image converted from input image
IplImage *iplTemp = 0; // converted image from input image with a change of bit depth
IplImage* iplDoGx = 0, *iplDoGxClone; // filtered image by DoG in x-direction
IplImage* iplDoGy = 0, *iplDoGyClone; // filtered image by DoG in y-direction
IplImage* iplEdgeX = 0, *iplEdgeY = 0; // edge-detected image by filtering in each direction, to be used as input in line-fitting
double minValx, maxValx; // minimum & maximum of pixel intensity values
double minValy, maxValy;
double minValt, maxValt;
int width, height; // window size of input frame
int kernel = 1; float edgethres; // parameters of NMS function
double rho = 0.8; // distance resolution in pixel-related units
double theta = 0.8; // angle resolution measured in radians
// "A line is returned by the function if the corresponding accumulator value is greater than threshold."
// int threshold = 24, rN = 5, tN = 5; // for grid pattern of 11x7 squares
int threshold = 20, rN = 5, tN = 5; // for grid pattern of lines with cross ratios
double h[] = { -1, -7, -15, 0, 15, 7, 1 }; // 1-D kernel of DoG filter
CvMat DoGx = cvMat( 1, 7, CV_64FC1, h ); // DoG filter in x-direction
CvMat* DoGy = cvCreateMat( 7, 1, CV_64FC1 ); // DoG filter in y-direction
cvTranspose( &DoGx, DoGy ); // transpose(&DoGx) -> DoGy
// output information of lines found by Hough transform with NMS
CvMemStorage* storageX = cvCreateMemStorage(0), *storageY = cvCreateMemStorage(0);
CvSeq* linesX = 0, *linesY = 0;
vector<CvPoint> p; // ordered intersection points on the "linesXorder" & "linesYorder"
// create windows
cvNamedWindow("input");
cvNamedWindow( "temp" );
char title_fx[200], title_fy[200];
sprintf(title_fx, "filtered image by DoGx");
sprintf(title_fy, "filtered image by DoGy");
cvNamedWindow(title_fx);
cvNamedWindow(title_fy);
char title_ex[200], title_ey[200];
sprintf(title_ex, "detected edges in x direction");
sprintf(title_ey, "detected edges in y direction");
cvNamedWindow(title_ex);
cvNamedWindow(title_ey);
// initialize capture from a camera
CvCapture* capture = cvCaptureFromCAM(0); // capture from video device #0
int frame = 0; // number of grabbed frames
while(1)
{
// get video frames from the camera
if ( !cvGrabFrame(capture) ) {
printf("Could not grab a frame\n\7");
exit(0);
}
else {
cvGrabFrame( capture ); // capture a frame
iplInput = cvRetrieveFrame(capture); // retrieve the caputred frame
cvSaveImage("original.bmp", iplInput);
if(iplInput) {
if(0 == frame) {
// create an image header and allocate the image data
iplGray = cvCreateImage(cvGetSize(iplInput), 8, 1);
iplTemp = cvCreateImage(cvGetSize(iplInput), IPL_DEPTH_32F, 1);
iplDoGx = cvCreateImage(cvGetSize(iplInput), IPL_DEPTH_32F, 1);
iplDoGy = cvCreateImage(cvGetSize(iplInput), IPL_DEPTH_32F, 1);
iplDoGyClone = cvCloneImage(iplDoGy), iplDoGxClone = cvCloneImage(iplDoGx);
iplEdgeX = cvCreateImage(cvGetSize(iplInput), 8, 1);
iplEdgeY = cvCreateImage(cvGetSize(iplInput), 8, 1);
width = iplInput->width, height = iplInput->height;
cvMoveWindow( "temp", 100+width+10, 100 );
cvMoveWindow( title_fx, 100, 100+height+30 );
cvMoveWindow( title_fy, 100+width+10, 100+height+30 );
cvMoveWindow( title_ey, 100, 100+(height+30)*2 );
cvMoveWindow( title_ex, 100+width+10, 100+(height+30)*2 );
}
// convert the input color image to gray one
cvCvtColor(iplInput, iplGray, CV_BGR2GRAY); // convert an image from one color space to another
cvSaveImage("gray.bmp", iplGray);
// convert one array to another with optional linear transformation
cvConvert(iplGray, iplTemp);
// increase the frame number
frame++;
}
// #1. DoG filtering
// convolve an image with the DoG kernel
// void cvFilter2D(const CvArr* src, CvArr* dst, const CvMat* kernel, CvPoint anchor=cvPoint(-1, -1)
cvFilter2D( iplTemp, iplDoGx, &DoGx ); // convolve an image with the DoG kernel in x-direction
cvFilter2D( iplTemp, iplDoGy, DoGy ); // convolve an image with the DoG kernel in y-direction
// convert negative values to positive to filter the image in reverse direction
cvAbs(iplDoGx, iplDoGx); cvAbs(iplDoGy, iplDoGy);
// normalize the pixel values
cvMinMaxLoc( iplDoGx, &minValx, &maxValx ); // find global minimum and maximum in image array
cvMinMaxLoc( iplDoGy, &minValy, &maxValy );
cvMinMaxLoc( iplTemp, &minValt, &maxValt );
cvScale( iplDoGx, iplDoGx, 1.0 / maxValx );
cvScale( iplDoGy, iplDoGy, 1.0 / maxValy );
cvScale( iplTemp, iplTemp, 1.0 / maxValt );
// display windows
cvShowImage( "temp", iplTemp );
cvShowImage( title_fx, iplDoGx ); cvShowImage( title_fy, iplDoGy );
// save images to files
cvSaveImage("temp.bmp", iplTemp);
cvSaveImage("DoGx.bmp", iplDoGx); cvSaveImage("DoGy.bmp", iplDoGy);
// #2. separate selected edges into vertical and horizontal
// arrange vertical lines from left to right
cout << "vertical" << endl;
indexLinesY(linesYorder, linesY, iplInput );
// arrange horizontal lines from up to bottom
cout << "horizontal" << endl;
indexLinesX(linesXorder, linesX, iplInput );
// calculate and index intersection points
indexIntersections(p, linesXorder, linesYorder, iplInput);