神戸のデータ活用塾!KDL Data Blog

KDLが誇るデータ活用のプロフェッショナル達が書き連ねるブログです。

【やってみた】OpenCVで動画フレームからパノラマ画像の生成

株式会社神戸デジタル・ラボ DataIntelligenceチームの原口です。

今回はOpenCVを用いて動画フレームからパノラマ画像の生成にチャレンジします!

パノラマ画像とは?

パノラマ画像とは広大な風景を一枚の画像に収めたものを指します。

通常、スマートフォンやデジタルカメラで撮影できる画像サイズでは下の図の赤枠部分のように、広大な風景の一部しか撮影できません。

一方パノラマ画像は縦長・横長に風景を撮影できるため、通常の画像サイズと比較してより大きな感動を与えることができ、風景の撮影に適しています。

通常の画像サイズでの撮影

パノラマでの撮影

パノラマ画像の撮影方法

普段何気なく利用しているパノラマ画像撮影機能ですが、どのように撮影されているのでしょうか?

今回はソフトウェアの合成によって作成するパノラマ画像ついて説明します。

スマートフォンを用いたパノラマ撮影では、下の図のような撮影を行います。

スマートフォンを用いたパノラマ撮影

この時スマートフォン内部では、スマートフォンを左から右へスライドさせるごとに画像の撮影が行われています。今回はスマートフォンを右にスライドした際に下の図のような画像が撮影されたと仮定します。

撮影された画像

このような画像をパノラマ写真にするには画像を連結する必要があります。例として左から1・2枚目を用いて連結します。

画像を連結する方法① 画像から特徴抽出

これらの画像を連結するには、まず画像から特徴抽出を行います。特徴抽出とは、画像の持つ輝度(明るさの度合い)の変化や輝度勾配(輝度変化の激しさの度合い)の方向のような様々な特徴を抽出することを指します。

特徴抽出の方法にはORB・SIFT・SURF・AKAZEなどのアルゴリズムがありますが、よく使われるのはSIFTです。

SIFTは2020年3月に特許が切れた最も有名な特徴検出アルゴリズムの一つです。AIが発展した今でも、画像マッチングの問題があれば「とりあえずSIFTで試してみる」と言われるほど強力で有名な手法です。

SIFTを用いて各画像から特徴を抽出した結果が下の図です。それぞれのマルが特徴を表しています。

SIFTは画像を様々な画像サイズに変換し特徴抽出を行います。マルの大きさは特徴が抽出された画像サイズを、マル内の線は輝度勾配の回転方向を表しています。

特徴点抽出結果

画像を連結する方法② 特徴を基に画像連結

続いてパノラマ画像を生成するために画像同士を連結します。両方の画像に対して類似した特徴が存在した場合、その領域が重なるように画像を連結します。

まずは二枚の画像間で類似する特徴を検索します。緑のラインはマッチングした点同士を結んでいます。

マッチング結果

続いてマッチングした領域を合成します。

合成結果

パノラマ画像の合成は上のような処理が複数回行われることで実現されています!

パノラマ画像生成を実装

それでは実際にパノラマ画像生成の実装を行いましょう!

動画の読み込み

まずは撮影した風景動画を読み込み、確認しましょう。path/to/video.mp4には、撮影した動画までのパスを入力してください。

撮影した動画フレームが表示されれば成功です!

import cv2 # OpenCVの呼び出し

video = cv2.VideoCapture('path/to/video.mp4') # 動画の読み込み

while True:
    ret, frame = video.read() #1フレームずつ取り出す
    if ret == False:          #動画が終わっている場合は終了する
        break
    cv2.imshow("test",frame)  #取り出したフレームを表示する
    cv2.waitKey(int(1 / video.get(cv2.CAP_PROP_FPS) * 1000))#撮影時のフレームレートを自動で認識し、それに応じて待機。(60FPSなら1/60)
cv2.destroyAllWindows() #ウィンドウを閉じる
フレームの切り出し

パノラマ画像の生成を行うにはマッチングに利用する画像が必要です。今回は動画から画像を切り出したものを利用しましょう!

動画の読み込みの際はcv2.imshow("test",frame)という処理を利用しましたが、今回は画像の切り出しなのでcv2.imwrite(f"./cut_img/{counter:05}.jpg",frame)に変更し、フレームごとに保存します。

動画の連結時には、数フレームに一枚の画像があれば実行可能です。今回はskip_frame = 30で、30フレームごとに画像を保存する処理にしています。

動画内の場面変化が遅い場合はskip_frameの数字を大きくすることで切り出し枚数を抑えることができ、変化が激しい場合は小さくすることでより精巧な合成が可能になります。

import os   #osというライブラリを呼び出す
os.makedirs("./cut_img",exist_ok=True)
video = cv2.VideoCapture('./movie.MOV') # 動画の読み込み

skip_frame = 30 #スキップするフレーム数
frame_num = 0
counter =0
while True:
    ret, frame = video.read() #1フレームずつ取り出す
    if ret == False:          #動画が終わっている場合は終了する
        break
    if frame_num % skip_frame==0: #スキップ数に達すると保存する
        cv2.imwrite(f"./cut_img/{counter:05}.jpg",frame)
        counter += 1
    frame_num += 1

完了するとcut_imgフォルダが作成され、下の図のように切り出された画像が保存されています。

切り出し結果

画像を結合する

最後に画像を結合しましょう。画像の結合にはcv2.Stitcher_create()という、画像をマッチング&合成することができる関数を利用します。

画像の合成は撮影した動画サイズによりますが、30秒ほど時間を要します。

合成が完了すると、プログラムコードが保存されているフォルダにPanorama.jpgが保存されます。

import glob
img_list = glob.glob(os.path.join("./cut_img/","*.jpg"))

imgs = [cv2.imread(path) for path in img_list]

stitcher = cv2.Stitcher_create()

result = stitcher.stitch(imgs)[1]
cv2.imwrite("Panorama.jpg",result)

合成結果

すごいですね!画像と画像のつなぎ目が全く分かりません・・・。

技術の適応先

本技術は大きな壁面画像の撮影に役立ちます。

一般的に大きな壁面を写真撮影する際は、全体を写すために遠くから撮影する必要があります。この場合全体を写すことはできますが、壁面の細部の情報を写真に収めることが難しくなります。

遠くから撮影する場合
この問題はパノラマ撮影を行うことで解消できる可能性があります。

壁面に近い位置で動画を撮影し、その動画から画像を切り出してパノラマ画像を作成することで、一枚の大きな画像を作ることができます。 この方法であれば、壁面の近くで撮影しているので、細部の情報を収めつつ全体を見ることができます。 パノラマ撮影の技術はこういった利用方法もあります!

パノラマ撮影の技術による壁面撮影

まとめ

今回は動画からパノラマ画像を合成する手法について解説・実装しました。

パノラマ画像は複数の画像を繋ぎ合わせることで、一枚の大きな画像を生成していることを確認しました!

また、パノラマ画像は風景だけでなく、大きな物体を撮影する際も利用することができます!

ぜひお試しください!

原口俊樹

データインテリジェンスチーム所属
データエンジニアを担当しています。画像認識を得意としており、画像認識・ニューラルネットワーク系の技術記事を発信していきます