まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

IBM Watson から提供されている Visual Recognition(画像認識) API の1つで、ずっとベータ版として提供されていた Similarity Search (類似画像検索)API が正式公開となる前にサービス終了となりました:
Visual Recognition API – Similarity Search Update


非常に簡単に使うことができて、機能も便利で、ベータ版と知りつつ自分もいくつかのプロダクトの中で使ったり、そのサービスをブログやイベント等で紹介したりもしていました。詳しい経緯はわかりませんが、サービスが終了になったことは非常に残念でした。

実は以前にも同じようなことがありました。いわゆる「トレードオフ分析」を行う機能を API として提供していた Watson Tradeoff Analytics API は 2017/05/11 を以ってサービスの追加ができなくなりました(作成済みのサービスは 2018/04 まで使えます)。この API はちょうど自分がこの分析手法を勉強し始めた頃にサービス終了がアナウンスされ、とても残念でした。

この API に関してはちょうどトレードオフ分析を勉強しはじめた当初だったこともあり、自分でもトレードオフアルゴリズムを理解し始めた頃でもありました。で、こんな互換 API を作って公開したのでした:
Yet Another Watson Tradeoff Analytics API


さて話を元に戻して今回の Similarity Search API のサービス終了です。これも非常に残念なことでした。トレードオフ分析の時と異なるのは自分が画像検索アルゴリズムについて勉強していたわけではなく、互換 API を作るにはちとハードルが高かったということでした。

が、調べてみると画像検索にはいくつかの考え方やアルゴリズム、それらごとの向き/不向きがあって、比較的簡単に実現する方法もないわけではない、ということがわかりました。その1つがカラーヒストグラムを使った方法でした:
類似画像検索とカラーヒストグラム


オリジナルの Similarity Search API がこのアルゴリズムを使っていたかどうかは分からない(おそらく使っていない)のですが、とりあえずこのアルゴリズムによって「画像を学習する」&「学習した画像の中から類似した画像を見つける」という最低限必要な機能は実装できそうだ、と思えるようになりました。というわけで今回も互換 API の開発に挑戦してみたのでした。そしてとりあえず出来上がったものをオープンソース化したのがこちらです:
YASS(Yet Another Similarity Search)

2017092400



実装の対象としたのは Watson API Explorer の Visual Recognition API のページを参考に "Collections" カテゴリと "collection images" カテゴリにある全 12 API 、そしてオリジナルにはなかった「学習画像のバイナリを取得する API 」を合わせた 13 API です。オリジナル API のパラメータについては元のものとほぼ互換性を付けたつもりです。また新しい API のパラメータは URL 部分以外にないので特に説明は不要と思っています。なお「ほぼ互換性」という表現を使いましたが、互換性のない箇所は以下のとおりです:
  • オリジナル API の実行時に指定が必要だった API Key を廃止しました。実行時に API Key の指定は不要です。代わりに(必要であれば)Basic 認証をかけることができるようにしました。
  • オリジナル API の実行時に指定が必要だった version パラメータを廃止しました。実行時に version の指定は不要です。正確には version = '2016-05-20' の仕様に従ったものを実装しています。
  • オリジナル API の制限事項であった「1コレクションに対して 1,000,000 画像まで」という制約はありません。ただし画像バイナリごと Cloudant に格納されるため、Cloudant の容量にはお気をつけください。
  • 学習させた画像のバイナリ(画像そのもの)を取得するための API : GET /v3/collections/{collection_id}/images/{image_id}/binary を新たに追加しました。collection_id と image_id を指定して実行すると Content-Type と合わせて画像バイナリが API サーバーから返されます。
  • API 実行後の HTTP レスポンスには status という属性を含めるようにしました(このため、実行結果のフォーマットには互換性がありません)。status = true の場合は成功、false の場合はなんらかのエラーが発生したことを意味しています。

【前提環境】
この互換 API は Node.js で記述されています。従って API サーバーには Node.js のランタイム(と npm)がインストールされている必要があります。また画像や検索に必要な情報は Cloudant データベースに格納されます。2017/Sep/24 現在の仕様では IBM Bluemix 上の Cloudant を使う前提で記述されているため、IBM Bluemix のアカウントを取得して、Cloudant サービスを1つインスタンス化し、その username と password を用意してください。


【インストール方法】
(1) 上記サイトから git clone するか、zip download &展開して、ソースコードを取得します。
(2) ソースコードの settings.js を編集します(青字はコメント。最低限編集が必要になるのは上2つ):
exports.cloudant_username = '(Cloudant Username)';  // Cloudant の username(必須)
exports.cloudant_password = '(Cloudant Password)';  // Cloudant の password(必須)
exports.cloudant_db_prefix = 'yass_';               // Cloudant に作成するデータベース名の prefix(必須だが変更しなくてもよい)
exports.basic_username = 'username';                // Basic 認証をかける場合の username(''にした場合は Basic 認証なし)
exports.basic_password = 'password';                // Basic 認証をかける場合の password(''にした場合は Basic 認証なし)
exports.api_base_path = '/v3';                      // API の基本パス(''でもよいし、変更してもしなくても良い)
(3) npm install を実行
例) $ npm install

(4) Node.js で app.js を実行
例) $ node app.js

(5) curl やアプリケーションから API を利用
例) $ curl -XGET 'http://localhost:6001/v3/collections'


【システム構成】
Similarity Search API の "Collection" に相当する部分を「Cloudant のデータベース」に、画像データに相当する部分を「Cloudant のドキュメント」にみなして格納するようにしました。ドキュメントには attachment として画像ファイルそのものを格納すると同時に、類似検索ができるよう正規化されたカラーヒストグラム情報や、画像データの metadata を合わせて格納するようにしています:
2017092401


類似検索 API(POST /v3/collections/{collection_id}/find_similar)を実行した時の挙動はまず Collection から全画像を取り出し(つまり Collection に該当する Cloudant データベースから全ドキュメントを取り出し)、カラーヒストグラム情報を1つずつ取り出しては色の類似性を調べて、その上位何件かを返すというアルゴリズムにしています。必ずしも効率のよい検索方法ではないと理解していますが、カラーヒストグラムの比較を効率よく行う方法が思いついていないので、現状はこの方法にしています:
2017092402



【所感】
カラーヒストグラムアルゴリズムを採用したこの互換 API の検索精度はオリジナルのものとは同じではない可能性が高いと思っています(色が重要、どちらかというとイラストよりも写真の類似性に向いている、など)。あまり期待通りでなかったとしてもオープンソース化されているので該当部分のアルゴリズムを変更する、という方法もあると思っています(ちなみに MIT ライセンスで公開してます)。

あと、IBM Watson の互換 API を作ったのは今回が2回目ですが、このように「はじめから仕様が決まっている(仕様が変わらないことも決まっている) API を作る」のは楽です。ある程度、仕様として完成されているが故の安心感もありますし、何度も使っていた API なので動作確認も楽でした。実際、今回の互換 API は休暇を利用して(学習部分も含めて)ほぼ3日で作り上げました(「3日で作れる程度のアルゴリズム実装」と言えなくもないと思います)。


 

クラウドファインディングサイト "KICK STARTER" で出資した「世界最小4Gスマホ」を唄う "Jerry Pro" の完成品が届きました:
2017092301


箱を開けた状態がこれです。比較のため iPhone6 を隣においてますが、厚み以外は Jerry Pro の箱の方が小さいですw:
20170923


ここでサイズのスペックを書いておくと、本体は 43mm x 92.3mm x 13.3mm で、バッテリー込みで60.4 グラムです。今回購入した Jerry "Pro" とは別に Jerry というモデルもありますが、RAM と ROM のサイズだけです(Jerry($79) は 1GB + 8GB、Jerry Pro($95) は 2GB + 16GB)。バッテリーサイズは 850mAh で、最近のスマホと比べると流石に心もとないかもしれませんが、「大画面で動画を観る」ような使い方はないので大丈夫、なはず!! (^^;

カメラは背面 8MP + 正面 2MP で、画面サイズは 2.45 インチ(!)で 240 x 432 ピクセルです。そうそう、こういうのが欲しかったわけですよ。

ちなみに色は白、黒、青から選択できました。自分はなんとなく「青を買わないといけない」気がしたのでケースと併せて青にしておきました。 (^^;

左手でグリップした時の違いはこんな感じ。iPhone6 は親指を立てて握ることになりますが、Jerry Pro を同じ位置で握ると親指が余ってしまうので親指も曲げて握る感じになります:
20170923


最近のスマホでは珍しいと思いますが、裏蓋を(爪とかで)開けるためのロッチがあります:
IMG_1552



バリバリッ、と開けるとこんな感じ。バッテリーは(入手できればですが)自分で交換することもできそうです(最近のは精密になりすぎて、こういう仕組みが少ないのが残念):
IMG_1553


充電池をはずすとカード類を格納する部分が現れます。デュアルSIM機なので nano SIM が2つ同時に入ります。MicroSD カードもここから挿します。なおこの仕様により、SIM の差し替えには一度電源を OFF にする必要があります(電源を入れたまま SIM を差し替えることはできません):
IMG_1554


別売り(今回の KICKSTARTER のでは付属)のジャケットを換装して握り直してみた様子がこんな感じです:
IMG_1555


ここからは実際に電源を入れた画面になります。スクリーンショットでもよかったのですが、サイズ感をわかりやすくする目的で手と一緒に別カメラで撮影しました。ちなみに自分の手は男性としてはかなり小さいはずです(トレーニンググローブだとS サイズ)。


電源を入れた起動直後の画面です。うーん、この時点で見たこと無いくらい小さい。アイコンは3列しかなさそう:
IMG_1556


設定画面からスペックを確認しました。Android 7.0、クアッドコア、RAM メモリ 2GB。ROM の 16GB は MicroSD で拡張できるので気にしてません。最近アンドロイドスマホをあまり買っていなかったこともありますが、Android 7.0 搭載機を使うのは初めてかも:
IMG_1557


普段使いの日本語入力 simeji を入れて日本語フリック入力画面を確認してみました(検索履歴は無視してくださいw)。やっぱ入力はキツそうだな~(なぜか嬉しそう):
IMG_1560


英語フルキーボードだとこんな感じ、これはかなりキツいぞ・・・実際、普段のスマホと比べると打ち間違い率はそれなりに高い印象です:
IMG_1561


個人的にはあまり使わないのですが、テンキー入力はこんな感じ:
IMG_1562


Android なので普通にできることですが、日本語表示+日本ロケールに設定しました:
IMG_1564


パターンロック解除画面です。これでもかなりコンパクトになってるのがわかるかも:
IMG_1565


試しに Google Play Store を起動してみました。全体的に「詰まってる」感がありますね:
IMG_1566


とりあえずはこんな感じ。正直、テキスト入力にはあまり適したデバイスとは思えないので、今まで以上に音声入力を駆使することになるかもしれません。

でもその難点を克服して余りある魅力の詰まった端末です。最近のスマホは大型化がトレンドになっていて、昔のウォークマンみたいな「限られた小さい空間に技術を詰め込む」のが大好きな人間としては「このトレンドに一石を投じる意味でも、こういう考えのスマホが欲しかった」のでした。


類似画像検索の API というか機能というか、、を作っています。いずれはオープンソース化を念頭に入れているので、その成果もいずれ公開するつもりですが、今自分が作ろうとしている仕組みについて調べたことをまとめる目的で公開します。


類似画像検索では「ある2つの画像が似ているかどうか?」を数値で判断し、「こっちの画像よりもあっちの画像の方がより似ている」、「この画像に似ているのはこれとこれとこれと・・」みたいな識別ができるようにする必要があります。

その判断アルゴリズムにはいくつかの方法(基準)があります。おおまかに言うと「色合いが似てる」とか「輪郭が似てる」とかです。以下で紹介するカラーヒストグラムはそのひとつで、「同じような色合いの画像を似ていると判断する」方法です:
Color Histogram - Wikipedia


※この方法は「同じような色」の画像を「類似している」と判断します。輪郭や形を意識しているわけではありません。したがってあるカラー画像のモノクロ画像はあまり似ていない、という判断になると思います。また元の画像を中心から180度回転させたもの(上下逆さまにしたもの)は使っている色の構成自体は同じものなので「似ている」と判断することになります。


例えば横幅 W ピクセル、縦幅 H ピクセルの画像があったとします。ピクセル総数は W * H です。この ( W * H ) 個のピクセル毎に RGB 値を取得して、RGB の出現頻度のヒストグラム(階級度数分布)を作ります。ただし通常の RGB 値は #RRGGBB (RR, GG, BB は全て16進2桁の数)と表されるように R, G, B 各色毎に 256 種類の値を持ちますので、そのまま出現頻度を求めようとすると 256 * 256 * 256 = 16777216 種類もの出現パターンがあります。これだとカラーヒストグラムを作りにくい(傾向が出にくい)し、16777216 次元のベクトル演算が必要になるためちと面倒です。そこで各色毎の値を 4 種類として(つまり RR, GG, BB の各上位 2 ビットの値のみ使うように減色して)、4 * 4 * 4 = 64 次元のカラーヒストグラムを作ることにします。

あるピクセル ( x, y ) の RGB 値が #RRGGB という値であったとして、RR, GG, BB それぞれの上位2ビットを取得した結果が rr, gg, bb であったとします。このピクセルのカラー値 c(x,y) は 16 * rr + 4 * gg + bb で表すことができ、この値は 0 から 63 までの 64 種類の値を取り得ます。

この c(x,y) を全ての x と y(0 <= x < W, 0 <= y < H) について計算し、求めた c(x,y) の分布結果でカラーヒストグラムを作成します。例えばこの画像(314 * 340):
baseball


のカラーヒストグラム(分布表)を計算すると、以下のようになりました:
2017092001


カラーヒストグラムの見方は横軸が上記方法で計算したカラー値、縦軸がその出現個数です。一番左の数はカラー値が0、つまり「黒」の数で、一番右は「白」の数です。どうしてもこの2つの値は多くなりがちであると思いますが、それ以外にも点々とした箇所があり、これが画像の(色合いの)特徴を表しているわけです。

もう1つ、こちらの画像(340 * 340):
basketball



で同じカラーヒストグラムを作ると、以下のようになります:
2017092001


このようにしてあらかじめ画像を登録する際にカラーヒストグラムの計算結果を合わせて記憶するようにします。そして類似画像検索時にはカラーヒストグラムの近いものを探す、というアルゴリズムで実装できます。例えばこちらの画像(340 * 340)が上記2つの画像のどちらに似ているか、という識別をしてみます:
snooker


この画像のカラーヒストグラムはこのようになりました:
2017092002


簡単にいうと、このヒストグラムだけを比較して、「ヒストグラムはどの画像に近いか?」を判断することになります。もちろん画像のサイズは全て同じではないのでそこは正規化する必要もありますが、ロジックそのものはこれで実現できそうです。


もちろん「このロジックで精度の高い類似画像検索が実現できるか?」は別に考慮する必要があります。たとえば先程の上限逆さまにしたこの画像:
snooker


これを「元の画像と似ている」と判断すべきか、「似ていない」と判断すべきかはケース・バイ・ケースだと思っています(似ていると判断したいケースもあれば、似ていないと判断したいケースもある)。このカラーヒストグラムを使ったアルゴリズムの場合は回転結果は全く同じカラーヒストグラムを生成することになるので「似ている」と判断したい時に有効なアルゴリズムの1つと言えますが、「似ていない」と判断させたいのであれば輪郭などを元に判断するアルゴリズムを採用する必要がある、という意味です。

自前で類似画像検索を作成しようとしている人の参考になれば。


このページのトップヘ