Flutter ML(Digital ink recognition)

원장·2025년 3월 4일

플러터 기초

목록 보기
18/36

뭐냐 넌?

숫자나 글자를 화면에 그렸을 때 인식하게 해주는 AI!_!

어떻게 쓰지?

google_mlkit_digital_ink_recognition을 사용!

minSdkVersion은 21로 변경!

디지털 잉크에는 x,y 외에도 시간도 있음!

왜 굳이 디지털 잉크 인식인데 x,y 이외에 시간이 들어가는가?

1.획의 순서 정보도 모델이 학습함. => 그럼 획의 순서로 글씨체가 일치하는지도 알 수 있는건가?

2.획이 끊기는 시점으로 새로운 글자를 인식하기 위함.

실제로 글씨체 분석과 필적 감정에도 위와 같은 AI가 사용된다고함. FBI 및 Interpol에서 쓰인다는디..?

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:google_mlkit_digital_ink_recognition/google_mlkit_digital_ink_recognition.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HandwritingRecognitionScreen(),
    );
  }
}

class HandwritingRecognitionScreen extends StatefulWidget {
  @override
  _HandwritingRecognitionScreenState createState() => _HandwritingRecognitionScreenState();
}

class _HandwritingRecognitionScreenState extends State<HandwritingRecognitionScreen> {
  final DigitalInkRecognizer recognizer = DigitalInkRecognizer(languageCode: 'en-US');
  List<List<Point>> storedHandwritingData = []; // 저장된 글씨 데이터 (사용자 필체)

  List<Point> currentStroke = []; // 현재 사용자의 입력 데이터

  @override
  void dispose() {
    recognizer.close();
    super.dispose();
  }

  // 필기 데이터 저장
  void saveHandwritingData() {
    if (currentStroke.isNotEmpty) {
      storedHandwritingData.add(List.from(currentStroke));
      currentStroke.clear();
      print("✅ 필기 데이터 저장 완료!");
    }
  }

  // Dynamic Time Warping(DTW) 알고리즘으로 유사도 계산
  double calculateDTW(List<Point> storedData, List<Point> newData) {
    int n = storedData.length;
    int m = newData.length;
    List<List<double>> dtw = List.generate(n + 1, (_) => List.filled(m + 1, double.infinity));

    dtw[0][0] = 0;

    for (int i = 1; i <= n; i++) {
      for (int j = 1; j <= m; j++) {
        double cost = distance(storedData[i - 1], newData[j - 1]);
        dtw[i][j] = cost + min(dtw[i - 1][j], min(dtw[i][j - 1], dtw[i - 1][j - 1]));
      }
    }

    return dtw[n][m];
  }

  // 좌표 거리 계산
  double distance(Point p1, Point p2) {
    return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2)) + (p1.t - p2.t).abs();
  }

  // 새로운 입력과 저장된 데이터 비교하여 글씨체 유사도 계산
  void compareHandwriting() {
    if (storedHandwritingData.isEmpty) {
      print("❌ 비교할 필기 데이터가 없습니다.");
      return;
    }

    double minDistance = double.infinity;
    for (var storedData in storedHandwritingData) {
      double similarity = calculateDTW(storedData, currentStroke);
      if (similarity < minDistance) {
        minDistance = similarity;
      }
    }

    double similarityScore = 100 - (minDistance / 100); // 점수화 (0~100)
    print("🔍 글씨체 유사도: ${similarityScore.toStringAsFixed(2)}%");
  }

  // UI
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Handwriting Recognition")),
      body: Column(
        children: [
          Expanded(
            child: GestureDetector(
              onPanUpdate: (details) {
                setState(() {
                  currentStroke.add(Point(details.localPosition.dx, details.localPosition.dy, DateTime.now().millisecondsSinceEpoch));
                });
              },
              onPanEnd: (details) {
                saveHandwritingData();
              },
              child: Container(
                color: Colors.grey[200],
                child: CustomPaint(
                  painter: HandwritingPainter(strokes: [currentStroke]),
                ),
              ),
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              ElevatedButton(
                onPressed: () => setState(() => currentStroke.clear()),
                child: Text("초기화"),
              ),
              ElevatedButton(
                onPressed: compareHandwriting,
                child: Text("글씨체 비교"),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

// 필기 UI 표시용
class HandwritingPainter extends CustomPainter {
  final List<List<Point>> strokes;

  HandwritingPainter({required this.strokes});

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

    for (var stroke in strokes) {
      for (int i = 0; i < stroke.length - 1; i++) {
        canvas.drawLine(
          Offset(stroke[i].x, stroke[i].y),
          Offset(stroke[i + 1].x, stroke[i + 1].y),
          paint,
        );
      }
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}

// 필기 데이터 저장용 클래스
class Point {
  final double x;
  final double y;
  final int t; // 시간 데이터

  Point(this.x, this.y, this.t);
}

위처럼 쓰면 글씨체 유사도를 판단해줌.

Dynamic Time Warping(DTW) 알고리즘으로 유사도를 검사.

profile
나 원장이 아니다

0개의 댓글