OpenCVで特徴量の座標を取得する

この記事では、OpenCVで特徴量のマッチングおよび座標を取得する方法をまとめます。

# ライブラリのインストール

それぞれの好みや環境によると思うので状況に合わせてインストールしてみてください。
私はAnaconda経由でインストールする方法が楽なのでよく使います。

```common-lisp:
conda install opencv==3.3.1 --channel conda-forge
```
バージョン指定せずにインストールすると、最新版のOpenCVがインストールされると思います。

## 環境
- Python3.6.4
- OpenCV 3.3.1

# 特徴量マッチング
今回は無難にAKAZEを使用して実験をします。
まず、マッチングまでのコードは以下です。

OpenCV側の該当項目は以下のリンクです。
http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_feature2d/py_matcher/py_matcher.html

```python:
import cv2
import numpy as np
import matplotlib.pyplot as plt 

# fp は適当に置き換えてください
img1 = cv2.imread(fp, cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread(fp, cv2.IMREAD_GRAYSCALE)

akaze   = cv2.AKAZE_create()
matcher = cv2.BFMatcher(cv2.NORM_HAMMING)

# 特徴量検出
kp1, desc1 = akaze.detectAndCompute(img1, None)
kp2, desc2 = akaze.detectAndCompute(img2, None)

# ブルートフォースマッチング
matches = matcher.Match(desc1, desc2)
```

## 可視化
入力画像とマッチングの結果があれば、何も考えず描画するのは難しくありません。

```python:
output = cv2.drawMatches(img1, kp1, img2, kp2, matches, None, flags=4)
plt.imshow(output, cmap='gray')
plt.show()
```

flagsでは、描画の指定ができます。公式を参照すると

```C++:
struct DrawMatchesFlags
{
    enum
    {
        DEFAULT = 0, // Output image matrix will be created (Mat::create),
                     // i.e. existing memory of output image may be reused.
                     // Two source images, matches, and single keypoints
                     // will be drawn.
                     // For each keypoint, only the center point will be
                     // drawn (without a circle around the keypoint with the
                     // keypoint size and orientation).
        DRAW_OVER_OUTIMG = 1, // Output image matrix will not be
                       // created (using Mat::create). Matches will be drawn
                       // on existing content of output image.
        NOT_DRAW_SINGLE_POINTS = 2, // Single keypoints will not be drawn.
        DRAW_RICH_KEYPOINTS = 4 // For each keypoint, the circle around
                       // keypoint with keypoint size and orientation will
                       // be drawn.
    };
};
```

## kNNを用いたマッチング 
マッチングの際に、近い特徴量をいくつか取ってくる実装もあります。
ソースコードを確認していないので、予測でしかないですがk=1の時はMatchと同等なのではないでしょうか。

公式のドキュメントを参照すると、k=2でマッチングさせてマッチング性能の良いものだけを使用するratio-testが紹介されています。エッセンスとしては、近い二つの特徴点が存在する際に、より大きい方の特徴点（n.distance）の距離を少し小さくしても、小さい方の特徴点の距離(m.distance)が小さければ採択するテストです。

```python
knn = matcher.MatchKnn(desc1, desc2, k=2)
good = []
for n, m in knn:
    if m.distance < ratio * n.distance:
        good.append([m])
knn_img = cv2.drawMatchesKnn(img1, kp1, img2, kp2, knn, None, flags=4)
plt.imshow(knn_img, cmap='gray')
plt.show()
```

## 座標の照合
上記の二つのイメージを描画して見るとわかりますが、画像同士の特徴点をエッジで結んだ画像が得られていると思います。その座標を如何にして取得するか、いよいよ本題です。

まず、ソースコードから

```python

img1_pt = [list(map(int, kp1[m.queryIdx].pt)) for m in matches]
img2_pt = [list(map(int, kp2[m.trainIdx].pt)) for m in matches]
```

これだけです。
難しくないですね。

OpenCVの公式ドキュメントによると

*  http://opencv.jp/opencv-2.2/cpp/features2d_common_interfaces_of_descriptor_matchers.html
*  https://docs.opencv.org/3.4/d4/de0/classcv_1_1DMatch.html

matchesに格納されているDMmatchというクラスには、四つのパブリックな属性値が存在しています。

* DMatch.distance - Distance between descriptors. The lower, the better it is.
* DMatch.trainIdx - Index of the descriptor in train descriptors
* DMatch.queryIdx - Index of the descriptor in query descriptors
* DMatch.imgIdx - Index of the train image.

ブルートフォースのマッチングアルゴリズムでは、

```python:
matches = cv2.BFMatch().match(query_descriptor, train_descriptor)
```

でマッチングさせています。学習descriptorに対して、クエリのdescriptorを照会することで、どれくらいそれらの距離が離れているかを取得し、どれとどれをマッチングしているのか判定できるようにしています。
さらに、AKAZEのキーポイントには、座標情報が格納されているので

```
kp.pt -> (y:float, x:float)
```
をintにキャストすれば、画像配列中で座標を用いた操作をすることができるようになります。

#Implementation
とても簡単な例で確認しました。詳しいことは以下を参照してください

https://github.com/hayata-yamamoto/ReMindMe/blob/master/src/matching.ipynb

実装する際には、いらないものをKnnで切ったりすると適切な対応関係を持つ座標のみを利用することができます。
※ハズレのデータを取り除く作業は、データに応じて決めてください

この辺の記事がなかったので、誰かの参考になれば