Java OpenCV Lv2 〜JavaFXでの画像表示〜

イントロダクション

前回は、写真などを表示しようと試みましたがImShowメソッドが使用できないということで、JavaFXを使用してやる方向にしました。

参考サイト;Java版OpenCVチュートリアル(英語です)

<工程>

  1. Eclipseのインストール
  2. OpenCVインストール
  3. JavaでOpenCVのライブラリを起動する(工程2の記事でやりました)
  4. JavaFXで写真を表示してみる(今回実行します)

画像(イメージ)表示の準備

こちらのサイトを参考にしてやります。

大雑把に上記の<工程>にあるようなことが書いてありました。今までに<工程>の1、2をやったので3からいきます。

<前提>

JavaFXでの画面作成のためにSceneBuiderをセットアップします。

  1. Eclipse SceneBuilderを追加する
  2. JavaFX SceneBuilder 〜EclipseとSceneBuilder連携~


JavaFXの実装

ソースコードはGITにあるものを参照(参考サイトより)

<今回の作成するアプリ構成>

ソースはGitにアップロードしてあります。

<Main.java>

import org.opencv.core.Core;

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.fxml.FXMLLoader;

/**
 * OpenCVnogaの学習用のGUIを作成する。
 * JavaFXで画面を作成している
 * 1.SceneBuilderでFXMLを出力
 * 2.FXMLで取得したコンポーネントを表示する
 * 
 * @author takunoji
 * 2018/11/18
 */
public class Main extends Application {
	/**
	 * このアプリケーション(Mainメソッド)が起動する前に
	 * 起動(OpenCVのロード)する
	 */
	static {
		System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
	}
	/**
	 * スーパクラスApplication)のstartメソッドを起動する
	 * オーバーライドしているので、本当は親クラスのメソッドが起動するが
	 * 子クラスで上書きするので、このクラスを起動した時にはこちらのメソッドが起動する。
	 */
	@Override
	public void start(Stage primaryStage) {
		try {
			// FXMLをロード
			BorderPane root = (BorderPane)FXMLLoader.load(getClass().getResource("OpenCvTest.fxml"));
			// 表示領域を作成する
			Scene scene = new Scene(root,400,400);
			// JavaFX用のCSSを適用する
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			// Sceneをステージに設定する
			primaryStage.setScene(scene);
			// 表示処理
			primaryStage.show();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	/**
	 * メインメソッド。
	 * JavaFXの規定部品
	 * Application{@link #start(Stage)}を起動する
	 * 
	 * @param args プログラム引数
	 */
	public static void main(String[] args) {
		// スーパークラス(親)のメソッドを起動する
		launch(args);
	}
}

<OpenCvController.java>

/**
 * FXMLで定義したコントローラクラス。
 * 参考サイトを写経しようとしたが、ソースを少しいじる
 * 基本的には写経している。
 * @see 
 * @author takunoji
 * 2018/11/17
 */
public class OpenCvController {
	 /** FXML定義のボタン */
	@FXML
	private Button button;
	/** FXML定義のImageViewer */
	@FXML
	private ImageView currentFrame;
	
	/** ビデオキャプチャ */
	private VideoCapture capture = new VideoCapture();
	
	private ScheduledExecutorService timer;
	// a flag to change the button behavior
	private boolean cameraActive = false;
	// the id of the camera to be used
	private static int cameraId = 0;
	
	/**
	 * ボタン押下時のアクション処理。
	 * "@FXML"アノテーションでFXMLとの同期を取っている
	 * @param event ボタン押下のイベント
	 */
	@FXML
	protected void startCamera(ActionEvent event) {
		// カメラがアクティブ状態の時は停止する
		if (this.cameraActive) {
			this.cameraActive = false;
			this.button.setText("Start Camera");
			this.stopAcquisition();
			// 処理終了
			return;
		}
		// カメラ
		this.capture.open(this.cameraId);
		// カメラが開いていない時
		if (this.capture.isOpened() == false) {
			// エラーログを出力して処理を終了する
			System.err.println("Impossible to open the camera connection...");
			return;
		}
		// カメラが正常に開いている時
		this.cameraActive = true;
		// 匿名クラス
		Runnable frameGrabber = new Runnable() {
			@Override
			public void run() {
				Mat frame = grabFrame();
				Image imageToShow = Utils.mat2Image(frame);
				updateImageView(currentFrame, imageToShow);
			}
		};
		this.timer = Executors.newSingleThreadScheduledExecutor();
		this.timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);
		this.button.setText("Stop Camera");
	}

	/**
	 * 開いているビデオストリームからフレームを取得する
	 * @return {@link Mat}
	 */
	private Mat grabFrame() {
		Mat frame = new Mat();
		if (this.capture.isOpened()) {
			try {
				this.capture.read(frame);
				if (frame.empty() == false) {
					Imgproc.cvtColor(frame, frame, Imgproc.COLOR_BGR2GRAY);
				}
				
			} catch(Exception e) {
				System.err.println("Exception during the image elaboration: " + e);
			}
		}
		return frame;
	}

	private void stopAcquisition() {
		if (this.timer != null && this.timer.isShutdown() == false) {
			try {
				this.timer.shutdown();
				this.timer.awaitTermination(33, TimeUnit.MICROSECONDS);
			} catch(Exception e) {
				// log any exception
				System.err.println("Exception in stopping the frame capture, trying to release the camera now... " + e);
			}
		}
		// @FIXME-[カメラを解放するだけで良い?]
		if (this.capture.isOpened()) {
			this.capture.release();
		}
	}
		/**
		 * Update the {@link ImageView} in the JavaFX main thread
		 * 
		 * @param view
		 *            the {@link ImageView} to update
		 * @param image
		 *            the {@link Image} to show
		 */
		private void updateImageView(ImageView view, Image image) {
			Utils.onFXThread(view.imageProperty(), image);
		}
		
		/**
		 * On application close, stop the acquisition from the camera
		 */
		protected void setClosed() {
			this.stopAcquisition();
		}
}

ボタンを押下したらエラーが出た!

下のキャプチャのようにbottunの変数名とIDが違っていると次のようなエラーが出ます。

 

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException

<BorderPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="zenryokuservice.opencv.fx.OpenCvController">
   <center>
      <BorderPane prefHeight="522.0" prefWidth="410.0" BorderPane.alignment="CENTER">
         <bottom>
            <Button fx:id="start_btn" mnemonicParsing="false" onAction="#startCamera" text="StartCamera" BorderPane.alignment="CENTER" />
         </bottom>
         <center>
            <ImageView fx:id="currentFrame" fitHeight="248.0" fitWidth="234.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER" />
         </center>
      </BorderPane>
   </center>
</BorderPane>

上のコード赤字の部分を変数名に合わせます。「button」に修正。

<問題>

このVideoCaptureを起動し、閉じるときにメモリの使用量の部分(そのほかにもあるかも?)で問題があります。

ソースの方では、カメラを停止したときに「System.gc()」でメモリを解放するようにしたのですが、操作を早くするとクラッシュします。

この部分は、根深い問題なのでとりあえずは、飛ばしてOpenCVを学んでいくようにします。。。

エラーログ

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007fffcfe13017, pid=1748, tid=0x000000000001560f
#
# JRE version: Java(TM) SE Runtime Environment (8.0_144-b01) (build 1.8.0_144-b01)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode bsd-amd64 compressed oops)
# Problematic frame:
# C  [libobjc.A.dylib+0x7017]  objc_msgSend+0x17
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /XXX/git/OpenCvFX/OpeCvFX/hs_err_pid1748.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

 

関連ページ一覧

<JavaFXの準備>

  1. EclipseにSceneBuilderを追加する
  2. JavaFX SceneBuilder 〜EclipseとSceneBuilder連携~
  3. Java OpenCv Lv1 〜入門: 写真の表示〜
  4. Java OpenCV Lv2 〜JavaFXでの画像表示〜
    1. バグを回収しました。