물체인식에 관한 이야기 - (3) SURF 특징추출하기에 이어서 
열심히(?) 추출한 특징점들의 매칭에 대해서 알아보겠습니다.

영상으로부터 SIFT나 SURF의 국소특징들을 추출해 보았기 때문에
이제는 그것을 응용해 보겠습니다.

특징점의 매칭은 두 개의 영상간의 대응하는 점들을 찾는 과정입니다.
즉, 앞에서 언급하였던 특정물체인식에 있어서
미리 만들어 둔 물체의 모델 데이터베이스와 입력되는 영상간에 대응되는 점들을 찾으므로써
가장 많이 대응되는 점을 가지는 물체 짝들을 동일한 물체로 인식하겠다는 것으로
생각할 수 있습니다.

아래의 그림을 보시면 어떻게 진행되는지 대략적인 이해를 할 수 있을겁니다.
아래의 두 영상 사이를 연결하는 직선들은 
양단 키포인트의 특징 벡터들간의 거리가 가까운 점들을 서로 연결하고 있습니다.


아래가 매칭을 위한 프로그램입니다.

실행 - 실행파일 이름과 입력영상 1, 2의 파일명을 차례로 입력하면 됩니다.
 keypoint_matching.exe [입력영상 1번의 파일명] [입력영상 2번의 파일명]

코드 - cpp

#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;  // 線をつなぐ距離の閾値

/**
 * 2개 벡터의 유클리드거리의 계산해서 반환
 *
 * @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의 키포인트를 짝(pair)으로 해 반환
 *
 * @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개라고 하면 
전부 800 x 800 = 640,000의 거리계산이 필요하게 됩니다.

선형 탐색은 - 계산량의 측면에서 특별히 - 효율성이 나쁘기 때문에
다음에 조금 더 고속 검색을 위한 방법에 대해 알아보도록 하겠습니다.

여기서, SURF는 라플라시안이 일치하지 않는 점끼리는 닮지 않았다고 판정해도 좋다고 언급하고 있습니다. 알고리즘 부분은 후에 따로 정리하고 내용을 추가하겠습니다.

키포인트간의 유클리드 거리는 특징벡터(128차원)의 거리(L2)로 측정하고 있습니다.
유클리드 거리가 THRESHOLD(=0.3)보다 작은 경우에 대응하는 것으로 판단하고
직선으로 두 점을 연결하여 나타내고 있습니다.
실제는 거리의 비교이므로 거리의 제곱을 사용해도 문제 없습니다.
(다만 임계값을 조정해주어야 합니다.)

대량으로 계산이 필요하므로 sqrt()를 사용하지 않고 int형으로 계산하는 편이
속도가 더 빠를지도 모르겠다고 하네요.


SURF 국소특징의 강인성 검증

SIFT와 SURF는 영상의 스케일 변화, 평행 이동, 회전, 은폐(Occlusion)에 대해서
강인하다고 얘기하고 있습니다.
그래서 영상 2를 변경해서 키포인트의 매칭을 테스트 해 보도록 하겠습니다.

<Scale>


<Rotation>


<Occlusion>



영상의 크기 변경, 회전, 은폐등은 아래의 코드를 참조하시면 가능합니다.
(직접 코딩하시거나 알씨에서 손으로 하셔도 무방합니다만.)
은폐는 왼쪽 절반을 흰색으로 칠을 합니다.

영상의 사이즈 변경
영상의 회전
은폐

    // 축소
    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를 여러 물체를 결합한 영상으로 구성해 보았습니다.

영상 2(오른쪽 아래 영상)는 많은 물체 영상을 정리한 영상이지만,
다음 장에서는 물체 모델 데이터베이스,
대량의 물체 영상의 키포인트와 특징 벡터를 격납한 데이터베이스에 옮겨 놓을 예정이라고 하네요.

저는 영상 만드는 것이 귀찮아서. ^-^
같은 영상에 임계값만 0.2와 0.3을 비교하고 넘어 가도록 하겠습니다.

<임계값 0.2>

<임계값 0.3>

아직은 임계값의 역할이 무엇인지 정확하게 이야기할 수 있을만큼
매칭 알고리즘을 정확하게 이해하고 있는 상태는 아니지만 임계값을 0.3으로 하였을 경우에는
0.2인 경우에 비해서 매칭되는 점들이 많아집니다만,
그 만큼 에러도 많아지네요.
(쉽게 아실 수 있겠지만 두 영상간의 닮은 정도, 또는 벡터들의 오차값이
0.3보다 큰 경우와 0.2보다 큰 경우로 생각해보면 
위의 임계값이 어떤 역할을 하고 있는지는 쉽게 이해가 될 것으로 생각됩니다.)

에러는 많이 보이지만, 영상1과 같은 영상2의 시계 영상에 매칭되는 선들이
집중 되는 것을 알 수 있습니다.

실제로 특징점의 매칭을 특정 물체 인식에 사용하는 경우에는
잘못된 매칭점들이 다소 있더라도 큰 문제가 되지는 않습니다.
아래와 같이 정답의 영상에 매칭하는 선이 집중되고 있으면 (득표수가 많으면)
제대로 인식한다고 판단합니다.

다소 잘못된 매칭 부분이 있더라도 인식할 수 있다는 것을 이용하여 
인식을 고속화시키는 방법도 제안되고 있습니다라고 얘기하고 있네요.

잘못된 매칭 부분이 있더라도 인식할 수 있다는 얘기는
특정물체인식의 경우 구성된 데이터베이스 내에서 제일 매칭되는 부분이 많은 영상을
입력영상(여기에서는 영상1)과 동일한 영상으로 인식할 수 있다는 얘기로
이해하면 좋을 것 같습니다..

다음은 물체인식에 관한 이야기 - (5) 물체 모델의 데이터 베이스 작성입니다.
--

물체 인식에 관한 이야기 - 목록
물체인식에 관한 이야기 - (1) 시작하기
물체인식에 관한 이야기 - (2) SIFT특징 추출
물체인식에 관한 이야기 - (3) SURF 특징추출하기
물체인식에 관한 이야기 - (4) 특징점의 매칭
물체인식에 관한 이야기 - (5) 물체 모델의 데이터 베이스 작성
물체인식에 관한 이야기 - (6) 선형탐색을 이용한 특정 물체 인식
물체인식에 관한 이야기 - (7) 최근접탐색의 고속화
--

* 본 자료의 내용은 aidiary의 내용을 바탕으로 하고 있습니다.
* 일본어가 가능하신 분은 직접 aistudy 블로그에 엑세스하셔서 보는 것이
저의 허접한 번역과 부족한 코멘트 보다는 훨씬 이해도 쉽고 유익하리라 판단됩니다.
지난번 물체인식에 관한 이야기 - (2) SIFT 특징추출하기에 이어서
이번에는 SURF (Speeded Up Robust Features) 특징점을 추출해 보겠습니다.

SIFT와 추출 방법은 다르지만,
영상으로부터 키포인트와 특징벡터를 추출하는 점에서는 동일합니다.

처리 속도는 SIFT보다 몇 배 빠르다고 하지만, 정확도는 다소 떨어진다고 합니다.
하지만 리얼타임 처리를 위해서는 SURF가 좋은 것 같다고 합니다.
뭐 아무래도 이름이 스피디드 어푸니까.

SURF는 OpenCV에도 구현되어 있다네요.
SURF도 지난번 SIFT와 마찬가지로 자세한 알고리즘은 후에 정리하기로 하고,
우선 구현해 보는 것에 초점을 맞추도록 하겠습니다.

영상으로부터 SURF 추출하기

아래의 프로그램은
영상으로부터 SURF 특징점을 표현하고 특징량을 파일에 저장하는 코드입니다.
이 프로그램은 OpenCV의 find_obj.cpp(C:\OpenCV2.0\samples\c)를 참고하여 작성되었습니다. 그 중에서 특징을 추출하는 부분만을 발췌하였다고 합니다.

실행 - 컴파일 후에 실행은 다음과 같이 해주시면 됩니다.
 surf.exe [입력영상 파일명] [SURF정보 저장 이름]

코딩 - 주석만 한글로 변경하였습니다.
(표현이 어색한 부분은 알아서 이해해주세요. -_-)

#include <cv.h>
#include <highgui.h>
#include <iostream>
#include <fstream>

using namespace std;

const int DIM_VECTOR = 128;

/**
 * SURF 정보를 파일에 출력
 * @param[in]   filename            SURF정보를 저장하는 파일명
 * @param[in]   imageKeypoints      SURF키포인트 정보
 * @param[in]   imageDescriptors    SURF특징벡터 정보
 * @return 없음
 */

void writeSURF(const char* filename, CvSeq* imageKeypoints, CvSeq* imageDescriptors)
{
    fstream fout;

    fout.open(filename, ios::out);
    if (!fout.is_open()) {
        cerr << "cannot open file: " << filename << endl;
        return;
    }

    // 1행은 키포인트의 수와 특징량의 차원수를 기록
    fout << imageKeypoints->total << ' ' << DIM_VECTOR << endl;

    // 2행부터는 키포인트 정보와 특징 벡터를 기록
    for (int i = 0; i < imageKeypoints->total; i++) {
        CvSURFPoint* point = (CvSURFPoint*)cvGetSeqElem(imageKeypoints, i);
        float* descriptor = (float*)cvGetSeqElem(imageDescriptors, i);
        // 키포인트 정보 기록 (X좌표, Y좌표, 사이즈, 라플라시안)
        fout << point->pt.x << ' ' << point->pt.y << ' ' << point->size << ' ' << point->laplacian << ' ';
        // 특징벡터 정보 기록
        for (int j = 0; j < DIM_VECTOR; j++) {
            fout << descriptor[j] << ' ';
        }
        fout << endl;
    }

    fout.close();
}

int main(int argc, char** argv)
{
    const char* imageFile = argc == 3 ? argv[1] : "image/accordion_image_0001.jpg";
    const char* surfFile  = argc == 3 ? argv[2] : "image/accordion_image_0001.surf";

    // SURF추출을 위한 입력 영상을 그레이스케일로 읽음
    IplImage* grayImage = cvLoadImage(imageFile, CV_LOAD_IMAGE_GRAYSCALE);
    if (!grayImage) {
        cerr << "cannot find image file: " << imageFile << endl;
        return -1;
    }

    // 결과에 키포인트를 표현하기 위해 컬러로도 읽음
    IplImage* colorImage = cvLoadImage(imageFile, CV_LOAD_IMAGE_COLOR);
    if (!colorImage) {
        cerr << "cannot find image file: " << imageFile << endl;
        return -1;
    }

    CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq* imageKeypoints = 0;
    CvSeq* imageDescriptors = 0;
    CvSURFParams params = cvSURFParams(500, 1);

    // 영상으로부터 SURF 특징 추출
    cvExtractSURF(grayImage, 0, &imageKeypoints, &imageDescriptors, storage, params);
    cout << "Image Descriptors: " << imageDescriptors->total << endl;

    // SURF 정보 파일에 기록
    writeSURF(surfFile, imageKeypoints, imageDescriptors);

    // 영상에 키포인트 표현
    for (int i = 0; i < imageKeypoints->total; i++) {
        CvSURFPoint* point = (CvSURFPoint*)cvGetSeqElem(imageKeypoints, i);
        CvPoint center;  // 키포인트 중심좌표
        int radius;      // 키포인트 반경
        center.x = cvRound(point->pt.x);
        center.y = cvRound(point->pt.y);
        radius = cvRound(point->size * 1.2 / 9.0 * 2.0);
        cvCircle(colorImage, center, radius, cvScalar(0,255,255), 1, 8, 0);
    }

    cvNamedWindow("SURF");
    cvShowImage("SURF", colorImage);
    cvWaitKey(0);

    // 후처리 - 메모리 해제 등
    cvReleaseImage(&grayImage);
    cvReleaseImage(&colorImage);
    cvClearSeq(imageKeypoints);
    cvClearSeq(imageDescriptors);
    cvReleaseMemStorage(&storage);
    cvDestroyAllWindows();

    return 0;
}


후처리 부분이 많아서 귀찮다고 하시네요. ^^; 그래서 C++이 싫다고 하네요. 하.
뭔가 부족한 부분이 있다면 알려주세요.

이번에도 beaver.png영상으로 실험해 보았습니다.

SURF정보가 저장되는 파일의 내용(*.surf)은 지난번 SIFT 특징추출에서 *.sift와 동일합니다.
1행은 첫 번째 키포인트의 정보를 기록하고 있습니다.

190 128 --> 키포인트의 수와 SURF의 차원수
37.5957 13.1186 15 -1 0.00369608 0.00389732 0.000638634 ...
--> 첫 번째 키포인트의 X좌표, Y좌표, 사이즈, 라플라시안, 이하는 128차원의 특징벡터
99.1948 12.8436 13 -1 0.0135164 0.015291 -0.00366134 ...
--> 두 번째 키포인트의 정보

SURF 역시 특징 추출에 대한 자세한 내용을 알고 이해하기 위해서는 알고리즘에 대한 공부가 필요하겠네요. 일단은 패스.

인화 누님사진에도 적용을 시켜 봅니다.


SURF도 SIFT와 마찬가지로 그레이 스케일에서 처리를 하고 있네요.
다른 함수들의 사용법은 위의 샘플로 대부분 이해를 할 수 있지만,
키포인트의 정보(imageKeypoints)와 키포인트의 특징벡터(imageDescriptors)의 정보는
다른 CvSeq에 기록되고 있는 것 같습니다. (무슨말인지 잘 모르겠네요.-_-)

다만 첨자는 대응하고 있습니다.
예를 들면, imageKeypoints의 i번째 키포인트의 특징벡터는
imageDescriptors의 i번째에 기록됩니다.

키포인트 정보는 CvSURFPoint라고 하는 구조체에 정리하고 있으며,
특징벡터는 float의 배열이 그대로 들어가 있는 것 같습니다.

SURF의 파라미터는 CvSURFParams에서 지정합니다.

OpenCV의 메뉴얼(doc폴더에 있는 opencv.pdf)에 의하면 

첫 번째 인자는 특징량의 차원의 지정을 하는 인자로써
「 0 --> 64차원, 1 --> 128차원」 입니다.
차원을 다르게 하면 어떻게 달라지는가? 결과에 어떤 영향을 미치는가?

두 번째 인자는 최소의 임계값(역치?)으로
키포인트의 Hessian이 임계값보다 큰 점만 검출됩니다.

적절한 반응을 일으키는 최소의 임계값은 300~500이라고 하네요.
이 값을 크게하면 할수록 추출되는 점의 수는 작아지겠지요.

몇개의 영상을 시험해 보겠습니다.
SIFT와 비교하면 배경과 물체사이의 에지(edge)추출이 어떤 것이 쉬운지...

아, 적절한 테스트 영상을 아직 준비 못한 관계로 실험후에 업데이트 하도록 하겠습니다.


영상으로부터 SURF의 실시간 추출하기

아래는 위의 프로그램을 조금 확장하여,
웹 카메라로부터 캡쳐 한 동영상으로부터 SURF를 실시간으로 추출하는 프로그램입니다.

현재 저는 캠이 없어서 구현한 결과는 후에 직접 해보고 내용을 추가하도록 하겠습니다.
캠이 있으신분은 아래의 코드를 이용해서 본인의 입맛에 맛게 구현해 보세요.

* 이 프로그램은 웹 카메라가 연결되어 있지 않으면 동작하지 않는다고 하네요.

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

using namespace std;

int main(int argc, char** argv) {
    CvCapture* capture;

    // 카메라 초기화
    if ((capture = cvCreateCameraCapture(0)) == NULL) {
        cerr << "cannot find camera" << endl;
        return -1;
    }

    // 윈도우 생성
    cvNamedWindow("SURF");

    IplImage* captureImage = cvQueryFrame(capture);
    while (true) {
        CvMemStorage* storage = cvCreateMemStorage(0);
        CvSeq* imageKeypoints = 0;
        CvSeq* imageDescriptors = 0;
        CvSURFParams params = cvSURFParams(500, 1);

        captureImage = cvQueryFrame(capture);

        // 그레이스케일 변환
        IplImage* grayImage = cvCreateImage(cvGetSize(captureImage), 8, 1);
        cvCvtColor(captureImage, grayImage, CV_BGR2GRAY);

        // 프레임 영상으로부터 SURF 추출
        cvExtractSURF(grayImage, 0, &imageKeypoints, &imageDescriptors, storage, params);

        // 영상에서 키포인트 추출
        for (int i = 0; i < imageKeypoints->total; i++) {
            CvSURFPoint* point = (CvSURFPoint*)cvGetSeqElem(imageKeypoints, i);  // i번째의 키포인트
            CvPoint center;  // 키포인트의 중심좌표
            center.x = cvRound(point->pt.x);
            center.y = cvRound(point->pt.y);
            cvCircle(captureImage, center, 2, cvScalar(0,255,255), CV_FILLED);
        }
        cvShowImage("SURF", captureImage);

        // 루프내에서 작성된 오브젝트의 처리
        cvReleaseImage(&grayImage);
        cvClearSeq(imageKeypoints);
        cvClearSeq(imageDescriptors);
        cvReleaseMemStorage(&storage);

        // ESC키를 눌릴경우 루프에서 빠져나감
        int key = cvWaitKey(30);
        if (key == 27) {
            break;
        }
    }

    // 후처리 - 메모리 해제 등
    cvReleaseCapture(&capture);
    cvDestroyAllWindows();

    return 0;
}


루프내에서 생성한 오브젝트들은 루프내에서 해제 해주세요.
그렇지 않으면 메모리 리크(leak) 등으로 프로그램이 멈추어 버린다고 하네요.

아직 제가 직접 확인해보지 못해서 잘은 모르겠지만,
동영상의 처리에 있어서는 SIFT에 비해서
SURF의 처리 속도가 빠름을 느낄 수 있다고 하네요.
(지금부터는 특별히 언급하지 않는 한 SURF를 사용한다고 합니다.)

캠을 확보하는대로 위의 내용을 확인해보고 결과물은 업데이트 하겠습니다.
--

이제 특징들을 추출하였으니 다음은 추출한 특징들의 매칭을 위한 방법에 대해서
공부해 보겠습니다.
물체인식에 관한 이야기 - (4) 특징점의 매칭
--

참고 문헌

SURF: Speeded Up Robust Feature - SURF 홈페이지 (프로그램 있음)
Herbert Bay, Andress Ess, Tinne Tuytelaars, and Luc Van Gool, "Speeded-Up Robust Features (SURF)" (PDF), Computer Vision and Image Understanding (CVIU), 110(3), pp.346-359, 2008.
SURFmex: A MATLAB SURF interface - 매트랩에서 SURF 구현
SURF - Wikipedia 설명 
--

물체 인식에 관한 이야기 - 목록
물체인식에 관한 이야기 - (1) 시작하기
물체인식에 관한 이야기 - (2) SIFT특징 추출
물체인식에 관한 이야기 - (3) SURF 특징추출하기
물체인식에 관한 이야기 - (4) 특징점의 매칭
물체인식에 관한 이야기 - (5) 물체 모델의 데이터 베이스 작성
물체인식에 관한 이야기 - (6) 선형탐색을 이용한 특정 물체 인식
물체인식에 관한 이야기 - (7) 최근접탐색의 고속화
--

* 본 자료의 내용은 aidiary의 내용을 바탕으로 하고 있습니다.
* 일본어가 가능하신 분은 직접 aistudy 블로그에 엑세스하셔서 보는 것이
저의 허접한 번역과 부족한 코멘트 보다는 훨씬 이해도 쉽고 유익하리라 판단됩니다.
--

20100811
Local feature 기반의 물체인식을 위한 방법(SIFT, SURF 등)에 관한 내용입니다
--

물체인식에 관한 이야기 - (1) 시작하기 (20100723)
--

오늘은 "3일에 만드는 고속물체인식시스템"의 1일차에 해당하는
SIFT특징 추출 부분을 구현하고 그 내용을 정리해 보고자 합니다.

* 주의
SIFT는 특허에 등록(U.S. Patent 6,711,293)되어 있으니
상용화 등에 이용하실 분들은 David Lowe(lowe@cs.ubc.ca) 선생님께 문의해주세요.

■ SIFT 특징 추출
알고리즘에 대한 부분은 후에 또 정리하겠습니다.
먼저 공부하실 분은 아래 논문을 참고 해주세요.
(위의 특허에도 알고리즘은 설명이 잘 되어 있습니다. 참고하세요.)

* SIFT 논문

여기에서는 코딩에 초점을 맞추겠습니다.
OpenCV에는 SIFT함수가 없으므로
Rob Hess씨가 C언어로 이쁘게 만들어주신 라이브러리를 이용하기로 합니다.
(우선 퍼뜩 결과를 시험해 보고 싶으니까요.)

OpenCV를 사용하기 위해서는 사전에 설치가 필요하기 때문에 인스톨 해주세요.
작년부터는 OpenCV2.x 버전이 업데이트 되고 있는 것 같던데,
다운로드 및 자세한 설치 방법은 
OpenCV 2.0 설치 (VS 2008, 윈도우 7)을 참조해주세요.

아, 제 작업환경은 Windows 7, VS2008, OpenCV2.0입니다.
Rob Hess씨의 프로그램은 OpenCV1.1에서는 정상적으로 동작을 하고,
OpenCV2.0에서도 조금만 수정하면 동작이 가능하다고 하는군요.

그럼
Rob Hess씨의 홈페이지로부터
SIFT Feature Detector의 압축파일(zip, 9.4M)을 다운로드 받으셔셔
압축을 푸시면 여러가지 파일들이 있지만,
특징의 추출을 위해서 최소한으로 필요한 내용은 아래와 같습니다.
(매칭 단계는 이후에 차근차근 해보도록 하겠습니다.)
siftfeat.c       : 메인 함수
sift.c / sift.h  : SIFT 추출 함수
imgfeatures.c. / imagefeatures.h : SIFT 특징 표현 및 보존 함수
utils.c utils.h : 유틸리티 함수
beaver.png    : 테스트 영상

위의 함수들을 자신의 프로젝트에 추가해서 사용하면 됩니다.
* David Lowe선생님의 SIFT descriptor와 Oxford VGG의 Affine covariant feature detectors의 내용을 포함하고 있는 듯 합니다.

OpenCV2.0에서 MinGW로 컴파일을 하니 몇개의 에러가 있다고 하는군요.
그에 따른 저자의 수정은 다음과 같습니다.

imagefeatures.c의 373행
 cvEigenVV( &M, &V, &E, DBL_EPSILON, 0, 0 );

siftfeat.c의 28, 29행과
char* img_file_name = "beaver.png";
char* out_file_name = "beaver.sift";

72행의
 cvSaveImage( out_img_name, img, 0 );
을 수정하였다고 합니다.
아직까지 여러가지 경고들은(저의 경우 23개) 남아있지만 컴파일은 가능합니다.

저도 우선 프로그램의 동작을 확인 해보는 것이 목적이므로 콘솔기반에서
위의 내용들을 시도해 보았습니다.

특별히 별다른 에러는 없었지만 저도 파일의 경로 부분은 수정을 하였습니다.
siftfeat.c의 28행의 입력영상 경로와 파일이름,
그리고 29행의 키포인트의 128차원 벡터의 내용이 저장되는 파일의 이름을 수정하였습니다.

beaver.png을 입력영상으로 프로그램을 돌려봅니다.
정확하게 나왔는지는 모르겠지만, 아래와 같은 결과를 얻을 수 있습니다.


위의 결과 영상에서 화살표의 시작점은 특징점의 위치를 나타냅니다.
그리고 화살표의 길이는 스케일,
화살표의 방향은 경사도가 가장 강한 방향을 나타내고 있는 듯 합니다.

위 영상의 경우 114개의 특징점이 검출 되었네요.

SIFT 특징점의 표시방법은
키포인트의 점만 표시 하거나, 화살표, 원 등의 여러가지가 있는 듯 합니다.

특징점의 스케일이나 화살표의 방향 등에 대해서 더 정확하게 알기 위해서
아무래도 이제부터는 알고리즘 적인 부분에 대한 이해가 필요한 것 같네요.

하지만, 우선은 각 특징점을 128차원의 특징 벡터로 나타내는 것에 대해서만
알아보도록 하겠습니다.
각각의 키포인트 128차원의 내용은 beaver.sift라는 파일에 텍스트로 저장되어 있습니다.

화살표 하나에 128차원 벡터입니다. 파일의 내용은 아래와 같습니다.
114 128 --> 키포인트의 개수(114)와 특징벡터의 차원수(128차원)
101.350424 136.130888 40.169873 0.771085  // 특징점 #1
--> 첫 번째 키포인트의 Y좌표, X좌표, 스케일, 방향
 0 0 0 0 3 1 0 0 2 23 46 15 18 3 0 0 6 20 13 1
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 88 36 0 0
 81 95 57 47 185 114 2 7 185 155 19 6 19 6 1 22 22 0 0 0
 0 0 0 1 0 0 0 0 37 8 0 0 91 12 0 1 185 144 11 35
 185 50 0 0 23 28 8 95 40 1 0 0 0 0 0 4 0 0 0 0
 0 0 0 0 11 5 0 0 4 2 0 0 49 20 0 0 1 0 0 1
 0 0 0 0 0 0 0 0
--> 벡터의 값들로 각 행의 개수는 20
127.871534 71.100559 15.768594 -2.024589 // 특징점 #2
 1 2 2 72 63 12 1 1 133 93 1 4 2 7 4 44 133 115 0 0
 0 0 0 20 9 4 0 0 0 0 0 0 23 0 1 9 107 20 1 8
 133 5 0 0 0 1 5 133 132 14 0 0 0 0 8 133 14 1 0 0
 0 0 0 8 26 0 0 0 126 37 8 22 133 47 0 0 0 0 3 52
 131 41 0 0 0 0 2 36 1 0 0 0 0 0 0 2 2 0 0 0
 34 105 80 24 111 15 0 0 0 1 55 66 79 21 0 0 0 0 0 5
 0 0 0 0 0 0 0 0 

즉,
위의 beaver.png영상의 경우 128차원의 벡터 114개에 의해서 표현된다는 것을 의미합니다.
이 벡터 114개를 물체 데이터베이스에 등록해 두게 됩니다.
128x114 = 14592, 꽤 방대한 수치입니다.

저는 가로축을 X축, 세로축을 Y축, 그리고 좌측 상단을 (0,0)으로 하는
영상좌표계를 기준으로 생각하고 위 표내의 주석을 나타내었습니다.

아직 잘은 모르겠습니다만,
화살표의 크기가 클수록 더 중요한 키포인트로 해석해도 좋을까요?

키포인트를 점으로 표현하기

여기에서는 키포인트를 화살표가 아닌 점으로 표현해 보겠습니다.
특징점을 표현하는 부분인 imagefeatures.c의 595~597행을
아래와 같이 수정해주면 됩니다.
// cvLine( img, start, end, color, 1, 8, 0 );
// cvLine( img, end, h1, color, 1, 8, 0 );
// cvLine( img, end, h2, color, 1, 8, 0 );


cvCircle(img, cvPoint(start_x, start_y), 2, cvScalar(0,255,255,255), -1, 0, 0);



인화 누님의 사진도 한 번 돌려봅니다.



SIFT란 무엇인지, 그 속을 들여다 봅시다.

위에서 추출된 SIFT의 키포인트는 영상의 특징을 잘 표현하고 있는 것을 알았습니다.
정말 그런가?

그럼, 위의 키포인트라는 녀석들은 어떤 방법으로 선택되어 지는가?
128차원의 벡터는 어떤식으로 구성되어 지는가?
각각의 구성값들은 어떻게 얻어 지는가?
등을 알기 위해서는 위에서도 언급되었듯이 알고리즘에 대한 세부적인 공부가 필요합니다.

SIFT는 상당히 복잡한 연산과정들을 가지고 있고,
가우시안 평활화, Difference of Gaussian(DoG), 영상의 미분, 강도의 기울기 등
영상처리에 관련된 이론들을 모른다면 이해하기가 어려워 보입니다.

저자 또한 어슴푸레 대략적으로 내용을 이해하고 있다고 하네요.
그런 가운데에서 한 가지 착각했던 것은,
SIFT는 색상정보를 사용하지 않는 것일까 였다고 하네요.
실제로도 그레이 스케일 영상으로부터 추출하고 있네요.

아래에 SIFT 관련 참고문헌(일본어, 영어)들을 아래에 정리해 두었습니다.
참고하시면 도움이 되실 듯 합니다.
그리고 이것들을 참고해 그 중 C++의 SIFT프로그램을 만들어 볼 생각이라고 합니다.

* SIFT 관련 참고문헌

SIFT Tutorial (일문) - 후지요시(藤吉)선생님의 SIFT 해설자료, 그림이 많고 이해하기 쉽다.
SIFT 1부터의 작성하는 블로그 (일문)
- 순서에 따라 조금씩 구현하고 있으므로 이해하기 쉽다.
Rob Hess씨의 SIFT 특징 추출 모듈 사용해 보기 (일문)
- Rob Hess씨의 프로그램 실행방법
SIFT++ (영문)
SIFT for MATLAB (영문) - MATLAB을 이용한 SIFT
Scale Invariant Feature Transform (영문) - Wikipedia
Keypoint Detector (영문) - David Lowe선생님의 홈페이지
David G. Lowe, Object Recognition from Local Scale-Invariant Features (PDF), Proc. of IEEE Intl. Conf. on Computer Vision (ICCV), pp. 1150-1157, 1999
David G. Lowe, Distinctive Image Features from Scale-Invariant Keypoints (PDF), Intl. Jour. of Computer Vision (IJCV), 60(2), pp.91-110, 2004
--

다음은 물체인식에 관한 이야기 - (3) SURF 특징추출하기를 공부해 보겠습니다.
--

물체 인식에 관한 이야기 - 목록
물체인식에 관한 이야기 - (1) 시작하기
물체인식에 관한 이야기 - (2) SIFT특징 추출
물체인식에 관한 이야기 - (3) SURF 특징추출하기
물체인식에 관한 이야기 - (4) 특징점의 매칭
물체인식에 관한 이야기 - (5) 물체 모델의 데이터 베이스 작성
물체인식에 관한 이야기 - (6) 선형탐색을 이용한 특정 물체 인식
물체인식에 관한 이야기 - (7) 최근접탐색의 고속화
--

* 본 자료의 내용은 aidiary의 내용을 바탕으로 하고 있습니다.
* 일본어가 가능하신 분은 직접 aistudy 블로그에 엑세스하셔서 보는 것이
저의 허접한 번역과 부족한 코멘트 보다는 훨씬 이해도 쉽고 유익하리라 판단됩니다.
--

20100810

Local feature 기반의 물체인식을 위한 방법(SIFT, SURF 등)에 관한 내용입니다.

--

구성 
1. 잡담
2. 물체인식
3. 3일에 만드는 고속물체인식시스템
--

1. 잡담

컴퓨터 비전 분야에서 물체인식을 위한 대부분의 알고리즘들은
실험환경에 따라 그 결과가 확연히 달라집니다.
물론 비교적 일반적인 환경에서도 좋은 결과(인식률, 성능 등)을 보여주는 방법들이
많이 제안되고 있지만, 아직까지도 한계는 분명히 존재합니다.

즉, 이 분야의 문제들은 안타깝게도 대부분 케이스 바이 케이스의 문제라는 것이다.

어찌보면 그렇기 때문에 우리에게는 일거리가 많아져
먹고 살거리가 많아진다는 얘기로 받아 들일 수도 있겠지만.

* "먹고 살거리"라는 단어를 보니 갑자기 떠오르는 이야기

--

기본적인 비전시스템의 구성은 다음과 같이 생각할 수 있습니다.
1. 영상의 입력 --> 2. 영상정보의 처리 --> 3. 어플리케이션(User interface)
(* input, system, output으로 간단하게 생각하면 접근이 쉬워진다는 개인적인 판단하에)

카메라로 영상을 입력 받기까지만(1. 영상의 입력 단계)해도 엄청난 노이즈를 발생시키는 팩터(factor)들이 무자비하게 많지만, 제대로 영상이 입력되었다 해도 영상내의 정보(2. 영상정보의 처리 단계) 또한 만만치 않을테니. 그리고 처리된 정보를 활용해서 필요한 목적을 위한 무엇인가를 만들어 내는 단계(3. 어플리케이션)까지.

하나의 시스템을 만든다는 것이 그리 간단하지 않음을 또 한번 절실히 느낍니다.
위의 단계 이외에도 상용화를 위해서 해야 할 일들은
최적화부터 시작해서 엄청나게 많을테니까요.

아무래도 제가 공부하고 있는 것의 주요 초점사항은 2. 영상정보의 처리가 되어 왔지만 앞으로는 3. 어플리케이션(SmartPal)과 함께 열심히 무한 삽질을 해나가려 합니다.

산 넘어 산입니다만 그래도 한 걸음씩 걸어가야 합니다.
--

제일 위에서 언급하였듯이 비전 시스템은 어떤 환경에서는 100%에 가까운 정확도를 가지지만 또 다른 환경에서는 전혀 인식이 불가할 수도 있습니다.

이미 SIFT, SURF 등 수도 없이 많은 방법들이 제안되어 왔고 좋은 결과들을 보여주고 있으며 단점들이 보완된 여러가지 방법들, 그리고 다른 접근방법들도 매년 여러 학회, 저널들을 통해서 쏟아져 나오고 있습니다.
적어도 그들의 실험환경에서는.

지금 제 연구의 시발점에도 카메라를 이용해서 물체 인식을 하는 단계가 필요합니다.
아무리 좋은 알고리즘이라도 본인이 구성할 실험환경에 적절한가는 직접 해보지 않는 이상 알 수가 없습니다.
물론 똑같은 방법을 그대로 답습하고 싶은 생각은 없습니다만.

어찌되었던 그게 무엇이고 어떻게 생겨먹은 친구인지
우리 실험환경에서는 왜 좋은지, 안 좋은지에 대한 이전 연구들의 분석은 반드시 필요합니다.
그래야 새로운 것들을 만들 수 있습니다.

이미 30년 이상 머리 좋다는 사람들이 잔뜩 연구해 둔 이 분야에서 내가 새로운 무엇인가를
만들어 낸다는 것은 너무(?) 어려운 일일지라도 모른다는 생각이 압도적입니다.
하지만 기존 연구들의 리뷰와 함께 그것이 무엇인지 가장 기본적인 것 부터 알아둘 필요는 분명히 있습니다.
창조는 모방으로부터. 그리고 모든일은 철저하게 기초부터. 조금 느리게 가더라도.
사실은 제가 아는 것이 너무 부족하기 때문이기도 합니다.

* 기초의 중요성

또한 그런 과정없이 맨땅에 헤딩을 해서 10년이 걸려 새로운 것을 만들었을 때,
이미 오래전에 다 만들어져 있는 내용들이라면 얼마나 허무할까 하는 생각도 듭니다.
--

2. 물체인식

쓸데없는 얘기들이 너무 길어져 버렸습니다.

각설하고.
Bag of Features에 대한 정보를 얻으라고 센세가 알려주신 웹사이트를 뒤적거리다가
물체인식에 대한 구현의 내용들이 있어서 함께 정리해 나가고자 합니다.

제목은 「3日で作る高速特定物体認識システム」인데,
굳이 우리말로 번역하자면
「3일만에 만들어보는 특정물체의 고속인식 시스템 정도」가 아닐까 싶네요.

일본의 정보처리학회(情報処理学会)의 학회지 "정보처리(情報処理)"의 2008년 9월호(Vol.49, No.9)에 실린 특집기사의 내용을 위 블로그의 주인장이 친절하게 설명해 두었습니다.
http://d.hatena.ne.jp/aidiary/20091018/1255862734
aidiary 주인장의 이야기처럼 3일만에 가능할런지는 모르겠지만. 하.
코딩은 OpenCV를 이용하고 있습니다.

일본에는 이런 자료들이 많은 것 같습니다.
다양한 종류의 책에서부터 블로그까지 많은 정보들을
모국어(일본어)로 친절한 설명과 실험방법까지 상세히 기록하고 있는 자료들이 많습니다.

저의 허접한 번역과 이 기록들이 한국어를 모국어로 하는 누군가 단 한사람에게라도
작은 도움이라도 된다면 저는 너무나 기쁠 것 같습니다.

안타깝게도 위 논문의 pdf파일은 학회의 회원이 아닌경우에는 무료로 다운로드는 불가합니다.
그리고 함께 참고하면 도움이 된다는 2009년 인공지능학회(人工知能学会)에 실린 「セマンティックギャップを超えて―画像・映像の内容理解に向けてー(시맨틱 갭을 넘어서 - 영상, 화상에 대한 이해를 위해서)」또한 무료로 다운로드가 불가하다네요.

■ 물체인식(物体認識、Object Recognition)

물체인식은 입력되는 영상내에 무엇이 있는가를 인식하는 처리과정으로서 특정물체인식(特定物体認識)일반물체인식(一般物体認識)으로 나누어집니다.

특정물체인식
은 어떤 특정물체와 동일한 물체가
영상내에 존재하는지를 찾는 identification의 처리이며,
일반물체인식은 영상내의 자동차, 사람, 책상, 의자, 침대 등과 같은
일반적인 물체의 카테고리를 찾아내는 classification의 처리입니다.

특정물체인식은 컴퓨터의 성능의 발달로 상용화(휴대폰의 얼굴인식기능) 되어가고 있는 단계입니다.
그에 반해 일반물체인식은 아직 어려운 부분들이 많이 남아 있어서 최근의 연구는 이 부분에서 조금은 더 활발하게 진행되고 있다고 합니다.

일반 물체의 어려운 부분 중 하나인
시맨틱 갭(セマンティックギャップ、semantic gap)입니다.

예를들면,
같은 자동차라고 하는 카테고리에서도 여러가지 형태, 색, 종류를 가지기 때문에
자동차에 대한 본질적인(의미론적)인 내용을 알지 못하는 컴퓨터(로봇)는
자동차의 여러가지 형태, 색 등이 바뀌어 버릴 경우에는 인식을 하는데 어려움이 있습니다.

이와 같은 인공지능의 어려움은 기계(컴퓨터, 로봇)가 의미(음성에서도 영상에서도)를
이해할 수 없다는 것에서 부터 시작됩니다.
최근에는 웹상에서 태그를 부착해서 대량의 화상 데이터와 머신러닝을 이용해
스스로 학습할 수 있는 시스템을 만드는 연구들이 진행되고 있습니다.

이제, 원래 하고자 했던 3일에 만드는 고속물체인식시스템을 살펴 보겠습니다.
--

3. 3일에 만드는 고속물체인식시스템 (3日で作る高速特定物体認識システム)

■ 물체인식시스템의 구성

전체적인 시스템 구성은 아래의 그림과 같습니다.
출처: http://www.m.cs.osakafu-u.ac.jp/IPSJ_3days/

특정물체인식은 인식하고 싶은 물체들의 특징을 추출하여
물체모델 데이터베이스에 미리 등록해 둡니다.
인식을 위해서는 새롭게 입력되는 영상속에서 물체의 특징을 추출해서,
사전에 등록해둔 물체모델 데이터베이스에 있는 각 물체의 특징과의 유사도를 계산합니다.
그 중에서 유사도가 가장 높게 나타나는 물체를 인식의 결과로 출력합니다.

이를 위해서 위의 논문은 아래와 같은 순서로 진행됩니다.
여기에서는 특정물체인식만을 다루고 있는 것 같습니다.

(1) 저속 특정물체인식시스템의 구축 (低速特定物体認識システムの構築) - 1일차
Local feature descriptor의 대표적인 방법인 SIFT(Scale Invariant Feature Transform)를 이용하여 물체의 국소특징을 추출하고, 물체데이터베이스모델과의 대응을 위해서 가장 단순한 선형 최근접점 탐색으로 제일 가까운 점(유사도가 가장 가까운 점)을 찾습니다.
단순 선형탐색은 데이터베이스내의 모든 물체들과의 비교를 해야하기 때문에
계산량이 너무 많아서 (저속) 실용적이지 않지만
우선 동작하는 시스템을 만드는 것이 목표입니다.

(2) 근사 최근접점탐색을 이용한 고속화 (近似最近傍探索を用いた拘束化) - 2일차
선형탐색은 계산 속도가 너무 늦기 때문에 물체모델데이터베이스의 국소 특징량을 KD-tree또는 Locality Sensitive Hashing(LSH)를 이용하여 색인(index)을 만들어 최근접 탐색을 고속화 합니다.

(3) 유저 인터페이스의 구축 (ユーザインタフェースの構築) - 3일차
물체모델 데이터베이스의 작성과 인식 결과의 표시 등을 위한 인터페이스를 구축합니다.

■ 국소특징량(局所特徴量、local feature)
* Local feature를 우리말로는 어떻게 표현해야 할까요. 
 국부특징(?), 국소특징(?) 하. 어색합니다.
위에서도 국소 특징으로 사용했으니 일본 용어를 따라 국소 특징이라 부르기로 합니다.

특정물체인식 시스템에서는 데이터베이스에 등록된 물체만 인식이 가능합니다.

그럼 전혀 다른 영상이어도 인식이 가능할까요?

영상에서 추출한 다수의 국소특징들을 이용하기 때문에,
물체의 포즈, 크기가 다소 변하거나 일부(말그대로 조그마한 일부)가 가려져도 제대로 인식이 가능하다고 하네요. 뭐 저도 그렇게 보고 배웠습니다만.
직접 해보면 어느정도인지 왜 그런지는 더 정확히 알 수 있겠지요.

영상의 국소 특징에 의한 물체의 모델화는 이 분야를 크게 발전시킨 것 같습니다.
(ブレークスルー = break through)
아래 영상은 SIFT의 예제입니다.

(a) Original image


(b) Test image

영상 (a)는 원래의 레나 누님(?)의 영상입니다.
그리고 (b)는 회전, 크기, 가림현상 등의 테스트를 위해 (a)의 영상을
사이즈를 80% 축소(scale), 시계방향으로 90˚회전 (rotation),
명도 낮춤(illumination), 주위 배경을 은폐(occlusion)시킨 테스트 영상입니다.

각각의 노란색점이 키포인트 (interest point, 특징점을 부르는 법은 여러가지)로
각 키포인트가 각각 128차원의 SIFT 특징들을 가집니다.

위 영상의 경우 대략 1,000개 정도의 키포인트를 얻을 수 있습니다.
두 영상을 보면 알 수 있듯이 영상은 다소 바뀌었지만
키포인트의 위치는 거의 유사하고 SIFT특징량의 값도 가까운 것을 알 수 있습니다.
(이 부분은 저의 부족한 내공으로는 사실 설명만큼 잘 와닿지는 않는 부분입니다만.)

뭐 그래서 SIFT를 영상의 크기(scale), 조명(illumination), 평행이동, 회전(rotation), 은폐(occlusion)에 대해서 강인하다고 하는 이유라고 하네요.

즉, (a)영상의 국소특징을 데이터베이스에 등록해두면,
다소 변화된 (b)영상이 입력으로 들어 올 경우에 유사한 키포인트값들을 가지기 때문에
인식이 가능하다는 것입니다.

유사화상시스템만들기(Global Feature)에서는 컬러 히스토그램(color histogram)을 특징으로 사용하는데 Global Feature로 영상 전체로 부터 1개의 특징을 얻습니다.

반면에, 국소 특징은 영상 전체가 아니라
영상의 국소로부터 다수 (위의 경우 약 1,000개)의 특징을 얻습니다.

* Global feature 부분은 local feature를 마친 후에 정리 하겠습니다 
--

아무래도 직접 구현 해보는 것이 가장 빠른 길인 것 같습니다.
무언가 잡히는 듯 하면서도 붕 떠서 정리가 잘 안됩니다.

자, 그럼 다음은 1일차 내용인
물체인식에 관한 이야기 - (2) SIFT특징 추출하기를 공부해 봅시다.
아, 3일에 만드는 고속물체인식시스템의 진행은 다음과 같습니다.

물체 인식에 관한 이야기 - 목록
물체인식에 관한 이야기 - (1) 시작하기
물체인식에 관한 이야기 - (2) SIFT특징 추출
물체인식에 관한 이야기 - (3) SURF 특징추출하기
물체인식에 관한 이야기 - (4) 특징점의 매칭
물체인식에 관한 이야기 - (5) 물체 모델의 데이터 베이스 작성
물체인식에 관한 이야기 - (6) 선형탐색을 이용한 특정 물체 인식
물체인식에 관한 이야기 - (7) 최근접탐색의 고속화
--

* 본 자료의 내용은 aidiary의 내용을 바탕으로 하고 있습니다.
* 일본어가 가능하신 분은 직접 aistudy 블로그에 엑세스하셔서 보는 것이
저의 허접한 번역과 부족한 코멘트 보다는 훨씬 이해도 쉽고 유익하리라 판단됩니다.
--

References

3日で作る高速特定物体認識システム http://www.m.cs.osakafu-u.ac.jp/IPSJ_3days/
aidiary http://d.hatena.ne.jp/aidiary/20091018/1255862734
SIFT http://www.cs.ubc.ca/~lowe/keypoints/
SURF http://www.vision.ee.ethz.ch/~surf/
--

20100723

+ Recent posts