首页 > 解决方案 > Flutter:将图形叠加在 Firebase 视觉检测到的条形码上

问题描述

我正在尝试在 Flutter 中实现一个条形码扫描仪应用程序,其中相机可以作为小部件嵌入,而不是使其全屏显示。

所以我选择了flutter_qr_mobile_vision包,因为我们可以使用原生相机功能并限制相机预览大小。

但是上面的包只给出了 MLKit 检测到的条形码字符串,而不是条形码的详细信息,如边界框、条形码类型和其他信息,所以我把上面的包的源代码做了一些更改,以便我得到边界框,可用于在检测到的条形码上覆盖图形。

我试图做的代码可以在这里找到

现在我在使用这些更改时遇到了两个问题(目前只有 Android 实现)

  1. 当我尝试在条形码顶部覆盖边界框时,多个设备的结果不一致。

例如:

在全屏模式下在 oneplus 上运行良好

一加上的结果

在 Redmi 5 上输出相同的代码

红米5的结果

默认情况下,我将所有设备的相机分辨率保持为 (1280 x 720),并在叠加之前缩放输出

我试图了解在不同设备上造成这种情况的原因

  1. 现在如果我尝试调整相机预览的小部件高度,结果在oneplus上也不一致

在此处输入图像描述

我觉得这与缩放部分有关,但我不确定。

以下代码用于相机预览和叠加图形

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:qr_mobile_vision/qr_camera.dart';
import 'package:qr_mobile_vision/qr_barcode.dart';
import 'package:vision_demo/barcode_detctor_painter.dart';
import 'package:vision_demo/overlay.dart';

void main() {
  debugPaintSizeEnabled = false;
  runApp(new HomePage());
}

class HomePage extends StatefulWidget {
  @override
  HomeState createState() => new HomeState();
}

class HomeState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(home: new MyApp());
  }
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool camState = false;
  List<Barcode> barcode = List<Barcode>();

  @override
  initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Demo app'),
      ),
      body: new Center(
        child: new Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            camState
                ? new SizedBox(
                    width: MediaQuery.of(context).size.width,
                    height: MediaQuery.of(context).size.height - 300 - AppBar().preferredSize.height,
                    child: Stack(
                      children: [
                        new QrCamera(
                          onError: (context, error) => Text(
                            error.toString(),
                            style: TextStyle(color: Colors.red),
                          ),
                          qrCodeCallback: (code) {
                            setState(() {
                              barcode = code;
                            });
                          },
                          child: new Container(
                            decoration: new BoxDecoration(
                              color: Colors.transparent,
                            ),
                          ),
                        ),
                        Container(
                            constraints: const BoxConstraints.expand(),
                            decoration: ShapeDecoration(
                              shape: QrScannerOverlayShape(cutOutSize: MediaQuery.of(context).size.width - 160, borderColor: Colors.white),
                            )),
                        LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
                          return _buildResults(constraints);
                        })
                      ],
                    ),
                  )
                : Expanded(child: new Center(child: new Text("Camera inactive"))),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
          child: new Text(
            "press me",
            textAlign: TextAlign.center,
          ),
          onPressed: () {
            setState(() {
              camState = !camState;
            });
          }),
    );
  }

  Widget _buildResults(BoxConstraints constraints) {
    const Text noResultsText = Text('No results!');

    if (barcode == null) {
      return noResultsText;
    }

    CustomPainter painter;

    final Size imageSize = Size(720.0, 1280.0);

    if (barcode is! List<Barcode>) return noResultsText;
    painter = BarcodeDetectorPainter(imageSize, barcode);

    return CustomPaint(
      size: Size(double.maxFinite, double.maxFinite),
      painter: painter,
    );
  }
}

下面是我用来调整大小以覆盖图形的代码。

import 'package:flutter/material.dart';
import 'package:qr_mobile_vision/qr_barcode.dart';

class BarcodeDetectorPainter extends CustomPainter {
  BarcodeDetectorPainter(this.absoluteImageSize, this.barcodeLocations); // absoluteImageSize will always be (1280 x 720) 

  final List<Barcode> barcodeLocations;
  final Size absoluteImageSize;

  @override
  void paint(Canvas canvas, Size size) {
    final double scaleX = size.width / absoluteImageSize.width;
    final double scaleY = size.height / absoluteImageSize.height;

    Rect scaleRect(Barcode barcode) {
      return Rect.fromLTRB(
        barcode.boundingBox.left * scaleX,
        barcode.boundingBox.top * scaleY,
        barcode.boundingBox.right * scaleX,
        barcode.boundingBox.bottom * scaleY,
      );
    }

    final Paint paint = Paint()
      ..style = PaintingStyle.fill
      ..strokeWidth = 2.0;

    for (Barcode barcode in barcodeLocations) {
      paint.color = Colors.green;

      canvas.drawRect(scaleRect(barcode), paint);
    }
  }

  @override
  bool shouldRepaint(BarcodeDetectorPainter oldDelegate) {
    return oldDelegate.absoluteImageSize != absoluteImageSize || oldDelegate.barcodeLocations != barcodeLocations;
  }
}


解决这两个问题很重要,因为在此之后我想限制扫描区域,以便我只能捕获与我添加的切口尺寸对齐的条形码。

任何形式的帮助表示赞赏。

标签: androidflutterbarcode-scannerfirebase-mlkit

解决方案


我能够解决这个问题,问题是在多个设备中具有恒定的分辨率,某些设备可能没有我设置的分辨率,因此基于屏幕尺寸以编程方式设置最佳最小分辨率解决了我的问题。


推荐阅读