OpenCV-Pythonは2枚の写真を自動的にパノラマにつなぎます。


背景の紹介
写真のパノラマは今では珍しくなくなりました。現在のスマートカメラと携帯カメラは基本的に写真自動パノラマの機能を持っていますが、撮影者に設備の安定と一方向の移動写真を維持するように要求します。これは、つなぎ合わせの画像の間には、相似の領域が必要であり、つなぎ合わせの正確さと完全性を保証するためである。本論文は主にPythonとOpenCVライブラリを使って、2枚の画像の自動的な綴り合わせを実現する方法を簡単に説明します。まず、2枚の写真のつなぎ合わせの原理を簡単に紹介します。
基本原理
2枚の写真の単純な綴りを実現するには、2枚の写真の中の似ている点(少なくとも4つは、homography行列の計算に少なくとも4つの点が必要です。)を見つけて、1枚の写真を計算すると、他のピクチャの変換行列(homography単応性マトリックス)に変換できます。このマトリクスでその画像を変換して別の写真の位置に置きます。このようにして、簡単なパノラマスティッチングが可能になります。もちろん、つなぎ合わせた後に写真が重なってしまうので、画像の重畳部分のピクセル値を再計算する必要があります。だから、まとめてみると、実は二つのステップです。
1.2枚の写真の似ている点を探して、変換行列を計算します。
2.一枚の写真を変えて別の写真の適当な位置に置き、重複領域の新しいピクセル値を計算する(ここは写真の融合に必要な戦略です。)
具体的に実現する
似たような点を探す
もちろん、似たような点は手で探すことができますが、これはちょっと面倒くさいです。似たような点が多ければ多いほど、あるいは似たような点に対応する位置が正確であればあるほど、結果は良いのですが、人間の肉眼で探している位置にはいつも誤差があります。したがって、スマートな人が自動的に似ている点を探すアルゴリズムを設計しました。ここではSIFTアルゴリズムを使いました。OpenCVもSIFTアルゴリズムのインターフェースを提供してくれましたので、自分で苦労して実現する必要がありません。次のように二つのテスト画像の原図と似ている点を見つけた後の写真です。


赤色の点はSIFTアルゴリズムによって見出された類似点であり、緑色の線はすべての見出された類似点の中で選別された信頼性の高い類似点を表している。アルゴリズムで見つけた類似点は必ずしも100%が正しいとは限らないからです。その後、これらのフィルタリングされた類似点から変換行列を計算することができ、もちろんOpenCVも対応するインターフェースを提供し、具体的なコード実装はOpenCVのPython tutorilに[1]を見つけることができる。
写真の続き
変換行列を計算したら、次は第二のステップで、計算した変換行列で一つの図を変換し、変換した写真を他の写真と重ねて、重複領域の新しいピクセル値を再計算します。重なり領域のピクセル値を計算するには、良い融合効果を実現するための様々な方法があります。直白とは、画像の線形グラデーションを実現し、重複した領域に対して、左の部分に近く、左の画像の内容を多く表示させ、右の部分に近く、右の画像の内容を多く表示させます。式で表すと、alphaは画素点の横軸から左右の重複領域の境界横軸までの距離を仮定して、新しいピクセル値はnewpixel=左図のピクセル値となります。× (1-アルファ)+右画像の素の値× アルファ。このようにして簡単な融合効果を実現することができます。より複雑でより良い効果を実現するためには、multi-band融合を検索してみてもいいです。最後に実現した結果とコードを添付します。ご参考ください。

Pythonコードは以下の通りです。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

if __name__ == '__main__':
    top, bot, left, right = 100, 100, 0, 500
    img1 = cv.imread('test1.jpg')
    img2 = cv.imread('test2.jpg')
    srcImg = cv.copyMakeBorder(img1, top, bot, left, right, cv.BORDER_CONSTANT, value=(0, 0, 0))
    testImg = cv.copyMakeBorder(img2, top, bot, left, right, cv.BORDER_CONSTANT, value=(0, 0, 0))
    img1gray = cv.cvtColor(srcImg, cv.COLOR_BGR2GRAY)
    img2gray = cv.cvtColor(testImg, cv.COLOR_BGR2GRAY)
    sift = cv.xfeatures2d_SIFT().create()
    # find the keypoints and descriptors with SIFT
    kp1, des1 = sift.detectAndCompute(img1gray, None)
    kp2, des2 = sift.detectAndCompute(img2gray, None)
    # FLANN parameters
    FLANN_INDEX_KDTREE = 1
    index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
    search_params = dict(checks=50)
    flann = cv.FlannBasedMatcher(index_params, search_params)
    matches = flann.knnMatch(des1, des2, k=2)

    # Need to draw only good matches, so create a mask
    matchesMask = [[0, 0] for i in range(len(matches))]

    good = []
    pts1 = []
    pts2 = []
    # ratio test as per Lowe's paper
    for i, (m, n) in enumerate(matches):
        if m.distance < 0.7*n.distance:
            good.append(m)
            pts2.append(kp2[m.trainIdx].pt)
            pts1.append(kp1[m.queryIdx].pt)
            matchesMask[i] = [1, 0]

    draw_params = dict(matchColor=(0, 255, 0),
                       singlePointColor=(255, 0, 0),
                       matchesMask=matchesMask,
                       flags=0)
    img3 = cv.drawMatchesKnn(img1gray, kp1, img2gray, kp2, matches, None, **draw_params)
    plt.imshow(img3, ), plt.show()

    rows, cols = srcImg.shape[:2]
    MIN_MATCH_COUNT = 10
    if len(good) > MIN_MATCH_COUNT:
        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)
        M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)
        warpImg = cv.warpPerspective(testImg, np.array(M), (testImg.shape[1], testImg.shape[0]), flags=cv.WARP_INVERSE_MAP)

        for col in range(0, cols):
            if srcImg[:, col].any() and warpImg[:, col].any():
                left = col
                break
        for col in range(cols-1, 0, -1):
            if srcImg[:, col].any() and warpImg[:, col].any():
                right = col
                break

        res = np.zeros([rows, cols, 3], np.uint8)
        for row in range(0, rows):
            for col in range(0, cols):
                if not srcImg[row, col].any():
                    res[row, col] = warpImg[row, col]
                elif not warpImg[row, col].any():
                    res[row, col] = srcImg[row, col]
                else:
                    srcImgLen = float(abs(col - left))
                    testImgLen = float(abs(col - right))
                    alpha = srcImgLen / (srcImgLen + testImgLen)
                    res[row, col] = np.clip(srcImg[row, col] * (1-alpha) + warpImg[row, col] * alpha, 0, 255)

        # opencv is bgr, matplotlib is rgb
        res = cv.cvtColor(res, cv.COLOR_BGR2RGB)
        # show the result
        plt.figure()
        plt.imshow(res)
        plt.show()
    else:
        print("Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT))
        matchesMask = None
Reference
[1]OpenCVチュートリアル:https://docs.opencv.org/3.4.1/d1/de0/tutorial_py_フィーチャーhomography.
ここで、OpenCV-Pythonについて2枚の写真を自動的にパノラマにするという文章を紹介しました。OpenCVの写真は自動的にパノラマになります。以前の文章を検索してください。または下記の関連記事を引き続きご覧ください。これからもよろしくお願いします。