LoginSignup
5
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-10-22

はじめに

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

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

\(^o^)/

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

画面イメージ
image

環境

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を使うクラスでロード処理を記述する必要がありました。

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

動画から画像を抽出する

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

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

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

    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枚それぞれの比較結果の平均を動画全体の比較結果としました。

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

    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アノテーションで行います。

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

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

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

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

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

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

  • Button

    • 実行
    • 追加
    • 削除
  • ListView

  • TableView

    • 選択
  • ImgView

最後に

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

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

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

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

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

      • プログレスバーの実装
      • 処理状況ログの表示
  • 画像の比較の精度向上

    • 特徴量による比較。
    • その他…?

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

参考

OpenCV: OpenCV modules

クライアント・テクノロジ: Java Platform, Standard Edition (Java SE) 8リリース8

JavaFX Scene Builder 1.x Archive

デザイナを使用してEclipseでJavaFX8開発をする - Qiita

bitWalk's: JavaFX: CheckBox 付き ListView

5
4
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
5
4