LIREで画像の類似検索をしてみる

2018.02.13

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

LIREという画像検索のためのライブラリを試してみました。 本記事では以下の内容について記載しています。

  • LIREとは
  • サンプルを動かしてみる
  • インデックス作成と検索のコードを書いてみる

LIREとは

LIRE (Lucene Image Retrieval) is an open source library for content based image retrieval, which means you can search for images that look similar.

LIREは、似ている画像を検索するためのOSSのライブラリです。Javaです。

画像の特徴に対するLuceneのインデックスを作成し、作成したインデックスに対して画像での検索が行えます。

ドキュメントはこちらからご確認ください。

サンプルを動かしてみる

README.mdいわくアプリケーションに組み込みたい場合はサンプルアプリケーションを見てみると良い、とのことなのでざっくり動かしてみます。

サンプルでは、「ディレクトリ配下の画像ファイルのインデクシング」と「指定した画像に似ている画像のランク付け」ができます。

ダウンロード

こちらのSample Applicationからプロジェクトをダウンロードします。

解凍してコンソールからプロジェクトのルートディレクトリに移動します。

サンプルに利用する画像のディレクトリ設定

runIndexingのargsに画像ファイルが含まれるディレクトリのパスを、runSearchのargsに検索対象の画像ファイルのパスを指定します

task runIndexing(type: JavaExec) {
    classpath = sourceSets.main.runtimeClasspath

    main = 'net.semanticmetadata.lire.sampleapp.ParallelIndexing'

    // Define the directory where to find the images to index.
    args '/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_young_woman.jpg'
}

task runSearch(type: JavaExec) {
    classpath = sourceSets.main.runtimeClasspath

    main = 'net.semanticmetadata.lire.sampleapp.Searcher'

    // Define the image to be used as query
    args '/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_young_woman.jpg'
}

動かしてみる

インデクシング

指定した画像ファイルをインデクシングします。

$ ./gradlew runIndexing
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:runIndexing
Indexing images in /Users/inaba.jun/Desktop/ss/test
Getting all images in /Users/inaba.jun/Desktop/ss/test including those in subdirectories ~ Found 10 images
===================================================================================
SetUp:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set of GlobalFeatures:
AutoColorCorrelogram
CEDD
FCTH
===================================================================================
No need for sampling and generating codebooks.....
Indexing 10 images
Analyzed 10 images in 00:09 ~ 967.70 ms each.
Total time of indexing: 00:09.
Properties saved!
Finished indexing.

BUILD SUCCESSFUL

Total time: 31.392 secs

作成されたインデックスはindexディレクトリ配下に格納されます。

$ ls -la index
total 104
drwxr-xr-x  11 inabajunmr  staff    352  1 30 18:27 .
drwx------@ 15 inabajunmr  staff    480  1 30 19:45 ..
-rw-r--r--   1 inabajunmr  staff  21063 1 30 18:27 _2.fdt
-rw-r--r--   1 inabajunmr  staff     93  1 30 18:27 _2.fdx
-rw-r--r--   1 inabajunmr  staff    218  1 30 18:27 _2.fnm
-rw-r--r--   1 inabajunmr  staff    419  1 30 18:27 _2.si
-rw-r--r--   1 inabajunmr  staff    110  1 30 18:27 _2_Lucene50_0.doc
-rw-r--r--   1 inabajunmr  staff   1809  1 30 18:27 _2_Lucene50_0.tim
-rw-r--r--   1 inabajunmr  staff    243  1 30 18:27 _2_Lucene50_0.tip
-rw-r--r--   1 inabajunmr  staff    143  1 30 18:27 segments_2
-rw-r--r--   1 inabajunmr  staff      0  1 30 18:26 write.lock

検索

画像を検索します。 検索対象の画像には以下を使います。

$ ./gradlew runSearch
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:runSearch
0.0:    /Users/inaba.jun/Desktop/ss/test/kaisya_desk1_syachou_young_woman.jpg
3.7023253150876627:     /Users/inaba.jun/Desktop/ss/test/kaisya_desk1_syachou_young_man.jpg
14.759058277769086:     /Users/inaba.jun/Desktop/ss/test/kaisya_desk1_syachou_woman.jpg
15.771364119843213:     /Users/inaba.jun/Desktop/ss/test/kaisya_desk1_syachou_man.jpg
46.36033535484118:      /Users/inaba.jun/Desktop/ss/test/banzai_school.jpg
53.132664072463285:     /Users/inaba.jun/Desktop/ss/test/business_sabori_pc_solitaire.jpg
57.03463611351899:      /Users/inaba.jun/Desktop/ss/test/surprise_party_woman.jpg
58.86276653909239:      /Users/inaba.jun/Desktop/ss/test/surprise_party_man.jpg
59.136212624584715:     /Users/inaba.jun/Desktop/ss/test/moratorium_man.jpg
62.14486553621638:      /Users/inaba.jun/Desktop/ss/test/moratorium_woman.jpg

BUILD SUCCESSFUL

Total time: 3.955 secs

サンプルアプリケーションでは、似ている順に30位までのスコアと画像ファイルのパスが出力されます。 ちなみにスコアが小さい方がより似ている画像です。

検索対象の画像は以下です。

検索結果をスコア順に並べると以下のようになります。だいぶそれっぽい結果が出ました。

サンプルアプリケーションのソースコードは以下です。

サンプルと同じようなアプリを自分で実装してみる

実際にライブラリをどう使うのか確認するために画像のインデックス作成と検索機能を実装してみます。

Lireのビルド

Maven Centralに対象のライブラリがなかったので今回はローカルでビルドしたものを使います。

こちらのページからLireをダウンロードします。

ダウンロードしたファイル解凍し当該のディレクトリに移動してから、以下のコマンドでビルドします。

$ cd /Users/inaba.jun/Downloads/Lire-1.0b4
$ ./gradlew build

build/libsにビルドされたjarファイルができます。

コード

Gradleプロジェクトを新規作成します。 libディレクトリ配下に先ほどのjarファイルを配置します。

build.gradlew

version '1.0-SNAPSHOT'

apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'org.apache.lucene', name: 'lucene-core', version: '6.3.0'
    compile group: 'org.apache.lucene', name: 'lucene-analyzers-common', version: '6.3.0'
    compile group: 'org.apache.lucene', name: 'lucene-queryparser', version: '6.3.0'
    compile group: 'commons-io', name: 'commons-io', version: '2.5'
    compile group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1'
    compile group: 'com.sangupta', name: 'jopensurf', version: '1.0.0'
    compileOnly 'org.projectlombok:lombok:1.16.10'

    compile fileTree(dir: 'lib', include: '*.jar')
}

task index(type: JavaExec) {
    classpath = sourceSets.main.runtimeClasspath

    main = 'IndexMain'

    // 第一引数に画像ファイルを配置したディレクトリ、第二引数にインデックスの生成先
    args('/Users/inaba.jun/git/lire-sample/src/main/resources/imgs','/Users/inaba.jun/git/lire-sample/src/main/resources/index')
}

task search(type: JavaExec) {
    classpath = sourceSets.main.runtimeClasspath

    main = 'SearchMain'

    // 第一引数に検索対象の画像ファイル、第二引数にインデックスの生成先
    args('/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_young_woman.jpg','/Users/inaba.jun/git/lire-sample/src/main/resources/index')
}

Java

インデクシング
<br />import net.semanticmetadata.lire.builders.GlobalDocumentBuilder;
import net.semanticmetadata.lire.imageanalysis.features.global.AutoColorCorrelogram;
import net.semanticmetadata.lire.imageanalysis.features.global.CEDD;
import net.semanticmetadata.lire.imageanalysis.features.global.FCTH;
import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.FSDirectory;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class IndexMain {

    public static void main(String[] args) {
        String imageDir = args[0];
        String indexPath = args[1];

        indexDir(Paths.get(imageDir), Paths.get(indexPath));
    }

    private static void indexDir(Path imageDirPath, Path indexPath) {
        try {
            // ディレクトリの全てのファイルからインデックス作成
            Files.list(imageDirPath).forEach(f -> indexFile(f, indexPath));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void indexFile(Path imageFilePath, Path indexPath) {

        IndexWriterConfig conf = new IndexWriterConfig(new WhitespaceAnalyzer());
        try (IndexWriter indexWriter = new IndexWriter(FSDirectory.open(indexPath), conf);) {
            GlobalDocumentBuilder globalDocumentBuilder = new GlobalDocumentBuilder(false, false);
            globalDocumentBuilder.addExtractor(CEDD.class);
            globalDocumentBuilder.addExtractor(FCTH.class);
            globalDocumentBuilder.addExtractor(AutoColorCorrelogram.class);

            BufferedImage img = ImageIO.read(new FileInputStream(imageFilePath.toString()));

            // 指定したディレクトリに画像のインデックスを作成
            Document document = globalDocumentBuilder.createDocument(img, imageFilePath.toString());
            indexWriter.addDocument(document);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
検索
import net.semanticmetadata.lire.builders.DocumentBuilder;
import net.semanticmetadata.lire.imageanalysis.features.global.CEDD;
import net.semanticmetadata.lire.searchers.GenericFastImageSearcher;
import net.semanticmetadata.lire.searchers.ImageSearchHits;
import net.semanticmetadata.lire.searchers.ImageSearcher;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.store.FSDirectory;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;

public class SearchMain {

    public static void main(String[] args) {
        String imageFile = args[0];
        String indexPath = args[1];

        // 検索対象の画像とインデックスのパスを指定して検索
        search(Paths.get(imageFile), Paths.get(indexPath));
    }

    private static void search(Path imagePath, Path indexPath) {
        BufferedImage img = null;
        try {
            img = ImageIO.read(imagePath.toFile());

            IndexReader indexReader = DirectoryReader.open(FSDirectory.open(indexPath));
            // 最大10件まで検索
            ImageSearcher searcher = new GenericFastImageSearcher(10, CEDD.class);
            ImageSearchHits hits = searcher.search(img, indexReader);
            for (int i = 0; i < hits.length(); i++) {
                Set<String> fields = new HashSet<>();
                fields.add(DocumentBuilder.FIELD_NAME_IDENTIFIER);
                String identifier = indexReader
                        .document(hits.documentID(i), fields)
                        .getField(DocumentBuilder.FIELD_NAME_IDENTIFIER).stringValue();

                // マッチした画像のスコアとパスを出力
                System.out.println("Score:" + hits.score(i) + "\tFile:" + identifier);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

実行してみる

インデクシング
$ ./gradlew index
:compileJava UP-TO-DATE
:processResources
:classes
:index

BUILD SUCCESSFUL

Total time: 4.251 secs
検索
$ ./gradlew search
:compileJava
:processResources UP-TO-DATE
:classes
:search
Score:0.0       File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_young_woman.jpg
Score:3.7023253150876627        File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_young_man.jpg
Score:14.759058277769086        File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_woman.jpg
Score:15.771364119843213        File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_man.jpg
Score:46.36033535484118 File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/banzai_school.jpg
Score:53.132664072463285        File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/business_sabori_pc_solitaire.jpg
Score:59.136212624584715        File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/moratorium_man.jpg
Score:62.14486553621638 File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/moratorium_woman.jpg

BUILD SUCCESSFUL

Total time: 6.081 secs

やっていることは同じなのでサンプルと同様の結果が出ました。

まとめ

  • LIREは、似ている画像を検索するためのJavaのライブラリ
  • 画像の特徴に対するLuceneのインデックスを作成したり、インデックスに対して検索したりできる

本記事では、LIREを使って「画像のインデクシング」と「画像の検索」をしてみました。

私からは以上です。