Python + OpenCVでらんてくんとキュゥ◯えの類似点を検出する

# 概要
この記事では、顔認証の技術などでも使われる顔の類似度判定を、OpenCVを使い実装したいと思います。
OpenCVは、画像・動画に関する処理機能をまとめたオープンソースのライブラリです。今回はらんてくんとQB（キュゥ◯え）の特徴点を比較してどれだけらんてくんがQBに似ているのかAKAZEという検出器で検証しました。

# 類似点判定の例
類似点を判定するために
- 2つの画像の特徴点を抽出する
- 2つの画像の特徴点から類似点を判定する

の手順で進めていきます。

下図の円で囲まれたところが特徴点です。特徴点抽出はその名の通り、画像の中から"特徴的な"ポイントを抽出するアルゴリズムです。使われる特徴としては角 (コーナー) が多いようですが、輝度の勾配なども使われるそうです。
![runtequn_02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520341/00c56d2a-76ed-8d94-9105-07f8d2813cdd.jpeg)![runtequn_03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520341/17aa62df-2eb4-ee2f-0d38-c95c764d0845.jpeg)

類似点を判定するにAKAZEと呼ばれる検出器を用います。下図のように特徴点が円で囲まれ類似している円と円が線で結ばれます。
![comparing_runtequn.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520341/8877f03a-824e-95cc-2f81-5e153122677b.jpeg)

# 比較画像
今回検証を行う画像です。ほぼ同じに見えます。
| ファイル名 | 画像 | 説明 |
|:-----------:|:------------:|:------------:|
| RQ.png | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520341/bbb29dd9-2cc7-8d8e-f6c3-31d686d32996.jpeg" alt="05" width="200px"> | らんてくん | 
| QB.png | <img src="https://qiita-image-store.s3.amazonaws.com/0/2520341/907f36d1-aa41-b58d-5210-9b348baf0f2c.jpeg" alt="05" width="200px"> | QB | 

# 環境構築
3ステップで環境を構築します。
1. pyenvをインストール
1. pythonをインストール
1. 仮想環境を構築

## pyenvのインストール
今回はHomebrewは入っている前提で話を進めます。
pyenvはpythonのバージョンを簡単に切り替えることができるツールです。Homebrewを使ってインストールしてみましょう。
```
$ brew install pyenv
```
次にpyenvのパスを通すためにシェルを編集します。vimは使いません。
```
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
$ echo 'eval "$(pyenv init -)"' >> ~/.zshrc
```
続いてシェルの保存をして適用します。
```
$ source ~/.zshrc
```
インストールが完了したら確認するためにバージョンを表示させてみます。
```
$ pyenv --version     
pyenv 2.3.6
```
pyenvのインストールを確認しました。

## pyenvでpythonをインストール
pyenvを使ってpythonをインストールしましょう。
バージョン切り替えを体験するために２つほどバージョンが違うpythonをインストールしてみます。
```
$ pyenv install 2.7.10
$ pyenv install 3.9.10
```
pythonのバージョンを切り替えてみます。localの場合は今いるディレクトリに、globalの場合は全体に反映されます。
```
pyenv local 3.9.10
```
ここできちんと変更が反映されているか確認するためpythonのバージョンを調べます。
```
$ python -V
Python 3.9.10
```
今いるディレクトリにPython3.9.10が反映されました。

## 仮想環境を作り、OpenCVを導入
venvはPython3.3以降に標準搭載されている仮想環境の構築ツールです。作成した仮想環境ごとにライブラリのインストールなどが可能です。

今回は~/Projというパスがあった時にRUNTEQ_Nというフォルダを作り，その環境内でOpenCVのコードを動かすことにします。
```
$ cd ~/Proj
$ mkdir RUNTEQ_N
$ cd RUNTEQ_N
```
実際に仮想環境を作成するには、このように書きます。
```
RUNTEQ_N $ python3 -m venv .venv
```
```.venv``` のところは任意で決められますが、```.venv``` とすることが多いので、仮想環境の名前を```.venv```にします。

仮想環境を有効化するには、下記のコマンドを使います。
```
RUNTEQ_N $ source .venv/bin/activate
```
仮想環境が有効であるときには、ターミナルの行の先頭に仮想環境の名前が書かれます。
```
(.venv) $
```
仮想環境が有効化されている状態で、必要なライブラリをpipを使ってインストールします。
```
(.venv) $ pip install numpy
(.venv) $ pip install opencv-python
(.venv) $ pip install opencv-contrib-python
```
これで環境は整いました。ようやく下準備完了です。

# 実装
それでは画像の類似度検証に入っていきます。
## ディレクトリ構成
初めにディレクトリ構造を示しておきます。
```shell-session
scripts
 ├─ feature_QB.py
 └─ feature_RQ.py
 └─ hist.py
comparing
 ├─ QB.png
 ├─ RQ.png
output
```

# 特徴抽出
それぞれの画像に対して次のプログラムを実行します。いきなり類似度の判定をすることもできるのですが、特徴抽出でどこが特徴点として抽出されるのかを確認しておきましょう。今回はコードの詳細説明は省きます。
```feature_RQ.py
# 特徴とはパターン認識に役立つ情報量の多い部分のこと
import cv2
import numpy as np
import copy
# 画像の読み込み
img_rq = cv2.imread("comparing/RQ.png")
# imgのコピー
img_kaze = copy.deepcopy(img_rq)
# 特徴抽出キーをAKAZEの中に入れる
kaze = cv2.AKAZE_create()
# 特徴点の情報を入れる
kp1 = kaze.detect(img_rq, None)
img_kaze = cv2.drawKeypoints(img_kaze, kp1, None)
# 画像の出力
cv2.imwrite("output/QB.jpg",img_kaze)
```
### 特徴抽出の結果
特徴点が多く検出されたので面白い結果が期待できそうです。
|らんてくん|QB|
|:-----:|:-----:|
| ![RQ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520341/6d759189-6cb8-5a97-7d00-585d557c808f.jpeg) | ![QB1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520341/501cfbb6-ce05-a666-63b7-6f88469db797.jpeg)|

### 類似点の判定
コードがやっていることは
1. 2つの画像の読み込み
1. 画像のグレースケール化
1. AKAZE検出器の生成
1. 検出器を使い特徴点を検出
1. BFMatcherによる特徴点の比較
1. 検出結果を描画
1. 検出結果を描画した画像を出力

になります。
```hist.py
#OpenCVをインポート
import cv2  

# １枚目の画像を読み込む
img_rq = cv2.imread("comparing/RQ.png") 
# ２枚目の画像を読み込む
img_qb = cv2.imread("comparing/QB.png") 

# １枚目の画像をグレースケールで読み出し
gray_rq = cv2.cvtColor(img_rq,cv2.COLOR_BGR2GRAY) 
# ２枚目の画像をグレースケールで読み出し
gray_qb = cv2.cvtColor(img_qb,cv2.COLOR_BGR2GRAY) 

# AKAZE検出器の生成
akaze = cv2.AKAZE_create() 
# gray1にAKAZEを適用、特徴点を検出
kp1, des1 = akaze.detectAndCompute(gray_rq,None) 
# gray2にAKAZEを適用、特徴点を検出
kp2, des2 = akaze.detectAndCompute(gray_qb,None) 

# BFMatcherオブジェクトの生成
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

# Match descriptorsを生成
matches = bf.match(des1, des2)

# matchesをdescriptorsの似ている順にソートする 
matches = sorted(matches, key = lambda x:x.distance)

# 検出結果を描画する
img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None, flags = cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)

#検出結果を描画した画像を出力する
cv2.imwrite("output/comparing_runtequn.jpg",img3)
```

### 類似点の判定結果
何も表示されない、、、ってことは類似点はない？！
類似点が判定されなくてちょっと悲しい
![QB_RQ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2520341/43b51624-a390-3121-45de-04299f32ef6c.jpeg)

## まとめ
今回使用した検出器であるAKAZEではらんてくんとQBが類似点がないことが分かりました。
この論争は終止符です。