Closed10
FlutterでOpenCVを使えるか
SwiftでOpenCVを使う
Swift(ContenView.swift)
struct ContentView: View {
private let grayImage = OpenCVManager.gray(UIImage(named: "icon"))!
var body: some View {
VStack {
Image("icon")
Image(uiImage: grayImage)
}
}
}
Objective-C++(OpenCVManager.mm)
#ifdef __cplusplus
#import <opencv2/opencv.hpp>
#import <opencv2/highgui.hpp>
#import <opencv2/imgcodecs/ios.h>
#import "OpenCVManager.h"
#endif
#import <Foundation/Foundation.h>
@implementation OpenCVManager : NSObject
+ (UIImage*)gray:(UIImage*)image {
cv::Mat img_Mat;
UIImageToMat(image, img_Mat);
cv::cvtColor(img_Mat, img_Mat, cv::COLOR_BGR2GRAY);
return MatToUIImage(img_Mat);
}
@end
ヘッダファイル(UseOpenCVwithSwift-Bridging-Header.h)
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "OpenCVManager.h"
@interface OpenCVManager : NSObject
+ (UIImage *)gray:(UIImage *)image;
@end
ヘッダファイル(OpenCVManager.h)
何も変更しないよ。(自動で作られたコードのみ)
#ifndef OpenCVManager_h
#define OpenCVManager_h
#endif /* OpenCVManager_h */
実行結果
大量の警告文
CocoapodでOpenCVを導入した場合、大量の警告文が表示される。この事例は多く、公式の対応を待つしかなさそうかな?
参考文献
UInt8List ⇆ UInt8 及び画像変換の技術調査
MethodChannelは、現状MessageCodecしか対応していない。
そのため、
- Dart
- Java
- byte[]
- Kotlin
- ByteArray
- Swift
以上のバイトデータを画像に変換し、OpenCVの処理をしなければならない。また、Unit8List
は符号なし8bit固定長の整数値型のため、符号なし8bit整数値型のUInt8
で処理した方がデータ変換でエラーは減ると思う。(SInt32,Sint64,Float64の情報がなく不安という理由もあり)
Dart側
Image -> Unit8List
// Dart Imageのバイトデータ
Uint8List bytes = image.getBytes()
// v = List<int> を Uint8List に変換
Uint8List bytes = Uint8List.fromList(v);
v = List<int> -> Image
// v = List<int> を Dart Image に変換
imgLib.Image image = imageLib.Image.fromBytes(width, height, v);
Flutter Widgetでの画像表示
// Dart Image を List<int> に変換
List<int> _imageBytes = imgLib.encodeJpg(image);
// Flutter の Image ウィジェットを以下のように書きます
Image.memory(_imageBytes),
Swift側
UIImage -> Data
// pngに変換する
image.pngData()
// jpegに変換する
// compressionQualityには0~1の範囲で圧縮率を指定する
image.jpegData(compressionQuality: 1)
Data -> UInt8 に変換
UInt8 -> UIImage に変換
let data = NSData(bytes: [UInt8], length: Int)
let image = UIImage(data: data)
SwiftでUIImage ⇆ UInt8の相互変換
コード全体
import SwiftUI
struct ContentView: View {
let image = UIImage(named: "image")!
@State var change = UIImage(named: "icon")!
var body: some View {
VStack {
Image(uiImage: image)
Image(uiImage: change)
}
.onAppear(perform: {
// UIImage -> (JPEG)Data
let jpegData = image.jpegData(compressionQuality: 1)!
// Data -> Array<UInt8>
let arrayUInt8 = jpegData.encodedHexadecimals!
// Array<UInt8> -> Data
let data = Data(arrayUInt8)
// Data -> UIImage
change = UIImage(data: data)!
})
}
}
extension Data {
/// Data型をUInt8の配列に変換
var encodedHexadecimals: [UInt8]? {
let responseValues = self.withUnsafeBytes({ (pointer: UnsafeRawBufferPointer) -> [UInt8] in
let unsafeBufferPointer = pointer.bindMemory(to: UInt8.self)
let unsafePointer = unsafeBufferPointer.baseAddress!
return [UInt8](UnsafeBufferPointer(start: unsafePointer, count: self.count))
})
return responseValues
}
}
実行結果
- 上側 : 変換前
- 下側 : 変換後
課題
- UIImage ⇆ UInt8 変換すると画質は少し落ちる
- データ容量がまだ大きい
- 本開発では画像変換に加え、OpenCVの処理をリアルタイムで実施する
- 非同期処理で結果をFlutter(Dart)側に送信する必要がある
- ホスト側とクライアント側どちらも画像の圧縮方法をもう少し考える必要がある
- Array型での通信ができるかどうか
-
Uint8List
とUInt8
で通信処理をしようとしていた- 8bitの画像変換は無理
- ドキュメントには、
List ⇆ Array
しか書いていない- 謎が多い
- Listの形式は?
- Arrayの形式は?
- 要素数は?
- 謎が多い
-
画像
変換する画像
比較画像
解説
画像をPNG/JPEGのData型に変換
let pngData = image.pngData()!
let jpegData = image.jpegData(compressionQuality: 1)!
- PNG : 665368 bytes
- JPEG : 185349 bytes
圧縮率が高く、Data容量が小さいJPEG形式を採用する
(JPEG)Data -> UInt8の配列に変換
let arrayUInt8 = jpegData.encodedHexadecimals!
- データ型 :
Array<UInt8>
- 要素数 : 185349
UInt8の配列 -> Data
let data = Data(arrayUInt8)
- データ容量 : 185349 bytes
Data -> UIImage
change = UIImage(data: data)!
参考文献
パッケージの干渉
パッケージが干渉する例
ダメなimport文
import 'package:flutter/material.dart';
import 'package:image/image.dart' as pkgImg;
エラーメッセージ
lib/main.dart:60:13: Error: 'Image' is imported from both
'package:flutter/src/widgets/image.dart' and 'package:image/src/image.dart'.
Image
は両方から読み取られています。
解決策
import 'package:flutter/material.dart';
import 'package:image/image.dart' as pkgImg;
asでパッケージを別名で呼び出す。
Flutter(Dart)で画像のエンコード/デコード
標準ライブラリだと未対応のため(?)、image外部ライブラリで行う。
エンコード
import 'dart:typed_data';
import 'package:image/image.dart' as pkgImg;
...
final pathImage = File('/Users/ryo/github/UseOpenCVwithFlutter/MethodChannel/ImageConversion/check_image_conversion/image/icon.jpeg');
// JPEP Decode : List of bytes -> Jpeg Image
pkgImg.Image image = pkgImg.decodeJpg(pathImage.readAsBytesSync());
// JPEG Encode : Image -> List<Int>
List<int> jpg = pkgImg.JpegEncoder().encodeImage(image);
// List<Int> -> Unit8List
Uint8List bytesList = Uint8List.fromList(jpg);
デコード
// Unit8List -> Image
_image = Image.memory(bytesList);
参考文献
Image ⇆ Unit8List 変換し、画像を表示するサンプルプログラム
コード
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as pkgImg;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void _convertImage() {
final pathImage = File('/Users/ryo/github/UseOpenCVwithFlutter/MethodChannel/ImageConversion/check_image_conversion/image/icon.jpeg');
// JPEP Decode : List of bytes -> Jpeg Image
pkgImg.Image image = pkgImg.decodeJpg(pathImage.readAsBytesSync());
// JPEG Encode : Image -> List<Int>
List<int> jpg = pkgImg.JpegEncoder().encodeImage(image);
// List<Int> -> Unit8List
Uint8List bytesList = Uint8List.fromList(jpg);
setState(() {
// Unit8List -> Image
_image = Image.memory(bytesList);
});
}
Image _image = Image.asset('image/sample.jpg');
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset('image/sample.jpg'),
_image,
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _convertImage,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
実行結果
変更前
変更後
Uint8Listを非同期で相互通信は負担が大きすぎる
エラーコード
Could not cast value of type 'FlutterStandardTypedData' (0x10561bdd8) to 'NSArray' (0x10561c090).
Lost connection to device.
エラーの内容は、通信処理でメモリを大量消費する。その結果、メモリ不足でOSがアプリを強制終了する。
コード
Dart
Future<void> _getMethodNumber() async {
String methodText;
final pathImage = File('/Users/ryo/github/UseOpenCVwithFlutter/MethodChannel/ImageConversion/connect_image/images/icon.jpeg');
// JPEP Decode : List of bytes -> Jpeg Image
pkgImg.Image image = pkgImg.decodeJpg(pathImage.readAsBytesSync());
// JPEG Encode : Image -> List<Int>
List<int> jpg = pkgImg.JpegEncoder().encodeImage(image);
// List<Int> -> Unit8List
Uint8List bytesList = Uint8List.fromList(jpg);
try {
int result = await platform.invokeMethod('getMethodText', bytesList);
methodText = '$result';
} on PlatformException catch (e) {
methodText = "Failed to get : '${e.message}'";
}
}
参考文献
MethodChannelで画像データをコールバックする
Image
をBase64
にエンコードし、画像データをDart⇆Swfit間で非同期処理させる。
コード
Dart
import 'dart:convert';
import 'dart:io' show File;
import 'dart:typed_data' show Uint8List;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image/image.dart' as pkgImg;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('samples.flutter.dev/image');
Image _image = Image.asset('images/sample.jpg');
Future<void> _convertImage() async {
try {
final pathImage = File('/Users/ryo/github/UseOpenCVwithFlutter/MethodChannel/ImageConversion/connect_image/images/icon.jpeg');
// JPEP Decode : List of bytes -> Jpeg Image
pkgImg.Image image = pkgImg.decodeJpg(pathImage.readAsBytesSync());
// JPEG Encode : Image -> List<int>
List<int> encodeJPEG = pkgImg.JpegEncoder().encodeImage(image);
// base64 Encode : List<int> -> String(base64)
String base64 = base64Encode(encodeJPEG);
String result = await platform.invokeMethod('getBase64', base64);
// base64 Decode : String(base64) -> Unit8List
Uint8List bytes = base64Decode(result);
setState(() {
_image = Image.memory(bytes);
});
} on PlatformException catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/sample.jpg'),
ElevatedButton(
child: Text('Convert Image'),
onPressed: _convertImage,
),
_image,
],
),
),
);
}
}
Swift
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let methodChannel = FlutterMethodChannel(name: "samples.flutter.dev/image",
binaryMessenger: controller.binaryMessenger)
methodChannel.setMethodCallHandler(
{
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
guard call.method == "getBase64" else {
result(FlutterMethodNotImplemented)
return
}
let parameters = call.arguments
result(parameters)
}
)
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
実行結果
懸念点
final pathImage = File('/Users/ryo/github/UseOpenCVwithFlutter/MethodChannel/ImageConversion/connect_image/images/icon.jpeg');
// JPEP Decode : List of bytes -> Jpeg Image
pkgImg.Image image = pkgImg.decodeJpg(pathImage.readAsBytesSync());
// JPEG Encode : Image -> List<int>
List<int> encodeJPEG = pkgImg.JpegEncoder().encodeImage(image);
// base64 Encode : List<int> -> String(base64)
String base64 = base64Encode(encodeJPEG);
この部分を非同期で処理させた方が、パフォーマンスあがるかな?
非同期処理 = 別スレットで処理
という感じで、ザックリしか理解していないから非同期処理をちゃんと勉強してから判断しよう。
参考文献
OpenCVをCocoapodsでインストールするとエラーを吐く
The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.
というエラーを吐き、pod update
や'pod install'で解決出来ない時
'OpenCVManager'というインターフェースの宣言を見つけることができない...
懸念点(問題の考えられる発生理由)
- FlutterでCocoapod経由だと多くのエラーがある
- Flutter/Debug.xcofig & Flutter/Release.xconfig がライブラリの導入後自動で編集されていた
- Objective-C追加時、ヘッダーファイルを自動で追加するか聞かれなかった(ヘッダーファイルは手動で追加した)
このスクラップは2021/11/17にクローズされました