숫자나 글자를 화면에 그렸을 때 인식하게 해주는 AI!_!
google_mlkit_digital_ink_recognition을 사용!
minSdkVersion은 21로 변경!

디지털 잉크에는 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) 알고리즘으로 유사도를 검사.