GISTで画像認識

せっかくなのでGISTで画像認識をやってみます。
GISTは画像全体のシーン認識を目的に設計されており、このタスクに特に適しているとされています。

まず、GISTの開発者が提供している、8クラスのシーン画像データセットで試してみます。
以下のページからImages.zipを落としてきて展開します。
http://people.csail.mit.edu/torralba/code/spatialenvelope/

一つのディレクトリの中に全ての画像が置かれていますが、扱いやすいようにクラスごとに分けることにします。

$ cd spatial_envelope_256x256_static_8outdoorcategories
$ find . -name "*jpg" | xargs -l -i basename {} | cut -d'_' -f1 | sort | uniq | xargs mkdir
$ mv mountain*jpg mountain
$ mv opencountry*jpg opencountry
$ mv forest*jpg forest
$ mv coast*jpg coast
$ mv tallbuilding*jpg tallbuilding
$ mv street*jpg street
$ mv insidecity*jpg insidecity
$ mv highway*jpg highway

例のコードはppm/pgmしか読めないので、ImageMagickで変換します。
ヘッダにコメント領域が残ってるとおかしくなることがあるので-stripをつけます。このデータセットの画像は256x256のサイズにもともと揃えてあるので、サイズに関してはこのままで大丈夫です*1

$ find . -name "*jpg" | sed 's/\.jpg//g' | xargs -l -i convert -strip "{}.jpg" "{}.ppm"
$ find . -name "*jpg" | xargs rm

準備ができたので、データセットの特徴を抽出します。perlで適当にスクリプトを書いてみました。

#!/usr/local/perl

use strict;
use List::Util;

my $Ntrain=100;
my $imgdir="/home/XXX/spatial_envelope_256x256_static_8outdoorcategories";
my $cmd="/home/XXX/lear_gist-1.1/compute_gist";

opendir(IMGDIR, $imgdir) or die($imgdir);
my @classes = readdir(IMGDIR);
close(IMGDIR);

open(TRAIN, ">train.txt");
open(TEST, ">test.txt");

foreach my $class(@classes){
    next if ($class =~ /^\./ || -f $class);

    opendir(CLASS, "$imgdir/$class");
    my @files = readdir(CLASS);
    closedir(CLASS);

    @files = List::Util::shuffle @files;

    my $count = 0;
    foreach my $file(@files){
        next if( -d "$imgdir/$class/$file");
        print "$imgdir/$class/$file\n";

        open(GIST, "$cmd $imgdir/$class/$file |") or die("$cmd");
        my $gist = <GIST>;
        close(GIST);
        next if(!$gist);

        if($count<$Ntrain){
            print TRAIN "$file $class $gist";
        }else{
            print TEST "$file $class $gist";
        }
	$count++;
    }
}

close(TRAIN);
close(TEST);

$imgdir以下におかれたディレクトリから、各カテゴリの学習サンプルを一定数サンプリングし、残りをテストサンプルとしています。
なんかオーバーヘッドで時間とられているような気もしますがとりあえず無視。

無事特徴もとれたので、RのSVMで識別をやってみます。
kernlabパッケージが必要なのでインストールします。

> install.packages('kernlab')

作った特徴ファイルを読み込んでSVMを実行します。

> library('kernlab')
> train <- read.table('train.txt')
> test <- read.table('test.txt')
> ot8.svm <- ksvm(train[,2]~.,data=train[,3:962],cross=5)
> ot8.pre <- predict(ot8.svm,test[3:962])
> (ot8.tab <- table(test[,2],ot8.pre))
> mean(diag(ot8.tab)/rowSums(ot8.tab))

[1] 0.8074061

という感じで、識別率は約80%となりました。本当はサンプルを入れ替えながら何回か試行するべきですが。

ついでにRandomForestも試してみました。パラメータはデフォルトで。

> install.packages('randomForest')
> library('randomForest')
> set.seed(20)
> ot8.rf <- randomForest(train[,2]~.,data=train[,3:962])
> ot8.pre <- predict(ot8.rf,test[3:962])
> (ot8.tab <- table(test[,2],ot8.pre))
> mean(diag(ot8.tab)/rowSums(ot8.tab))

[1] 0.7760976

ちなみに、前回やった判別分析だと

library('MASS')
set.seed(20)
ot8.lda <- lda(train[,2]~.,data=train[,3:962])
ot8.pre <- predict(ot8.lda,test[3:962])
(ot8.tab <- table(test[,2],ot8.pre$class))
mean(diag(ot8.tab)/rowSums(ot8.tab))

[1] 0.4530859

けっこう次元が大きいので、正則化項を入れてやらないと無理っぽいです。

*1:サイズも変えたいときは convert -strip -geometry 256x256! "{}.jpg" "{}.ppm" のようにしてやるとよいでしょう。!がないと長辺を256ピクセルにしてしまうので注意