Flutter から Android の OpenCV を使う方法を調べましたが、なかなか、MethodChannelで使う方法の日本語情報が見つかりませんでした。
今回ひとまず動作確認が取れたので、備忘録としてまとめておきます。
動作確認環境
- Flutter 3.38.9
- OpenCV SDK 4.12.0
1. Android 用 OpenCV SDK をダウンロード
OpenCV 公式リリースページから Android SDK を取得します。
👉 https://opencv.org/releases/
今回は 4.12.0 を使用しました。
2. Flutter プロジェクト作成
flutter create opencv_sample
プロジェクト名は任意です
3. OpenCV SDK をプロジェクトへコピー
ダウンロードした OpenCV SDK 内の
opencv-4.12.0-android-sdk/OpenCV-android-sdk/sdk
フォルダを、Flutter プロジェクトの
android/
直下にコピーします。
フォルダ名を sdk から openCVLibrary に変更します。
(※後の Gradle 設定と一致させてください)
4. android/settings.gradle.kts を編集
include(":app")
+ include(":openCVLibrary")
+ project(":openCVLibrary").projectDir = File(rootProject.projectDir, "./openCVLibrary")
5. android/app/build.gradle.kts を編集
Java のバージョン不一致でエラーが出たため、21 に統一しました。
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
}
kotlinOptions {
- jvmTarget = JavaVersion.VERSION_17.toString()
+ jvmTarget = JavaVersion.VERSION_21.toString()
}
+ dependencies {
+ implementation(project(":openCVLibrary"))
+ implementation("androidx.exifinterface:exifinterface:1.4.2")
+ }
6. android/openCVLibrary/build.gradle を編集
compileOptions {
- sourceCompatibility JavaVersion.VERSION_17
- targetCompatibility JavaVersion.VERSION_17
+ sourceCompatibility JavaVersion.VERSION_21
+ targetCompatibility JavaVersion.VERSION_21
}
7. Android 側に OpenCV 処理を書く
android/app/src/main/kotlin/.../MainActivity.kt
OpenCV を初期化し、MethodChannel で Flutter から画像を受け取ってグレースケール変換するサンプルです。
package com.example.opencv_sample
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Log
import android.graphics.Matrix
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import org.opencv.android.OpenCVLoader
import org.opencv.android.Utils
import org.opencv.core.Mat
import org.opencv.imgproc.Imgproc
import org.opencv.core.MatOfByte
import org.opencv.imgcodecs.Imgcodecs
import java.io.ByteArrayOutputStream
import java.io.ByteArrayInputStream
import androidx.exifinterface.media.ExifInterface // build.gradleのdependenciesにandroidx.exifinterface:exifinterface追加しておくこと
class MainActivity : FlutterActivity() {
private val CHANNEL = "opencv"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// OpenCV 初期化
if (!OpenCVLoader.initDebug()) {
Log.e("OpenCV", "OpenCV initialization failed")
} else {
Log.i("OpenCV", "OpenCV initialized successfully")
}
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"processImage" -> {
val bytes = call.argument<ByteArray>("bytes")
if (bytes != null) {
try {
val exif = ExifInterface(ByteArrayInputStream(bytes))
val orientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL
)
val inputBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
val rotatedBitmap = when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> rotateBitmap(inputBitmap, 90f)
ExifInterface.ORIENTATION_ROTATE_180 -> rotateBitmap(inputBitmap, 180f)
ExifInterface.ORIENTATION_ROTATE_270 -> rotateBitmap(inputBitmap, 270f)
else -> inputBitmap
}
// 回転済みのデータを渡す
val output = processWithOpenCV(rotatedBitmap)
// メモリ開放
inputBitmap.recycle()
rotatedBitmap.recycle()
result.success(output)
} catch (e: Exception) {
result.error("PROCESSING_ERROR", e.message, null)
}
} else {
result.error("INVALID_ARGUMENT", "Bytes are null", null)
}
}
else -> result.notImplemented()
}
}
}
private fun processWithOpenCV(inputBitmap: Bitmap): ByteArray {
// Bitmap → Mat
val mat = Mat()
Utils.bitmapToMat(inputBitmap, mat)
// グレースケール変換
val grayMat = Mat()
Imgproc.cvtColor(mat, grayMat, Imgproc.COLOR_RGBA2GRAY)
// Matを直接PNG形式のバイナリにエンコード
val matOfByte = MatOfByte()
Imgcodecs.imencode(".png", grayMat, matOfByte)
val retByteArray = matOfByte.toArray()
// メモリ解放(matOfByteも忘れずに)
mat.release()
grayMat.release()
matOfByte.release()
return retByteArray
}
fun rotateBitmap(bitmap: Bitmap, angle: Float): Bitmap {
val matrix = Matrix()
matrix.postRotate(angle)
return Bitmap.createBitmap(
bitmap, 0, 0,
bitmap.width, bitmap.height,
matrix, true
)
}
}
8. Flutter 側のブリッジクラス
import 'package:flutter/services.dart';
class OpenCvBridge {
static const platform = MethodChannel('opencv');
static Future<Uint8List> processImage(Uint8List bytes) async {
final result = await platform.invokeMethod('processImage', {
'bytes': bytes,
});
return result;
}
}
どこかで呼び出し
OpenCvBridge.processImage(Uint8List(0)); // 実際は画像データを渡す
9. 実行
flutter run -d emulator-xxxx
Android で実行してください。
まとめ
- Flutter から OpenCV を使うには Platform Channel を使用する方法がある
- OpenCV SDK を Android Module として追加する
- Java/Kotlin のバージョン不一致に注意
ひとまず、動作しました。
今後は Canny や輪郭抽出なども試してみる予定です。