Open droplet-js opened 1 year ago
示例代码
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'dart:math' as math;
import 'package:camera/camera.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_zxing/flutter_zxing.dart' as fzx;
import 'package:image/image.dart' as imglib;
import 'package:tuple/tuple.dart';
import 'package:zxing_lib/common.dart';
import 'package:zxing_lib/zxing.dart';
enum ZXFormat {
None(fzx.Format.None),
Aztec(fzx.Format.Aztec),
Codabar(fzx.Format.Codabar),
Code39(fzx.Format.Code39),
Code93(fzx.Format.Code93),
Code128(fzx.Format.Code128),
DataBar(fzx.Format.DataBar),
DataBarExpanded(fzx.Format.DataBarExpanded),
DataMatrix(fzx.Format.DataMatrix),
EAN8(fzx.Format.EAN8),
EAN13(fzx.Format.EAN13),
ITF(fzx.Format.ITF),
MaxiCode(fzx.Format.MaxiCode),
PDF417(fzx.Format.PDF417),
QRCode(fzx.Format.QRCode),
UPCA(fzx.Format.UPCA),
UPCE(fzx.Format.UPCE),
OneDCodes(fzx.Format.OneDCodes),
TwoDCodes(fzx.Format.TwoDCodes),
Any(fzx.Format.Any);
const ZXFormat(this.value);
final int value;
}
enum ZXECCLevel {
M(0),
L(1),
H(2),
Q(3);
const ZXECCLevel(this.value);
final int value;
}
class ZXDResult {
ZXDResult.from(
fzx.CodeResult result,
) : isValid = result.isValidBool,
format = ZXFormat.values.singleWhere((ZXFormat element) => element.value == result.format),
text = result.textString;
ZXDResult.fromDart(
Result result,
) : isValid = true,
format = ZXFormat.Any,
text = result.text;
final bool isValid;
final ZXFormat format;
final String? text;
}
class ZXEResult {
ZXEResult.from({
required fzx.EncodeResult result,
}) : isValid = result.isValidBool,
format = ZXFormat.values.singleWhere((ZXFormat element) => element.value == result.format),
text = result.textString,
bytes = Uint8List.view(result.bytes.buffer),
errorMessage = result.errorMessage;
final bool isValid;
final ZXFormat format;
final String? text;
final Uint8List bytes;
final String errorMessage;
}
class ZXingKit {
const ZXingKit._();
//
static Future<ZXDResult?> decodeImage(
String path, {
ZXFormat format = ZXFormat.Any,
double? cropPercent,
}) async {
return compute(_computeDecodeImage, Tuple3<String, ZXFormat, double?>(path, format, cropPercent));
}
static Future<ZXDResult?> _computeDecodeImage(Tuple3<String, ZXFormat, double?> tuple) async {
final String path = tuple.item1;
final ZXFormat format = tuple.item2;
final double? cropPercent = tuple.item3;
int cropSize = 0;
if (cropPercent != null) {
final imglib.Image? image = imglib.decodeImage(await File(path).readAsBytes());
if (image != null) {
cropSize = (math.min(image.width, image.height) * cropPercent).round();
}
}
final fzx.CodeResult? result = await fzx.readBarcodeImagePathString(
path,
format: format.value,
cropWidth: cropSize,
cropHeight: cropSize,
);
return result != null ? ZXDResult.from(result) : null;
}
//
static Future<ZXEResult> encodeImage(
String content, {
ZXFormat format = ZXFormat.QRCode,
int size = 256,
int margin = 0,
ZXECCLevel eccLevel = ZXECCLevel.M,
}) async {
return compute(_computeEncodeImage, Tuple5<String, ZXFormat, int, int, ZXECCLevel>(content, format, size, margin, eccLevel));
}
static Future<ZXEResult> _computeEncodeImage(Tuple5<String, ZXFormat, int, int, ZXECCLevel> tuple) async {
final String content = tuple.item1;
final ZXFormat format = tuple.item2;
final int size = tuple.item3;
final int margin = tuple.item4;
final ZXECCLevel eccLevel = tuple.item5;
final fzx.EncodeResult result = fzx.encodeBarcode(
content,
format: format.value,
width: size,
height: size,
margin: margin,
eccLevel: eccLevel.value,
);
return ZXEResult.from(result: result);
}
}
// isolates.compute 效率太低了,不适合实时检测结果
class ZXingWorker {
Isolate? _isolate;
SendPort? _workerSendPort;
bool get isRunning => _workerSendPort != null;
Future<void> start() async {
final ReceivePort initialize = ReceivePort();
_isolate = await Isolate.spawn<SendPort>(
(SendPort initializeSendPort) async {
final ReceivePort worker = ReceivePort();
initializeSendPort.send(worker.sendPort);
// 接收 _workerPort 发送的消息
await for (dynamic message in worker) {
if (message is _IsolateData) {
final CameraImage image = message.image;
final ZXFormat format = message.format;
final double cropPercent = message.cropPercent;
final SendPort callbackSendPort = message.callbackSendPort;
final Stopwatch stopwatch = Stopwatch()..start();
try {
final ZXDResult result = await _inferenceByZXingDart(image, format, cropPercent);
callbackSendPort.send(result);
} catch (e) {
callbackSendPort.send(e);
}
stopwatch.stop();
if (kDebugMode) {
print('total elapsed: ${stopwatch.elapsed}');
}
}
}
},
initialize.sendPort,
debugName: 'zxing_worker',
);
_workerSendPort = await initialize.first as SendPort;
}
// zxing dart 无码时很慢 -> 1 ~ 3 秒,有码时甚至比 ffi 还快
Future<ZXDResult> _inferenceByZXingDart(CameraImage image, ZXFormat format, double cropPercent) async {
final int cropSize = (math.min(image.width, image.height) * cropPercent).round();
if (Platform.isAndroid && image.format.group == ImageFormatGroup.yuv420) {
final WriteBuffer allBytes = WriteBuffer();
for (final Plane plane in image.planes) {
allBytes.putUint8List(plane.bytes);
}
final Uint8List yuvData = allBytes.done().buffer.asUint8List();
final LuminanceSource imageSource = PlanarYUVLuminanceSource(yuvData, image.width, image.height);
final BinaryBitmap bitmap = BinaryBitmap(HybridBinarizer(imageSource));
final Reader reader = MultiFormatReader();
final Result result = reader.decode(bitmap.crop((bitmap.width - cropSize) ~/ 2, (bitmap.height - cropSize) ~/ 2, cropSize, cropSize), <DecodeHintType, Object>{
DecodeHintType.TRY_HARDER: false,
DecodeHintType.ALSO_INVERTED: false,
});
return ZXDResult.fromDart(result);
}
throw UnsupportedError('Unsupport format(${image.format.group})');
}
// zxing ffi 无码时也很快
Future<ZXDResult> _inferenceByZXingFFI(CameraImage image, ZXFormat format, double cropPercent) async {
final Uint8List bytes = await _convertImage(image);
final int cropSize = (math.min(image.width, image.height) * cropPercent).round();
final fzx.CodeResult result = fzx.readBarcode(
bytes,
width: image.width,
height: image.height,
format: format.value,
cropWidth: cropSize,
cropHeight: cropSize,
tryHarder: false,
tryRotate: false,
);
return ZXDResult.from(result);
}
Future<Uint8List> _convertImage(CameraImage image) async {
if (Platform.isAndroid && image.format.group == ImageFormatGroup.yuv420) {
return image.planes.first.bytes;
} else if (image.format.group == ImageFormatGroup.bgra8888) {
final imglib.Image img = imglib.Image.fromBytes(
image.width,
image.height,
image.planes[0].bytes,
format: imglib.Format.bgra,
);
return img.getBytes(format: imglib.Format.luminance);
}
throw UnsupportedError('Unsupport format(${image.format.group})');
}
Future<ZXDResult> decodeImage(
CameraImage image, {
ZXFormat format = ZXFormat.Any,
double cropPercent = 0.5,
}) async {
assert(_workerSendPort != null);
final ReceivePort callback = ReceivePort();
_workerSendPort!.send(_IsolateData(image, format, cropPercent, callback.sendPort));
final dynamic result = await callback.first;
if (result is ZXDResult) {
return result;
} else {
// ignore: only_throw_errors
throw result as Object;
}
}
Future<void> stop() async {
_isolate?.kill(priority: Isolate.immediate);
_isolate = null;
_workerSendPort = null;
}
}
class _IsolateData {
_IsolateData(
this.image,
this.format,
this.cropPercent,
this.callbackSendPort,
);
final CameraImage image;
final ZXFormat format;
final double cropPercent;
final SendPort callbackSendPort;
}
dart慢是正常的。目前我还没有明确的优化方向。
我试下代码看看情况吧
dart慢是正常的。目前我还没有明确的优化方向。
我试下代码看看情况吧
static Future<Result> _inferenceByZXingDart(CameraImage image, double cropPercent) async {
final int cropSize = (math.min(image.width, image.height) * cropPercent).round();
late final LuminanceSource imageSource;
if (Platform.isAndroid && image.format.group == ImageFormatGroup.yuv420) {
final WriteBuffer allBytes = WriteBuffer();
for (final Plane plane in image.planes) {
allBytes.putUint8List(plane.bytes);
}
final Uint8List yuvData = allBytes.done().buffer.asUint8List();
imageSource = PlanarYUVLuminanceSource(yuvData, image.width, image.height);
} else if (image.format.group == ImageFormatGroup.jpeg) {
final imglib.Image img = imglib.decodeJpg(image.planes.first.bytes)!;// jpeg: rgb/bgr
final Uint8List rgbData = img.getBytes(format: imglib.Format.luminance);
imageSource = RGBLuminanceSource(image.width, image.height, rgbData);
} else if (image.format.group == ImageFormatGroup.bgra8888) {
final imglib.Image img = imglib.Image.fromBytes(
image.width,
image.height,
image.planes.first.bytes,
format: imglib.Format.bgra,
);
final Uint8List rgbData = img.getBytes(format: imglib.Format.luminance);
imageSource = RGBLuminanceSource(image.width, image.height, rgbData);
} else {
throw UnsupportedError('Unsupport format(${image.format.group})');
}
final BinaryBitmap bitmap = BinaryBitmap(HybridBinarizer(imageSource));
final Reader reader = MultiFormatReader();
final Result result = reader.decode(bitmap.crop((bitmap.width - cropSize) ~/ 2, (bitmap.height - cropSize) ~/ 2, cropSize, cropSize), <DecodeHintType, Object>{
DecodeHintType.TRY_HARDER: false,
DecodeHintType.ALSO_INVERTED: false,
});
return result;
}
iOS 上倒是不慢,就 Android 上慢
Is your feature request related to a problem? Please describe. 如题,我对比了 flutter_zxing(ffi)和 zxing_lib(dart)
Describe the solution you'd like 优化 zxing_lib 在无二维码/条形时的实时识别效率
Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered.
Additional context Add any other context or screenshots about the feature request here.