Windows上のGodot4(RC)でOpenCVを扱いたい

CloverField開発室です。
クロスプラットフォームの開発はとても骨の折れる作業で、
クロスプラットフォームで統一されたGUIを用いた画像処理を行う方法があればとても助かると思いたってWindows上のGodot EngineでOpenCVを使う方法について調べてみました。
ただ、結論から言うとOpenCVをビルドできるプラットフォームであることと個別のプラットフォームごとのビルドが発生するためUnityほどの利便性は無いと感じました。
また今回の記事はGodot4 release candidateバージョンでの記事なので後の変更によってうまく動かない場合があるかもしれません。
- Godot Engineの入手
 公式サイト
 Godot Engine – Free and open source 2D and 3D game engine
 からGodot4をダウンロードして解凍する。
 
- GDExtentionの入手(Godot EngineのC++ binding)
 godot-cpp
 GitHub – godotengine/godot-cpp: C++ bindings for the Godot script API
 からマスターブランチをcloneします。
 便宜上ここではC:\godot-cpp-masterにcloneした事とします。
 
- godot-cppのビルド
 C:\godot-cpp-master>cmake CMakeList.txt
 cmakeを呼び出してVisualStudioのprojectを生成します。
 そして生成されたgodot-cpp.slnを開いてビルドすることでgodot-cppのスタティックリンクライブラリを作成します。次にOpenCVをgodotに紐づけするためにダイナミックリンクライブラリを作成する必要があるので、
 C:\godot-cpp-master\test>cmake CMakeList.txt
 として同様にテスト用のVisualStudio projectを生成しておきます。
- OpenCVを使用したクラスの作成
 (3)で作成した
 C:\godot-cpp-master\test
 上のプロジェクトを開き、リンカーの設定に(opencv_world400d.lib/opencv_world400.lib)を追加しておきます。
 次にソースコードを編集します。C:\godot-cpp-master\test\src\exsample.h#include <godot_cpp/classes/image.hpp>
 #include <godot_cpp/classes/texture_rect.hpp>
 #include <opencv2/opencv.hpp>
 class CvCamera : public TextureRect {
 GDCLASS(CvCamera, TextureRect);cv::VideoCapture cap;
 PackedByteArray bytes;
 Ref<Image> gdImg;
 protected:
 static void _bind_methods();
 public:
 CvCamera();
 ~CvCamera();
 void open(int camera_id);
 void close();
 Ref<Image> updateImage();
 };追加するクラスを定義します。 
 今回VideoCaptureで取得した画像を表示するため、OpenCVのVideoCapture、
 VideoCaptureからgodotのImageを作成するためにPackedByteArray、
 Godotのスクリプトに渡すためのImageクラスの参照をそれぞれメンバとしています。
 また、継承元のTextureRectは画面描画するクラスの中で適当に選んだので特に深い意味はありません。次にクラス内部の実装です、 
 C:\godot-cpp-master\test\src\exsample.cppCvCamera::CvCamera()
 {
 UtilityFunctions::print("construct.");
 }CvCamera::~CvCamera()
 {
 close();
 }説明不要なコンストラクタとデストラクタ。 void CvCamera::open(int camera_id)
 {
 if (cap.isOpened())
 return;cap.open(camera_id);if (!cap.isOpened()) {
 UtilityFunctions::print("error");
 return;
 }cv::Mat cvImgBGR;
 cap >> cvImgBGR;
 UtilityFunctions::print("[WebCam resolution] w:", itos(cvImgBGR.cols), "h:", itos(cvImgBGR.rows));bytes.resize(cvImgBGR.cols * cvImgBGR.rows * 3);
 gdImg = Image::create_from_data(cvImgBGR.cols, cvImgBGR.rows, false, Image::FORMAT_RGB8, bytes);
 }キャプチャデバイスの初期化とバイトサイズの決定、空イメージの作成を行います。 void CvCamera::close()
 {
 if (cap.isOpened())
 cap.release();
 }終了時にキャプチャデバイスが開いていたら閉じる。void CvCamera::_bind_methods()
 {
 ClassDB::bind_method(D_METHOD("updateImage"), &CvCamera::updateImage);
 ClassDB::bind_method(D_METHOD("open", "camera_id"), &CvCamera::open);
 ClassDB::bind_method(D_METHOD("close"), &CvCamera::close);
 }ここでgodotのスクリプトで使用する関数をbindする。 Ref<Image> CvCamera::updateImage() {
 if (!cap.isOpened()) {
 UtilityFunctions::print("camera does not open");
 return gdImg;
 }cv::Mat cvImgBGR;
 cv::Mat cvImgRGB;
 cap >> cvImgBGR;cv::cvtColor(cvImgBGR, cvImgRGB, cv::COLOR_BGR2RGB);
 memcpy(bytes.ptrw(), (BYTE *)cvImgRGB.data, cvImgBGR.cols * cvImgBGR.rows * 3);
 gdImg->set_data(cvImgBGR.cols, cvImgBGR.rows, false, Image::FORMAT_RGB8, bytes);return gdImg;
 }画像更新を行う、open時に確保しておいたByteArrayに対してコピーを行ってからImageに設定する。 ここまででクラスのコーディングが完了したので後は作成したクラスをgodotに登録する必要があるのですがプロジェクトを流用しているので、 
 register_types.cpp内のinitialize_example_module関数内にClassDB::register_class<CvCamera>();の一文を追加するだけで完了です。この変更したtestプロジェクトをビルドしてダイナミックリンクライブラリを作成します。 
- Godot Projectの作成
 Godotを起動し、新規プロジェクトを作成します。
 作成したプロジェクトのリソース構成を
  
 上記のようにbinフォルダ,cv.gdextensionファイル,CvCamera.gdスクリプトの3つを作成します。
 binフォルダには(4)で作成したdll(gdexsample.dll)とopencv_world400.dllの2つを配置します。
 cv.gdextensionファイルにはdllの設定を記述します。[configuration]
 entry_symbol = "example_library_init"[libraries]
 windows.64 = "res://bin/gdexample.dll"configurationにはdllのエントリーポイントを、librariesにはそれぞれのプラットフォームに対応するライブラリを記述します。(今回はwinsowsのみなのでwindows.64だけ)最後のCvCamera.gdスクリプトはgodot内でのCvCameraのふるまいをgodot scriptで記述します。 extends CvCamera
 
 var tex = ImageTexture.new()
 
 func _ready():
 open(0)
 passfunc _process(_delta):
 var img = await updateImage()
 tex = await ImageTexture.create_from_image(img)
 texture = tex
 passこのスクリプトはCvCameraノードの配下として動作するスクリプトで準備完了時に(4)で作成したopen関数を呼びだしてデバイスの初期化を行い、 
 アイドル時にupdateImage関数によってカメラ画像の取得、create_from_imageでテクスチャの更新を行っています。ここまでできたらgodot engineにdllを読み込ませる為に一度godotを再起動します。 
 最後に
  
 ノードの構成を上記のように設定し、CvCameraノードのスクリプトにCvCamera.gdスクリプトを設定すれば完了です。 
今の所ニッチな情報だと感じたので記事に起こしてみました。
 ただ最終的な所感としてはビルド時間はまぁ良いとしてライブラリの読み込みにgodotの再起動が必要なので結構フラストレーションが溜まりますので、
 画像処理用途にgodotを使うのは現状強くお勧めはしません。

