Open15

dart:ffiでOpenCVを使う

天才リポジトリ

https://github.com/as1605/opencv_flutter_ffi

dart:ffiを使用して、FlutterアプリでOpenCV C ++ 4.5.3を実行します。
サンプルのガウシアンぼかし機能を備えたリリースを提供しました。このリポジトリを、Flutterでの画像処理が必要なプロジェクトのテンプレートとして使用できます。
OpenCV iOS Frameworkをインストールし、場所ios /opencv2.frameworkに配置してください

引用元:opencv_flutter_ffi Readme.md

セットアップ

$ git clone git@github.com:as1605/opencv_flutter_ffi.git
$ pwd
../opencv_flutter_ffi

$ flutter pub upgrade
$ cd ios
$ vim Podfile
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
  'Debug' => :debug,
  'Profile' => :release,
  'Release' => :release,
}

def flutter_root
  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
  unless File.exist?(generated_xcode_build_settings_path)
    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
  end

  File.foreach(generated_xcode_build_settings_path) do |line|
    matches = line.match(/FLUTTER_ROOT\=(.*)/)
    return matches[1].strip if matches
  end
  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
  pod 'OpenCV'
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
  end
end

$ pod install

実行結果


エミュレータの画像だとわかりにくいが、元画像からぼかされている。

libディレクトリを調査

ファイル

  • generated_plugin_registrant.dart
    • web対応用のファイル(web関係のパッケージがある)
  • livecamera.dart
    • カメラ関係の処理
  • main.dart
    • 画面構成関係
  • gaussian.cpp
    • ガウシアンぼかし
  • image_ffi.cpp
    • 画像処理(リサイズ?)
  • main.cpp
    • C++の関数(Gaussian(char *)image_ffi (unsigned char *, unsigned int *))を宣言

main.dart内のffi処理を読み解く

ダイナミックライブラリを読み込む

final dylib = Platform.isAndroid
      ? DynamicLibrary.open("libOpenCV_ffi.so")
      : DynamicLibrary.process();

AndroidはDynamicLibrary.openでpathからロードし、C++コードにアクセスできるようにする。
iOSはDynamicLibrary.processでC++コードのグローバルシンボルを保持し、ダイナミックライブラリを作成する。

⚠️libOpenCV_ffi.soは、CMakeLists.txtで指定されているらしいが、確認しても宣言されていない。iOS版を理解してから、調査する。

CmakeLists.txt
cmake_minimum_required(VERSION 3.6.0)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include)
add_library(lib_opencv SHARED IMPORTED)
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/../src/main/jniLibs/${ANDROID_ABI}/libopencv_java4.so)
set(SOURCES
    ../../lib/opencv-cpp/main.cpp
)
add_library(OpenCV_ffi SHARED ${SOURCES})
target_link_libraries(OpenCV_ffi lib_opencv)

C++の関数をラップし、Dartの関数として呼び出す

ラップする

final gaussian = dylib.lookupFunction<
   Void Function(Pointer<Utf8>),
   void Function(Pointer<Utf8>)>('Gaussian');

dylibの中からGaussian(Pointer<Utf8>)を呼び出し、gaussianにラップする。

呼び出し

gaussian(imagePath);

関数として普通に呼び出す。

「Lost connection to device.」でアプリがクラッシュする

画像の取得方法をImagePickerではなく、画像ファイルを指定した方法で挑戦してみた。
そしたらコンソールに「Lost connection to device.」と出力され、アプリがクラッシュした。

クラッシュの原因箇所

crash.dart
gaussian(imagePath);

FFIでC++関数をDart関数にラップする部分までは成功している。だが、ラップした関数を呼び出すとクラッシュする。そのためimagePathのデータに不備があり、クラッシュしたと思われる。

サンプルのプログラムでは...

sample.dart
// type: XFile
final imageFile = await _picker.pickImage(source: ImageSource.gallery);
// type: Pointer<Utf8>
final imagePath = imageFile?.path.toNativeUtf8() ?? "none".toNativeUtf8();
// type: (Pointer<Utf8>) => void
final gaussian = dylib.lookupFunction<
   Void Function(Pointer<Utf8>),
   void Function(Pointer<Utf8>)>('Gaussian');
gaussian(imagePath);

ImagePickerで画像データを取得し、FFI経由でOpenCVを活用している。

データ変換の流れ
1. XFile   // 画像データを取得
2. Pointer<Utf8>   // 画像データをポインタ変数に変換
3. (Pointer<Utf8>) => void   // OpenCVで画像変換

オリジナルのプログラムでは...

original.dart
// type: XFile
final imageFile = XFile('assets/img/default.jpg');
// type: Pointer<Utf8>
final imagePath = imageFile.path.toNativeUtf8();
// type: (Pointer<Utf8>) => void
final gaussian = dylib.lookupFunction<
   Void Function(Pointer<Utf8>),
   void Function(Pointer<Utf8>)>('Gaussian');
gaussian(imagePath);

画像を指定し、FFI経由でOpenCVを活用したいがクラッシュする。

データ変換の流れ
1. XFile   // 画像データをXFileに変換
2. Pointer<Utf8>   // 画像データをポインタ変数に変換
3. (Pointer<Utf8>) => void   // OpenCVで画像変換

クラッシュの原因は最適化されていないから?

https://pub.dev/packages/image_picker
https://pub.dev/packages/camera

サンプルのimage_picker版は正常に動作する。しかし、camera版は「Lost connection to device.」とコンソールに出力され、クラッシュする。

引数の比較

  • image_picker : パッケージから返された結果(XFile)をPointer<Utf8>に変換し、指定している。
  • camera : 「ヒープ(メモリ) X カメラ画像の縦横サイズ」をPointer<Uint8>に変換し、指定している。

考察

どちらのパッケージもflutter.devが開発しているが、型変換の方法は大きく違う。
image_pickerはすぐ型変換している。だが、cameraやオリジナルのプログラムはこねくり回し、型変換している。そのため、データが破損している可能性がある。

リアルタイムでOpenCVを使う

startImageStream() method
@override
  void initState() {
    super.initState();
    _controller = CameraController(
      widget.camera,
      ResolutionPreset.medium,
      imageFormatGroup: ImageFormatGroup.yuv420,
    );

    _initializeControllerFuture = _controller.initialize();

    _controller.initialize().then((_) async {
      await _controller.startImageStream((CameraImage image) => _processCameraImage(image));
    });
  }

  Future<void> _processCameraImage(CameraImage image) async {
    if(_detecting) {
      return;
    }

    _detecting = true;

    try {
      print(File.fromRawPath(image.planes.first.bytes));
      _detecting = false;
    } catch(e) {
      print(e);
    }
  }
実行結果
flutter: File: 'UUWUVXXUWWUXWYVVXWTXUVVWWUUVVXYWWYSXUWYVWZUVYYV[X[YXYZWWZ\YYYYX[\V[ZYYZYX[ZY[ZZY]ZU`]X\\[ZW\[[^\Z[\\\W]X\Y\^[[\][ZZ\][Z][]\^[Y^]]^Y^Ya_\]b_^^a`^a__db^`_abbbcbdac_cadcb`eacdecdcfdeddhghgfdfefghhehhijjjjkhkkkhionopqqmpmoqorqprsqqrqssqsqrpstrtssttvvuwvsxxxxxywxyyv}vxzzxyz{|y{}|\^?\^?\^?~\^?\^?\^?�\^?����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������

直接変換した場合、データが崩れる。

CameraImageをFileに変換する

CameraImage -> Uint8List -> File

CameraImage -> Uint8List -> File.php
print(File.fromRawPath(Uint8List.fromList(image.planes.first.bytes)));

// result
���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������\^?�������\^?���\^?�������~���\^?���~���y���z���z���z���w���w���v���v���t~��t~��t~��v~��u\^?��s}��r|��r|��pz��oy\^?�n{��n{��mz\^?�lv~�iu}�jv~�lx��kw\^?�jv~�iu}�gs{gr|�hs}�hs}�er{�er{�er{�dqz�cpy�box�cpy�dp|�amy�amy�`lx�`lx�]kw�[iu�Zht�^jv�\jv�[iu�[iu�Zht�Xit�Xit�Whs�Vgr�Whs�Zht�[iu�Ygt�Tdq�Rbo�Qan�Rbo�Rbo�Rbo�Qan�Qan�P`m�P`m�L^k�M_m�N`n�L^l�K]k�K]k�K]k�J\j�I[i�J\j�K]k�I[i�I[i�GXi�GZh�EXf�EXf�DVg�DVg�DVg�EWh�DVg�CUf�DVg�?Sd�AUg�CWi�AUg�@Tf�?Se�@Tf�?Se�>Rd�?Se�=Se�=Se�=Se�=Se�=Se�;Qc�<Rd�=Rg�<Rd�<Rd�<Rd�<Rd�<Rd�<Qf�<Qf�;Pe�;Pe�;Pe�=Rg�<Qf�:Rf�:Rf�9Qe�9Qe�9Qe�9Qe�9Qe�;Sg�9Qe�:Rf�;Sg�9Qe�8Pd�9Qe�:Rf�9Qe�;Sg�9Qe�;Sg�;Sg�9Qc�:Rd�<Tf�<Tf�;Se�<Tf�<Tf�>Vh�>Vh�?Wi�?Wi�@Xl�@Xj�@Xj�>Vh�>Vh�AYk�AYk�C[m�E[m�F\n�F\n�F]m�F]m�H_o�I`p�G`p�G_q�Ias�H`r�H`r�Kas�Lbt�Oew�Ndv�Ndv�Pfx�Rhz�Qgy�Qgy�Qgy�Si{�Uk}�Si{�Vl~�Wm\^?�Wo��Wo��Wo��Yq��Zr��]s��_u��`v��`v��`v��by��cz��by��dz��f|��f|��g~��k\^?��h\^?��i���l���l���k���l���m���l���n���o���q���p���s���v���w���w���w���w���w���w���w���w���w���{���{���{���}���~���~���\^?���\^?������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<>

文字化けがエグくなった。

CameraImage -> Uint8List -> ByteBuffer -> File

CameraImage -> Uint8List -> ByteBuffer -> File
Future<String> get getLocalPath async {
    final directory = await getApplicationDocumentsDirectory();
    return directory.path;
  }
  
Future<File> _writeLocalImagePath(Uint8List data) async {
   final path = await getLocalPath;
   final imagePath = '$path/image.png';
   File imageFile = File(imagePath);

   final buffer = data.buffer;
   final localFile = await imageFile.writeAsBytes(buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
   return localFile;
}

final File imageFile = await _writeLocalImagePath(image.planes.first.bytes);
final String imagePath = imageFile.path;
print(imagePath);

// result
flutter: /var/mobile/Containers/Data/Application/DEC2502A-5CAA-4A2F-BCF6-2EE5EA1B027C/Documents/image.png

ローカルにデータを一時的に保存し、Fileを取得している。

クラッシュする

ffiでOpenCVを使うとクラッシュする

try {
   final File imageFile = await test._writeLocalImagePath(image.planes.first.bytes);
   final String imagePath = imageFile.path;
   print(imagePath);
   _ffiBridge.changeImage(imagePath.toNativeUtf8()).then((value) => _detecting = false).catchError((error) => print(error));
} catch(e) {
   print(e);
}

実行結果

flutter: /var/mobile/Containers/Data/Application/1B990754-50F2-47C9-ABC9-97F7C1F0D993/Documents/image.png
libc++abi: terminating with uncaught exception of type cv::Exception: OpenCV(4.3.0) /Volumes/build-storage/build/master_iOS-mac/opencv/modules/imgproc/src/smooth.dispatch.cpp:606: error: (-215:Assertion failed) !_src.empty() in function 'GaussianBlur'


* thread #9, name = 'io.flutter.1.ui', stop reason = signal SIGABRT
    frame #0: 0x00000001b7c76964 libsystem_kernel.dylib`__pthread_kill + 8
libsystem_kernel.dylib`__pthread_kill:
->  0x1b7c76964 <+8>:  b.lo   0x1b7c76984               ; <+40>
    0x1b7c76968 <+12>: pacibsp 
    0x1b7c7696c <+16>: stp    x29, x30, [sp, #-0x10]!
    0x1b7c76970 <+20>: mov    x29, sp
Target 0: (Runner) stopped.
Lost connection to device.

try-catchを使わない

final File imageFile = await test._writeLocalImagePath(image.planes.first.bytes);
final String imagePath = imageFile.path;
print(imagePath);
_ffiBridge.changeImage(imagePath.toNativeUtf8()).then((value) => _detecting = false).catchError((error) => print(error));

上記と同じエラー

try-catch&catchErrorを使わない

final File imageFile = await test._writeLocalImagePath(image.planes.first.bytes);
final String imagePath = imageFile.path;
print(imagePath);
_ffiBridge.changeImage(imagePath.toNativeUtf8()).then((value) => _detecting = false);

上記と同じエラー

Fileのアウトプット方法が悪い

ちゃんとした保存方法ではない
Future<File> _writeLocalImagePath(Uint8List data) async {
   final path = await getLocalPath;
   final imagePath = '$path/image.png';
   File imageFile = File(imagePath);

   final buffer = data.buffer;
   final localFile = await imageFile.writeAsBytes(buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
   return localFile;
}

これはpngとして画像を保存しているわけではない。

詳細

指定したパスにデータを書き込んでいるだけ
final localFile = await imageFile.writeAsBytes(buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));

writeAsBytesは、指定したパスにデータを書き込んでいるだけ。
つまりaaaというデータを指定場所にimage.pngという名前で書き込んでいるだけであり、画像として保存しているわけではない。

結論

画像データを指定していないため、 「画像ファイルが認識できない」 とエラーを吐いている。

参考文献

https://qiita.com/Nw3965/items/5ef4bc82dd9c28e36a19

CameraImageを弄りまくる

https://pub.dev/packages/image

CameraImage -> Uint8List -> Image -> List<int> -> Uint8List -> File

CameraImage -> Uint8List -> Image -> List<int> -> Uint8List -> File
import 'package:image/image.dart' as pkgImg;

Uint8List list = image.planes.first.bytes;
final width = image.width;
final height = image.height;
pkgImg.Image test = pkgImg.Image.fromBytes(width, height, list);
List<int> _imageBytes = pkgImg.encodeJpg(test);
Uint8List convert = Uint8List.fromList(_imageBytes);
print(File.fromRawPath(convert));
result
flutter: File: '����

StackOverflowの情報

「convert YUV_420 888 to PNG」を改良

https://stackoverflow.com/questions/54230291/how-to-convert-flutter-cameraimage-to-a-base64-encoded-binary-data-object-from-a
convert YUV_420 888 to Uint8List
Uint8List convertYUV420(CameraImage image) {
   var img = pkgImg.Image(image.width, image.height); // Create Image buffer

   Plane plane = image.planes[0];
   const int shift = (0xFF << 24);

   // Fill image buffer with plane[0] from YUV420_888
   for (int x = 0; x < image.width; x++) {
      for (int planeOffset = 0;
         planeOffset < image.height * image.width;
         planeOffset += image.width) {
         final pixelColor = plane.bytes[planeOffset + x];
         // color: 0x FF  FF  FF  FF
         //           A   B   G   R
         // Calculate pixel color
         var newVal = shift | (pixelColor << 16) | (pixelColor << 8) | pixelColor;

         img.data[planeOffset + x] = newVal;
       }
    }

   // return img;
   return img.getBytes();
}


Uint8List check =con vertYUV420(image);
print(File.fromRawPath(check).path);
result
flutter: KKKJJJMMMHHHKKKLLLIIILLLIIIKKKMMMMMMIIILLLIIIMMMJJJMMMLLLJJJIIIJJJKKKJJJJJJMMMIIIKKKKKKIIIJJJMMMMMMIIIKKKJJJKKKJJJJJJMMMLLLJJJFFFKKKJJJIIIGGGJJJIIIKKKKKKHHHGGGMMMMMMJJJJJJGGGLLLJJJKKKGGGJJJIIIJJJLLLJJJLLLMMMKKKMMMLLLKKKKKKKKKLLLKKKIIIJJJIIIJJJJJJMMMKKKJJJNNNLLLHHHLLLLLLIIILLLLLLKKKIIIMMMKKKKKKJJJJJJJJJJJJJJJMMMKKKJJJJJJIIILLLKKKKKKKKKMMMLLLJJJIIIKKKKKKKKKGGGKKKMMMKKKKKKJJJJJJLLLKKKIIIJJJJJJLLLIIIIIIJJJKKKLLLNNNKKKKKKLLLLLLMMMNNNKKKJJJJJJPPPKKKLLLNNNLLLMMMMMMKKKLLLKKKKKKMMMLLLMMMMMMMMMNNNMMMJJJKKKLLLMMMLLLLLLLLLOOOMMMLLLJJJLLLNNNNNNOOOMMMOOONNNNNNMMMOOOLLLOOOPPPPPPLLLKKKNNNOOOMMMNNNNNNLLLPPPRRRPPPPPPOOOMMMOOOPPPPPPPPPQQQPPPPPPOOOOOOPPPKKKNNNUUURRRQQQPPPQQQQQQPPPRRRQQQPPPQQQQQQTTTRRRQQQSSSPPPTTTTTTQQQTTTRRRRRRRRRUUUQQQWWWSSSSSSUUUSSSRRRWWWRRRUUUVVVWWWVVVPPPUUUYYYVVVPPPVVVVVVVVVVVVVVVUUUUUUWWWTTTXXXXXXVVVYYYYYYXXXXXXYYYXXXXXXVVVZZZVVVZZZYYYVVVYYYXXXYYY]]]�\\\�YYYZZZ]]][[[^^^]]][[[ZZZ[[[ZZZYYY[[[]]]�\\\�]]]�\\\�[[[]]][[[ZZZ]]]^^^^^^^^^^^^�\\\�]]]]]]^^^]]]�\\\�^^^^^^^^^___^^^_____________________�aaa�^^^^^^�aaa�ccc�```�```�bbb�___```�```�bbb�ddd�ddd�\\\�ccc�bbb�ccc�eee�ccc�ccc�ccc�ccc�ddd�ddd�eee�eee�eee�eee�eee�ddd�hhh�eee�fff�hhh�hhh�hhh�ggg�ggg�ggg�hhh�iii�jjj�hhh�jjj�kkk�hhh�ggg�mmm�jjj�jjj�jjj�kkk�jjj�kkk�kkk�kkk�mmm�mmm�mmm�nnn�kkk�jjj�lll�nnn�nnn�mmm�mmm�ooo�nnn�ooo�rrr�ooo�qqq�rrr�sss�uuu�sss�qqq�rrr�rrr�ttt�sss�qqq�sss�sss�qqq�ttt�sss�nnn�sss�uuu�sss�rrr�uuu�uuu�ttt�vvv�xxx�www�yyy�uuu�sss�xxx�ttt�yyy�yyy�www�www�yyy�www�{{{{{{|||{{{{{{{{{|||{{{{{{�yyy�zzz�|||~~~�zzz�{{{||||||}}}~~~�������������\^?\^?\^?���������\^?\^?\^?�\^?\^?\^?���������\^?\^?\^?�����������������������������

データが壊れている。

一応、動くようになった

Future<void> _processCameraImage(CameraImage image) async {
   if(_detecting) {
      return;
   }
   _detecting = true;

   try {
     // CameraImage -> ui.Image
     final streamImage = await convertCameraImageToUiImage(image);

     // データ(ui.Image)をPNG形式に変換し、ローカルに保存
     final byteData = await streamImage.toByteData(format: ui.ImageByteFormat.png);
     if(byteData == null) {
       return;
     }
     Uint8List pngBytes = byteData.buffer.asUint8List();
     final imageFile = await writeLocalImagePath(pngBytes);
     imagePath = imageFile.path;

      _ffiBridge.changeImage(imagePath.toNativeUtf8()).then((value) => _detecting = false).catchError((error) => print(error));
   } catch(e) {
     print(e);
   }
}

Future<ui.Image> makeUiImage(List<int> pixels,int width,int height) {
   final c= Completer<ui.Image>();
   ui.decodeImageFromPixels(
      Uint8List.fromList(pixels),
      width,
      height,
      ui.PixelFormat.rgba8888,
      c.complete,
   );
   return c.future;
}

Future<ui.Image> convertCameraImageToUiImage(CameraImage image) async {
   int startTime= DateTime.now().millisecondsSinceEpoch;
   int time;
   pkgImg.Image img= convertYUV420(image);

   time= DateTime.now().millisecondsSinceEpoch;
   print("Converted in "+(time-startTime).toString()+"ms img "+img.toString()+" ("+img.width.toString()+","+img.height.toString()+")");
   startTime=time;
   ui.Image ret= await makeUiImage(img.getBytes(), image.width, image.height);
   return ret;
}


pkgImg.Image convertYUV420(CameraImage image) {
   var img = pkgImg.Image(image.width, image.height); // Create Image buffer

   Plane plane = image.planes[0];
   const int shift = (0xFF << 24);

   // Fill image buffer with plane[0] from YUV420_888
   for (int x = 0; x < image.width; x++) {
     for (int planeOffset = 0; planeOffset < image.height * image.width; planeOffset += image.width) {
        final pixelColor = plane.bytes[planeOffset + x];
        // color: 0x FF  FF  FF  FF
        //           A   B   G   R
        // Calculate pixel color
        var newVal = shift | (pixelColor << 16) | (pixelColor << 8) | pixelColor;

        img.data[planeOffset + x] = newVal;
     }
  }

  return img;
}

Future<String> get getLocalPath async {
   final directory = await getApplicationDocumentsDirectory();
   return directory.path;
}

Future<File> writeLocalImagePath(Uint8List data) async {
   final path = await getLocalPath;
   final imagePath = '$path/image.png';
   File imageFile = File(imagePath);

   final buffer = data.buffer;
   final localFile = await imageFile.writeAsBytes(buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
   return localFile;
}

エラーを吐かず、動作した。

実行結果



2枚目は、Image.file(File(path))で表示したもの。
恐らく、convertYUV420()の処理が問題だと思われる。

検証

convertYUV420()の処理をpkgImg.Image.fromBytes(image.width, image.height,image.planes.first.bytes);に差し替えたらどうなるのか?

結果

CameraImageからdart:ui Imageに変換する処理に問題があり。
**pkgImg.Image.fromBytes(image.width, image.height,image.planes.first.bytes)**ではうまくいかないため、バイト列をうまく計算しなければならない。

参考文献

https://gist.github.com/Alby-o/fe87e35bc21d534c8220aed7df028e03
https://stackoverflow.com/questions/65191142/flutter-how-to-save-widget-to-png-with-transparent-background
https://www.fixes.pub/program/326926.html

これもダメ

https://github.com/flutter/flutter/issues/26348
↑参考に
pkgImg.Image whate(CameraImage image) {
   final width = image.width;
   final height = image.height;
   var img = pkgImg.Image(width, height); // Create Image buffer
   try {
     // Fill image buffer with plane[0] from YUV420_888
     for(int x=0; x < width; x++) {
       for(int y=0; y < height; y++) {
         final pixelColor = image.planes[0].bytes[y * width + x];
         // color: 0x FF  FF  FF  FF
         //           A   B   G   R
         // Calculate pixel color
         img.data[y * width + x] = (0xFF << 24) | (pixelColor << 16) | (pixelColor << 8) | pixelColor;
       }
     }
   }catch(e) {
     print(e);
   }

  return img;
}

ffiで投げるのbase64はダメなのかな?

画像データをString型のbase64で投げ合えば処理できるのではないかな?

Dart側

CameraImage -> Uint8List -> base64

C++側

base64 -> Image -> OpenCV -> base64

C++でBase64にエンコード/デコード

C++ Base64
#include <iostream>
#include <fstream>

#include <opencv2/opencv.hpp>
using namespace cv;


// width220, height150の画像を作成
Mat src = Mat::zeros(150, 220, CV_8UC3);

// PNGでの出力用パラメータ
std::vector<int> param = std::vector<int>(2);
param[0] = IMWRITE_PNG_COMPRESSION;
param[1] = 3;//default(3)  0-9.

std::vector<uchar>buf;
cv::imencode(".png", src, buf, param); //エンコード

cv::Mat dst = cv::imdecode(Mat(buf), IMREAD_UNCHANGED); //デコード

フローしか理解しておらず、内容は意味がわからないがFlutterでビルドされ、FFI経由で実行してもエラーやクラッシュせずに実行できた。

参考文献

https://phst.hateblo.jp/entry/2020/04/12/080000
https://emotionexplorer.blog.fc2.com/blog-entry-364.html
ログインするとコメントできます