3日で作る高速特定物体認識システム (5) 物体モデルデータベースの作成
3일만에 만드는 고속 특정물체인식 시스템 (5) 물체 모델 데이터베이스의 작성
http://aidiary.hatenablog.com/entry/20091114/1258172261

내용의 확인 및 정리가 완료되지 않은 글입니다.
빠른 시일 내에 완성하도록 하겠습니다.

---
물체인식에 관한 이야기 - (4) 특징점의 매칭에 이어서
오늘은 고속 특정물체인식 시스템의 물체 모델 데이터베이스의 작성을 해보겠습니다.

Query로써 주어진 영상이 무엇인가는
이 물체 모델 데이터베이스를 검색하는 것으로 인식할 수 있게 됩니다.

특정물체인식에서는 query로 주어진 영상이 물체 모델 데이터베이스에
(국소특징을 이용하기 때문에 다소의 스케일 변화나 회전 등에는 대응할 수 있지만)
미리 등록되지 않은 경우에는 인식할 수 없습니다.

잊어 버리신 분은 물체인식 시스템의 구성을 참조해 주세요.

영상 데이터 셋(data set)의 준비

스스로 촬영한 화상을 등록할 수 있도록 하는 것이 가장 좋은 경우라고 생각하지만
(지난 번에도 언급했지만 환경에 따라 알고리즘의 성능은 달라질 수 있고, 지금 제가 원하는 것은 우리 실험환경에서 적절한 알고리즘의 개선이나 새로운 방법을 찾는 것이기 때문에)
우선은 대량의 화상을 촬영하고 구성하기에 앞서
널리 알려진(?) Caltech 101 영상 데이터 베이스를 이용해서
데이터 셋을 구성하는 방법에 대해서 먼저 알아보도록 하겠습니다.

유사영상검색 시스템 만들기의 영상데이터 셋에서 준비했던 것을 참고하여
Caltech 101 폴더에 영상 파일만 복사한 데이터를 준비합니다.
(저는 아직 유사영상검색 시스템 만들기 부분은 다 정리하지 못했네요. 여기서는 위의 Caltech 101 데이터베이스를 다운로드 받으셔서 사용하시면 전혀 문제 없으리라 생각됩니다.)

전부 다를 이용하기에는 조금 많기 때문에
각 카테고리로부터 10장씩의 영상을 선택(ID 0001에서 0010까지)해서
Caltech101_10이라고 하는 폴더를 만듭니다.
위의 폴더를 구성후에 아래의 스크립트를 실행하면 990장의 영상 데이터가 생성됩니다.
* 스크립트 실행에 대해서는 완전 무지한 저는 이제서야 Python을 설치하고 공부해보고 있습니다.

#coding:utf-8
import os
import shutil

IMAGE_DIR = "caltech101"
OUT_DIR = "caltech101_10"

for file in os.listdir(IMAGE_DIR):
    id = int(file[-8:-4])
    if id <= 10:
        print file
        shutil.copy("%s/%s" % (IMAGE_DIR, file), "%s/%s" % (OUT_DIR, file))


물체 모델 데이터 베이스의 작성

Caltech101_10의 990장의 영상으로부터 특징량을 추출하여
물체 모델 데이터베이스에 저장해 나간다고 합니다.

각 영상으로부터 추출한 키포인트의 특징량은 물체 ID와 연결하여 보존해 주어야 합니다.
그러기 위해서, 물체의 ID와 특징량을 보존하기 위한 테이블이 필요합니다.

物体IDテーブル:物体ID 画像ファイル名
特徴量テーブル:物体ID ラプラシアン キーポイントの特徴量

SQLite 등에서 데이터베이스를 만들어도 괜찮습니다만,
코드가 복잡해지므로 여기에서는 간단하게 텍스트 형식의 파일에 데이터를 보존합니다.

물체ID 파일(object.txt)특징량 파일(description.txt)입니다.

물체ID 파일은 아래와 같이 탭 단락의 서식입니다.
등록한 순서대로 물체 ID를 자동으로 할당할 수 있습니다.

0    accordion_image_0001.jpg
1    ant_image_0001.jpg
2    bonsai_image_0001.jpg
3    butterfly_image_0001.jpg
4    dolphin_image_0001.jpg

특징량 파일도 아래와 같이 탭 단락의 서식입니다.
모든 영상의 특징량은 1개의 파일에 저장하고 있습니다.
각 물체가 복수의 키포인트를 가지는 것을 주의해야 합니다.
그러므로 데이터베이스를 사용하는 경우에는
물체 ID를 주가되는 키(main key)로 할 수 없습니다.

0    1    0.03    0.05    ....   # 물체ID=0 의 1번째 키포인트
0    1    0.28    0.14    ....   # 물체ID=0 의 2번째 키포인트
0   -1    0.31    0.01    ....   # 물체ID=0 의 3번째 키포인트
....
1   -1    0.24    0.31    ....   # 물체ID=1 의 1번째 키포인트
1    1    0.15    0.22    ....   # 물체ID=1 의 2번째 키포인트
1    1    0.01    0.03    ....   # 물체ID=1 의 3번째 키포인트
....

Caltech_101의 모든 영상 파일로 부터
위의 물체 ID파일과 특징량 파일의 추출하는 프로그램입니다.
국소특징량은 SURF를 사용 하였습니다.
자세한 것은 SURF의 추출을 참조 해주세요.

디렉토리의 파일 스캔은 opendir()을 사용하고 있습니다.
이 함수는 MinGW의 g++에서 사용되고 있기 때문에,
Visual C++에서 아래의 코드를 사용할 경우에는 에러가 발생합니다.
그러니 여기를 참조해서 수정해서 사용해 달라고 언급해 두셨네요.

VC++에서 컴파일 해보니 dirent.h 포함파일을 열 수 없다고 나오네요.
에러가 발생하는 부분은 당연히 dirent.h를 참조하는 함수, 변수들이겠지요.


* 그래서 여기를 들어 가 보았습니다.
결국 이 부분의 역할은 현재 디렉토리의 파일 일람을 표시하는 것이네요.


#include<cv.h>
#include<highgui.h>
#include<iostream>
#include<fstream>
#include<sys/types.h>
#include<dirent.h>

using namespace std;

const char *IMAGE_DIR = "caltech101_10";
const char *OBJ_FILE = "object.txt";        // 물체ID 저장 파일
const char *DESC_FILE = "description.txt"// 특징량 저장 파일

const double SURF_PARAM = 400// SURF의 파라미터
const int DIM = 128;            // SURF특징량 차원수

/**
* SURF특징량의 추출하기
*
* @param[in]  filename         영상 파일명
* @param[out] imageKeypoints   키포인트(출력을 위해 참조)
* @param[out] imageDescriptors 각 키포인트의 SURF특징량(출력을 위해 참조)
* @param[out] storage          Memory Storage(출력을 위해 참조)
*
* @return 성공이면 0、실패라면 1
*/
int extractSURF(char *filename, CvSeq* &imageKeypoints, CvSeq* &imageDescriptors, CvMemStorage* &storage) {
    // 그레이스케일로 영상을 로드한다.
    IplImage *img = cvLoadImage(filename, CV_LOAD_IMAGE_GRAYSCALE);
    if (img == NULL) {
        cerr << "cannot load image file: " << filename << endl;
        return 1;
    }

    storage = cvCreateMemStorage(0);
    CvSURFParams params = cvSURFParams(SURF_PARAM, 1);
    cvExtractSURF(img, 0, &imageKeypoints, &imageDescriptors, storage, params);

    return 0;
}

/**
* 물체 모델을 파일에 저장
*
* @param[in]   objId              오브젝트 ID
* @param[in]   filename           영상파일명
* @param[in]   imageKeypoints     키포인트
* @param[in]   imageDescriptors   각 키포인트의 특징량
* @param[in]   objFile            물체ID 파일의 핸들러
* @param[in]   descFile           특징량 파일의 핸들러
*
* @return 성공이면 0、실패이면 1
*/
int saveFile(int objId, char *filename, CvSeq* imageKeypoints, CvSeq* imageDescriptors, ofstream& objFile, ofstream& descFile) {
    cout << objId << " " << filename << " " << imageDescriptors->total << endl;

    // 물체ID파일에 등록
    objFile << objId << "\t" << filename << endl;

    // 오브젝트 ID, 라플라시안, 128개의 숫자를 탭 단락으로 출력
    for (int i = 0; i < imageDescriptors->total; i++) {  // 각 키포인트의 특징량에 대해
        // 오브젝트 ID
        descFile << objId << "\t";

        // 특징점의 라플라시안(SURF특징량에서는 벡터의 비교시에 이용)
        const CvSURFPoint* kp = (const CvSURFPoint*)cvGetSeqElem(imageKeypoints, i);
        int laplacian = kp->laplacian;
        descFile << laplacian << "\t";

        // 128차원 벡터
        const float *descriptor = (const float *)cvGetSeqElem(imageDescriptors, i);
        for (int d = 0; d < DIM; d++) {
            descFile << descriptor[d] << "\t";
        }

        descFile << endl;
    }

    return 0;
}

int main(int argc, char **argv) {
    int ret;

    // 물체ID 파일의 열기
    ofstream objFile(OBJ_FILE);
    if (objFile.fail()) {
        cerr << "cannot open file: " << OBJ_FILE << endl;
        return 1;
    }

    // 특징량 파일의 열기
    ofstream descFile(DESC_FILE);
    if (descFile.fail()) {
        cerr << "cannot open file: " << DESC_FILE << endl;
        return 1;
    }

    // IMAGE_DIR의 영상 파일명을 주사(走査)
    DIR *dp = opendir(IMAGE_DIR);
    if (dp == NULL) {
        cerr << "cannot open directory: " << IMAGE_DIR << endl;
        return 1;
    }

    int objId = 0// 오브젝트 ID
    struct dirent *entry;
    while (1) {
        entry = readdir(dp);

        if (entry == NULL) {
            break;
        }

        // .과..는 무시한다.
        if (strncmp(entry->d_name, ".", 1) == 0 || strncmp(entry->d_name, "..", 2) == 0) {
            continue;
        }

        char *filename = entry->d_name;

        // SURF의 추출
        char buf[1024];
        snprintf(buf, sizeof buf, "%s/%s", IMAGE_DIR, filename);
        CvSeq *imageKeypoints = 0;
        CvSeq *imageDescriptors = 0;
        CvMemStorage *storage = 0;
        ret = extractSURF(buf, imageKeypoints, imageDescriptors, storage);
        if (ret != 0) {
            cerr << "cannot extract surf description" << endl;
            return 1;
        }

        // 파일에 출력
        ret = saveFile(objId, filename, imageKeypoints, imageDescriptors, objFile, descFile);
        if (ret != 0) {
            cerr << "cannot save surf description" << endl;
            return 1;
        }

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

        objId++;
    }

    objFile.close();
    descFile.close();
    closedir(dp);

    return 0;
}

위의 프로그램에서 990개의 영상으로부터 물체 ID파일과 특징량 파일의 작성에는,
저자의 환경에서는 약 5분 정도가 걸린다고 하네요.
description.txt는 420MB로 꽤 큽니다.
사이즈가 큰 것은 부동 소수점수(실수)를 텍스트로 저장하고 있습니다만,
바이너리로 저장하게 되면 사이즈는 더 작아집니다.
대규모의 영상을 대상으로 경우에는 공부가 더 필요합니다.

990장의 영상의 키포인트 수의 총합은 321,932개입니다.
1장당 평균 325개 정도네요.
Caltech 영상은 사이즈가 작기 때문에 적습니다.


물체 모델 데이터베이스를 특정물체인식에 어떻게 사용할까?

위의 과정에서 작성된 object.txt와 description.txt를
물체모델의 데이터베이스로 부르고 있네요.

이것을 특정물체인식 시스템에서는 어떻게 사용할지는
아래의 그림을 보면 대략적인 이해가 가능하다고 하네요.


이번 회에서는 990장의 영상을 위 그림의 우측 부분과 같이 물체 모델 데이터베이스를 작성해 보았습니다.






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

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


+ Recent posts