OpenCV
Python3

Python3+OpenCVでパノラマ写真

More than 1 year has passed since last update.

Python3+OpenCVでパノラマ写真

コードはGitHubにあります。
https://github.com/takayoshiotake/qiita/tree/master/Python3%2BOpenCV%E3%81%A7%E3%83%91%E3%83%8E%E3%83%A9%E3%83%9E%E7%94%BB%E5%83%8F/src

画像用意

実験用の画像を用意します。有名な女性の写真を利用させていただきます。

$ curl -O http://www.cs.cmu.edu/~chuck/lennapg/lena_std.tif

今回はこれを2倍して1024x1024にし、そこから左上、右上、左下、右下からそれぞれ640x640で切り出した4枚の画像を利用します。

fig_1.png

Stitcher

OpenCVのStitcherを利用すると簡単にパノラマ画像が作れます。

stitchimg.py

import argparse
import cv2

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Stitch given images.')
    parser.add_argument("inputs", metavar='input', type=str, nargs='+', help='input file')
    parser.add_argument("--output", type=str, default='a.png', help='output file (default=a.png)')
    args = parser.parse_args()

    input_images = []
    for i in args.inputs:
        image = cv2.imread(i)
        if image is None:
            print(f'Error: Unable to open file "{i}".')
            exit()
        input_images.append(image)

    if len(input_images) == 1:
        cv2.imwrite(args.output, input_images[0])
    else:
        stitcher = cv2.createStitcher(True)
        stitched = stitcher.stitch(input_images)
        cv2.imwrite(args.output, stitched[1])

$ python3 stitchimg.py --output fig_2.png test_tl.tif test_tr.tif test_bl.tif test_br.tif

そこそこ時間がかかります。画像はほぼ完璧です。

fig_2.png

1024x1024を期待していたのですが、1025x1023になってしまいました。

完全一致

完全一致する行または列を探して、画像を合成します。1回の合成では水平方向(入力は左,右)か垂直方向(入力は上,下)のどちらかのみ、かつ、合成する方向に依存しますが画像の横幅か縦幅が一致することが条件です。

matchimg.py

import argparse
import cv2
import numpy as np

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Overlay given images with perfect matching.')
    parser.add_argument("inputs", metavar='input', type=str, nargs=2,
                        help='input file (inputs are (left, right) or (top, bottom))')
    parser.add_argument("--output", type=str, default='a.png',
                        help='output file (default=a.png)')
    parser.add_argument("--direction", choices=['h', 'v'], default='h',
                        help='overlay direction (default=h, h=horizontal, v=vertical)')
    parser.add_argument("--threshold", type=float, default=0,
                        help='matching threshold (default=0)')
    args = parser.parse_args()

    input_images = []
    for i in args.inputs:
        image = cv2.imread(i)
        if image is None:
            print(f'Error: Unable to open file "{i}".')
            exit()
        input_images.append(image)

    if args.direction == 'h':
        if input_images[0].shape[0] != input_images[1].shape[0]:
            print('Error: height not matched')
            exit()
        if input_images[0].shape[2] != input_images[1].shape[2]:
            print('Error: channels not matched')
            exit()
        match_h = -1
        for h in range(input_images[1].shape[1]):
            # if np.array_equal(input_images[0][:, -1], input_images[1][:, h]):
            if np.allclose(input_images[0][:, -1], input_images[1][:, h], args.threshold):
                match_h = h + 1
                break
        if match_h == -1:
            print(f'Error: Unable to match.')
            exit()
        # print(f'match_h={match_h}')
        stitched = np.hstack((input_images[0], input_images[1][:, match_h:]))
        cv2.imwrite(args.output, stitched)
    else:
        if input_images[0].shape[1] != input_images[1].shape[1]:
            print('Error: width not matched')
            exit()
        if input_images[0].shape[2] != input_images[1].shape[2]:
            print('Error: channels not matched')
            exit()
        match_v = -1
        for v in range(input_images[1].shape[0]):
            # if np.array_equal(input_images[0][-1], input_images[1][v]):
            if np.allclose(input_images[0][-1], input_images[1][v], args.threshold):
                match_v = v + 1
                break
        if match_v == -1:
            print(f'Error: Unable to match.')
            exit()
        # print(f'match_v={match_v}')
        stitched = np.vstack((input_images[0], input_images[1][match_v:]))
        cv2.imwrite(args.output, stitched)

$ python3 matchimg.py --output fig_3.png --direction h test_tl.tif test_tr.tif
$ python3 matchimg.py --output fig_4.png --direction h test_bl.tif test_br.tif
$ python3 matchimg.py --output fig_5.png --direction v fig_3.png fig_4.png

(画像大きいので fig_3.png と fig_4.png は省略します)

fig_5.png

元の画像に戻りましたが、現実的には完全一致するシーンはほぼないでしょう。一応 --threshold オプションで一致判定を緩くすることも可能にしておきました。