Rubyでホーム画面の画像解析をしてみた


## はじめに
[Ruby Advent Calendar 2017 - Qiita](https://qiita.com/advent-calendar/2017/ruby) の18日目の記事です。

## 動作環境
Ruby - 2.4.3
OpenCV - 2.4.13.4

## モチベーション
個人開発でNakamyというサービスを開発しています。Nakamyは下記のようなサービスです。

[Nakamy | 友達のiPhoneの中身を覗けるサービス](https://nakamy.com/)

![ogp_image.png](https://qiita-image-store.s3.amazonaws.com/0/30233/74d0c8ee-190a-4d20-d1a7-45ca2946f333.png "ogp_image.png")

> Nakamyは友達のiPhoneの中身を覗けるサービスです。ホーム画面のスクリーンショットをアップロードすることで、Facebook上の友達とホーム画面を共有でき、友達がインストールしているアプリを見ることができます。

MVPとしてホーム画面のスクリーンショットをアップロードして友達と共有できる機能のみでα版としてリリースしたのですが、ゆくゆくは個人がどのアプリを保持しているのかという情報まで取得し、レコメンドなどに繋げたいと思っています。

そこで、ホーム画面のスクリーンショットの画像からどのアプリが入っているかという情報を得ることをゴールとします。

## 方法
今回は、OpenCVのテンプレートマッチングを試してみます。OpenCVはインテルが開発したオープンソースのコンピュータビジョン向けのライブラリです。

### OpenCVのインストール

ruby-opencvがまだ3系に対応していないので、2系をインストールします。

```console:console
$ brew install opencv@2

==> Summary
🍺  /usr/local/Cellar/opencv@2/2.4.13.4: 278 files, 35.6MB
```

### RubyでOpenCVを使えるようにする

こちらのライブラリを使います。

[ruby-opencv/ruby-opencv: Versioned fork of the OpenCV gem for Ruby](https://github.com/ruby-opencv/ruby-opencv)

READMEに

```console:console
$ gem install ruby-opencv -- --with-opencv-dir=/path/to/opencvdir
```

このように書かれているので、各自の環境に合わせて引数を渡してインストールします。

```console:console
$ gem install ruby-opencv -- --with-opencv-dir=/usr/local/Cellar/opencv@2/2.4.13.4
```

これで、rubyでOpenCVを使う環境が整いました。

### 元となるデータを用意する
検出するアプリケーションの画像として、App Storeより、「Facebook」のアプリケーションのアイコン画像を収集しました。

![facebook.jpg](https://qiita-image-store.s3.amazonaws.com/0/30233/d8b6ead9-71f5-033b-7be0-b49193169edc.jpeg "facebook.jpg")


今回は、「ホーム画面の中にFacebookアプリが含まれているかどうか」の判定を行います。

元となるホーム画面のスクリーンショットのデータとして、Facebookが含まれているパターンと含まれていないパターンのホーム画面を用意しました。

#### Facebookが含まれているパターン

僕のiPhoneのホーム画面のトップです。

<img src="https://qiita-image-store.s3.amazonaws.com/0/30233/8898933a-acf2-054f-8038-0f2d7c13b7b0.png" width=375px>

#### Facebookが含まれていないパターン

Facebookのところをekiboに変更しました。

<img src="https://qiita-image-store.s3.amazonaws.com/0/30233/aa1a9837-ddee-4fe3-6917-52cf528bf65d.png" width=375px>

### コード

用意したFacebookのアイコンをテンプレートとし、ホーム画面に対して、テンプレートマッチングを行う処理を書きました。

Facebookアイコンが含まれている場合はtrueを、含まれていない場合はfalseを出力します。

また、Facebookアイコンが含まれている場合はその箇所を赤色の矩形で囲み画像ファイルを出力します。

`min_score` の `0.1` は閾値です。高くするほど、検出率は上がりますが、誤検出の可能性が高くなります。似ているアイコンが多く存在する場合など、閾値は調整が必要です。

```ruby:home_screen_analyzer.rb
require 'opencv'
include OpenCV

image = OpenCV::IplImage.load("images/home_screens/001.png")
template = OpenCV::IplImage.load("images/apps/facebook.jpg")

result = image.match_template(template, CV_TM_SQDIFF_NORMED)
min_score, max_score, min_point, max_point = result.min_max_loc

if min_score < 0.1
  from = min_point
  to = OpenCV::CvPoint.new(from.x + template.width, from.y + template.height)
  image.rectangle!(from, to, :color => OpenCV::CvColor::Red, :thickness => 1)
  p true
else
  p false
end

image.save_image("images/outputs/001.png")
```

## 結果

### Facebookが含まれているパターン

```console:console
$ ruby home-screen-analyzer.rb
true
```

<img src="https://qiita-image-store.s3.amazonaws.com/0/30233/bee41057-c0c8-2bce-70bc-90e63f4870ee.png" width=375px>

赤線で囲われていますね。


### Facebookが含まれていないパターン
```console:console
$ ruby home-screen-analyzer.rb
false
```

<img src="https://qiita-image-store.s3.amazonaws.com/0/30233/fd88adda-f530-8f7c-d251-b012f59ca6e4.png" width=375px>

こちらの画像には赤線がありません。

## おわりに
今回は、RubyでOpenCVを用いたテンプレートマッチングを行い、ホーム画面の中に特定のアプリがあるかどうかの判定を行いました。

実際にサービスに導入する際は、検証するアプリの数が膨大に増えるため、テンプレートマッチングだと計算量が多くなりすぎてしまうと思っています。代替案として、アイコンの配置でマスキングして、バッジなどの雑音を取り除いた上でアイコン毎に画像を切り出し、BASE64化して比較するのが良いのではと思っています。この辺、こうやった方がいいんじゃない？といったアドバイスなどがございましたら、コメントいただけますと助かります。

### 参考
[ruby-opencvを利用して顔検出をする - Qiita](https://qiita.com/yucchiy/items/59d2d55385452b17929d)