【Python】VidStabで手ぶれ補正【動画編集への道#2】

【Python】VidStabで手ぶれ補正【動画編集への道#2】

Pythonやシェルを駆使して動画編集を行なっていく企画の「第二弾」になります。前回の記事と合わせてご覧ください。

この記事では、Pythonで動画の手ぶれ補正をするライブラリ「VidStab」の使い方を中心に解説していきます。

VidStab(Python Video Stabilization)

VidStabモジュールは、コアにOpenCVを使用した動画の手振れを修正できるプログラムになります。OpenCVへの理解がなくても、VidStabを利用すれば手っ取り早く手ぶれ補正ができますのでご安心ください。

vidstab と OpenCV のインストール

Python3.xで動作させていきます。vidstabをpipでインストールします。

$ pip install vidstab

vidstabを動かすにはOpenCVも必要になりますので合わせてインストールしておいてください。

$ pip install opencv-python
$ pip install opencv-contrib-python

VidStab で手ぶれ補正する

それではさっそく、VidStab で手ぶれ補正してみましょう。自転車に乗りながらスマホをチェストマウントして撮影した動画になります。

元動画(左)、手ぶれ補正後(右)
元動画(左)、手ぶれ補正後(右)

元動画(左)ではブレブレだった映像も、手ぶれ補正後はだいぶマシになったように感じます。

プログラムは次のとおりです。たったの数行で手ぶれ補正ができちゃいます。

from vidstab import VidStab


stabilizer = VidStab(kp_method='ORB')
stabilizer.stabilize(input_path='../build/in.mp4', output_path='../build/out_orb.avi')

VidStabを使えば、驚くおほどカンタンに手ぶれ補正した動画が作れちゃいますね!ただし注意点としまして、avi 形式で保存しないと機能しませんでした。

黒いボーダーを reflect(反射)で埋める

OpenCVで追跡したオブジェクトに画像を重ねる
OpenCVで追跡したオブジェクトに画像を重ねる

手ぶれ補正すると、端の部分に黒い隙間ができてしまいます。黒い部分を含めないようにMoviePyなどでCrop処理しても良いのですが、VidStabのreflect(反射)機能を使うことで目立たなくさせることもできます。

from vidstab import VidStab


stabilizer = VidStab(kp_method='ORB')
#stabilizer = VidStab(kp_method='FAST', threshold=42, nonmaxSuppression=False)

stabilizer.stabilize(
    input_path='../build/in.mp4',
    output_path='../build/out_orb_reflect.avi',
    border_type='reflect')


他にもkp_method には replicate(複製)が指定できますので各自で試してみてください。

あとは、playback=True を指定すると、動画の書き出しを待たずにその場でプレビューできて便利です。

stabilizer.stabilize(input_path='../build/in.mp4', output_path='../build/out.avi', border_type='replicate', playback=True)

手ぶれ補正に向いてない動画もある

こちらの動画のように、元動画よりもガタガタになったり、追従が追いつかない場合もあります。こういった場合は、無理にVidStabを使う必要もないのかなと。または、LPFの強度を調整する方法を学ぶ必要がありますかね。

VidStabはC++で書かれたソース 「SIMPLE VIDEO STABILIZATION USING OPENCV」 をもとに作られているようです。

内容からするに、アルゴリズム的にはOpenCVでコーナーを抽出して、角度を算出、前後のフレームと比較して移動平均のフィルタ(LPF)をかけているようです。

移動平均のフィルタは結構単純でして、10日で作る!ラズパイ倒立振子ロボットProcessingでArduinoとシリアル通信、加速度センサとローパスフィルタで動きの視覚化でも使ったことがあるので、興味ある方はご参考ください。

移動平均などのローパスフィルタは、カンタンにプログラミングできてそれなりに有効というのがメリットです。しかし、フィルタを強くかけすぎると動きの速さに追従できなくなり、先ほどの動画のようにカメラの向きを変えたときに遅延が生じます。逆にフィルタを弱めれば、手ぶれ補正の役割を果たせなくなります。

そういった中で、さらに優秀なフィルタとしてカルマンフィルタが挙げられると思います。先ほどの 「SIMPLE VIDEO STABILIZATION USING OPENCV」 のサイトにも、カルマンフィルタバージョンの手ぶれ補正も公開されています。また別の機会に、試した結果をご紹介できればと思います。

numpyやOpenCVでフレームを処理する

VidStabでは他にもこんなことができます。手ぶれ補正した動画フレームに、その場でnumpyやOpenCVで処理させることができます。

チュートリアル にありましたサンプル例を分かりやすく書き直したものをご紹介しましょう。

OpenCVでオブジェクトを追跡する

OpenCVで追跡したオブジェクトに画像を重ねる
OpenCVで追跡したオブジェクトに画像を重ねる

このプログラムは、OpenCVのプレビューウィンドウが起動し、追跡したいオブジェクトをマウスでドラッグして選択後、Enterする必要があります。VidStabとOpenCVの処理がごっちゃになって書かれているので分かりずらいかもしれませんが、コメントアウトを頼りに解読してみてください。

from ast import IsNot
import os
import cv2
from vidstab import VidStab, layer_overlay, download_ostrich_video


object_tracker = cv2.TrackerCSRT_create()
stabilizer = VidStab(kp_method='ORB')
vidcap = cv2.VideoCapture("./build/in.mp4")

fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # ファイル形式(ここではmp4)
writer = cv2.VideoWriter('./build/stab_object_tracking.mp4', fmt, 30, (1280, 720)) # ライター作成


# Initialize bounding box for drawing rectangle around tracked object
object_bounding_box = None # オブジェクトを四角で囲む
counter = 0

while True:
    grabbed_frame, frame = vidcap.read()

    stabilized_frame = stabilizer.stabilize_frame(input_frame=frame, border_size=50)

    # If stabilized_frame is None then there are no frames left to process
    if stabilized_frame is None:
        print('stabilized_frame is None')
        break
    

    # Draw rectangle around tracked object if tracking has started
    if object_bounding_box is not None:
        success, object_bounding_box = object_tracker.update(stabilized_frame)

        if success:
            (x, y, w, h) = [int(v) for v in object_bounding_box]
            cv2.rectangle(stabilized_frame, (x, y), (x + w, y + h),
                          (0, 255, 0), 2)

    cv2.imshow('Frame', stabilized_frame) # プレビューウィンドの表示
    stabilized_frame = cv2.resize(stabilized_frame,(1280, 720)) # リサイズしないと動画が正しく書き出せない
    writer.write(stabilized_frame) 

    # cv2.imwrite("./build/tmp/{}.jpg".format(counter), stabilized_frame)
    # counter += 1

    key = cv2.waitKey(5)

    if stabilized_frame.sum() > 0 and object_bounding_box is None:
        # 追跡したいオブジェクトをマウスでドラッグして選択後、Enterする
        object_bounding_box = cv2.selectROI("Frame",
                                            stabilized_frame,
                                            fromCenter=False,
                                            showCrosshair=True)

        object_tracker.init(stabilized_frame, object_bounding_box)

    elif key == 27:
        print('key == 27')
        break

writer.release() 
vidcap.release()
cv2.destroyAllWindows()
print('released')

オブジェクトを四角で囲む処理はあくまでもOpenCVの機能です。これをさらに発展させれば、ターゲットを映像の中心に持ってきたり、人の顔などをモザイク処理できたりと、いろいろな場面で応用ができそうです。

OpenCVで追跡したオブジェクトに画像を重ねる

OpenCVで追跡したオブジェクトに画像を重ねる
OpenCVで追跡したオブジェクトに画像を重ねる

ターゲットにしたオブジェクトの位置に透過画像を重ねることもできます。

from ast import IsNot
import os
import cv2
from vidstab import VidStab, layer_overlay, download_ostrich_video


object_tracker = cv2.TrackerCSRT_create()
stabilizer = VidStab(kp_method='ORB')
vidcap = cv2.VideoCapture("./build/in.mp4")

fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
writer = cv2.VideoWriter('../build/stab_object_tracking_replace.mp4', fmt, 30, (1280, 720))

object_bounding_box = None # オブジェクトを四角で囲む
mask_img = cv2.imread("../assets/apple.png", cv2.IMREAD_UNCHANGED) # アルファチャンネル込みで読み込む
# https://qiita.com/smatsumt/items/923aefb052f217f2f3c5

while True:
    grabbed_frame, frame = vidcap.read()

    stabilized_frame = stabilizer.stabilize_frame(input_frame=frame, border_size=50, border_type='reflect')


    if stabilized_frame is None:
        print('stabilized_frame is None')
        break
    

    if object_bounding_box is not None:
        success, object_bounding_box = object_tracker.update(stabilized_frame)

        if success:
            (x, y, w, h) = [int(v) for v in object_bounding_box]
            #cv2.rectangle(stabilized_frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
            #stabilized_frame = cv2.addWeighted(src1=stabilized_frame, alpha=1.0, src2=mask_img, beta=1.0, gamma=0)
            mask_img = cv2.resize(mask_img, (100, 100))
            #stabilized_frame[y:y+100, x:x+100] = mask_img[:, :, :3]
            stabilized_frame[y:y+100, x:x+100] = stabilized_frame[y:y+100, x:x+100] * (1 - mask_img[:, :, 3:] / 255) + mask_img[:, :, :3] * (mask_img[:, :, 3:] / 255)

    cv2.imshow('Frame', stabilized_frame) # プレビューウィンドの表示
    stabilized_frame = cv2.resize(stabilized_frame,(1280, 720)) # リサイズしないと動画が正しく書き出せない
    writer.write(stabilized_frame) 


    key = cv2.waitKey(5)

    if stabilized_frame.sum() > 0 and object_bounding_box is None:
        # 追跡したいオブジェクトをマウスでドラッグして選択後、Enterする
        object_bounding_box = cv2.selectROI("Frame",
                                            stabilized_frame,
                                            fromCenter=False,
                                            showCrosshair=True)

        object_tracker.init(stabilized_frame, object_bounding_box)

    elif key == 27:
        print('key == 27')
        break

writer.release() 
vidcap.release()
cv2.destroyAllWindows()
print('released')
記事に関するご質問などがあれば、
Twitter または お問い合わせ までご連絡ください。
Python学習にオススメの本をご紹介!
Pandasでデータサイエンスはじめよう!
関連記事