JavaとOpenCVで似ている動画を検出するrev.1



# はじめに

ちょっと「似ている画像を検出する」ということについて調べる機会があり、その過程で動画について考えていました。

1. 動画から画像を抽出する
1. 画像を比較する

＼(＾o＾)／ 

というシンプルな考えが浮かび、ちょっと作ってみることにしました。
** 開発中です**

画面イメージ
![image](https://qiita-image-store.s3.amazonaws.com/0/197945/4c26d9ac-e000-4b7e-a3cd-ad2e6872619e.png)

# 環境

Windows上でJava(Java9)、IDEはEclipseを使用します。
GUIの作成はScene Builder、つまりJavaFXです。

- Windows7 64bit
- OpenCV 3.3.0
- Eclipse4.7.1(pleiades ulitimateを利用)
    - e(fx)clipse
- Scene Builder 9.0

OpenCVをインストールするとjarとdllが作成されます。
それぞれ読み込める場所に配置しておきます。

- opencv/build/java/x64/opencv_java330.dll

- opnecv/build/bin/opencv_ffmpeg330_64.dll

→ Windows\System32\ 下などにコピー

- opencv/build/java/opencv-330.jar

→ クラスパス下にコピー、Eclipseから追加

さらにJavaでは以下のようにOpenCVを使うクラスでロード処理を記述する必要がありました。

```java
	static {
		System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
	}
```

# 動画から画像を抽出する

動画からいくつかのフレームを画像として抜き出します。
  ここでは9枚の画像を抜きだすことにしました。根拠は特にありませんが、3×3でGUIで並べたときにキリがよさそうだという判断です。
  最初と最後のフレームは暗転していることが多そうなので、10分割した区切りの9フレームを保存します。
  -->FPS違いの検出…？

また、抽出した画像はすべて横幅が200pxとなるように拡大縮小してから保存します。
 -->サイズ違いの検出…？

↓こんな感じになりました。

```java
	static public VideoMetadata captureFrame(Config config, String videoFile) {
		// ------------------------------------------------------------------
		// 動画を開く
		// ------------------------------------------------------------------
		System.out.println(videoFile);
		VideoCapture capture = new VideoCapture(videoFile);
		if (!capture.isOpened()) {
			System.out.println("file not opened.");
			return null;
		}

		String outputDir = config.getTempDir() + "/" + String.valueOf(videoFile.hashCode()).replace("-", "_");
		File dir = new File(outputDir);
		if (!dir.exists()) {
			dir.mkdir();
		}

		// ------------------------------------------------------------------
		// 動画情報の取得
		// ------------------------------------------------------------------
		double fps = capture.get(Videoio.CV_CAP_PROP_FPS);
		int resizeHeight = (int) (config.getImageWidth() / capture.get(Videoio.CV_CAP_PROP_FRAME_WIDTH)
				* capture.get(Videoio.CV_CAP_PROP_FRAME_HEIGHT));
		double allFrameCnt = capture.get(Videoio.CV_CAP_PROP_FRAME_COUNT);
		int captureInterval = (int) (allFrameCnt / (config.getCaptureCount() + 1));
		long time = (long) (allFrameCnt / fps);

		// ------------------------------------------------------------------
		// 設定で指定した数のフレームを取得する
		// ------------------------------------------------------------------
		for (int i = 1; i < config.getCaptureCount() + 1; i++) {
			int frameIndex = captureInterval * i;
			Mat orgFrame = new Mat();
			Mat resizedFrame = new Mat();
			capture.set(Videoio.CV_CAP_PROP_POS_FRAMES, frameIndex - 1);
			if (!capture.read(orgFrame)) {
				continue;
			}
			Imgproc.resize(orgFrame, resizedFrame, new Size(config.getImageWidth(), resizeHeight));
			Imgcodecs.imwrite(outputDir + "/" + i + ".jpg", resizedFrame);

		}

		VideoMetadata metadata = new VideoMetadata();
		metadata.setFilename(videoFile);
		metadata.setFrameDirname(outputDir);
		metadata.setPlayTime(time);
		return metadata;
	}
```


# 画像を比較する

前項で1動画あたり、9枚の画像をしました。
今回はこれを比較します。

画像の比較ですが、ヒストグラムを比較することにしました。
これについては検討の余地が多いと思います。

9枚それぞれの比較結果の平均を動画全体の比較結果としました。

コードは以下の通りです。

```java
	static public VideoComparison compareImages(Config config, VideoMetadata video1, VideoMetadata video2) {

		List<Double> histList = new ArrayList<Double>();
		// ------------------------------------------------------------------
		// 画像毎に比較
		// ------------------------------------------------------------------
		for (int i = 1; i < config.getCaptureCount() + 1; i++) {
			String filename1 = video1.getFrameDirname() + "/" + i + ".jpg";
			String filename2 = video2.getFrameDirname() + "/" + i + ".jpg";
			File file1 = new File(filename1);
			File file2 = new File(filename2);
			if (!(file1.exists() && file2.exists())) {
				continue;
			}

			// ------------------------------------------------------------------
			// ヒストグラム
			// ------------------------------------------------------------------
			Mat img1 = Imgcodecs.imread(filename1);
			List<Mat> src1 = new ArrayList<Mat>();
			src1.add(img1);
			Mat hist1 = new Mat();
			Imgproc.calcHist(src1, new MatOfInt(0), new Mat(), hist1, new MatOfInt(256), new MatOfFloat(0, 256));

			Mat img2 = Imgcodecs.imread(filename2);
			List<Mat> src2 = new ArrayList<Mat>();
			src2.add(img2);
			Mat hist2 = new Mat();
			Imgproc.calcHist(src2, new MatOfInt(0), new Mat(), hist2, new MatOfInt(256), new MatOfFloat(0, 256));

			histList.add(Imgproc.compareHist(hist1, hist2, 0));

		}

		VideoComparison videoComparison = new VideoComparison();
		videoComparison.setVideo1(video1);
		videoComparison.setVideo2(video2);
		videoComparison.setHist(calcAvg(histList));
		return videoComparison;
	}
```

# GUIにしてみる

画像を扱うのでGUIにしたいところです。
悩みましたが、Java9リリース記念でJavaFXで作ることにしました。（JavaFXは8ですが…）

JavaFXではデザインレイアウトをXML（fxml）で記述でき、これをグラフィカルに編集できるのがScene Builderです。
XMLとコードの紐付けは、XML内のfx:id属性と.javaの@FMXLアノテーションで行います。

例えば実行ボタンのコントロールを以下のように配置した場合は、

```xml
<Button fx:id="executeBtn" layoutX="214.0" layoutY="31.0" mnemonicParsing="false" onAction="#doExecuteAction" prefHeight="38.0" prefWidth="115.0" text="execute" />
```

Javaコード側では以下のようにします。

```java
	@FXML
	void doExecuteAction(ActionEvent event) {
        // ...
    }
```

これでボタンを押した場合に実行される処理がアタッチできます。



今回使用するコントロールとアクションは以下です。

- Button

    - 実行

    - 追加

    - 削除

- ListView

- TableView

    - 選択

- ImgView

# 最後に

久しぶりにJavaでコードを書きましたが、パーソナルユーズではめんどくさい面が多いですね。
実は最初はPythonで書いていたのですが、GUIにしたかった、スレッドとかそのあたりでどうしようかな…→JavaでもOpenCVできるじゃん。ってことでJavaにしました。

JavaでGUIするのはAWT以来な気がしますがJavaFXは思ったより良さげでした。

残件は以下あたりですね。

- ファイルが多いと時間がかかる

    - parallelStreamで処理していますが、処理時間面でのチューニングの余地がありそう。

    - 処理状況の可視化

        - プログレスバーの実装

        - 処理状況ログの表示

- 画像の比較の精度向上

    - 特徴量による比較。

    - その他…？

休日プログラミングたのしかったです。おわり。

# 参考

[OpenCV: OpenCV modules](https://docs.opencv.org/master/)

[クライアント・テクノロジ: Java Platform, Standard Edition (Java SE) 8リリース8](http://docs.oracle.com/javase/jp/8/javase-clienttechnologies.htm)

[JavaFX Scene Builder 1.x Archive](http://www.oracle.com/technetwork/java/javafxscenebuilder-1x-archive-2199384.html)

[デザイナを使用してEclipseでJavaFX8開発をする - Qiita](https://qiita.com/xaatw0/items/f04e52d59faa2b46b87f)

[bitWalk's: JavaFX: CheckBox 付き ListView](http://bitwalk.blogspot.jp/2016/02/javafx-checkbox-listview.html)