차근차근/OpenCV

비슷한 이미지 검색 시스템을 만들자

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

http://aidiary.hatenablog.com/entry/20091003/1254574041


비슷한 이미지 검색 시스템을 만들자

C + + 버전의 OpenCV를 사용하여 컬러 히스토그램을 이용한 유사 이미지 검색을 실험 해 보았습니다. 일괄 처리 등의 스크립트는 Python을 사용하고 있습니다 만, Perl에서도 Ruby에서도 비슷한 느낌으로 할 수 있습니다.

지정된 이미지와 유사한 이미지를 검색하는 시스템은 비슷한 이미지 검색 시스템 이라고합니다. Google이나 Yahoo! 이미지 검색 쿼리에 키워드를 넣는 키워드에 관련된 이미지를 찾지 만, 비슷한 이미지 검색에서 쿼리에 이미지를주는 것이 특징입니다. 이 분야는 Content-Based Image Retrieval (CBIR) 이라고하고 최신 서베이 논문 (Datta, 2008)을 보면 1990 년대 초반과 꽤 옛날부터 연구되고 있습니다.

최신 기술은 색상, 모양, 질감, 특징점 등 다양한 특징 량을 이용하여 유사도를 판정하는 합니다만, 이번에는 가장 간단한 「색」을 이용한 유사 이미지 검색을 실험 해 보았다 있습니다. 즉, 지정된 이미지에 색이 비슷한 이미지를 검색합니다.

컬러 히스토그램의 계산

컬러 히스토그램은 이미지 중에 각 색깔이 무엇 픽셀인지 세어 만든 막대 입니다. 이미지는 컬러 히스토그램으로 나타낼 수 있습니다. 예를 들어,

f : id : aidiary : 20091003183015j : plain

컬러 히스토그램을 그리면

f : id : aidiary : 20091003112030p : plain

입니다. 각 막대는 특정 색이 화상 중에 몇 픽셀인지를 계산 한 값입니다. 위의 히스토그램에서 튀어 나온 것은 청색 네요! 이 히스토그램의 모양이 서로 비슷한 정도 색이 비슷한 이미지입니다. 컬러 히스토그램은 각 빈의 Y 축 값 만 저장하여두면 좋기 때문에 위 그림의 경우 64 차원 벡터로 나타낼 수 있습니다.

이미지의 색상 감소

이번 대상으로하는 컬러 이미지 빨강 성분 (R), 녹색 성분 (G), 청색 성분 (B)은 각 8 비트 (256)이므로, 표시 색상 수는 256x256x256 = 16777216 자리입니다. 이것은 색상이 너무 너무 (히스토그램 막대가 16777216 개, 16777216 차원 벡터) 때문에 발칵 잘라 64 색 까지 감색합니다.

감색은 RGB의 각 성분을 4 등분하여 중앙의 대표 값으로 대체하여 실시합니다. R이 4 거리, G가 4 거리, B가 4 가지이므로 표시 색상 수는 4x4x4 = 64 가지입니다. 원본 이미지의 RGB는 색상 감소 후 대표 값으로 바꿉니다. 예를 들어, 픽셀의 밝기가 RGB = (58, 150, 238)이라면 64 색 RGB = (32, 160, 224)로 바꿉니다.

 


f : id : aidiary : 20091003110507p : plain
※ 컬러 이미지 대칭 을 수정

 

OpenCV를 사용하여 대칭이 이미지를 어떻게 바뀔까 시도합니다.

f : id : aidiary : 20091003112029j : plain f : id : aidiary : 20091003183015j : plain

왼쪽이 원본 사진에서 오른쪽이 64 색에 減色 한 사진입니다. 음 64 색 있으면 적당히 알지요! 다음 64 色減 색상 이​​미지를 그리는 OpenCV 프로그램입니다.

# include "cv.h" # include "highgui.h" # include <cstdio> // 64 색 감색 // R : 4 등분, G : 4 등분, B : 4 등분으로 4x4x4 = 64 색 uchar decleaseColor ( int value) { if (value < 64 ) { return 32 ; } else IF (value < 128 ) { return 96 ; } else IF (value < 196 ) { return 160 ; } else { return 224 ; } return 0 ;

// 미 도달 } int Main ( int argc, char ** argv) { // 이미지의로드 IplImage * img = cvLoadImage ( "Data/dolphin_image_0001.jpg" , CV_LOAD_IMAGE_COLOR); if (img == NULL ) { printf ( "cannot load image \ n " ); return 1 ; } // 대칭 이미지의 메모리 확보 IplImage * outImage = cvCreateImage (cvGetSize (img), img-> depth, 3 ); // 64 색 감색 for ( int Y = 0 ; y <img-> height; y + +) { // y 라인 데이터의 시작 포인터를 가져 uchar * pin = (uchar *) (img-> imageData + y * img-> widthStep); uchar * pout = (uchar *) (outImage-> imageData + y * outImage-> widthStep); for ( int x = 0 ; x <img-> width; x + +) { int blue = pin [ 3 * x + 0 ]; int Green = pin [ 3 * x + 1 ]; int Red = pin [ 3 * x + 2 ]; // 64 색減色한 이미지를 만들어 pout [ 3 * x + 0 ] = decleaseColor (blue); pout [ 3 * x + 1 ] = decleaseColor (green); pout [ 3 * x + 2 ] = decleaseColor (red); } } // 윈도우를 생성 cvNamedWindow ( "Original Image" , CV_WINDOW_AUTOSIZE); cvNamedWindow ( "Declease Color Image" , CV_WINDOW_AUTOSIZE); // 그림을 그릴 cvShowImage ( "Original Image" , img); cvShowImage ( "Declease Color Image" , outImage); cvWaitKey ( 0 ); // 이미지 파일로 인쇄 cvSaveImage ( "original.jpg" , img); cvSaveImage ( "color64.jpg" , outImage); // 뒤처리 cvDestroyAllWindows (); cvReleaseImage (& img); cvReleaseImage (& outImage); return 0 ; }

k-Means는 클러스터링 알고리즘 감색 샘플 이있었습니다. 이것은 이번 목적에 맞지 않는다고 생각합니다. 이미지에 따라 사용 64 색 (팔레트)가 달라지기 때문입니다. 이제 서로 다른 이미지간에 색상 히스토그램을 비교할 수 없게합니다 * 1 .

히스토그램의 계산

히스토그램은 각 색상 (64 색)이 이미지 중에 무엇 픽셀인지 계산 한 것입니다. 방금 전 64 색減色했기 때문에 각 색깔에 0 ~ 63 번까지 번호를 달아 히스토그램의 빈 번호합니다 . 예를 들어, 픽셀의 RGB 값이 RGB = (58, 150, 238)이라면 감색하면 (redNo, greenNo, blueNo) = (0,2,3)입니다 (아래 그림 참조). 이 색을 0-63까지의 빈 번호로 변환하려면

f : id : aidiary : 20091003110507p : plain 

redNo * 4 * 4 + greenNo * 4 + blueNo

에 있습니다. 이렇게 정의하면 (redNo, greenNo, blueNo) = (0,0,0)의 색은 0, (redNo, greenNo, blueNo) = (3,3,3) 색상은 63 번에 할당 있습니다. 방금 전의 (redNo, greenNo, blueNo) = (0,2,3)은 11 번이 되는군요.

아래는 원본 이미지의 RGB 휘도 값에서 히스토그램의 빈 번호를 요구하는 함수입니다.

/ ** 
* 원본 이미지의 RGB 히스토그램의 빈 번호를 계산 
* @ param [in] red 빨강 밝기 (0-255) 
* @ param [in] green 녹색 밝기 (0-255) 
* @ param [in ] blue 파랑의 밝기 (0-255) 
* @ return 히스토그램의 빈 번호 (64 색 칼라 인덱스) 
* / 
int rgb2bin ( int Red, int Green, int blue) {
     int redNo = red / 64 ;
     int greenNo = green / 64 ;
     int blueNo = blue / 64 ;
     return  16 * redNo + 4 * greenNo + blueNo;
}

그런 다음 이미지의 모든 픽셀을 스캔하여 각 색깔이 무엇 픽셀인지 세어 컬러 히스토그램을 만듭니다. OpenCV에서 이미지를로드하고 이미지의 각 픽셀 값에 액세스하여 빈 번호로 변환하고 열거 할 수 있습니다. 컬러 히스토그램을 만들 뿐이라면 방금처럼 일부러 대칭 이미지를 만들 필요가 없습니다 .

/ ** 
* 히스토그램을 계산 
* @ param [in] filename 이미지 파일 이름 
* @ param [out] histogram 히스토그램 
* @ return 성공 0 충돌 -1 
* / 
int calcHistogram ( char * filename, int histogram [ 64 ]) {
     // 히스토그램을 초기화 
    for ( int I = 0 ; i < 64 ; i + +) {
        histogram [i] = 0 ;
    }

    // 이미지로드
    IplImage * img = cvLoadImage (filename, CV_LOAD_IMAGE_COLOR);
    if (img == NULL ) {
        cerr << "cannot open image :" << filename << endl;
         return - 1 ;
    }

    // 64 색 감색하여 히스토그램을 계산 
    for ( int Y = 0 ; y <img-> height; y + +) {
         // y 라인 데이터의 시작 포인터를 가져
        uchar * pin = (uchar *) (img-> imageData + y * img-> widthStep);
        for ( int x = 0 ; x <img-> width; x + +) {
             int blue = pin [ 3 * x + 0 ];
             int Green = pin [ 3 * x + 1 ];
             int Red = pin [ 3 * x + 2 ];

            // 색상 값 (0-63)를 계산 
            int bin = rgb2bin (red, green, blue);
            histogram [bin + = 1 ;
        }
    }

    cvReleaseImage (& img);

    return  0 ;
}

이 함수를 실행하면 histogram에는 각 빈의 픽셀 수가 포함됩니다. 컬러 히스토그램은 64 차원 벡터입니다.histogram [0]은 0 번째 색상의 픽셀 수, histogram [63]은 63 번째 색상의 픽셀 수입니다.

이 히스토그램은 검색시 일일이 다시 계산하고는 힘들 기 때문에 파일에 저장해야합니다. 각 숫자를 각 행에 출력 64 줄의 파일입니다.

/ ** 
* 히스토그램을 파일에 출력 
* @ param [in] filename 출력 파일 이름 
* @ param [in] histogram 히스토그램 
* @ return 성공 0 충돌 -1 
* / 
int writeHistogram ( char * filename, int histogram [ 64 ]) {
    ofstream outFile (filename);
    if (outFile.fail ()) {
        cerr << "cannot open file :" << filename << endl;
         return - 1 ;
    }

    for ( int I = 0 ; i < 64 ; i + +) {
        outFile << histogram [i] << endl;
    }

    outFile.close ();

    return  0 ;
}

이 과정을 정리 한 메인 함수입니다.

main.cpp 

/ ** 
* 메인 함수 hist.exe 입력 이미지 파일 이름 출력 히스토그램 파일 이름 
* @ param [in] argc 
* @ param [out] argv 
* @ return 성공 0 충돌 -1 
* / 
int Main ( int argc, char ** argv) {
     int ret;

    if (argc < 2 ) {
        cerr << "usage : hist.exe [image file] [hist file]" << endl;
         return - 1 ;
    }

    char * imageFile = argv [ 1 ];
     char * histFile = argv [ 2 ];

    cout << imageFile << "->" << histFile;

    // 히스토그램을 계산 
    int histogram [ 64 ];
    ret = calcHistogram (imageFile, histogram);
    if (ret < 0 ) {
        cerr << "cannot calc histogram" << endl;
         return - 1 ;
    }

    // 히스토그램을 파일로 출력
    ret = writeHistogram (histFile, histogram);
    if (ret < 0 ) {
        cerr << "cannot write histogram" << endl;
         return - 1 ;
    }

    cout << "... OK" << endl;
     return  0 ;
}

컴파일하면 exe 파일이 있습니다. (주) OpenCV 설치 및 링커 설정이 필요합니다 . Eclipse에서 OpenCV (2009-10-16) 참조.

hist.exe dolphin_image_0001.jpg dolphin_image_0001.hst

과 같이 사용합니다. dolphin_image_0001.hst은 히스토그램을 저장할 파일 이름입니다.

이미지 데이터 세트를 준비

실험에 사용되는 이미지 데이터 세트를 제공합니다. 자신이 가지고있는 이미지 데이터 및 Flickr에서 모아도 좋다고 생각 합니다만, 유명한 테스트 데이터가 일부 공개되어 있기 때문에 그것을 사용합니다. 이러한 데이터 집합은 일반 물체 인식 작업 데이터 세트인데 뭐 좋을까? 만약 비슷한 이미지 검색을위한 데이터를 알고 있다면 코멘트 기쁩니다.

이 실험에서는, Caltech 101을 사용했습니다. 131MB, 9145 이미지 및 데이터 규모도 편리합니다. 사실은 150 만장의 tinyimages에서 시도 싶지만 더 고급 검색 기술을 사용하지 않으면 경쟁 할 수 없어 지요. 다운로드 한 데이터의 압축을 풀면 101_ObjectCategories라는 폴더에 101 개 범주 폴더 (102 개 있지만 BACKGROUND_Google가 불필요한?)가 있고 그 안에 이미지가 많이 있습니다. 예를 들어, airplanes 폴더 (카테고리)에 비행기 사진이 많이 들어 있습니다 * 2 . 크기는 각각 다르지만 대략 300x300 정도입니까? 여러가지 이미지가있어보고있는 것만으로도 즐겁습니다.

(주) 이하, BACKGROUND_Google과 Faces 폴더를 삭제합니다.

폴더마다 이미지 파일이 분산되고 있기 때문에 일괄 처리 할 수​​ 있도록 caltech101라는 폴더에 이미지 파일 만 정리합니다. 또한 각 카테고리 폴더에있는 이미지 파일 이름은 image_0001.jpg 이라든지 붙어 있습니다. airplanes (비행기)에도 dolphin (돌고래)에도 파일 이름이 붙고 있기 때문에 구별 못해 귀찮습니다. 그래서 아래와 같이 변환합니다.

airplanes/image_0001.jpg -> caltech101/airplanes_image_0001.jpg 
dolphin/image_0001.jpg -> caltech101/dolphin_image_0001.jpg

일괄 변환 python 스크립트입니다.

# coding : utf-8 import 코덱 import OS import shutil TARGET = "101_ObjectCategories" OUTDIR = "caltech101" for Category in os.listdir (TARGET) : for file in os.listdir ( "% s / % s" % (TARGET, category)) : image_file = "% s / % s / % s" % (TARGET, category, file )

# 101_ObjectCategories/airplanes/image_0001.jpg rename_file = "% s / % s_ % s" % (OUTDIR, category, file )

# caltech101 / airplanes_image_0001.jpg print "% s -> % s" % (image_file, rename_file) shutil.copyfile (image_file, rename_file)

이제 9145 장의 이미지 파일 만 caltech101 폴더에 리네이무 복사됩니다. 이제 히스토그램 추출 처리 등 쉬워집니다.

컬러 히스토그램의 일괄 계산

다음은 아까 만든 컬러 히스토그램을 요구 hist.exe를 사용하여 이미지에서 히스토그램을 일괄 계산합니다. 폴더 처리 라든지는 C + +보다 Python이 더 편해서 Python 스크립트에서 hist.exe를 실행하도록했습니다. hist 폴더를 만든 후 실행하면 caltech101 폴더의 모든 이미지의 히스토그램 파일이 만들어집니다. 파일 이름은 xxx.jpg 경우 xxx.hst입니다. 우리 시스템은 CPU가 Core i7 8 코어, 메모리가 3GB이지만, 9000 장의 이미지 히스토그램 변환 처리에 25 분 40 초 걸렸습니다.

# coding : utf-8 
import 코덱
 import OS

TARGET = "caltech101" 
OUTDIR = "hist"

for  file  in os.listdir (TARGET) :
    image_file = "% s / % s" % (TARGET, file )           # caltech101/xxx.jpg 
    hist_file = "% s / % s.hst" % (OUTDIR, file [: - 4 ])   # hist / xxx.hst 
    OS . system ( "hist.exe % s % s" % (image_file, hist_file))

이미지의 유사성

이제 모든 이미지의 컬러 히스토그램을 계산하여 파일에 저장되었습니다. 다음은 드디어 비슷한 이미지 검색합니다. 이미지 사이의 유사성은 Swain 1991에서 제안 된 Histogram Intersection 를 사용해보십시오.

Histogram Intersection은 2 가지 색상 히스토그램이 주어 졌을 때의 유사성을줍니다. 유사도이므로 두 히스토그램이 비슷한만큼 큰 값이됩니다. 정의는 간단하고 두 히스토그램을 H1, H2, 히스토그램 H의 i 번째 빈 값을 H [i] 정의하면

f : id : aidiary : 20091003190614p : plain

입니다. min (X, Y)은 X와 Y의 작은 값을 반환하는 함수입니다. 즉, 2 개의 히스토그램의 해당 각 빈 값으로 작은 숫자를 합한 가면 좋은 것이군요. 왜 이것할지 조금 고민 버립니다 만 · · · 히스토그램은 어딘가 出張る 어딘가이 탑재 성질이있는 것이 미소 일까? 2 개의 막대 그래프가 동일한 경우 최대 값을 취합니다.

그러나 히스토그램은 픽셀이므로 이미지 크기가 클수록 히스토그램이 높아지고 버립니다. 그래서 픽셀 수에 따라 값이 변하지 않도록 아래와 같이 정규화합니다. 정규화하면 Histogram Intersection 값은 0.0에서 1.0 사이의 값입니다. 1.0의 경우 두 히스토그램은 정확히 일치합니다 .

f : id : aidiary : 20091003190615p : plain 

비슷한 이미지 검색

쿼리로 준 이미지와 기타 모든 이미지와 Histogram Intersection을 계산하여 유사도가 높은 순으로 상위 10 위를 반환 Python 스크립트입니다. Python의 이미지 처리 라이브러리 PIL (Python Image Library)을 사용하기 때문에 별도 설치하십시오. 모든 이미지 히스토그램은 미리 메모리에로드 해 둡니다 . 메모리에 실린 않으면 결과가 되돌아 오는 것이 몹시 늦어 진다고 생각합니다. 더 많은 이미지되면 메모리에 실리 같이 좀더 고급 궁리가 필요하게 될지도.

# coding : utf-8 
import 코덱
 import OS
 import sys
 from PIL import Image

IMAGE_DIR = "caltech101" 
HIST_DIR = "hist"

# 히스토그램을로드하는 
def  load_hist () :
    hist = {}
    for  file  in os.listdir (HIST_DIR) :
        h = []
        print  "load % s ..." % file ,
        fp = open ( "% s / % s" % (HIST_DIR, file ), "r" )
         for LINE in FP :
            line = line.rstrip ()
            h.append ( int (line))
        fp.close ()
        hist [ file ] = h
         print  "OK" 
    return hist

# 정규화 Histogram Intersection을 계산하기 
def  calc_hist_intersection (hist1, hist2) :
    total = 0 
    for I in  range ( len (hist1)) :
        total + = min (hist1 [i], hist2 [i])
     return  float (total) / sum (hist1)

def  Main () :
    hist = load_hist ()
    while  True :
         # 쿼리가되는 히스토그램 파일 이름을 입력 
        query_file = raw_input ( "query?>" )
        
        # 종료 
        IF query_file == "quit" :
             break
        
        # 존재하지 않는 히스토그램 파일 이름의 경우 이동 
        IF  not hist.has_key (query_file) :
             print  "no histogram" 
            continue
        
        # 쿼리와 다른 모든 이미지 사이의 유사도를 계산
        result = []
        query_hist = hist [query_file]
        for target_file in hist.keys () :
            target_hist = hist [target_file]
            d = calc_hist_intersection (query_hist, target_hist)
            result.append ((d, target_file))
        
        # 유사도 큰 순서대로 정렬 
        result.sort (reverse = True )
        
        # 상위 10 위를 표시 (1 위는 쿼리 이미지) 
        # PIL을 사용하여 300x300를 1 단위로 2 행 5 열 10 개의 이미지를 나란히 그릴 
        p = 0 
        canvas = Image.new ( "RGB" ( 1500 , 600 ), ( 255 , 255 , 255 ))   # 흰 캔버스 
        for score, filename in result [ 0 : 10 :
             print  "% f \ t % s " % (score, filename)
            img = Image. open ( "caltech101 /" + filename [: - 4 + "jpg" )
            pos = ( 300 * (p % 5 ), 300 * (p / 5 ))
            canvas.paste (img, pos)
            p + = 1 
        canvas.resize (( 1500 / 2 , 600 / 2 ))
        canvas.show ()
        canvas.save (query_file + "jpg" , "JPEG" )

IF __ name__ == "__main__" :
    main ()

실험

시험 삼아 아래 돌고래의 이미지 (dolphin_image_0001.hst)를 쿼리로 비슷한 이미지를 찾아 봅니다. 쿼리 문자열은. hst이 필요하므로주의하십시오 .

f : id : aidiary : 20091003112029j : plain 

query?> dolphin_image_0001.hst 
1.000000 dolphin_image_0001.hst 
0.833222 hawksbill_image_0087.hst 
0.826443 dolphin_image_0008.hst 
0.801974 watch_image_0033.hst 
0.797467 airplanes_image_0332.hst 
0.776834 revolver_image_0054.hst 
0.767412 brain_image_0086.hst 
0.764227 airplanes_image_0562.hst 
0.760596 dolphin_image_0028.hst 
0.754693 airplanes_image_0475.hst

왼쪽의 숫자는 쿼리와 대상 이미지 간의 Histogram Intersection (유사도)입니다. 클수록 비슷합니다. 그림이 없다고 모르기 때문에 상위 10 위까지 화상을 싣고 있습니다. 기본적으로 1 위 쿼리 화상과 같은 유사성은 1.0입니다.

f : id : aidiary : 20091003205004j : plain

속에 비친있는 것이 같은지 여부는 관계 없습니다. 지금은 색만으로 유사성을 판단하는 때문입니다. 글쎄 파랑이고 비슷하다고 말하면 비슷한 있네요 -. 다음 다른 예입니다. 왼쪽이 쿼리 이미지입니다.

f : id : aidiary : 20091003205005j : plain
f : id : aidiary : 20091003205006j : plain
f : id : aidiary : 20091003205007j : plain
f : id : aidiary : 20091003205008j : plain
f : id : aidiary : 20091003205009j : plain

색상 만 닮았 네요!

정리

이번에는 색을 이용한 유사 이미지 검색을 만들었습니다.

  • 비 RGB 표색계 (HSV 든가)을 가진
  • 감색 수의 영향을보기
  • Histogram Intersection 대​​신 유클리드 거리, Earth Mover 's Distance (EMD) 등 다른 히스토그램 거리 지표를 사용
  • 큰 이미지 데이터베이스를 대상으로 한 효율적인 근사 검색 알고리즘의 도입 병렬화
  • 다양한 데이터 세트에서 시도

라든지 여러 가지 시험하고 싶은 일이 있으므로 실험 해보고 지요. 단지, 이번 결과에서 알 수 있듯이 색상만으로는 비슷하다고는 생각되지 않는 이미지가 들어가 버립니다 . 앞으로는 색 이외의 특징 량을 이용하거나, 이미지 분할 진행 예정입니다. 또한 일반 물체 인식에 가면 이미지 속에 비친 물체를 식별하여 이미지를 검색 할 수 있을지도? 청하는 기대!

* 1 컬러 히스토그램이 아닌 Earth Mover 's Distance (2012/8/4)라는 거리 척도를 사용 가능합니다

* 2 이 데이터는 일반 물체 인식 작업에서 자주 사용되는 것 같기 때문에 비행기 사진을 보여 airplanes 판정 있다면 정답으로하는 걸까요?

 

 


반응형