2つの動画を時間方向に連結する方法の次は、空間方向に連結する方法を学びます。ここではPythonのOpenCVを使って、2つの異なる動画ファイルを時間ではなく空間的に横方向に連結する方法を習得します。
こんにちは。wat(@watlablog)です。
動画編集プログラムは目で見れるから面白いですね!ここでは2つの動画を横方向に連結させる方法を紹介します!
動画の空間連結概要と必要な2つの技術おさらい
動画の空間連結とは?
「動画の空間連結」というと、何やら仰々しい言葉ですが、動画の時間方向の連結と差別化したく僕が名付けた言葉です。一般的な用語では無いのでご注意下さい。
ちなみに、時間方向の連結の方法は「Python/OpenCV動画編集!複数動画を連結させる方法」に記載しましたので、ご興味のある方は是非ご覧になって下さい。
動画の空間連結とは、ここでは二つの動画を横に並べて同時に再生することを意味しています。
このプログラムを書くためには、以下の2つの技術が必要です。
①画像の連結技術が必要⇒過去記事
動画といってもフレームの集合体であるため、この空間連結を実現させる方法はいたって簡単で、フレーム毎の処理を行う時に画像同士を結合すれば良いのです。
この方法は既に過去記事として「Python/OpenCVで画像連結!横と縦に繋げてみた」で紹介しました。
画像連結の方法のイメージ図を以下に再掲します。
今回の動画処理でも、この記事で紹介した方法と同じ方法を使います。
②動画のフレーム処理技術が必要⇒過去記事
動画編集の途中途中でフレーム毎に上記画像結合処理をすることで、動画の連結は簡単にできます。
その際必要な技術としては動画のフレーム処理技術が挙げられます。
例えば過去記事である「Python/OpenCVで動画速度(FPS)を変えて再保存」や、「Python/OpenCVで動体検知!動画の動いている部分を検出」では、動画ファイルを読み込み、取得したフレームに対して画像処理や設定変更をするというスタイルを取っていました。
今回実施するフレーム処理もこれとほぼ同じですが、今回は2つの動画を同時に読み込むという所が違いになります。
Pythonによる2つの動画を横に並べて1つのファイルとするコード
今回は少々長いので、画像を連結する関数と動画処理関数という順番で解説をしていきます。最後にコピペ用の全コードも載せますので、手っ取り早く実行を試したいという方は下の方までスクロールしてみて下さい。
画像を横方向に連結する関数
以下は上記①に記載した画像を連結させるための方法を関数にしたものです。
「Python/OpenCVで画像連結!横と縦に繋げてみた」で紹介したコードとは引数の部分が異なります。
先の記事では画像ファイルへのパスを渡していましたが、ここでは画像そのものとカラーかどうかの判別値のタプルを渡しています。後は過去記事内のコードと同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# 2つの画像を横に連結する関数 def image_hcombine(im_info1, im_info2): img1 = im_info1[0] # 1つ目の画像 img2 = im_info2[0] # 2つ目の画像 color_flag1 = im_info1[1] # 1つ目の画像のカラー/グレー判別値 color_flag2 = im_info2[1] # 2つ目の画像のカラー/グレー判別値 # 1つ目の画像に対しカラーかグレースケールかによって読み込みを変える if color_flag1 == 1: h1, w1, ch1 = img1.shape[:3] # 画像のサイズを取得(グレースケール画像は[:2] else: h1, w1 = img1.shape[:2] # 2つ目の画像に対しカラーかグレースケールかによって読み込みを変える if color_flag2 == 1: h2, w2, ch2 = img2.shape[:3] # 画像のサイズを取得(グレースケール画像は[:2] else: h2, w2 = img2.shape[:2] # 2つの画像の縦サイズを比較して、大きい方に合わせて一方をリサイズする if h1 < h2: # 1つ目の画像の方が小さい場合 w1 = int((h2 / h1) * w2) # 縦サイズの変化倍率を計算して横サイズを決定する h1 = h2 # 小さい方を大きい方と同じ縦サイズにする img1 = cv2.resize(img1, (w1, h1)) # 画像リサイズ else: # 2つ目の画像の方が小さい場合 h2 = h1 # 小さい方を大きい方と同じ縦サイズにする w2 = int((h1 / h2) * w1) # 縦サイズの変化倍率を計算して横サイズを決定する img2 = cv2.resize(img2, (w2, h2)) # 画像リサイズ img = cv2.hconcat([img1, img2]) # 2つの画像を横方向に連結 return img |
動画のフレーム処理関数
以下は上で書いた②動画のフレーム処理を関数にしたコードです。
基本的な構成は過去記事に書いたものと同じものですが、path1, path2、またはmovie1_obj, movie2_objと2つの動画ファイルに対応させるために変数が2つずつあるのが特徴です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# 動画を空間方向に連結させる関数 def m_space_hcombine(movie1, movie2, path_out, scale_factor): path1 = movie1[0] # 1つ目の動画のパス path2 = movie2[0] # 2つ目の動画のパス color_flag1 = movie1[1] # 1つ目の動画がカラーかどうか color_flag2 = movie2[1] # 2つ目の動画がカラーかどうか # 2つの動画の読み込み movie1_obj = cv2.VideoCapture(path1) movie2_obj = cv2.VideoCapture(path2) # ファイルからフレームを1枚ずつ取得して動画処理後に保存する i = 0 # 第1ループ判定用指標 while True: ret1, frame1 = movie1_obj.read() # 1つ目の動画のフレームを取得 ret2, frame2 = movie2_obj.read() # 2つ目の動画のフレームを取得 check = ret1 and ret2 # 2つのフレームが共に取得できた時だけTrue(論理演算) if check == True: im_info1 = [frame1, color_flag1] # 画像連結関数への引数1 im_info2 = [frame2, color_flag2] # 画像連結関数への引数2 frame_mix = image_hcombine(im_info1, im_info2) # 画像連結関数の実行 if i == 0: # 動画ファイル保存用の設定 fps = int(movie1_obj.get(cv2.CAP_PROP_FPS)) # 元動画のFPSを取得 fps_new = int(fps * scale_factor) # 動画保存時のFPSはスケールファクターをかける frame_size = frame_mix.shape[:3] # 結合したフレームのサイズを得る h = frame_size[0] # フレームの高さサイズを取得 w = frame_size[1] # フレームの横サイズを取得 fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # 動画保存時のfourcc設定(mp4用) video = cv2.VideoWriter(path_out, fourcc, fps_new, (w, h)) # 保存動画の仕様 i = i + 1 # 初期ループ判定用指標を増分 else: pass video.write(frame_mix) # 動画を保存する else: break # 動画オブジェクトの解放 movie1_obj.release() movie2_obj.release() return |
.readでそれぞれret1とret2を読み込んでいますが、これはフレームを取得できた時にTrueを返す変数です。
今回のプログラムの仕様はフレーム数の少ない方の動画フレームに合わせてループを終了させるようになっています。
正直これだけだと、再生時間は同じだけどフレームレートが異なる動画を用意した時に不具合が出ます。
改善版はそのうち書こうと思いますが、今はWebカメラで撮影した30FPSの動画通しをくっつけるような目的で使うのでこのままにしておきます(是非改善してみて下さい!(他力本願))。
動画のフレームレートが異なる場合は動画ファイルのリサンプリングとかを行えば良いかも?イメージはできるのでいずれやってみますかね。
全コード
以下に全コードを載せます。実行フォルダに「movie1.mp4」と「movie2.mp4」を置いて、カラー画像かそれ以外かを示すTrue/Falseをmovie1, movie2という変数で設定すれば、同フォルダに[movie_out.mp4」が生成されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
import cv2 # 2つの画像を横に連結する関数 def image_hcombine(im_info1, im_info2): img1 = im_info1[0] # 1つ目の画像 img2 = im_info2[0] # 2つ目の画像 color_flag1 = im_info1[1] # 1つ目の画像のカラー/グレー判別値 color_flag2 = im_info2[1] # 2つ目の画像のカラー/グレー判別値 # 1つ目の画像に対しカラーかグレースケールかによって読み込みを変える if color_flag1 == 1: h1, w1, ch1 = img1.shape[:3] # 画像のサイズを取得(グレースケール画像は[:2] else: h1, w1 = img1.shape[:2] # 2つ目の画像に対しカラーかグレースケールかによって読み込みを変える if color_flag2 == 1: h2, w2, ch2 = img2.shape[:3] # 画像のサイズを取得(グレースケール画像は[:2] else: h2, w2 = img2.shape[:2] # 2つの画像の縦サイズを比較して、大きい方に合わせて一方をリサイズする if h1 < h2: # 1つ目の画像の方が小さい場合 w1 = int((h2 / h1) * w2) # 縦サイズの変化倍率を計算して横サイズを決定する h1 = h2 # 小さい方を大きい方と同じ縦サイズにする img1 = cv2.resize(img1, (w1, h1)) # 画像リサイズ else: # 2つ目の画像の方が小さい場合 h2 = h1 # 小さい方を大きい方と同じ縦サイズにする w2 = int((h1 / h2) * w1) # 縦サイズの変化倍率を計算して横サイズを決定する img2 = cv2.resize(img2, (w2, h2)) # 画像リサイズ img = cv2.hconcat([img1, img2]) # 2つの画像を横方向に連結 return img # 動画を空間方向に連結させる関数 def m_space_hcombine(movie1, movie2, path_out, scale_factor): path1 = movie1[0] # 1つ目の動画のパス path2 = movie2[0] # 2つ目の動画のパス color_flag1 = movie1[1] # 1つ目の動画がカラーかどうか color_flag2 = movie2[1] # 2つ目の動画がカラーかどうか # 2つの動画の読み込み movie1_obj = cv2.VideoCapture(path1) movie2_obj = cv2.VideoCapture(path2) # ファイルからフレームを1枚ずつ取得して動画処理後に保存する i = 0 # 第1ループ判定用指標 while True: ret1, frame1 = movie1_obj.read() # 1つ目の動画のフレームを取得 ret2, frame2 = movie2_obj.read() # 2つ目の動画のフレームを取得 check = ret1 and ret2 # 2つのフレームが共に取得できた時だけTrue(論理演算) if check == True: im_info1 = [frame1, color_flag1] # 画像連結関数への引数1 im_info2 = [frame2, color_flag2] # 画像連結関数への引数2 frame_mix = image_hcombine(im_info1, im_info2) # 画像連結関数の実行 if i == 0: # 動画ファイル保存用の設定 fps = int(movie1_obj.get(cv2.CAP_PROP_FPS)) # 元動画のFPSを取得 fps_new = int(fps * scale_factor) # 動画保存時のFPSはスケールファクターをかける frame_size = frame_mix.shape[:3] # 結合したフレームのサイズを得る h = frame_size[0] # フレームの高さサイズを取得 w = frame_size[1] # フレームの横サイズを取得 fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # 動画保存時のfourcc設定(mp4用) video = cv2.VideoWriter(path_out, fourcc, fps_new, (w, h)) # 保存動画の仕様 i = i + 1 # 初期ループ判定用指標を増分 else: pass video.write(frame_mix) # 動画を保存する else: break # 動画オブジェクトの解放 movie1_obj.release() movie2_obj.release() return # ここからメイン実行文 movie1 = ['movie1.mp4', True] # 元動画のパス1, カラーはTrue movie2 = ['movie2.mp4', False] # 元動画のパス2, 白黒はFalse path_out = 'movie_out.mp4' # 保存する動画のパス scale_factor = 1 # FPSにかけるスケールファクター # 複数動画を連結させる関数を実行 m_space_hcombine(movie1, movie2, path_out, scale_factor) |
実行結果例
同一サイズ同一フレーム数の動画を連結した場合
上記コードの実行例をいくつか紹介します。
まずは以下の動画です。この結果は、Webカメラで撮影した動画とその動画に対して動画処理を施した動画を連結させたものなので、動画のサイズやフレーム数は同一です。
これは正に並んで再生させるための題材として良さそうですね。
異なるサイズ異なるフレーム数の動画を連結した場合
次は先ほどの左側の動画と、別途PCの画面キャプチャで作成した動画の連結です。
これは動画のサイズ自体も異なりますが、フレーム数も異なるというケースです。
実行結果は縦方向のサイズがプログラムによって揃えられ、フレーム数が右側の動画に合わせて短くなっているといった結果になりました。
これも想定(仕様)通り。「もっとこうしたい」という明確な目的が出てくれば改善しようと思います。
まとめ
本記事はこれまでの動画編集、画像処理技術を活用させて動画を横方向に連結させるコードを紹介しました。
ほとんどが過去記事を少し変えた内容になっていますので、ご興味のある方は是非関連記事も読んでみて下さい!
今回の動画連結は色々改善点も目立つけど、2つの動画を同時に読み込んでフレーム処理するというイメージを持つことができました!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!
渡辺と申します。
一点、
if h1 < h2: # 1つ目の画像の方が小さい場合
h1 = h2 # 小さい方を大きい方と同じ縦サイズにする
w1 = int((h2 / h1) * w2) # 縦サイズの変化倍率を計算して横サイズを決定
h1をh2に変更してから割り算しているため、常にh2/h1=1 となっています。
計算順を入れ替えたら画像が正しく整形されました。
ご訪問、ご指摘ありがとうございました!
サイズ変更検証に穴があったようです。
教えていただき助かります。