格ゲーの動画を解析したい 〜JavaFXでGUIツールを作る〜 7フレーム目

OpenCVで格ゲーの動画を解析したい連載の7回目です。
前回、平均背景法について学びました。
今回はJavaFX+Kotlinで輪郭にラベルを付けるGUIアプリケーションを作成しようと思います。
JavaFXを使うとxml形式でGUIを記述できます(通称FXML
また、IntelliJにはグラフィカルにFXMLを編集できるツールが付属しているので便利🤘

IntelliJでFXMLをグラフィカルに編集している図。通称Scene Builder。

最小構成のJavaFxアプリケーションを作成する

さっそく作っていく。
コードは以下の1ファイルだけで良い。
ちなみにStageアプリケーションが表示される場所の事。例えばウィンドウなど。
Sceneページに該当し、複数のNodeで構成される。
Nodeテキストやボタンといった要素の事。

package jp.t_kuni.kotlin_app_skelton.views

import javafx.application.Application
import javafx.scene.Group
import javafx.scene.Scene
import javafx.scene.text.Font
import javafx.scene.text.Text
import javafx.stage.Stage

class MyApp: Application() {
    override fun start(stage: Stage?) {
        val text = Text(10.0, 40.0, "Hello World!")
        text.setFont(Font(40.0))
        val scene = Scene(Group(text))

        stage!!.setTitle("Welcome to JavaFX!")
        stage.setScene(scene)
        stage.sizeToScene()
        stage.show()
    }
}

fun main(args: String) {
    Application.launch(args);
}

実行するとこんな感じのウィンドウが表示される
簡単で良いですねぇ😀

FXMLでGUIを作成してみる

src/main/resourcesFXMLファイルを作成する

FXMLの内容はこんな感じ。

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

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

<AnchorPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="jp.t_kuni.kotlin_app_skelton.views.Controller"
            prefHeight="400.0" prefWidth="600.0">

</AnchorPane>

FXMLファイルを開いて、左下にあるタブをScene Builderに切り替えるとグラフィカルに編集できる

FXMLファイルに対応するControllerクラスを用意する
Controllerクラスにはボタンが押下されたときの処理などを記述していく。

package jp.t_kuni.kotlin_app_skelton.views

import javafx.fxml.Initializable
import java.net.URL
import java.util.*

class Controller: Initializable {
    override fun initialize(p0: URL?, p1: ResourceBundle?) {

    }
}

エントリーポイントとなるメインクラスがこんな感じ
FXMLLoaderでFXMLファイルを読み込んでSceneに登録している。

package jp.t_kuni.kotlin_app_skelton.views

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

class MyApp: Application() {
    override fun start(stage: Stage?) {
        val url = javaClass.getResource("/app.fxml");
        val loader = FXMLLoader(url)
        val root = loader.load<Any>() as Pane
        val controller: Controller = loader.getController()
        val scene = Scene(root)

        stage!!.setTitle("Welcome to JavaFX!")
        stage.setScene(scene)
        stage.sizeToScene()
        stage.show()
    }
}

fun main(args: String) {
    Application.launch(args);
}

実行すると空のウィンドウが表示される
後はFXMLファイルを書き換えるだけで画面を作っていける💯

プロパティとのバインド

Controllerから画面上の要素にアクセスしたい。
FXMLからScene Builderを開いて、右ペインのfx:idに適当な名前を入力する

Controllerに以下の様に記述する事で、画面上の要素にアクセスできる。
変数名は前述のfx:idに合わせる必要がある。

class Controller : Initializable {
    @FXML
    private var imageView = ImageView()

なお、ImageView()でインスタンスを代入しているのはnull代入を避けたかったため。
nullを許容するならprivate var imageView: ImageView? = nullでも可。

OpenCVのMatクラスをレンダリングしたい

以下の様にjavafx.scene.imageに変換した後、ImageView.imageに代入すればOK

fun mat2image(mat: Mat): Image {
    val buffer = MatOfByte()
    Imgcodecs.imencode(".png", mat, buffer);
    return Image(ByteArrayInputStream(buffer.toArray()));
}

イベントを取得しようとしてハマった

マウス座標を取得しようとしてマウス移動イベントを取得しようとしたが以下のエラーが発生した。

Caused by: javafx.fxml.LoadException: Error resolving onMouseMoved='#onMouseMove', either the event handler is not in the Namespace or there is an error in the script

引数の型がjavafx.scene.input.MouseEventではなくjava.awt.event.MouseEventになっていた。
javafxと同名のクラスがswingにもあってこんな感じでハマるので注意。

概ね出来てきた。

輪郭にラベルを付けるツールが概ね出来てきた。
後は保存処理を作る。

保存処理を作る

java(kotlin)にはphpのserialize()みたいな雑にオブジェクトを保存できる関数は無いっぽい。
なんでkotlinx.serializationを使ってみる。
READMEを参考にbuild.gradle.ktsに追記して再ビルドする
以下のエラーが発生した。

Unresolved reference: KotlinCompilerVersion

以下を追記しないといけないらしい。

import org.jetbrains.kotlin.config.KotlinCompilerVersion

ビルドはできるが、IntelliJが補完してくれない。(No suggestionsになる)

Reimport All Gradle Projectsをすれば直った
以下の様にshiftを2回押すと表示される窓でreimportで検索すると出てくる。
つーかこれ前もハマったやつじゃねーか!

コードはリポジトリのREADMEを見ていただくとして、輪郭とラベルの情報をjsonに保存する事ができた

複数のエントリポイントを持ちたい

輪郭にラベルを付けるツールはだいたい出来たので、また別の処理を書いていく。
なので、同じプロジェクトで複数のエントリーポイント(メインクラス)を持ちたい
gradle.build.ktsmainClassNamemainClassというパラメータで上書きできる様にする。

application {
    mainClassName = if (project.hasProperty("mainClass")) project.properties["mainClass"] as String else "jp..(中略)..MyApp"
}

IntelliJの実行構成を新たに作成し、上記のGUIアプリとは異なるメインクラスを-PmainClassに指定する

こうする事で、実行構成を切り替える事で、処理をまるごと切り替える事ができる

ちなみにここで結構ハマったポイントがあって
kotlinでコンパイルされたクラスのclasspath名は、末尾にKtを付けないといけないらしいっす。
例えばAppクラスならAppKtと指定する必要がある。
Ktを付け忘れるとクラスが見つからなくて以下のエラーが出ます。

エラー: メイン・クラスjp.t_kuni...を検出およびロードできませんでした
原因: java.lang.ClassNotFoundException: jp.t_kuni...

今日はここまで!

「格ゲーの動画を解析したい 〜JavaFXでGUIツールを作る〜 7フレーム目」への1件のフィードバック

コメントする