차근차근/OpenCV

3 일에 만드는 고속 특정 물체 인식 시스템 (4) 특징점의 매칭

예쁜꽃이피었으면 2014. 7. 30. 10:14

http://aidiary.hatenablog.com/entry/20091102/1257167398

3 일에 만드는 고속 특정 물체 인식 시스템 (3) SURF 추출 (2009-10-30) 한 벌.

이미지에서 SIFT와 SURF 같은 국소 특징 량을 추출 할 수있게 되었기 때문에 이쯤에서 그것을 응용 해보십시오. 특징점의 매칭을 취하는 것으로 두 이미지 사이에서 해당 위치를 구할 수있게됩니다. 아래의 예와 같은 느낌입니다.아래 그림에서 2 개의 키 포인트 사이에 걸린 직선은 양단 키포인트의 특징 벡터가 비슷한 (거리가 작은) 것을 나타냅니다 .

f : id : aidiary : 20091102211403p : plain

다음 프로그램입니다.

keypoint_matching.exe 그림 1의 파일 이름] [사진 2 파일 이름

같이 2 개의 이미지 파일을 입력으로 주면 위와 같이 일치 이미지가 표시됩니다.

# include <cv.h> 
# include <highgui.h> 
# include <iostream> 
# include <vector> 
# include <cmath>

using  namespace Std;

const  int DIM_VECTOR = 128 ;     / / 128 차원 벡터 
const  Double THRESHOLD = 0.3 ;   / / 선을 연결 한계 거리

/ ** 
* 두 벡터의 유클리드 거리를 계산하여 반환 
* 
* @ param [in] vec 벡터 1의 배열 
* @ param [in] mvec 벡터 2의 배열 
* @ param [in] length 벡터의 길이 
* 
* @ return 유클리드 거리 
* / 
Double euclidDistance ( float * vec1, float * vec2, int Length) {
     Double sum = 0.0 ;
     for ( int I = 0 ; i <length; i + +) {
        sum + = (vec1 [i] - vec2 [i]) * (vec1 [i] - vec2 [i]);
    }
    return sqrt (sum);
}

/ ** 
* 최근 방점을 검색 
* 
* @ param [in] vec 특징 벡터 
* @ param [in] laplacian 라플라시안 
* @ param [in] keypoints 키포인트의 집합 (이 중에서 최근 방점을 검색) 
* @ param [ in] descriptors 특징 벡터의 집합 
* 
* @ return 최근 방점 인덱스 (발견되지 않을 때는 -1) 
* / 
int NearestNeighbor ( float * vec, int Laplacian, CvSeq * keypoints, CvSeq * descriptors) {
     int Neighbor = - 1 ;
     Double minDist = 1e6 ;

    for ( int I = 0 ; i <descriptors-> total; i + +) {
        CvSURFPoint * pt = (CvSURFPoint *) cvGetSeqElem (keypoints, i);
        / / 라플라시안이 다른 키포인트는 무시 
        if (laplacian! = pt-> laplacian) continue ;
         float * v = ( float *) cvGetSeqElem (descriptors, i);
         Double D = euclidDistance (vec, v, DIM_VECTOR);
         / / 더 가까운 점이 있다면 대체 
        if (d <minDist) {
            minDist = d;
            neighbor = i;
        }
    }

    / / 최근 방점에서도 거리가 임계 값 이상이라면 무시 
    if (minDist <THRESHOLD) {
         return Neighbor;
    }

    / / 최근 방점이 없으면 
    return - 1 ;
}

/ ** 
* 이미지 1 키포인트와 가까운 화상 2의 키포인트를 쌍으로하여 반환 
* 
* @ param [in] keypoints1 이미지 1 키포인트 
* @ param [in] descriptors1 이미지 1의 특징 벡터 
* @ param [ in] keypoints2 화상 2의 키포인트 
* @ param [in] descriptors2 이미지 2의 특징 벡터 
* @ param [out] ptpairs 비슷한 키 포인트 인덱스의 열 (2 개 1 조) 
* 
* @ return 없음 
* / 
void findPairs (CvSeq * keypoints1, CvSeq * descriptors1,
                CvSeq * keypoints2, CvSeq * descriptors2,
                vector < int > & ptpairs) {
    ptpairs.clear ();
    / / 이미지 1의 각 키포인트에 관해서 최근 방점을 검색 
    for ( int I = 0 ; i <descriptors1-> total; i + +) {
        CvSURFPoint * pt1 = (CvSURFPoint *) cvGetSeqElem (keypoints1, i);
        float * desc1 = ( float *) cvGetSeqElem (descriptors1, i);
         / / 최근 방점을 검색 
        int NN = nearestNeighbor (desc1, pt1-> laplacian, keypoints2, descriptors2);
         / / 찾으면 사진 1의 인덱스와 이미지 2 인덱스를 차례로 등록 
        if (nn> = 0 ) {
            ptpairs.push_back (i);
            ptpairs.push_back (nn);
        }
    }
}

int Main ( int argc, char ** argv) {
     const  char * filename1 = argc == 3 ? argv [ 1 ] : "image/dolphin_image_0001.jpg" ;
     const  char * filename2 = argc == 3 ? argv [ 2 ] : "image/dolphin_image_0001.jpg" ;

    cvNamedWindow ( "Keypoint Matching" );

    / / 이미지는 그레이 스케일로로드
    IplImage * grayImage1 = cvLoadImage (filename1, CV_LOAD_IMAGE_GRAYSCALE);
    IplImage * grayImage2 = cvLoadImage (filename2 등 CV_LOAD_IMAGE_GRAYSCALE);
    if (! grayImage1 | |! grayImage2) {
        cerr << "cannot find image file" << endl;
        exit (- 1 );
    }

    / / 그리기 용 컬러​​로로드
    IplImage * colorImage1 = cvLoadImage (filename1, CV_LOAD_IMAGE_ANYCOLOR | CV_LOAD_IMAGE_ANYDEPTH);
    IplImage * colorImage2 = cvLoadImage (filename2 등 CV_LOAD_IMAGE_ANYCOLOR | CV_LOAD_IMAGE_ANYDEPTH);

    / / 매칭 용의 그림을 그릴
    CvSize sz = cvSize (colorImage1-> width + colorImage2-> width, colorImage1-> height + colorImage2-> height);
    IplImage * matchingImage = cvCreateImage (sz, IPL_DEPTH_8U, 3 );

    / / 이미지 1을 그리기 
    cvSetImageROI (matchingImage, cvRect ( 0 , 0 , colorImage1-> width, colorImage1-> height));
    cvCopy (colorImage1, matchingImage);
    / / 이미지 2를 그리기
    cvSetImageROI (matchingImage, cvRect (colorImage1-> width, colorImage1-> height, colorImage2-> width, colorImage2-> height));
    cvCopy (colorImage2, matchingImage);
    cvResetImageROI (matchingImage);

    CvSeq * keypoints1 = 0 * descriptors1 = 0 ;
    CvSeq * keypoints2 = 0 * descriptors2 = 0 ;
    CvMemStorage * storage = cvCreateMemStorage ( 0 );
    CvSURFParams params = cvSURFParams ( 500 , 1 );

    / / SURF 추출 
    cvExtractSURF (grayImage1, 0 , & keypoints1 & descriptors1, storage, params);
    cvExtractSURF (grayImage2, 0 , & keypoints2 & descriptors2, storage, params);

    / / 특징 벡터의 유사도가 높은 키포인트끼리 선으로 연결 
    vector < int > ptpairs;   / / keypoints의 인덱스가 2 개씩 쌍으로되도록 포함
    findPairs (keypoints1, descriptors1, keypoints2, descriptors2, ptpairs);

    / / 2 개씩 쌍으로 된 선을 긋는 
    for ( int I = 0 ; i <( int ) ptpairs.size (); i + = 2 ) {
        CvSURFPoint * pt1 = (CvSURFPoint *) cvGetSeqElem (keypoints1, ptpairs [i]);      / / 이미지 1 키포인트 
        CvSURFPoint * pt2 = (CvSURFPoint *) cvGetSeqElem (keypoints2, ptpairs [i + 1 ]); / / 이미지 2 키 포인트
        CvPoint from = cvPointFrom32f (pt1-> pt);
        CvPoint to = cvPoint (cvRound (colorImage1-> width + pt2-> pt.x) cvRound (colorImage1-> height + pt2-> pt.y));
        cvLine (matchingImage, from, to, cvScalar ( 0 , 255 , 255 ));
    }

    / / 키 포인트 매칭 한 그림을 그릴 
    cvShowImage ( "Keypoint Matching" , matchingImage);
    cvWaitKey ( 0 );

    / / 뒤처리
    cvReleaseImage (& grayImage1);
    cvReleaseImage (& grayImage2);
    cvReleaseImage (& colorImage1);
    cvReleaseImage (& colorImage2);
    cvClearSeq (keypoints1);
    cvClearSeq (descriptors1);
    cvClearSeq (keypoints2);
    cvClearSeq (descriptors2);
    cvReleaseMemStorage (& storage);

    return  0 ;
}

사진 1 (왼쪽 이미지)의 각 키포인트에 대해 이미지 2 (오른쪽 아래 사진)에서 최근 방점을 검색합니다. 검색 방법은 선형 탐색 에서 모든 키포인트 간의 거리를 계산하여 가장 가까운 것을 선택합니다. 따라서 이미지 1 키포인트 수가 800, 화상 2의 키포인트 수가 800이라고하면 전부 800x800 = 640,000 시간의 거리 계산이 필요합니다. 선형 탐색은 매우 효율이 나쁘기 때문에 나중에 더욱 빠른 검색 방법을 도입 예정입니다. 여기서, SURF는 라플라시안이 일치하지 않는 점 끼리는 닮지 않은 것으로 판단하고있다 것 같습니다. 왜 공부 중입니다 ...

키 포인트 사이의 거리는 특징 벡터 (128 차원)의 유클리드 거리 (L2 규범)로 측정하고 있습니다. 그리고 유클리드거리가 THRESHOLD (= 0.3) 이하의 경우, 대응하는 점이라고 판단하고 직선을 끌고 있습니다. 실제로, 거리의 비교이므로 유클리드 거리의 2 승을 사용해도 문제 없습니다 (역치는 바뀔 필요). 대량으로 거리 계산이 필요하므로 sqrt ()를 사용하지 않고 int 형으로 계산하는 것이 빠르고도.

국소 특징 량의 견고 확인

SIFT와 SURF는 이미지의 스케일 변화, 평행 이동, 회전, 은폐 (폐색)에 튼튼한 것으로 알려져 있습니다. 그런 이유로 이미지 1 (왼쪽 사진)을 변형하여 키포인트의 매칭 수 있는지 시험해 보았습니다. 사진을 클릭하면 확대 할 수 있습니다.

f : id : aidiary : 20091102211552p : plain
f : id : aidiary : 20091102211648p : plain
f : id : aidiary : 20091102211617p : plain

이미지 크기 조정, 회전, 은폐는 아래와 같은 코드 수 있습니다. 은폐는 왼쪽 절반을 흰색으로 채 있습니다. 자세한 내용은 아래 링크를 참조하십시오.

    / / 축소 
    IplImage * dst = cvCreateImage (cvSize (src-> width / 2 , src-> height / 2 ), src-> depth, src-> nChannels);
    cvResize (src, dst);

    / / 은폐 (왼쪽 반을 채울)
    IplImage * dst = cvCloneImage (src);
    cvRectangle (dst, cvPoint ( 0 , 0 ), cvPoint (dst-> width / 2 , dst-> height), cvScalar ( 255 , 255 , 255 ), CV_FILLED);

    / / 회전
    IplImage * dst = cvCloneImage (src);
    int angle = 45 ;   / / 회전 각도 
    float m [ 6 ];
    CvMat M;
    m [ 0 ] = ( float ) (cos (angle * CV_PI / 180 ));
    m [ 1 ] = ( float ) (-sin (angle * CV_PI / 180 ));
    m [ 2 ] = src-> width * 0.5 ;
    m [ 3 ] =-m [ 1 ];
    m [ 4 ] = m [ 0 ];
    m [ 5 ] = src-> height * 0.5 ;
    cvInitMatHeader (& M, 2 , 3 , CV_32FC1 m, CV_AUTOSTEP);
    cvGetQuadrangleSubPix (src, dst & M);

특정 물체 인식에의 응용

키 포인트의 매칭은 물체 이미지가있는 장면의 일부에있는 경우에도 가능합니다. 예를 들어, 이미지 2 (오른쪽 아래 사진)을 여러가지 물체를 결합한 이미지 해 보았습니다. 선을 긋는 유클리드 거리의 임계 값을 0.2으로하고 있습니다. 제대로 돌고래가 어디에 있는지 알고 있군요. 현재 상황에서는 이미지 2 (오른쪽 아래 사진)는 많은 물체 이미지를 정리 한 장 화면입니다 만, 잠시 후에 물체 모델 데이터베이스, 대량의 물체 이미지의 키포인트와 특징 벡터를 저장 한 데이터베이스 로 대체 예정입니다.

f : id : aidiary : 20091101102616p : plain

다음 예제는 유클리드 거리의 역치를 0.3으로 올려 조금 조건을 느슨하게하여 보았습니다. 이렇게하면 선은 많이 끌려이지만 그만큼 실수도 커지고 있습니다.

f : id : aidiary : 20091101102757p : plain

다른 예입니다. 이것도 유클리드 거리의 임계 값은 0.3입니다. 실수는 많지만 이미지 1과 같은 피아노의 이미지에 선이 집중하고있는 것은 압니다. 실제로 특징점의 매칭을 특정 물체 인식에 사용하는 경우는 실수가 다소 있어도 허용됩니다 . 아래와 같이 정답의 이미지에 선이 집중되고 있으면 (득표 수가 많으면) 제대로 인식 할 것입니다.이 다소 실수가 있어도 인식 할 수있는 성질을 이용하여 인식을 고속화시키는 방법도 제안되고 있습니다.

f : id : aidiary : 20091101102758p : plain

3 일에 만드는 고속 특정 물체 인식 시스템 (5) 물체 모델 데이터베이스 만들기 (2009-11-14)에 계속됩니다.


반응형