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

## **はじめに**
過去に作成しようとして私的な理由によりとん挫したクロマキー合成を利用した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のインストールは[こちら](https://qiita.com/developer-kikikaikai/items/6c0394ffbaa9dc76a048)を参照ください。
eclipseは、、、頑張って！(笑)

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

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

### **JavaCVでの動画入力**

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

#### 1.動画の読み込み開始

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

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

```java:grabber.java
frame=mInputGrabber.grab();
```

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

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

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

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

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

```java:grabber
framerate = mInputGrabber.getFrameRate());
```

### **JavaCVでの動画出力**

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

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

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

```java: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でタイムスタンプを設定するのがポイントです。

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

mRecorder.record(mYuvImage);
```

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

```java:recorder
mRecorder.setFrameNumber(mFrameNumber++);
mRecorder.record(frame);
```

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

```java: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変換があるから余計に厄介。

```java: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を利用して編集した動画例**

これらの機能を利用すれば、[前回記事](https://qiita.com/developer-kikikaikai/items/47a13cbcb6fdb535345a)で紹介した動画合成や、クロマキー合成や透過合成が出来ます。

**クロマキー合成:**
![overlay.gif](https://qiita-image-store.s3.amazonaws.com/0/252277/123e6626-5188-638d-5902-fa87dec26a68.gif)

**透過合成:**(こちらも元は背景が黒い動画です)
![transfer.gif](https://qiita-image-store.s3.amazonaws.com/0/252277/8a4313a8-ce81-a8dd-d60e-26ea0eba574a.gif)

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

**前回のクロマキー**
![before.gif](https://qiita-image-store.s3.amazonaws.com/0/252277/999b2fb5-4279-25b5-c3e9-32dcb027f751.gif)

## **次回**

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

フィルターが使えない話、クロマキー処理の話については、次回の記事で説明したいと思います。
⇒[こちら](https://qiita.com/developer-kikikaikai/items/1572019427833bd2e915)に記載しました

## **参考**

色空間などの参考。画処理を学んでいた後輩に教えてもらった教科書です。やっぱり教科書は基本的な考えを抑えるには便利ですね。
　[デジタル画像処理(書籍)](https://www.amazon.co.jp/%E3%83%87%E3%82%A3%E3%82%B8%E3%82%BF%E3%83%AB%E7%94%BB%E5%83%8F%E5%87%A6%E7%90%86/dp/4903474011)
ゴリラさんのサイトは情報の整理された親切なサイトです。
　[ゴリラになる知識　色の扱い](http://gori-naru.blogspot.com/2012/11/blog-post_789.html)
サイズ変更等の参考
　[画像処理¶](http://opencv.jp/cookbook/opencv_img.html)
Mat仕様参考
　[Class opencv_core.Mat](http://bytedeco.org/javacpp-presets/opencv/apidocs/org/bytedeco/javacpp/opencv_core.Mat.html)

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

-----

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