image-matchの使い方

## はじめに
[image-match](https://github.com/ascribe/image-match)は平たく言えば画像検索エンジンです。
任意の画像を登録し、画像をキーに画像を見つけることができます。Elasticsearchと組み合わせることで非常に高速に画像検索を行うことも可能です。

## 前提条件
| 項目 | 説明 |
|------|------------------|
| 検証日 | 2016.04.08 |
| OS   | Mac OS X 10.10.5 |
| Python | 2.7.11 |
| Elasticsearch | 2.2.1 |

## 事前準備
```
$ brew install py2cairo cairo elasticsearch
$ pip install numpy scipy image_match
```

## 使い方

### 画像比較してみる
image-matchのREADMEの例をやってみます。

```first.py
from image_match.goldberg import ImageSignature

gis = ImageSignature()
# a = gis.generate_signature('https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg/687px-Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg')
a = gis.generate_signature('687px-Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg')
#b = gis.generate_signature('https://pixabay.com/static/uploads/photo/2012/11/28/08/56/mona-lisa-67506_960_720.jpg')
b = gis.generate_signature('mona-lisa-67506_960_720.jpg')

print(gis.normalized_distance(a, b))

c = gis.generate_signature('https://upload.wikimedia.org/wikipedia/commons/e/e0/Caravaggio_-_Cena_in_Emmaus.jpg')
print(gis.normalized_distance(a, c))

d = gis.generate_signature('https://c2.staticflickr.com/8/7158/6814444991_08d82de57e_z.jpg')
print(gis.normalized_distance(a, d))
```

実行してみます。

```
$ python first.py
0.220951701409
0.684462753815
0.422527138625
```
対象画像にURLを指定すると画像を取得して検索することができます。時間が少しかかるので、事前に`curl`などで落として直接ファイルのパスを指定するとよいでしょう。

### with Elasticsearch
Elasticsearchと一緒に使ってみましょう。

```
$ elasticsearch
[2016-04-08 15:18:47,952][INFO ][node                     ] [Alexander Lexington] version[2.2.1], pid[59417], build[d045fc2/2016-03-09T09:38:54Z]
[2016-04-08 15:18:47,952][INFO ][node                     ] [Alexander Lexington] initializing ...
[2016-04-08 15:18:48,945][INFO ][plugins                  ] [Alexander Lexington] modules [lang-expression, lang-groovy], plugins [], sites []
[2016-04-08 15:18:48,998][INFO ][env                      ] [Alexander Lexington] using [1] data paths, mounts [[/ (/dev/disk1)]], net usable_space [69.9gb], net total_space [232.6gb], spins? [unknown], types [hfs]
[2016-04-08 15:18:48,998][INFO ][env                      ] [Alexander Lexington] heap size [990.7mb], compressed ordinary object pointers [true]
[2016-04-08 15:18:48,999][WARN ][env                      ] [Alexander Lexington] max file descriptors [10240] for elasticsearch process likely too low, consider increasing to at least [65536]
[2016-04-08 15:18:52,884][INFO ][node                     ] [Alexander Lexington] initialized
[2016-04-08 15:18:52,884][INFO ][node                     ] [Alexander Lexington] starting ...
[2016-04-08 15:18:53,095][INFO ][transport                ] [Alexander Lexington] publish_address {127.0.0.1:9300}, bound_addresses {[fe80::1]:9300}, {[::1]:9300}, {127.0.0.1:9300}
[2016-04-08 15:18:53,109][INFO ][discovery                ] [Alexander Lexington] elasticsearch_hattori-h/FnX_ySN8RP2my8GcBTZsvw
[2016-04-08 15:18:56,148][INFO ][cluster.service          ] [Alexander Lexington] new_master {Alexander Lexington}{FnX_ySN8RP2my8GcBTZsvw}{127.0.0.1}{127.0.0.1:9300}, reason: zen-disco-join(elected_as_master, [0] joins received)
[2016-04-08 15:18:56,182][INFO ][http                     ] [Alexander Lexington] publish_address {127.0.0.1:9200}, bound_addresses {[fe80::1]:9200}, {[::1]:9200}, {127.0.0.1:9200}
[2016-04-08 15:18:56,182][INFO ][node                     ] [Alexander Lexington] started
[2016-04-08 15:18:56,230][INFO ][gateway                  ] [Alexander Lexington] recovered [0] indices into cluster_state
[2016-04-08 15:34:22,441][INFO ][cluster.metadata         ] [Alexander Lexington] [images] creating index, cause [auto(index api)], templates [], shards [5]/[1], mappings [image]
[2016-04-08 15:34:22,889][INFO ][cluster.routing.allocation] [Alexander Lexington] Cluster health status changed from [RED] to [YELLOW] (reason: [shards started [[images][4]] ...]).
[2016-04-08 15:34:22,998][INFO ][cluster.metadata         ] [Alexander Lexington] [images] update_mapping [image]
```
Elasticsearchを起動後に、以下のスクリプトを実行します。

```first_with_es.py
import json
from elasticsearch import Elasticsearch
from image_match.elasticsearch_driver import SignatureES

es = Elasticsearch()
ses = SignatureES(es)

image_set = (
    'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg/687px-Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg',
    'https://pixabay.com/static/uploads/photo/2012/11/28/08/56/mona-lisa-67506_960_720.jpg',
    'https://upload.wikimedia.org/wikipedia/commons/e/e0/Caravaggio_-_Cena_in_Emmaus.jpg',
    'https://c2.staticflickr.com/8/7158/6814444991_08d82de57e_z.jpg',
)
for img in image_set:
    print("add image to Elasticsearch. img=%s" % img)
    ses.add_image(img)


search_image_set = (
    'https://pixabay.com/static/uploads/photo/2012/11/28/08/56/mona-lisa-67506_960_720.jpg',
    'http://i.imgur.com/CVYBCCy.jpg',
    'http://i.imgur.com/T5AusYd.jpg',
)
for img in search_image_set:
    print("=== search %s ===" % img)
    print(json.dumps(ses.search_image(img, all_orientations=True), indent=2))
```

```
$ first_with_es.py
add image to Elasticsearch. img=https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg/687px-Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg
add image to Elasticsearch. img=https://pixabay.com/static/uploads/photo/2012/11/28/08/56/mona-lisa-67506_960_720.jpg
add image to Elasticsearch. img=https://upload.wikimedia.org/wikipedia/commons/e/e0/Caravaggio_-_Cena_in_Emmaus.jpg
add image to Elasticsearch. img=https://c2.staticflickr.com/8/7158/6814444991_08d82de57e_z.jpg
=== search https://pixabay.com/static/uploads/photo/2012/11/28/08/56/mona-lisa-67506_960_720.jpg ===
[
  {
    "path": "https://pixabay.com/static/uploads/photo/2012/11/28/08/56/mona-lisa-67506_960_720.jpg",
    "score": 2.435569,
    "dist": 0.0,
    "id": "AVP0lC4XSbcGjA3_XZUG"
  },
  {
    "path": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg/687px-Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg",
    "score": 0.029808408,
    "dist": 0.22095170140933634,
    "id": "AVP0lCRBSbcGjA3_XZUF"
  },
  {
    "path": "https://c2.staticflickr.com/8/7158/6814444991_08d82de57e_z.jpg",
    "score": 0.004886414,
    "dist": 0.42325822368808808,
    "id": "AVP0lDflSbcGjA3_XZUI"
  }
]
=== search http://i.imgur.com/CVYBCCy.jpg ===
[
  {
    "path": "https://pixabay.com/static/uploads/photo/2012/11/28/08/56/mona-lisa-67506_960_720.jpg",
    "score": 0.20739666,
    "dist": 0.15454905655638429,
    "id": "AVP0lC4XSbcGjA3_XZUG"
  },
  {
    "path": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg/687px-Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg",
    "score": 0.016346568,
    "dist": 0.24980626832071956,
    "id": "AVP0lCRBSbcGjA3_XZUF"
  },
  {
    "path": "https://c2.staticflickr.com/8/7158/6814444991_08d82de57e_z.jpg",
    "score": 0.0031033582,
    "dist": 0.43156216266051023,
    "id": "AVP0lDflSbcGjA3_XZUI"
  }
]
=== search http://i.imgur.com/T5AusYd.jpg ===
[
  {
    "path": "https://pixabay.com/static/uploads/photo/2012/11/28/08/56/mona-lisa-67506_960_720.jpg",
    "score": 1.5544797,
    "dist": 0.069116439263706961,
    "id": "AVP0lC4XSbcGjA3_XZUG"
  },
  {
    "path": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg/687px-Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg",
    "score": 0.029808408,
    "dist": 0.22484320805049718,
    "id": "AVP0lCRBSbcGjA3_XZUF"
  },
  {
    "path": "https://c2.staticflickr.com/8/7158/6814444991_08d82de57e_z.jpg",
    "score": 0.004886414,
    "dist": 0.42394015619010844,
    "id": "AVP0lDflSbcGjA3_XZUI"
  }
]
```
`score`の数値が大きい（`dist`が小さい,近い）ほど似ている画像ということになります。