OpenCVには動画カットするメソッドはありません。
▼ でも次のことを実現したかった
- 例えば1分30秒の動画あって、
- 開始地点25秒からとして、
- 終了地点50秒までカットしたい
結論から書くとOpenCVでもできます。
どうしても他ライブラリを使いたくない人のために、
Python+OpenCVで範囲時間で動画カットする方法を解説
このページの目次
0.OpenCVで動画カットするまでの大まかな流れ
ここでは次のような手順を踏んでいきます。
▼ このような流れになる
- 動画のカット開始地点をミリ秒で指定
- 動画のカット終了地点もミリ秒で指定
- カット動画をmp4として保存する
▼ さらに詳細な流れはこうなる
- 動画の始点と終点のフレーム番号を取得
OpenCVではダイレクトに「̻○○ミリ秒~○○ミリ秒まで」のような操作はできない。フレーム単位なので開始・終了地点のミリ秒をフレーム番号(インデックス)に直しておく
- その範囲内のフレームを画像に分割
例えば100フレーム目から350フレーム目までのように、カットしたい部分のフレームを全て画像として取得する。ただし音声は考えないことにする
- フレーム画像を1つのmp4動画に再構成する
あとは分割したそれぞれの画像を1つのmp4動画として再構成するだけ。やってることはパラパラ漫画を作るのと全く同じ
こういう風な流れ
今回は音声無しで動画カットしてみます。
1.FPS・フレーム数・始点/終点フレーム番号を取得
初めは下準備から
FPS、総フレーム数、始点・終点のフレーム番号…
これらをまとめて取得・算出しておきます。
▼ つまりはこのようなコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
## カット開始地点のミリ秒 startMillis = 3000 ## = 3秒 ## カット終了地点のミリ秒 stopMillis = 7000 ## = 7秒 videoCapture = cv2.VideoCapture("/path/to/video.mp4") ## FPS : Frames per Second fps = videoCapture.get(cv2.CAP_PROP_FPS) ## 総フレーム数 totalFrames = videoCapture.get(cv2.CAP_PROP_FRAME_COUNT) ## カット開始のフレームインデックス startFrameIndex = math.ceil(fps * startMillis / 1000) ## カット終了のフレームインデックス stopFrameIndex = math.ceil(fps * stopMillis / 1000) ## 範囲を越えたフレームインデックス修正 if(startFrameIndex < 0): startFrameIndex = 0 if(stopFrameIndex >= totalFrames): stopFrameIndex = totalFrames-1 ## 開始地点まで動画をシークする videoCapture.set(cv2.CAP_PROP_POS_FRAMES, startFrameIndex) ## このあと使う変数 frameIndex = startFrameIndex |
ここまでが動画カットの下準備コード
恐らく「こんな断片的なコードを見せられても…」と思ってるでしょうが、最後にコード全体をまとめて載せるので安心してください。
動画カット用のフレームインデックスを計算してます。
2.カット開始~終了地点までのフレームを画像分割
そしたら開始~終了までのフレームを画像分割します。
▼ 開始~終了までのフレーム分割のコード例
1 2 3 4 5 6 |
## 開始~終了までを画像に分割 imgArr = [] while(frameIndex <= stopFrameIndex): _,img = videoCapture.read() imgArr.append(img) frameIndex += 1 |
ここは短いですね。
OpenCVではフレーム画像を videoCapture.read() で取得できるのですが、フレームインデックス等が渡せないのが曲者でした。だから遠回りな方法を取ってます。
ちなみにフレーム分割は次でも詳しく触れました。
▼ Python+opencvで動画のnフレーム目を画像保存
この記事ではフレームを画像保存までしてます。
でも今回の動画カットではメモリ上に展開しています。
それ以外では特別に説明する箇所なし
3.フレーム画像配列を1つのmp4動画に再構成する
最後に切り出したフレーム達をmp4に再構成します。
▼ こういったコードで再構成する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
## 出力動画の形式(ascii4文字) fourcc = cv2.VideoWriter_fourcc('m','p','4','v') ## 出力動画 video = None ## 出力動画のファイル名 videoFileName = "trimmed.mp4" for img in imgArr: if(video is None): ## 動画の幅・高さ h,w,_ = img.shape ## FPS20で動画オブジェクト作成 video = cv2.VideoWriter(videoFileName, fourcc, 20.0, (w,h)) ## 動画末尾にフレーム書き出し video.write(img) ## 最期にリリース。忘れずに! video.release() |
何をしてるかはコメント参照
本当にやってることはパラパラ漫画と同じです。先ほど開始~終了地点までのフレーム画像配列を作ったので、それをひたすら動画に追加していってるだけです。
▼ ちなみに cv2.VideoWriter について
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.
- Parameters
filename Name of the output video file. fourcc 4-character code of codec used to compress the frames. For example, VideoWriter::fourcc('P','I','M','1') is a MPEG-1 codec, VideoWriter::fourcc('M','J','P','G') is a motion-jpeg codec etc. List of codes can be obtained at Video Codecs by FOURCC page. FFMPEG backend with MP4 container natively uses other values as fourcc code: see ObjectType, so you may receive a warning message from OpenCV about fourcc code conversion. fps Framerate of the created video stream. frameSize Size of the video frames. isColor If it is not zero, the encoder will expect and encode color frames, otherwise it will work with grayscale frames (the flag is currently supported on Windows only). 引用元 : https://docs.opencv.org/3.4/dd/d9e/classcv_1_1VideoWriter.html#ac3478f6257454209fa99249cc03a5c59
こんな便利なものがOpenCVでは用意されてます。
4.最後に全体のコードをまとめて載せておく
分かりやすく関数化しました。
▼ 関数化した結果コード
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 |
import cv2 ## 動画を開始ミリ秒~終了ミリ秒までカットする ## def trimVideo(targetFileName, destFileName, startMillis, stopMillis): ## FPS、フレーム数・開始終了フレーム番号取得 videoCapture = cv2.VideoCapture(targetFileName) fps = videoCapture.get(cv2.CAP_PROP_FPS) totalFrames = videoCapture.get(cv2.CAP_PROP_FRAME_COUNT) startFrameIndex = math.ceil(fps * startMillis / 1000) stopFrameIndex = math.ceil(fps * stopMillis / 1000) if(startFrameIndex < 0): startFrameIndex = 0 if(stopFrameIndex >= totalFrames): stopFrameIndex = totalFrames-1 videoCapture.set(cv2.CAP_PROP_POS_FRAMES, startFrameIndex) frameIndex = startFrameIndex ## 開始~終了地点までフレーム分割 imgArr = [] while(frameIndex <= stopFrameIndex): _,img = videoCapture.read() imgArr.append(img) frameIndex += 1 ## 分割フレームをmp4動画に再構成 fourcc = cv2.VideoWriter_fourcc('m','p','4','v') video = None tmpVideoFileName = destFileName for img in imgArr: if(video is None): h,w,_ = img.shape video = cv2.VideoWriter(tmpVideoFileName, fourcc, 20.0, (w,h)) video.write(img) video.release() if __name__ == "__main__": ## こういう風に使う trimVideo( "/path/to/target.mp4", "/path/to/dest.mp4", 3200 ## => 3.2秒から 12800 ## => 12.8秒まで ) |
ミリ秒単位で指定できるから直観的
以上、Python+OpenCVで動画カットでした。
もしコードに誤りがあればご指摘ください。