【Python】VidStabで手ぶれ補正【動画編集への道#2】
Pythonやシェルを駆使して動画編集を行なっていく企画の「第二弾」になります。前回の記事と合わせてご覧ください。
この記事では、Pythonで動画の手ぶれ補正をするライブラリ「VidStab」の使い方を中心に解説していきます。
VidStab(Python Video Stabilization) GitHub - AdamSpannbauer/python_video_stab: A Python package to stabilize videos using OpenCV
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(反射)で埋める
手ぶれ補正すると、端の部分に黒い隙間ができてしまいます。黒い部分を含めないように 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日で作る!ラズパイ倒立振子ロボット や 加速度センサで角度の計算|ArduinoとMMA8452Q でも使用しました。
移動平均などのローパスフィルタは、簡単にプログラミングできてそれなりに有効というのがメリットです。しかし、フィルタを強くかけすぎると動きの速さに追従できなくなり、先ほどの動画のようにカメラの向きを変えたときに遅延が生じます。逆にフィルタを弱めれば、手ぶれ補正の役割を果たせなくなります。 そういった中で、さらに優秀なフィルタとしてカルマンフィルタが挙げられます。先ほどの 「SIMPLE VIDEO STABILIZATION USING OPENCV」 のサイトにも、カルマンフィルタバージョンの手ぶれ補正も公開されてます。また別の機会に、試した結果をご紹介します。
numpyやOpenCVでフレームを処理する
VidStabでは他にもこんなことができます。手ぶれ補正した動画フレームに、その場でnumpyや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で追跡したオブジェクトに画像を重ねる
ターゲットにしたオブジェクトの位置に透過画像を重ねることもできます。
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')