18
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FFmpeg・OpenCVのJava版ラッパーであるJavaCVを使って、動画を編集しよう!

Last updated at Posted at 2018-08-28

はじめに

過去に作成しようとして私的な理由によりとん挫したクロマキー合成を利用したWindows Java/Android向けのアプリケーションを作りました。まあまあ作り込んだので眠らせておくのはもったいないので、新たなクロマキー合成アプリとして陽の目を見せてあげようと計画しています。

今回は、このアプリケーションで利用するJavaCVについて簡単に紹介したいと思います。

JavaCVとは?OpenCV + FFmpegのJavaラッパー

JavaCVとは、画像の加工などで使用されるC/C++ライブラリのOpenCVというツールをJavaで利用できるようにしたラッパーです。
さらにこのJavaCVは、前回記事で紹介した動画エンコードツールであるFFmpegのラッパーも含んでいるため、動画合成 && 画像処理を一手に処理することが出来ます。
つまり**これを使うとJava(つまりAndroidアプリケーション)で動画編集を行うことが出来る!**ということに。

日本語の情報サイトは少ないですが、公式のsampleも豊富なのと、結局根っこはOpenCV or FFmpegの技術に紐づくので、豊富なOpenCV or FFmpeg情報から、JavaCVのsampleで利用されている方法が紐づけば、大抵のことは出来ます。

今回の動作環境

開発環境
OS: Windows
開発環境:Eclipse IDE for Java Developers

ソフトウェアバージョン

開発当時:
JavaCV: 1.3.3
opencv in JavaCPP: 3.2.0-1.3
ffmpeg in JavaCPP: 3.2.0-1.3

※以下でも簡単に動作確認済み
JavaCV: 1.4.2
opencv in JavaCPP: 3.4.2-1.4.2
ffmpeg in JavaCPP: 4.0.1-1.4.2

JavaCVのインストールはこちらを参照ください。
eclipseは、、、頑張って!(笑)

JavaCVでのFFmpeg機能(動画の入出力)

FFmpegではffmpeg -i 入力動画 オプション色々設定 出力動画という風に1手で動画の入力⇒出力を実行していましたが、JavaCVでは細かな制御が出来るように入力, 出力それぞれようのクラスが存在し、1フレーム毎に制御を行う仕様となっています。

JavaCVでの動画入力

FFmpegFrameGrabberクラスを使います。基本は3手順

1.動画の読み込み開始

grabber.java
import org.bytedeco.javacv.FFmpegFrameGrabber;
mInputGrabber = new FFmpegFrameGrabber(inputFile);
mInputGrabber.start();

2.フレーム毎にフレーム取得(音声データも動画データもどちらも"Frame"として扱われます)

grabber.java
frame=mInputGrabber.grab();

grabImageとgrabSamplesで動画だけ、音声だけの取得も可能です。

3.必要な読み込みが終わったらclose

grabber.java
mInputGrabber.stop();
mInputGrabber.release();

動画情報は1フレームごとに取得できるので、OpenCV機能を使って加工した上で動画に保存するのがいいでしょうね。

その他、フレームレートなどの基本情報が取得できます。

grabber
framerate = mInputGrabber.getFrameRate());

JavaCVでの動画出力

こちらもJavaCVでの動画入力と同様、1フレームごとの操作となります。

1. FFmpegFrameRecorderクラスを作成する。(FFmpegのエンコードパラメーターを渡すと、動画を保存できる口だけ返してくれる)

以下を見てもらえるとわかるのですが、FFmpegのオプションを色々使用することが出来ます。filterも一部使用可能(本記事で記述)

recorder.java
FFmpegFrameRecorder mRecorder = new FFmpegFrameRecorder(streamUrl, imageWidth, imageHeight, audioSample);//パス、サイズ、音声を使うかを指定
mRecorder.setFormat(format);//動画の拡張子
mRecorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);//動画の拡張子
mRecorder.setVideoQuality(videoQuality);//映像品質
mRecorder.setFrameRate(frameRate);//フレームレート
if(audioSample != 0) {
    mRecorder.setAudioQuality(audioQuality);//音声フレームレート
    mRecorder.setSampleRate(sampleAudioRateInHz);//音声サンプルレート
    mRecorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);//音声コーデック。大体AAC
}
mRecorder.setVideoOption("preset", "ultrafast");//エンコード速度設定。FFmpegならはるかに速くなりますが、こちらでどこまで効くかは未検証
mRecorder.setTimestamp(0);//フレーム制御なのでtimestampで動画時間を調整します
mRecorder.setGopSize((int)((double)frameRate*gop));//GoP長(Iフレーム間隔) 長いとサムネが取れないけど動画サイズが大きくなるジレンマ

mRecorder.start();//録画開始

2. 同クラスにOpenCVの機能で作成したフレーム情報を登録

画像フレームの場合は以下。setTimestapmでタイムスタンプを設定するのがポイントです。

recorder
frame.mTimestamp = 1000 * (System.currentTimeMillis() - startTime);
//保存
if (mRecorder.getTimestamp() < frame.mTimestamp) {
    mRecorder.setTimestamp(frame.mTimestamp);
}

mRecorder.record(mYuvImage);

フレーム番号をずらしていくという方法もあります。こっちの方が安定するかな

recorder
mRecorder.setFrameNumber(mFrameNumber++);
mRecorder.record(frame);

音声フレームの場合は以下。methodが違うんですね。動かしている感じsetTimestapmはいらないです。

recorder
mRecorder.recordSamples(audio);

後は処理が重いので、WindowsならまだいいですがAndroidなら音声と動画の保存を分けてmulti thread化するなど、負荷分散の仕組みを実施するといいですね。

JavaCVでのopenCV機能(画像処理)

こちらはMatと呼ばれるOpenCVで使用される画像データを管理するためのクラスへの変換処理がいるだけで、後は大体OpenCVと同じことが出来ます。
(一部古い機能が非サポートだったりしますが)
Matは、OpenCVで使用される画像データを管理するためのクラスです。唯の配列なんですが、画像の色空間等によってデータの配列が変わる為画像処理、特に色空間に関する知識が必要です。

1.FrameをMatに変換する。

FrameはJavaCV独自のクラスなので、OpenCVの恩恵を受けるためにFrame⇒Matに変換, MatでOpenCVの機能を利用、再度Frame変換という手順が必要です。
確かMatはnewするけど、convert自体はshallowコピーだったはず。
ここだけに限らずMat操作は結構shallow copy/deep copyの違いでメモリリーク or ぬるぽが発生するのがハマるポイントです。Frame変換があるから余計に厄介。

convert
//Frame⇒Mat
OpenCVFrameConverter.ToMat toMat = new OpenCVFrameConverter.ToMat();
toMat.convert(frame);

//Mat⇒Frame
OpenCVFrameConverter.ToMat toMat = new OpenCVFrameConverter.ToMat();
toMat.convert(frame);

2.OpenCVでのMatを利用した画像処理

Matを使った操作は色々なものがあります。私が利用した機能を抜粋して軽く紹介。
クロマキー合成のメイン処理については次回詳細を紹介します。

メソッド 機能 備考
copyTo(srcMat, mRetMat); Matの上書き 同サイズでないと使用できない
Rect(x, y, width, height); 画像の部分抜き出し 上記制限に利用
cvtColor(srcMat, retMat,色空間); 色空間の変換
threshold(gray, retMat, mThresholdValue, mMaxValue, mThresholdType ); 画像を白と黒の2値化する
addWeighted(effectMat, mSrcWeight, srcMat, mEffectWeight, mAllWeight, mDistMat); 全体の透過

JavaCVを利用して編集した動画例

これらの機能を利用すれば、前回記事で紹介した動画合成や、クロマキー合成や透過合成が出来ます。

クロマキー合成:
overlay.gif

透過合成:(こちらも元は背景が黒い動画です)
transfer.gif

参考までに前回のクロマキー合成動画も乗せておきます。微妙にですけど出力の感じが違うのがわかるかと思います。

前回のクロマキー
before.gif

次回

ここまででJavaCVでの動画編集で使用した基本的な機能を紹介しました。ここで勘のいい方なら、**「JavaCVでFFmpeg使えるなら、フィルター機能を使えばいいじゃん!」**と思ったかもしれません。しかし、少なくとも自分の環境では色々試してみてもFFmpegのfilter_complexでの綺麗な合成が出来なかったんですよね。
(色空間がぐちゃぐちゃになっていしまいました)
なのでクロマキー処理は自作しています。次に計画してるアプリももうちょっとクロマキーを工夫したい部分もあるので新バージョンでの検証もしないです。

フィルターが使えない話、クロマキー処理の話については、次回の記事で説明したいと思います。
こちらに記載しました

参考

色空間などの参考。画処理を学んでいた後輩に教えてもらった教科書です。やっぱり教科書は基本的な考えを抑えるには便利ですね。
 デジタル画像処理(書籍)
ゴリラさんのサイトは情報の整理された親切なサイトです。
 ゴリラになる知識 色の扱い
サイズ変更等の参考
 画像処理¶
Mat仕様参考
 Class opencv_core.Mat

その他様々なOpenCVに詳しい方のサイト達。沢山参考にしたのですが、サイト情報記録がありませんでした、申し訳ありません。。


…関係ないですが、眠れないからってQiitaの記事を書くのはよくないですね。振り返ると色々気になる点が出てくるから余計眠れなくなる。

18
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?