JavaFX ワンポイント 〜Fontを指定する〜

TextFIeldやTextAreaを使用するときにFontの設定をしてやると大きさなど変更できます。

設定方法は以下のようなコードです。

newWindow = new Stage();
StackPane stack = new StackPane();
textArea = new TextArea();
textArea.setFont(Font.font("MS UI Gothic", FontWeight.BLACK, 19));
textArea.setOnKeyPressed(keyEvent -> {
    KeyCode code = keyEvent.getCode();
    if (keyEvent.isShiftDown() && KeyCode.SPACE.equals(code)) {
        newWindow.close();
        isTextIn = false;
    }
});
textArea.setOnKeyReleased(keyEvent -> {
    KeyCode code = keyEvent.getCode();
    if (keyEvent.isShiftDown() && KeyCode.ENTER.equals(code)) {
        textArea.setText("");
        textArea.positionCaret(0);
    }
});
stack.getChildren().add(textArea);
Scene scene = new Scene(stack, 450, 100);
newWindow.setScene(scene);
newWindow.initModality(Modality.WINDOW_MODAL);
newWindow.initOwner(primary);
newWindow.setX(primary.getWidth() * 0.4);
newWindow.setY(primary.getHeight() * 0.8);
newWindow.show();

return newWindow;

TextAreaで実装しましたが、これはTextFieldにも使用できます。

でわでわ。。。



関連ページ一覧

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseCDTをインストール〜
  3. Setup OpenGL with JavaJOGLを使う準備 for Eclipse
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder EclipseSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 GitからソースをPullしよう〜
  3. IntelliJ IDEA GitGitリポジトリからクローン〜

JavaFX ワンポイント 〜新しいWindowを立ち上げる〜

JavaFXを起動するときにすでにウィンドウを1つ起動していますが、さらにウィンドウを使いたいとき。。。

今回はそんなところを「ワンポイントレッスン」的に記載したいと思います。

ちなみに、JavaFXはこんな感じのものです。余計な音が入っています。そして、文言を表示する部分でnewWindowを使用しています。

ウィンドウを新しく作る

WindowはStageです。なので、下のように実装したらできました。
参考にしたサイトはこちらです。

private Stage createNewWindow(Stage primary) {
    newWindow = new Stage();
    StackPane stack = new StackPane();
    textArea = new TextArea();
    textArea.setOnKeyPressed(keyEvent -> {
        KeyCode code = keyEvent.getCode();
        if (keyEvent.isShiftDown() && KeyCode.SPACE.equals(code)) {
            newWindow.close();
            isTextIn = false;
        }
    });
    textArea.setOnKeyReleased(keyEvent -> {
        KeyCode code = keyEvent.getCode();
        if (keyEvent.isShiftDown() && KeyCode.ENTER.equals(code)) {
            textArea.setText("");
            textArea.positionCaret(0);
        }
    });
    stack.getChildren().add(textArea);
    Scene scene = new Scene(stack, 250, 50);
    newWindow.setScene(scene);
    newWindow.initModality(Modality.WINDOW_MODAL);
    newWindow.initOwner(primary);
    newWindow.setX(primary.getWidth() * 0.4);
    newWindow.setY(primary.getHeight() * 0.8);
    newWindow.show();

    return newWindow;
}

作成したウィンドウにキーイベントを追加して見ました。

ちなみに、
簡単ですが、こんな感じで。。。

でわでわ。。。



関連ページ一覧

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseCDTをインストール〜
  3. Setup OpenGL with JavaJOGLを使う準備 for Eclipse
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder EclipseSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 GitからソースをPullしよう〜
  3. IntelliJ IDEA GitGitリポジトリからクローン〜

Java Basic Class 〜メインメソッドを変更しないで処理を変える〜

クラスを使用すると

メインメソッドを変更しなくても処理を変更できます。

具体的に、下のようにやります。
以前作成したメインメソッド

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String input = scan.next();

        FirstCls first = new FirstCls();
        boolean isNumber = first.isNumberString(input);
        if (isNumber) {
            System.out.println(input + "は数字です。");
        } else {
            System.out.println(input + "は数字ではありません。");
        }
    }
}

このクラスから呼び出しているFirstClsを修正してやれば、Mainクラスは修正する必要がありません。

なんか「とんち」のような話ですが(笑)

応用レベル1

現段階では、応用幅が小さいのでMainメソッドを修正します。

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String input = scan.next();

        FirstCls first = new FirstCls();
        if ("exe".equals(input)) {
            first.execute();
        }
        boolean isNumber = first.isNumberString(input);
        if (isNumber) {
            System.out.println(input + "は数字です。");
        } else {
            System.out.println(input + "は数字ではありません。");
        }
    }
}

FirstClsのexecuteメソッドを起動するように修正しました。

クラスに処理を追加する

はじめの入力で「exe」と入力するとFirdtClsのexecuteメソッドが起動します。

単純に、FirstClsの中に、適当な処理を実装しただけです。

public void execute(Scanner scan) {
    System.out.println("*** EXECUTEを起動します ***");
    Map<String, String> map = new HashMaplt;>();
    map.put("test", "Testing this program!");
    map.put("prac", "Practice this program!");
    map.put("try", "Try this program!");
    while(true) {
        System.out.print("コマンドを入力してください: ");
        String str = scan.next();
        if (map.containsKey(str) == false) {
            break;
        }
        System.out.println(map.get(str));
    }
}

今回は、Mapインターフェースを使用して見ました。
キーと値がセットになって、データの取り出し、保存を行うことができます。
ここで言う保存はJavaが起動している間だけの保存です。

このままでは、大した発展があったようには思えませんので、一捻り加えます。ちなみに、execute()の処理が終わった後に、何かしらの文言が出ます。

一捻り

単純ですが、FirstClsにあるメソッドisNumberString()を呼び出すように修正します。

public void execute(Scanner scan) {
    System.out.println("*** EXECUTEを起動します ***");
    Map<String, String> map = new HashMap<>();
    map.put("test", "Testing this program!");
    map.put("prac", "Practice this program!");
    map.put("try", "Try this program!");
    while(true) {
        System.out.print("コマンドを入力してください: ");
        String str = scan.next();
        if (map.containsKey(str) == false) {
            break;
        }
        isNumberString(str);
        System.out.println(map.get(str));
    }
}

こんな感じです。
今日のところは、ここまでにしておきます。

でわでわ。。。



Java Basic Class 〜クラスを使うサンプル〜

前回は、クラスを作成してそれを撃とかして見ました。

しかし、コードと説明だけでした。今回は、それを実行してみようと思います。

前回やったこと

メインメソッドがプログラムの起動する主軸になります。

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String input = scan.next();

        FirstCls first = new FirstCls();
        first.handleInput(input);
    }
}

この処理は、標準入力を受け付け、受け取った文字列をFirstClsのメソッドhandleInput()に渡しています。
これを下のような書き方で表現できます。
「FirstCls#handleInput()に受け取った文字列を渡す」

実際に作成したFirstClsの内容は以下のようになっています。

public class FirstCls {

    public void handleInput(String input) {
        System.out.println("入力値: " + input);
        if (isNumberString(input)) {
            System.out.println(input + "は、数字です");
        } else {
            System.out.println(input + "は、数字ではありません");
        }
    }

    public boolean isNumberString(String str) {
        // [0-9]は正規表現と言います。
        boolean isNumber = str.matches("[0-9]");
        return isNumber;
    }

    /** 使用しないので下のアノテーションをつける */
    @Deprecated
    public void printSomething(String[] args) {
        //  プログラム引数に値があるときは表示する
        if (args.length != 0) {
            for (int i = 0; i < args.length; i++) {
                System.out.println("プログラム引数[" + i + "]: " + args[i]);
            }
        }
        System.out.println("1 + 1 = " + (1 + 1));
    }
}

呼び出しているメソッドはhandleInputなので、他のメソッドは関係ありません。

なので、今回は、別のメソッドを呼び出すように変えて見ました。

ポイント

クラスといっても結局はメソッド呼び出しと変わりません。
なので、下のようにメソッドを呼び出すのも、クラスのインスタンスを取得してメソッドを呼び出すのも大した違いはありません。

public static void main(String[] args) {
    Scanner scan = new Scanner(System.in);
    String input = scan.next();

    FirstCls first = new FirstCls();
    boolean isNumber = first.isNumberString(input);
    if (isNumber) {
        System.out.println(input + "は数字です。");
    } else {
        System.out.println(input + "は数字ではありません。");
    }
}
public static void main(String[] args) {
    Scanner scan = new Scanner(System.in);
    String input = scan.next();

    FirstCls first = new FirstCls();
    boolean isNumber = input.matches("[0-9]{1,}");
    if (isNumber) {
        System.out.println(input + "は数字です。");
    } else {
        System.out.println(input + "は数字ではありません。");
    }
}

このように、「クラス」と言うオブジェクトの中に様々な処理を実装して、インスタンスを取得すれば、その処理を呼び出すことができます。

インスタンスを取得しなくても、呼び出す方法がありますが、それはまた別の機会にしましょう。
ちなみに、それは「静的呼び出し」といいstatic修飾子を使用します。

とりあえずは、こんなところで。。。

でわでわ。。。



Java Basic Class 〜クラスを作ってクラスを理解する〜

久しぶりに、Javaの基本をやろうと思います。
Javaの基本といってもいろいろあるので、クラスの作り方をメインにやろうと思います。

早速作りましょう

とりあえずは、動かすのにメインメソッドが必要ですので、このメインメソッドを作成します。

作り方は、今までにやってきたので割愛します。

ここでのポイントは、MainメソッドのあるMainクラスを作成すると言うところです。

my.sample.Main

public class Main {
    public static void main(String[] args) {
        // 中身はまだない。。。
    }
}

このクラスから処理が始まります。

何をするか考える

設計の工程になります。。。

Step1

しかし、初めての人にもわかるよう、ハローワールドを実装します。

public class Main {
    public static void main(String[] args) {
        //  ハローワールド
        System.out.println("Hello World!");
    }
}
Step⒉

しかしこれでは、物足りない。。。
プログラム引数に、値を渡して、プログラム引数に値がある場合に表示すると言うものにしましょう

public class Main {
    public static void main(String[] args) {
        //  プログラム引数に値があるときは表示する
        if (args.length != 0) {
            System.out.println("プログラム引数:" + args[0]);
        }
        System.out.println("Hello World!");
    }
}
Step3

いやいや。。。プログラム引数が1つだけとは限らないので、そいつをどうにかしよう。

つまり、プログラム引数の数だけ表示しよう!

public class Main {
    public static void main(String[] args) {
        //  プログラム引数に値があるときは表示する
        if (args.length != 0) {
            for (int i = 0; i < args.length; i++) {
                System.out.println("プログラム引数[" + i + "]: " + args[0]);
            }
        }
        System.out.println("Hello World!");
    }
}
Step4

次は、文字表示だけじゃ面白くないから計算をしよう。

public class Main {
    public static void main(String[] args) {
        //  プログラム引数に値があるときは表示する
        if (args.length != 0) {
            for (int i = 0; i < args.length; i++) {
                System.out.println("プログラム引数[" + i + "]: " + args[0]);
            }
        }
        System.out.println("1 + 1 = " + (1 + 1));
    }
}

Step5

ここまできたら、クラスも作ってみよう。
しかし、今まで作成したコードは、書き換えてきたから、また呼び出したい時には、どうしたら良いだろうか?

クラスを使う

クラスを使うと、呼び出すメソッドを変えてやるだけで今までの作成したコードを実行できます。

Step1〜4までに作成したコードはどこかにいってしまいましたが、今後作成するプログラムは残せるように、クラスを作りながら進みます。

Step6: クラスを作る

とりあえず、今メインメソッドにある処理は邪魔なので、作成したクラスへ移動してしまいます。
新たに作成したクラスはFirstClsです、パッケージが違うのでインポートする必要があります。

そして、Mainクラスはこのようになりました。

public class Main {
    public static void main(String[] args) {
        FirstCls first = new FirstCls();
        first.printSomething(args);
    }
}

FirstCls

public class FirstCls {
    public void printSomething(String[] args) {
        //  プログラム引数に値があるときは表示する
        if (args.length != 0) {
            for (int i = 0; i < args.length; i++) {
                System.out.println("プログラム引数[" + i + "]: " + args[0]);
            }
        }
        System.out.println("1 + 1 = " + (1 + 1));
    }
}

処理の結果は変わりません。

そして、Eclipseでのパッケージ構成は下のようになっています。

ここからです。

Step7: クラスを使うメリット

現状、作成したプログラムは、
なんとなく作成した、意味のない処理が動いている状態です。

これは、とりあえずFirstClsによけておき、入力を受け取るプログラムを追加したいともいます。
これは、全ての処理の始まりになりますので、Mainクラスに実装します。実際に動かした時の動画です。

Step8: 標準入力を受ける

今までに処理結果をコンソールに表示していました。
ここは、「標準出力」と言う場所で、System.outで表現される(メモリ)領域です。

とりあえずここに表示するSystem.out.printメソッドで今まで表示を行なっていました。

次は、逆に入力、出力に対して入力を受け付けるプログラムを書きます。
Mianクラスに実装します。

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String input = scan.next();

        FirstCls first = new FirstCls();
        first.printSomething(args);
    }
}

そして、取得した標準入力をコントロールするためのメソッドをFirstClsに作成します。

public class FirstCls {

    public void handleInput(String input) {
        System.out.println("入力値: " + input);
    }

    /** 使用しないので下のアノテーションをつける */
    @Deprecated
    public void printSomething(String[] args) {
        //  プログラム引数に値があるときは表示する
        if (args.length != 0) {
            for (int i = 0; i < args.length; i++) {
                System.out.println("プログラム引数[" + i + "]: " + args[i]);
            }
        }
        System.out.println("1 + 1 = " + (1 + 1));
    }
}
Step9: まずはハローワールド

入力値をコンソールに表示するプログラムを作成しました。

上のような出力ができます。

作成したhandleInput()をそのままにしておき、次は引数が数字かどうか判定するメソッドを作成します。

public boolean isNumberString(String str) {
    // [0-9]は正規表現と言います。
    boolean isNumber = str.matches("[0-9]");
    return isNumber;
}

そして、これを初めに作ったメソッドから呼び出すようにします。

public void handleInput(String input) {
    System.out.println("入力値: " + input);
    if (isNumberString(input)) {
        System.out.println(input + "は、数字です");
    } else {
        System.out.println(input + "は、数字ではありません");
    }
}

こんな感じの実行結果が見れます。

作成したコードはこちらから参照(ダウンロード)できます
Mainクラス
FirstClsクラス

まとめ

このように、主軸になる処理を中心にして、処理を繋げられるように、処理を分断(機能レベル、処理レベル色々ありますが。。。)。

そして、組み合わせて実行!と言うように、なるべく修正、追加する作業を減らせるように作るとクールなプログラムと言えるでしょう。

あー自分も美しいプログラムが書けるようになりたい!

でわでわ。。。

関連ページ

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseCDTをインストール〜
  3. Setup OpenGL with JavaJOGLを使う準備 for Eclipse
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder EclipseSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 GitからソースをPullしよう〜
  3. IntelliJ IDEA GitGitリポジトリからクローン〜


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のついたメソッドに対応しています。

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