【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
= VidStab(kp_method='ORB')
stabilizer ='../build/in.mp4', output_path='../build/out_orb.avi') stabilizer.stabilize(input_path
VidStabを使えば、驚くおほど簡単に手ぶれ補正した動画が作れちゃいますね!ただし注意点としまして、avi
形式で保存しないと機能しませんでした。
黒いボーダーを reflect(反射)で埋める

手ぶれ補正すると、端の部分に黒い隙間ができてしまいます。黒い部分を含めないように
MoviePyなどでCrop処理
しても良いのですが、VidStabのreflect
(反射)機能を使うことで目立たなくさせることもできます。
from moviepy.editor import *
from moviepy.video.tools.segmenting import findObjects
import moviepy.video.fx.all as vfx
import os
def crop(clip, region, margin = 50):
= clip.size
w,h
= clip.set_mask(region.mask).set_pos(region.screenpos)
clip
= vfx.crop(clip,
clip =(margin), width=(w - margin * 2),
x1=(margin), height=(h - margin * 2)
y1
)return clip.resize(region.size)
if __name__ == "__main__":
= ImageClip("../assets/splitter_800x900.png")
im
# Loacate the regions, return a list of ImageClips
= findObjects(im) # region ( リージョン): 範囲、領域、分野、(身体の)部位の意味
regions
print(len(regions)) # ←これが0なら、マスク画像の作成で何か間違ってる。(黒枠でちゃんと囲えてないとか、透過だとダメで白黒でやる)
= crop(VideoFileClip("../build/out_orb.avi", audio=False), regions[0])
clip1 = crop(VideoFileClip("../build/out_orb_reflect.avi", audio=False), regions[1])
clip2
= CompositeVideoClip([clip1, clip2], im.size).subclip(0.5, clip2.duration - 0.5)
final_clip
= "../build/pip_orb_reflect.mp4"
out_path
final_clip.write_videofile(out_path)
'open ' + out_path) os.system(
他にもkp_method
には
replicate
(複製)が指定できますので各自で試してみてください。
あとは、playback=True
を指定すると、動画の書き出しを待たずにその場でプレビューできて便利です。
='../build/in.mp4', output_path='../build/out.avi', border_type='replicate', playback=True) stabilizer.stabilize(input_path
手ぶれ補正に向いてない動画もある
こちらの動画のように、元動画よりもガタガタになったり、追従が追いつかない場合もあります。こういった場合は、無理にVidStabを使う必要もないのかなと。または、LPFの強度を調整する方法を学ぶ必要がありますかね。
VidStabはC++で書かれたソース 「SIMPLE VIDEO STABILIZATION USING OPENCV」 をもとに作られているようです。 内容からするに、アルゴリズム的にはOpenCVでコーナーを抽出して、角度を算出、前後のフレームと比較して移動平均のフィルタ(LPF)をかけているようです。
移動平均のフィルタは結構単純でして、 や でも使用しました。
移動平均などのローパスフィルタは、簡単にプログラミングできてそれなりに有効というのがメリットです。しかし、フィルタを強くかけすぎると動きの速さに追従できなくなり、先ほどの動画のようにカメラの向きを変えたときに遅延が生じます。逆にフィルタを弱めれば、手ぶれ補正の役割を果たせなくなります。 そういった中で、さらに優秀なフィルタとしてカルマンフィルタが挙げられます。先ほどの 「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
= cv2.TrackerCSRT_create()
object_tracker = VidStab(kp_method='ORB')
stabilizer = cv2.VideoCapture("./build/in.mp4")
vidcap
= cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # ファイル形式(ここではmp4)
fmt = cv2.VideoWriter('./build/stab_object_tracking.mp4', fmt, 30, (1280, 720)) # ライター作成
writer
= None # オブジェクトを四角で囲む
object_bounding_box = 0
counter
while True:
= vidcap.read()
grabbed_frame, frame
= stabilizer.stabilize_frame(input_frame=frame, border_size=50)
stabilized_frame
# 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:
= object_tracker.update(stabilized_frame)
success, object_bounding_box
if success:
= [int(v) for v in object_bounding_box]
(x, y, w, h) + w, y + h),
cv2.rectangle(stabilized_frame, (x, y), (x 0, 255, 0), 2)
(
'Frame', stabilized_frame) # プレビューウィンドの表示
cv2.imshow(= cv2.resize(stabilized_frame,(1280, 720)) # リサイズしないと動画が正しく書き出せない
stabilized_frame
writer.write(stabilized_frame)
# cv2.imwrite("./build/tmp/{}.jpg".format(counter), stabilized_frame)
# counter += 1
= cv2.waitKey(5)
key
if stabilized_frame.sum() > 0 and object_bounding_box is None:
# 追跡したいオブジェクトをマウスでドラッグして選択後、Enterする
= cv2.selectROI("Frame",
object_bounding_box
stabilized_frame,=False,
fromCenter=True)
showCrosshair
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
= cv2.TrackerCSRT_create()
object_tracker = VidStab(kp_method='ORB')
stabilizer = cv2.VideoCapture("./build/in.mp4")
vidcap
= cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
fmt = cv2.VideoWriter('../build/stab_object_tracking_replace.mp4', fmt, 30, (1280, 720))
writer
= None # オブジェクトを四角で囲む
object_bounding_box = cv2.imread("../assets/apple.png", cv2.IMREAD_UNCHANGED) # アルファチャンネル込みで読み込む
mask_img
while True:
= vidcap.read()
grabbed_frame, frame
= stabilizer.stabilize_frame(input_frame=frame, border_size=50, border_type='reflect')
stabilized_frame
if stabilized_frame is None:
print('stabilized_frame is None')
break
if object_bounding_box is not None:
= object_tracker.update(stabilized_frame)
success, object_bounding_box
if success:
= [int(v) for v in object_bounding_box]
(x, y, w, h) #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)
= cv2.resize(mask_img, (100, 100))
mask_img #stabilized_frame[y:y+100, x:x+100] = mask_img[:, :, :3]
+100, x:x+100] = stabilized_frame[y:y+100, x:x+100] * (1 - mask_img[:, :, 3:] / 255) + mask_img[:, :, :3] * (mask_img[:, :, 3:] / 255)
stabilized_frame[y:y
'Frame', stabilized_frame) # プレビューウィンドの表示
cv2.imshow(= cv2.resize(stabilized_frame,(1280, 720)) # リサイズしないと動画が正しく書き出せない
stabilized_frame
writer.write(stabilized_frame)
= cv2.waitKey(5)
key
if stabilized_frame.sum() > 0 and object_bounding_box is None:
# 追跡したいオブジェクトをマウスでドラッグして選択後、Enterする
= cv2.selectROI("Frame",
object_bounding_box
stabilized_frame,=False,
fromCenter=True)
showCrosshair
object_tracker.init(stabilized_frame, object_bounding_box)
elif key == 27:
print('key == 27')
break
writer.release()
vidcap.release()
cv2.destroyAllWindows()print('released')
関連記事
- 【Python】OpenCVでコーナーの検出【Harris/Shi-Tomasi】
- 【Python】OpenCVで図形の描画からアニメーションまで【線・四角・丸・塗りつぶし】
- 【Python】OpenCVで特徴点の追跡【メダカの軌跡】
- Kotlinのobjectを理解する【Androidアプリ開発】
- 【Python】MoviePyで動画編集の自動化【動画編集への道#1】