🌆

[Flutter] MethodChannelでOpenCVを使ってみよう!

2021/11/01に公開

上記GIFのようにFlutterでOpenCVを活用し、画像処理の方法について説明しようと思います。
(本記事はiOS側の動作を前提にしており、Android版のコードは含まれていません。)

1.MethodChannelとは?

詳細はドキュメントに記載されていますが、大雑把に表現すると「ネイティブ(iOS/Android)のメソッドを非同期で呼び出す」処理のことです。

2.大まかな処理の流れ

画像データを文字型に変換し処理します。なぜなら、画像データを直接MethodChannelで取り扱うことができないからです。

2-1.Dart側

  1. 画像データ -> ByteData に変換する
ByteData imageData = await rootBundle.load('images/icon.jpeg');
  1. ByteData -> Uint8List に変換する
Uint8List list = Uint8List.view(imageData.buffer);
  1. Uint8List -> String(Base64) に変換する
String base64 = base64Encode(list);
  1. String(Base64) をMethodChannelでネイティブ側に渡す
static const platform = const MethodChannel('samples.flutter.dev/image');
String result = await platform.invokeMethod('getBase64', base64);

2-2.ネイティブ側(Swift)

  1. String(Base64) -> Data に変換する
let encodedBase64 = Data(base64Encoded: parameters)
  1. Data -> UIImage に変換する
let convert = UIImage(data: encodedBase64!)
  1. UIImageOpenCV で画像変換する
let image = OpenCVManager.gray(convert)
  1. UIImage(変換済み) -> Data に変換する
let jpegData = image!.jpegData(compressionQuality: 1)
  1. Data -> String(Base64) に変換する
let base64 = jpegData!.base64EncodedString()
  1. String(Base64) をMethodChannelでDart側に渡す
result(base64)

3.プログラム

3-1.全体

https://github.com/r0227n/Learn_Flutter_App/tree/main/OpenCV/methodchannel_opencv

3-2.Dart側

static const platform = const MethodChannel('samples.flutter.dev/image');

以下略

Future<String> _processOpenCVWithMethodChannel() async {
    ByteData imageData = await rootBundle.load('images/icon.jpeg');
    String base64 = base64Encode(Uint8List.view(imageData.buffer));
    String result = await platform.invokeMethod('getBase64', base64);
    return result;
}

3-3.Swift側

let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
      let methodChannel = FlutterMethodChannel(name: "samples.flutter.dev/image",
                                             binaryMessenger: controller.binaryMessenger)
      
      methodChannel.setMethodCallHandler(
          {
              (call: FlutterMethodCall, result: FlutterResult) -> Void in
              
              guard call.method == "getBase64" else {
                  result(FlutterMethodNotImplemented)
                  return
              }
              
              let parameters = call.arguments as! String
              // String(Base64) -> Data
              let encodedBase64 = Data(base64Encoded: parameters)
              
              // Data -> UIImage
              let convert = UIImage(data: encodedBase64!)
              let image = OpenCVManager.gray(convert)

              // UIImage -> (JPEG)Data
              let jpegData = image!.jpegData(compressionQuality: 1)
              // Data -> String(Base64)
              let base64 = jpegData!.base64EncodedString()
              result(base64)
          }
      )

まとめ

OpenCVはC++製のため、dart:ffiが活用できます。
既存ネイティブアプリをFlutterでリプレースする場合、MethodChannelの方が仕事量が少ないため楽に見えるがパフォーマンス面や保守性を考えるとFFIの方が最適です。
ただしcameraパッケージでリアルタイムに画像処理する場合、FFIはエラーを吐き実装できません。そのため、MethodChannelで実装する手法も理解していて損はないと思います。

参考文献

https://flutter.dev/docs/development/platform-integration/platform-channels
https://qiita.com/john-rocky/items/0ac8ffcbf9b8f4227fd5
https://qiita.com/takeoverjp/items/564e1c3725a4be48e40a

スクラップ

https://zenn.dev/r0227n/scraps/21d33c3e76c327
https://zenn.dev/r0227n/scraps/a5e8b0f9e50c85

Discussion