画像から特徴量を抽出し、透視変換行列を導出して画像を変形する

入力画像中に目的画像がある場合に、入力画像を目的画像にフィットするように透視変換行列を使って変形させる方法
※opencv-python 3.4にて動作確認済み

# サンプル画像

今回は例として目的画像をレトロな写真とし、入力画像は目的画像をPIXUSでL判印刷して適当な角度で撮影したものを使用している。この工程によって用紙の変形や照明の反射が映りこむなどの外乱を発生させられる。

|目的画像| ![showa-0.JPG](https://qiita-image-store.s3.amazonaws.com/0/227399/7834c940-e7fc-f665-6153-709552919334.jpeg)
|---|---|
|**入力画像**| ![showa-1.jpg](https://qiita-image-store.s3.amazonaws.com/0/227399/8244e806-9232-d052-3951-7fed9528b705.jpeg)
|**変換結果**|![aligned_00000.jpg](https://qiita-image-store.s3.amazonaws.com/0/227399/79de530d-3bb2-4b3c-a4d6-c45c508f579d.jpeg)

# ソースコード

- `img1`: 目的画像
- `img2`: 入力画像
- `max_pts`:検出する特徴量の最大数
- `good_match_rate`:全体の特徴量から透視変換行列に使用する割合（上位から取得する）

```python3
import cv2

def alignImages(img1, img2,
                max_pts=500, good_match_rate=0.15, min_match=10):
    # https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_feature2d/py_feature_homography/py_feature_homography.html#feature-homography

    # [1] ORBを用いて特徴量を検出する
    # Initiate ORB detector
    detector = cv2.ORB_create(max_pts)
    # find the keypoints and descriptors with SIFT
    kp1, des1 = detector.detectAndCompute(
        cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY), None
    )
    kp2, des2 = detector.detectAndCompute(
        cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY), None
    )
    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

    # [2] 検出した特徴量の比較をしてマッチングをする
    # Match descriptors.
    matches = bf.match(des1, des2)

    # Sort them in the order of their distance.
    matches = sorted(matches, key=lambda x: x.distance)
    good = matches[:int(len(matches) * good_match_rate)]

    # [3] 十分な特徴量が集まったらそれを使って入力画像を変形する
    if len(good) > min_match:
        src_pts = np.float32(
            [kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
        dst_pts = np.float32(
            [kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)
        # Find homography
        h, mask = cv2.findHomography(dst_pts, src_pts, cv2.RANSAC)

        # cv2.imwrite('draw_match.jpg', cv2.drawMatches(
        #     img1, kp1, img2, kp2, matches, None, flags=2))

        # Use homography
        height, width, channels = img1.shape
        dst_img = cv2.warpPerspective(img2, h, (width, height))
        return dst_img, h
    else:
        return img1, np.zeros((3, 3))
```
# 各工程の解説

## [1] ORBを用いて特徴量を検出する

SIFTやSURFなど特徴量検出器はいろいろ種類があるが、速度を重視してORBを選択した。ORB以外の検出器を利用したい場合は下記を参照。

**Feature Detection and Description**
<https://docs.opencv.org/3.0-beta/modules/features2d/doc/feature_detection_and_description.html>

## [2] 検出した特徴量の比較をしてマッチングをする

目的画像の特徴量`des1`と、入力画像の特徴量`des2`を比較してマッチングする。今回は総当たりのBasics of Brute-Force Matcherを使用しているが、FLANNなど他のマッチング方法を利用したい場合は下記を参照。

**Feature Matching**
<https://docs.opencv.org/3.0-beta/doc/py_tutorials/py_feature2d/py_matcher/py_matcher.html>

## [3] 十分な特徴量が集まったらそれを使って入力画像を変形する

マッチングした特徴量を変換し、目的画像特徴点`src_pts`と入力画像特徴点`dst_pts`を用意する。`findHomography()`ではRANSACを用いて透視変換行列`h`を導出している。RANSACにより使用された特徴量の情報は`mask`に格納されている。最後`warpPerspective()`に入力画像と先ほど得られた`h`を入力して画像を変換する。

**参考（特徴量の可視化）**
![a.jpg](https://qiita-image-store.s3.amazonaws.com/0/227399/13156745-cffe-8eb7-0403-42c37f2303ff.jpeg)

**findHomography**
<https://docs.opencv.org/3.0-beta/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#findhomography>

**warpPerspective**
<https://docs.opencv.org/3.0-beta/modules/imgproc/doc/geometric_transformations.html#warpperspective>

以上