Java OpenCV 〜輪郭:輪郭検出処理の順序〜

OpenCVでの輪郭抽出の処理手順を確認しようと思います。

今までに、OpenCV(JavaCV)での、学習準備と基本的な処理方法の学習を学習しました。

輪郭抽出のことを調べるとPythonでの処理とかアルゴリズムの理論とか、肝心要の部分が見つからず。。。

ようやく。。。

輪郭抽出の処理順序

  1. 画像を読み込む
  2. グレースケール(白黒画像)に変更する
  3. グレースケール画像から輪郭を抽出

大まかに、上のような手順で処理を行うと表示できる。
ただし、グレースケースの画像から輪郭を取得するのでわりかしリアルな白黒だと、外側の枠しか輪郭が取得できない。

ちょっとわかりづらいけど、緑色の線が輪郭です。

そして、これをわかりやすくするには
Imgproc.threshold()を使用する必要がある。

動かしてみると下のようなイメージです。

ここまできたら、あとはトライアンドエラーで星輪郭が得られるように、下の値を調整してやります。

ちょと画像は違いますが、下のような感じで試しました。

しかし、これは、写真により値を変更しないと欲しい輪郭が取れない。。。

よく見かけるサンプルでは、輪郭がくっきりしているので、問題ないだろうが、ちょっと考え直す必要があると思いました。

→輪郭の取得方法に関して、考え直すと言う意味です。



Java OpenCV 〜画像の一部分を書き換える〜

画像の一部分を変更して、他のファイルから読み取った画像を書き込む方法

結論


下の画像のように、道の写真に「Tea!」と書いた絵を重ねたようなものに書き換える処理です。

手こずったところ

Roiの使い方、つまりはMat#submat()とMat#copyTo()の使い方に手こずりました。

以下のような画像を使用しました。
道路のイメージ=img1
Teaのイメージ=img2

Mat roi = img.submat(new Rect(20, 20, tea.width(), tea.height()));
Mat dst = new Mat();
tea.copyTo(roi);

上の処理で、土台になるいmg1にimg2の内容を書き込みます。
あとは、表示するだけです。

ソースはGithubにあります。

ポイント

画像の取得部分

URL url = this.getClass().getResource("/road.png");
Mat img = Imgcodecs.imread(url.getPath(), Imgcodecs.IMREAD_UNCHANGED);

画像の抜き出し

Mat roi = img.submat(new Rect(20, 20, tea.width(), tea.height()));

変数teaはimg2の画像です。
つまりは、道路の画像からx=20, y=20からteaのサイズ分を抜き出していると言うことです。

書き込み

tea.copyTo(roi);

これだけです。

ここにたどり着くのに、3日くらいかかりました。(笑)

しかし!

これでは、いまいち納得がいきませんでした。
なぜなら、「重ねる」のが目的であるからです。
これじゃ「上書き」です。

そんなわけで

やっと見つけました。こうすれば良いと言うものです。
理論的には、以下のような手順です。

  1. 書き込みたい画像を取得する
  2. 取得した画像と同じサイズ文のROI(画像の一部分)を取得する
  3. 抜き出したROIをCore.addWeighted()で書き込みたい画像とROIを重ねます。
  4. copyToで重ねたデータをROIにコピーする
URL url = this.getClass().getResource("/road.png");
Mat img = Imgcodecs.imread(url.getPath(), Imgcodecs.IMREAD_UNCHANGED);
printAttribute(img);

 URL url2 = this.getClass().getResource("/Tea.png");
 Mat tea = Imgcodecs.imread(url2.getPath(), Imgcodecs.IMREAD_UNCHANGED);
// 文字列の配置
Imgproc.putText(img, "sample!", new Point(10, 10), Core.FONT_HERSHEY_SIMPLEX, 0.5, Scalar.all(0));
// 
Mat roi = img.submat(new Rect(20, 20, tea.width(), tea.height()));
Mat dst = new Mat();
Core.addWeighted(tea, 0.2, roi, 0.8, 0.5, dst);
dst.copyTo(roi);

すると以下のような画像になります。

元にした画像が、透過PNG担っていないのが気になりますが、今回はここら辺で

でわでわ。。。



OpenCV エラー 〜Sizes of input arguments do not match〜

下のようなエラー(Exception)が出ました。

(-209:Sizes of input arguments do not match) The operation is neither 'array op array' (where arrays have the same size and the same number of channels), nor 'array op scalar', nor 'scalar op array' in function 'arithm_op'
]

結論から言うと、

「入力と出力する画像のサイズが違います」

と言うことでした。あと、チャンネルも一緒である必要がある

翻訳すると。。。

操作は、「array op array」(配列のサイズとチャネル数が同じ)、「array op scalar」、「arithm_op」の「scalar op array」のいずれでもありません



Java OpenCV 〜基礎: 画像上の基本的な処理〜

参考にするサイトはこちらです。
実はPython版のチュートリアルなのですが、これをJavaに置き換えて学んでいきます。。。(Java版だと動画からしか見つからなかった〜)

基本1

参考サイトによると以下の内容を学びます。

  1. 画素値のアクセス及び変更方法
  2. 画像の属性情報の取得
  3. 画像中の注目領域(ROI)の設定
  4. 画像の分割と統合

まず初めにカラー画像を読み込みましょう:

とりあえずはその通りに、実装してみます。
作成したクラスは下のクラスです。準備で実装したCommandIFを実装(implements)したクラスを作成します。

実行結果は下のような感じです。

使用しているCanvasのサイズが200x200なのではみ出しています。。。

実行するコードはこんな感じです。

@Override
public void execute(Pane pane) throws Exception {
    ObservableList<Node> obsList = pane.getChildren();
    Canvas before = null;
    for (Node node : obsList) {
        before = (Canvas) node.lookup("#testCanvasBefore");
    }
    // イメージファイルパスを取得する
    URL url = getClass().getResource("/himawari.png");
    // イメージファイルをロードして行列(Mat)に格納
    Mat img = Imgcodecs.imread(url.getPath(), Imgcodecs.IMREAD_COLOR);
    MatOfByte bikeByte = new MatOfByte();
    // 画像データをMatOfByteに書き込む
    Imgcodecs.imencode(".jpeg", img, bikeByte);
    // BuffereImageを取得する
    BufferedImage outImage = ImageIO.read(new ByteArrayInputStream(bikeByte.toArray()));
    // Canvasへの描画準備
    GraphicsContext g = before.getGraphicsContext2D();
    // 描画ポイントを指定して描画する
    g.drawImage(SwingFXUtils.toFXImage(outImage, null), 0, 0);

}

これで、起動できるのは、CommandIFの実装によるところです。
詳細はこちらの記事に記載しております。

大まかな作りは、下のような感じです。

OpenCVの処理

ようやく、本題に入ります。
OpenCVでの基本ということで、まずは「画像を描画する」というところに焦点を置き実装しました。
Javaでの実装の場合はテクノロジー的には以下のものを使用します。

  1. java.awt(描画関連API)
  2. java.io(入出力関連API)
  3. opencvライブラリ

具体的には、以下の通りです。番号で対応しています。

  1. BufferedImage(java.awt.image)
  2. ByteArrayInputStream(java.io)
  3. Imagcodecs(org.opencv.core.imagecodecs)

描画処理の部分

イメージをロードしてMatクラスを取得した後にはjavafxの部品を使用してCavasよりGraphicContextを取得して
Mat(画像データ)を描画しています。

上のキャプチャはキャンバスサイズが読み込んだ画像よりも小さかったので、表示した時に中途半端なことになりました。

あまり詰め込んでも仕方ないので今回はここまでにします。
あー頭イタイ。。。



Java OpenCV 〜学習準備のまとめ〜

こちらのサイトを参考にしてOpenCVの基本から学習していこうと思います。

理由は、輪郭取得もよくわからなかったからです。。。

学習準備

今までの記事でOpenCVを学習する準備をしていました。

  1. Java OpenCV 〜背景除去、輪郭を学習する準備1: SceneBuilder〜
  2. Java OpenCV 〜背景除去、輪郭を学習する準備2コントロラー追加〜
  3. Java OpenCV 〜背景除去、輪郭を学習する準備3:画像を表示する〜
  4. Java OpenCV 〜背景除去、輪郭を学習する準備4:コマンドで起動する実装〜

余談ですが、こちらのやってきたことをまとめると以下のようになります。

準備の概要

  1. SceneBuilderを用いて、簡単に画面の土台を作成する
  2. 作成した土台を元に、コントローラーを追加
  3. 起動確認を兼ねて画像を表示する
  4. コントローラーから、コマンド的に新規に作成するプログラムを起動できる仕組みを作る

プログラムの解説

githubに作成したプログラムをアップロードしてあります。

この階層にある「Main.java」クラスがこのアプリを起動するクラスになります。

Main.java

/** ネイティブライブラリを読み込む */
static {
    System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
/**
 * メインメソッド。
 * @param args
 */
public static void main(String[] args) {
    // 親クラス(Superクラス)のメソッド起動
    launch();
}

/* (non-Javadoc)
 * @see javafx.application.Application#start(javafx.stage.Stage)
 */
@Override
public void start(Stage primaryStage) throws Exception {
    primaryStage.initStyle(StageStyle.TRANSPARENT);
    FXMLLoader loader = new FXMLLoader(ClassLoader.getSystemResource("TestingCv.fxml"));
    BorderPane root = (BorderPane) loader.load();
    Scene scene = new Scene(root, 800, 600);
    scene.setFill(null);
    scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());

    // 作成するクラス
    final TestingCvController controller = loader.getController();
    controller.setPane(root);
    primaryStage.setOnCloseRequest((new EventHandler<WindowEvent>() {
        public void handle(WindowEvent we) {
            controller.setClosed();
        }
    }));
    xPos = 200;
    yPos = 200;
    // キーアクションを追加する
    scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent event) {
            if (event.getEventType() == KeyEvent.KEY_PRESSED) {
                try {
                keyHandle(event.getCode(), root, primaryStage, controller);
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }
    });
    primaryStage.setTitle("Video Processing");
    primaryStage.setScene(scene);
    primaryStage.show();
}

Mainプログラムの主な部分を抜粋しました。
シンプルに、mainメソッドからアプリケーションを起動します。

/** ネイティブライブラリを読み込む */
static {
    System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}

このstaticで囲んだ部分がJavaを起動した時に(はじめに)読み込まれます。これで、OpenCVのライブラリを読み込みます。

そして、以下のコードでFXMLをロード(読み込み)して作成した画面の土台を取得。

FXMLLoader loader = new FXMLLoader(ClassLoader.getSystemResource("TestingCv.fxml"));
BorderPane root = (BorderPane) loader.load();
Scene scene = new Scene(root, 800, 600);
scene.setFill(null);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());

// 作成するクラス
final TestingCvController controller = loader.getController();
controller.setPane(root);
primaryStage.setOnCloseRequest((new EventHandler<WindowEvent>() {
    public void handle(WindowEvent we) {
        controller.setClosed();
    }
}));

今回使用したFXMLファイルはTestingCv.fxmlです。

読み込んだFXMLで定義しているコントローラーTestingCvControllerクラスを取り出しています。このコントローラで

final TestingCvController controller = loader.getController();

SceneBuilderで作成した土台にあるコンポーネントのオブジェクトをコントローラークラスで管理します。
具体的には。。。

コントローラー

SceneBuilderで作成した画面が下の画像です。

この土台には、下のようなコンポーネントを追加しています。

そして、洗濯している部分は。。。

上のようにフォーカスされます。

こんな感じで、追加したコンポーンネントがコントローラーで操作することができます。

TestingCvController.java

public class TestingCvController {

    @FXML
    private Canvas testCanvasBefore;
    @FXML
    private Canvas testCanvasAfter;

    @FXML
    private TextField input;

    private Properties prop;

    private CommandIF cmd;

    private Pane pane;

    /** コンストラクタ */
    public TestingCvController() {
        this.testCanvasBefore = new Canvas();
        this.testCanvasAfter = new Canvas();
//      this.testing = new LearnOpenCv();
        this.prop = new Properties();
        String propPath = "/command.properties";
        try {
            this.prop.load(this.getClass().getResourceAsStream(propPath));
        } catch (IOException e) {
            System.out.println(">>> Error! プロパティファイルの読み込みに失敗しました。" + propPath);
            e.printStackTrace();
        }
        // 確認
        System.out.println("プロパティ: " + prop.get("hello"));
    }

    /**
     * 画面のExecuteボタンを押下した時に起動する処理
     */
    @FXML
    protected void clickExecute() throws Exception {
        // 初期化する
        cmd = null;
        // 入力確認用
//      System.out.println(this.input.getText());
        String inputStr = this.input.getText();
        cmd = this.getCommand(inputStr);
        if (cmd == null) {
            // プロパティファイルに、コマンドがない
            System.out.println("コマンドがあません。" + this.input.getText());
        } else {
            // コマンド実行
            cmd.execute(this.pane);
        }
    }

    @FXML
    public void setClosed() {
        // 現状は空実装
    }

    /**
     * Clearボタンを押下した時の処理
     */
    @FXML
    public void clear() {
        System.out.println("Clear");
        // 描画したものをクリアする
        this.testCanvasBefore.getGraphicsContext2D().clearRect(0, 0, this.testCanvasBefore.getWidth(), this.testCanvasBefore.getHeight());
        // 描画したものをクリアする
        this.testCanvasAfter.getGraphicsContext2D().clearRect(0, 0, this.testCanvasAfter.getWidth(), this.testCanvasAfter.getHeight());
    }

    @FXML
    private void terminated() {
        System.exit(0);
    }

    public void setPane(Pane pane) {
        this.pane = pane;
    }
}

上のコードでcode>@FXMLアノテーションのついているフィールドは以下のものです。

@FXML
private Canvas testCanvasBefore;
@FXML
private Canvas testCanvasAfter;

@FXML
private TextField input;

TestingCV.fxml

<TextField fx:id="input" promptText="App No" />
<Canvas fx:id="testCanvasBefore" height="200.0" width="200.0" />
<Canvas fx:id="testCanvasAfter" height="200.0" width="200.0">

上記の部分が上のクラスにあるcode>@FXMLをつけたフィールド変数と対応しています。

同様に。。。

<Button mnemonicParsing="false" onAction="#clickExecute" text="Execute" />
<Button mnemonicParsing="false" onAction="#clear" text="Clear" />
<Button mnemonicParsing="false" onAction="#terminated" text="Exit" />

の部分はcode>@FXMLのついたメソッドに対応しています。

動かして見ると、下のような感じです。(コーディング〜なので最後の方に表示した画面があります)



Java OpenCV 〜Error: Empty JPEG image〜

下のようなエラーメッセージが出ました、

Empty JPEG image (DNL not supported) in function 'throwOnEror'

javaFXでのCanvasに「バイク.jpg」を描画しようとした時です。

ここのサイトによると、スマホで撮影した画像は変換が必要なようで。。。

結局自分は、スマホで撮ったファイルは使用しない方向に切り替えました(笑)

以前、ファイルをレンタルサーバーにアップロードするときもスマホの写真がエンコードできなくてつまづきました。

変換ツールがいるんだなぁ。。。

でわでわ。。。



Java OpenCV 〜輪郭:輪郭検出処理の調査〜

今回は、とりあえずは、輪郭を取得するfindContours()メソッドを使用して輪郭の情報を取得します。

前回は、画像データの中身を見て画面の白い部分を透明にする処理を実装しました。

なので、作成したアプリの改造をして背景をJavaFXに出力します。

今回の表示する画像は自分の顔です(笑)
具体的には下のような感じです。

この表示した画像の背景部分が顔の輪郭から外の部分。。。

しかし、今回は背景も、顔の内側も全て「白」なので前回のように色指定をして「この色の時はアルファ値を透明にする」というようなことができません。。。

とりあえず輪郭を表示

調べて見ると[findContours(http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_contours/py_contours_begin/py_contours_begin.html)]()を使用して輪郭を取得することができるようです。

とりあえずは実行して見る!

なんかチビクロサンボみたいだな。。。

定数:RETR_TREE

とりあえずは輪郭の表示ができているようです。
この輪郭の表示(データの書き込み)に関しては下のコードで行なっています。
Imgproc.findContours(gray, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);

定数:RETR_EXTERNAL

上の赤文字の部分をImgproc.RETR_EXTERNALに変更すると真っ黒になります(笑)

定数:RETR_LIST

同様にRETR_LISTに変更

定数:RETR_CCOMP

調べて見た定数を使用して表示した白い部分が輪郭として検出されている部分になります。
つまり、輪郭部分の座標が取得できているということです。

第三引数を変えて見る

// CHAIN_APPROX_NONE
// CHAIN_APPROX_SIMPLE
// CHAIN_APPROX_TC89_L1
// CHAIN_APPROX_TC89_KCOS


結局のところは

よくわかりませんでした。というか輪郭部分を取得(白色)して表示した状態です。しかし肝心の輪郭に関してはいまいちな状態です。ちょいと調査します。。。

でわでわ。。。



OpenCVエラー 〜contours.cpp:199: error〜

下のようなエラーが出ました。

opencv/modules/imgproc/src/contours.cpp:199: error: (-210:Unsupported format or combination of formats) [Start]FindContours supports only CV_8UC1 images when mode != CV_RETR_FLOODFILL otherwise supports CV_32SC1 images only in function 'cvStartFindContours_Impl'

ここで改めてログを見て見ると

[Start]FindContours supports only CV_8UC1 images

TRY1

とあるので、ソースを下のようなものから(テスト中で。。。)

Mat charactor = Imgcodecs.imread(url.getPath(), Imgcodecs.IMREAD_UNCHANGED);
// 背景除去
Mat hierarchy = Mat.zeros(new Size(200, 200), CvType.CV_8UC1);
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Imgproc.findContours(charactor, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_TC89_L1);
Mat dstContuor = Mat.zeros(new Size(charactor.width(),charactor.height()),CvType.CV_8UC3);
Scalar color=new Scalar(255,255,255);  
Imgproc.drawContours(dstContuor, contours, -1, color,1); 

描画モード(引数に渡している定数を「CvType.CV_8UC1」統一しました。

しかし、また。。。

opencv/modules/imgcodecs/src/loadsave.cpp:925: error: (-215:Assertion failed) code in function 'imencode'

Try2

調べて見ると、画像はグレースケースでないとけ内容だ。。。

読み取りを下のように修正

Mat charactor = Imgcodecs.imread(url.getPath(), Imgcodecs.IMREAD_GRAYSCALE);
Mat gray = new Mat();
Imgproc.cvtColor(charactor, gray, Imgproc.COLOR_BGR2GRAY);

駄菓子菓子またもエラー

Invalid number of channels in input image:
'VScn::contains(scn)'
where
'scn' is 1
]

こんな感じのエラー。。。

TRY3

色々と試して見るが、どれもエラーになる。。。
ソースを少し見直して見た!

// 表示イメージを読み取る
Mat charactor = Imgcodecs.imread(url.getPath(), Imgcodecs.IMREAD_COLOR);
Mat gray = new Mat();
Imgproc.cvtColor(charactor, gray, Imgproc.COLOR_BGR2GRAY);
// 背景除去
Mat hierarchy = Mat.zeros(new Size(200, 200), CvType.CV_8UC1);
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Imgproc.findContours(gray, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);
Mat dstContuor = Mat.zeros(new Size(charactor.width(),charactor.height()),CvType.CV_8UC1);
Scalar color=new Scalar(255,255,255);  
Imgproc.drawContours(dstContuor, contours, -1, color,1); 

//      Size resize = new Size(before.getWidth() / 3, before.getHeight() / 3);
//      Mat reSizeChar = new Mat();
//      Imgproc.resize(charactor, reSizeChar, resize);
MatOfByte charaByteBefore = new MatOfByte();
// 表示イメージをcharaByteに書き込む
Imgcodecs.imencode(".png", dstContuor, charaByteBefore);

Mat dst = new Mat();
//      Imgproc.threshold(reSizeChar, dst, 100, 255, Imgproc.THRESH_BINARY);
MatOfByte charaByte = new MatOfByte();

// この行で落ちていた。。。。
☆Imgcodecs.imencode(".png", dst, charaByte);

上の☆マーク部分で落ちていたのですが、これは不要な処理でした。。。(色々と試した時の残骸です(笑))

そんなわけで実行することができました。

変更前は下のような感じです。ハナっからグレースケール(笑)

でわでわ。。。



Java OpenCV 〜背景除去、輪郭を学習する準備4:コマンドで起動する実装〜

画面に入力した文字列(コマンド)で起動する処理をプログラムを変更しないでも追加できるように実装を変更します。

現状の実装

  1. Executeボタンを押下すると処理が動く
  2. 画面の上部中央にあるテキストふフィールドの値を取得
  3. 画面の下にあるClearボタンで表示したイメージを消す

シンプルに、ボタンを押下→Canvas部分にイメージを描画

という処理を行っています。Clearボタンを押下するとCanvasに描画したイメージがクリアされて何もなくなります。

次の実装

実装した時の動画を作成しました。

行っていることは、下の通りです。

  1. CommandIF(インターフェースクラス)を作成
  2. LearnOpenCvクラスにCommandIFを実装(impllements)
  3. 起動確認

Githubにて、実装の差分を見ることができます。

とりあえず、ここまでの実装では、動き的にはじめのものと変わりません。そのように作りました。
ポイントとしては、CommandIFを追加したので、CommandIFを実装したクラスなら、なんでも実行することができるようになったというところです。

クラスを動的に変更して見る

ここで追加するのが、Propertyファイルです。
処理内容としては下のような手順です。

  1. Propetiesクラスを使用してプロパティファイルを読み込む
  2. キー(コマンド)がプロパティファイル内にないときはエラーメッセージをコンソールに出力
  3. キー(コマンド)で起動するクラスの完全名を取得する
  4. 完全クラス名でクラスのインスタン種を取得する
  5. 取得したクラスはCommandIFを実装しているのでexecute()を実行する
  6. 結果をGraphicsContextクラスに描画する→JavaFXのCanvasに描画される

こんな感じです。

実行した結果は動画にしました。余計な音が入っています。

前半

後半

次回

OpenCVでの学習を開始します。ようやく準備ができました。

でわでわ。。。

Java OpenCV 〜背景除去、輪郭を学習する準備3:画像を表示する〜

JavaFXを使用してOpenCVで行なった処理を表示する仕組み(アプリ)を作成します。

前回、SceneBuilderを使用して作成した画面の土台になる部分に、コントローラーを追加しました。
これにより「ボタンを押下した時にXXXの処理をする〜」というような処理を実装することができます。

画像を描画する

そして、やっとOpenCVの処理にたどり着きました。
イメージファイルを読み込み表示する。。。ハローワールド的な処理ですが、まずはハローワールドです。

出力した結果はい下のような感じで出力することができました。

実装(成果物)

「FXMLファイル」

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.canvas.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<BorderPane fx:controller="zenryokuservice.opencv.fx.controller.TestingCvController"
        maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
        prefHeight="400.0" prefWidth="600.0"
        xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <top>
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <children>
            <Label text="Input app no" />
            <TextField promptText="App No" />
            <Button mnemonicParsing="false" text="Execute" onAction="#clickExecute"/>
         </children>
      </HBox>
   </top>
   <center>
      <Canvas fx:id="testCanvas" height="200.0" width="200.0" BorderPane.alignment="CENTER" />
   </center>
   <bottom>
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <children>
            <Button mnemonicParsing="false" text="Clear"  onAction="#clear"/>
         </children>
      </HBox>
   </bottom>
</BorderPane>

コントローラー

public class TestingCvController {

    @FXML
    private Canvas testCanvas;

    /** コンストラクタ */
    public TestingCvController() {
        this.testCanvas = new Canvas();
    }

    @FXML
    protected void clickExecute() {
        System.out.println("Helllo");
        URL url = getClass().getResource("/pipo-charachip007.png");
        Mat charactor = Imgcodecs.imread(url.getPath(), CvType.CV_8UC4);
        MatOfByte charaByte = new MatOfByte();
        Imgcodecs.imencode(".png", charactor, charaByte);
        try {
//          Image img = new Image(url.getPath().toString());
            BufferedImage buf = ImageIO.read(new ByteArrayInputStream(charaByte.toArray()));
            GraphicsContext g = testCanvas.getGraphicsContext2D();
            g.drawImage(SwingFXUtils.toFXImage(buf, null), buf.getWidth(), buf.getHeight());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @FXML
    public void setClosed() {
        // 現状は空実装
    }

    @FXML
    public void clear() {
        System.out.println("Clear");
        this.testCanvas.getGraphicsContext2D().clearRect(0, 0, this.testCanvas.getWidth(), this.testCanvas.getHeight());
    }
}

処理内容

FXMLで、アクションを指定しています。

<Button mnemonicParsing="false" text="Execute" onAction="#clickExecute"/>

この行で指定している#clickExevuteの部分でコントローラークラスの「TestingCvController#clickExevute()
を呼び出す」という意味になります。
同様に、<Button mnemonicParsing="false" text="Clear" onAction="#clear"/>の部分は「TestingCvController#clear()」を呼び出す。という命令を指定しています。

画像を表示する

下の処理が起動するメソッドになります。

@FXML
protected void clickExecute() {
    System.out.println("Helllo");
    URL url = getClass().getResource("/pipo-charachip007.png");
    Mat charactor = Imgcodecs.imread(url.getPath(), CvType.CV_8UC4);
    MatOfByte charaByte = new MatOfByte();
    Imgcodecs.imencode(".png", charactor, charaByte);
    try {
        BufferedImage buf = ImageIO.read(new ByteArrayInputStream(charaByte.toArray()));
        GraphicsContext g = testCanvas.getGraphicsContext2D();
        g.drawImage(SwingFXUtils.toFXImage(buf, null), buf.getWidth(), buf.getHeight());
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

ビルドパス上にあるフォルダより「pipo-charachip007.png"」を読み込み、SceneBuilderで作成したCanvasに書き込み処理を行っています。
上に表示した画像は指定のpngファイルを表示したものです。

ここまで来たので

現状としては、固定のイメージファイルを表示しているだけですが、ここからアプリっぽくしていきます。
変更点としては下の部分です。

  1. テキストボックスに入力したファイル名を表示する
  2. Clearボタンを押下したら、現在表示している画像を消す

とりあえずはここまで実装します。

LearnOpenCvクラスを作る

設計としては以下のような実装にしようと考えております。

  1. テキストフィールドに処理の名前(コマンド)を入力
  2. コマンドに対応したクラスのexecute()を起動する→なのでコマンドはいくらでも増やすことができる

具体的にどのように実現するか?

すでに実装してあるclickExcecute()の処理を修正して下のように変更する

  1. 入力値を受け取り、プロパティファイルのキーの有無をチェック
  2. プロパティファイルにキーがあれば、キーに紐付くクラスを取得、execte()を起動する
  3. 実行結果をCanvasに描画する

なので、execute()には引数としてGraphicsContextを渡してあげる必要があります。(描画するためです)

実装は次回にいたします。

でわでわ。。。