Flutter插件zinnia_flutter的介绍与使用

Flutter插件zinnia_flutter的介绍与使用

zinnia_flutter 是一个围绕 zinnia 库的 Dart 封装。

Zinnia日本语)是一个基于支持向量机的手写识别系统。它简单、可定制且便携,可用于识别手写的日文字符(漢字)。模型文件可以在 这里 找到。

示例

以下是一个简单的示例代码:

var modelByteData = await rootBundle.load('assets/joyo-kanji.model');
var recognizer = ZinniaRecognizer()
    ..loadFromByteData(modelByteData);

var character = ZinniaCharacter(100, 100);
character.add([const Point(10, 50), const Point(90, 50)]);
character.add([const Point(50, 10), const Point(50, 90)]);

var list = recognizer.classifyToList(character, resultsLimit: 3);
/*
    list == [
        ZinniaResultEntry('十', 0.5172672867774963),
        ZinniaResultEntry('七', -0.3905687928199768),
        ZinniaResultEntry('斗', -0.5770313143730164)
    ]
*/

character.dispose();
recognizer.dispose();

完整的 Flutter 应用示例可以在这个链接找到:这里

许可证

有关此封装器、zinnia 和模型文件的许可证信息,可以在 LICENSE 文件中找到。


完整示例代码

以下是一个完整的 Flutter 示例代码,展示了如何使用 zinnia_flutter 插件进行手写汉字识别:

import 'dart:math';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:zinnia_flutter/zinnia_character.dart';
import 'package:zinnia_flutter/zinnia_recognizer.dart';
import 'package:zinnia_flutter/zinnia_result_entry.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _Painter _painter = _Painter();
  final ZinniaRecognizer _recognizer = ZinniaRecognizer();
  final ZinniaCharacter _character = ZinniaCharacter(300, 300);
  late final Future<bool> _initialized;
  List<ZinniaResultEntry> _results = [];

  [@override](/user/override)
  void initState() {
    super.initState();
    _initialized = _initZinnia();
  }

  Future<bool> _initZinnia() async {
    return _recognizer
        .loadFromByteData(await rootBundle.load('assets/joyo-kanji.model'));
  }

  void _addPointToCurrentStroke(Offset point) {
    if (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1) {
      return _endStroke();
    }

    setState(() {
      _painter.currentStroke.add(point);
    });
  }

  void _endStroke() {
    if (_painter.currentStroke.isEmpty) {
      return;
    }

    var characterStroke = _painter.currentStroke
        .map((e) =>
            e.scale(_character.width.toDouble(), _character.height.toDouble()))
        .map((e) => Point<int>(e.dx.round(), e.dy.round()));

    _character.add(characterStroke);
    _results = _recognizer.classifyToList(_character, resultsLimit: 50);

    setState(() {
      _painter.strokes.add(_painter.currentStroke);
      _painter.currentStroke = [];
    });
  }

  void _clear() {
    setState(() {
      _painter.strokes.clear();
      _painter.currentStroke.clear();
      _results = [];
      _character.clear();
    });
  }

  [@override](/user/override)
  void dispose() {
    _character.dispose();
    _recognizer.dispose();
    super.dispose();
  }

  Widget get _mainWidget {
    return LayoutBuilder(builder: (context, constraints) {
      var side = constraints.maxWidth;
      var direction = Axis.vertical;
      if (constraints.maxHeight < constraints.maxWidth) {
        side = constraints.maxHeight;
        direction = Axis.horizontal;
      }
      var size = Size(side, side);
      return Flex(
        direction: direction,
        children: [
          GestureDetector(
            onPanStart: (details) {
              _addPointToCurrentStroke(details.localPosition
                  .scale(1.0 / size.width, 1.0 / size.height));
            },
            onPanUpdate: (details) {
              _addPointToCurrentStroke(details.localPosition
                  .scale(1.0 / size.width, 1.0 / size.height));
            },
            onPanEnd: (details) {
              _endStroke();
            },
            child: CustomPaint(
              painter: _painter,
              size: size,
            ),
          ),
          Expanded(
            child: ListView.builder(
                itemCount: _results.length,
                itemBuilder: (context, index) {
                  var entry = _results[index];
                  return ListTile(
                    title: Text(entry.value),
                    subtitle: Text("Score: ${entry.score}"),
                  );
                }),
          ),
        ],
      );
    });
  }

  Widget get _initializationWidget {
    return const Text('Initialization...');
  }

  Widget _errorWidget(Object? error) {
    return Text("Error: $error");
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(
            title: const Text('Plugin example app'),
          ),
          body: FutureBuilder(
              future: _initialized,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  if (snapshot.data == true) {
                    return _mainWidget;
                  } else {
                    return _errorWidget("initialization failed");
                  }
                } else if (snapshot.hasError) {
                  return _errorWidget(snapshot.error);
                } else {
                  return _initializationWidget;
                }
              }
            ),
            floatingActionButton: FloatingActionButton(
              child: const Icon(Icons.clear),
              onPressed: _clear,
            )
          ),
    );
  }
}

class _Painter extends CustomPainter {
  List<List<Offset>> strokes = [];
  List<Offset> currentStroke = [];

  [@override](/user/override)
  void paint(Canvas canvas, Size size) {
    var borderPaint = Paint()
      ..style = PaintingStyle.stroke;

    canvas.drawRect(Offset.zero & size, borderPaint);

    var linePaint = Paint()
        ..strokeWidth = 5.0;

    for (var stroke in strokes) {
      canvas.drawPoints(PointMode.polygon,
          stroke.map((e) => e.scale(size.width, size.height)).toList(), linePaint);
    }

    linePaint.color = Colors.red;
    canvas.drawPoints(PointMode.polygon,
        currentStroke.map((e) => e.scale(size.width, size.height)).toList(), linePaint);
  }

  [@override](/user/override)
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

更多关于Flutter插件zinnia_flutter的介绍与使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter插件zinnia_flutter的介绍与使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


zinnia_flutter 是一个用于手写文字识别的 Flutter 插件,它基于 Zinnia 手写识别引擎。Zinnia 是一个开源的在线手写识别系统,支持多种语言的手写文字识别。通过 zinnia_flutter,你可以在 Flutter 应用中集成手写文字识别的功能。

1. 安装 zinnia_flutter 插件

首先,你需要在 pubspec.yaml 文件中添加 zinnia_flutter 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  zinnia_flutter: ^0.0.1

然后运行 flutter pub get 来安装依赖。

2. 初始化 zinnia_flutter

main.dart 文件中导入 zinnia_flutter 并进行初始化:

import 'package:flutter/material.dart';
import 'package:zinnia_flutter/zinnia_flutter.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await ZinniaFlutter.init();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Zinnia Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HandwritingRecognitionScreen(),
    );
  }
}

3. 创建手写识别界面

接下来,创建一个手写识别界面,用户可以在屏幕上绘制文字,然后通过 zinnia_flutter 进行识别。

class HandwritingRecognitionScreen extends StatefulWidget {
  [@override](/user/override)
  _HandwritingRecognitionScreenState createState() => _HandwritingRecognitionScreenState();
}

class _HandwritingRecognitionScreenState extends State<HandwritingRecognitionScreen> {
  List<Offset> points = [];
  String recognizedText = '';

  void _clearCanvas() {
    setState(() {
      points.clear();
      recognizedText = '';
    });
  }

  void _recognizeHandwriting() async {
    if (points.isEmpty) return;

    List<int> x = points.map((point) => point.dx.toInt()).toList();
    List<int> y = points.map((point) => point.dy.toInt()).toList();

    String result = await ZinniaFlutter.recognize(x, y);
    setState(() {
      recognizedText = result;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Handwriting Recognition'),
      ),
      body: Column(
        children: [
          Expanded(
            child: GestureDetector(
              onPanUpdate: (details) {
                setState(() {
                  RenderBox renderBox = context.findRenderObject() as RenderBox;
                  points.add(renderBox.globalToLocal(details.globalPosition));
                });
              },
              onPanEnd: (details) {
                points.add(Offset(-1, -1)); // Add a separator between strokes
              },
              child: CustomPaint(
                painter: HandwritingPainter(points),
                size: Size.infinite,
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Text('Recognized Text: $recognizedText'),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              ElevatedButton(
                onPressed: _clearCanvas,
                child: Text('Clear'),
              ),
              ElevatedButton(
                onPressed: _recognizeHandwriting,
                child: Text('Recognize'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class HandwritingPainter extends CustomPainter {
  final List<Offset> points;

  HandwritingPainter(this.points);

  [@override](/user/override)
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = Colors.black
      ..strokeWidth = 4.0
      ..strokeCap = StrokeCap.round;

    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != Offset(-1, -1) && points[i + 1] != Offset(-1, -1)) {
        canvas.drawLine(points[i], points[i + 1], paint);
      }
    }
  }

  [@override](/user/override)
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}
回到顶部