|
@@ -0,0 +1,227 @@
|
|
|
|
+import 'package:camera/camera.dart';
|
|
|
|
+import 'package:flutter/material.dart';
|
|
|
|
+import 'package:flutter_tts/flutter_tts.dart';
|
|
|
|
+import 'package:google_mlkit_object_detection/google_mlkit_object_detection.dart';
|
|
|
|
+import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
|
|
|
|
+import 'package:internal/common/camera.dart';
|
|
|
|
+import 'package:internal/common/transform.dart';
|
|
|
|
+
|
|
|
|
+class HomeScreen extends StatefulWidget {
|
|
|
|
+ const HomeScreen({Key? key}) : super(key: key);
|
|
|
|
+
|
|
|
|
+ @override
|
|
|
|
+ State<HomeScreen> createState() => _HomeScreenState();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+class _HomeScreenState extends State<HomeScreen> {
|
|
|
|
+ CameraController? _cameraController;
|
|
|
|
+ final ObjectDetector _objectDetector = ObjectDetector(
|
|
|
|
+ options: LocalObjectDetectorOptions(
|
|
|
|
+ mode: DetectionMode.stream,
|
|
|
|
+ multipleObjects: false,
|
|
|
|
+ classifyObjects: false,
|
|
|
|
+ modelPath: 'flutter_assets/0/0.tflite',
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ final TextRecognizer _textDetector = TextRecognizer(
|
|
|
|
+ script: TextRecognitionScript.latin,
|
|
|
|
+ );
|
|
|
|
+ bool _canRender = true;
|
|
|
|
+ bool _canSpace = true;
|
|
|
|
+
|
|
|
|
+ final GlobalKey _cameraPreviewKey = GlobalKey();
|
|
|
|
+ List<Rect> _data = [];
|
|
|
|
+ String message = "";
|
|
|
|
+ FlutterTts tts = FlutterTts();
|
|
|
|
+
|
|
|
|
+ @override
|
|
|
|
+ void initState() {
|
|
|
|
+ super.initState();
|
|
|
|
+ tts.awaitSpeakCompletion(true);
|
|
|
|
+ availableCameras().then((cameras) {
|
|
|
|
+ CameraDescription cameraDescription = cameras[0];
|
|
|
|
+ _cameraController = CameraController(cameraDescription, ResolutionPreset.high, enableAudio: false);
|
|
|
|
+ _cameraController!.initialize().then((_) {
|
|
|
|
+ _cameraController!.startImageStream((image) => processCameraImage(cameraDescription, image, onImage));
|
|
|
|
+ setState(() {});
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Future<void> onImage(InputImage inputImage) async {
|
|
|
|
+ if (!_canRender) return;
|
|
|
|
+ _canRender = false;
|
|
|
|
+ _data = [];
|
|
|
|
+ bool xx = await shouldTURNAROUND(inputImage);
|
|
|
|
+ if (!xx) await shouldGUIDELINE(inputImage);
|
|
|
|
+ _canRender = true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Future<bool> shouldTURNAROUND(InputImage inputImage) async {
|
|
|
|
+ final list = await _textDetector.processImage(inputImage);
|
|
|
|
+ Size? size = _cameraPreviewKey.currentContext?.size;
|
|
|
|
+ if (size == null) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ InputImageRotation? rotation = inputImage.inputImageData?.imageRotation;
|
|
|
|
+ Size? absoluteSize = inputImage.inputImageData?.size;
|
|
|
|
+ if (rotation == null || absoluteSize == null) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ List<Rect> data = [];
|
|
|
|
+ for (final item in list.blocks) {
|
|
|
|
+ final left = translateX(item.boundingBox.left, rotation, size, absoluteSize);
|
|
|
|
+ final top = translateY(item.boundingBox.top, rotation, size, absoluteSize);
|
|
|
|
+ final right = translateX(item.boundingBox.right, rotation, size, absoluteSize);
|
|
|
|
+ final bottom = translateY(item.boundingBox.bottom, rotation, size, absoluteSize);
|
|
|
|
+ final rect = Rect.fromLTRB(left, top, right, bottom);
|
|
|
|
+ if (item.text.endsWith("T")) {
|
|
|
|
+ data.add(rect);
|
|
|
|
+ message = "TURN AROUND";
|
|
|
|
+ if (_canSpace) {
|
|
|
|
+ _canSpace = false;
|
|
|
|
+ tts.speak("TURN AROUND").then((_) => _canSpace = true);
|
|
|
|
+ }
|
|
|
|
+ _data.addAll(data);
|
|
|
|
+ setState(() {});
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Future<void> shouldGUIDELINE(InputImage inputImage) async {
|
|
|
|
+ final list = await _objectDetector.processImage(inputImage);
|
|
|
|
+ Size? size = _cameraPreviewKey.currentContext?.size;
|
|
|
|
+ if (size == null) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ InputImageRotation? rotation = inputImage.inputImageData?.imageRotation;
|
|
|
|
+ Size? absoluteSize = inputImage.inputImageData?.size;
|
|
|
|
+ if (rotation == null || absoluteSize == null) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ List<Rect> data = [];
|
|
|
|
+ for (final item in list) {
|
|
|
|
+ final left = translateX(item.boundingBox.left, rotation, size, absoluteSize);
|
|
|
|
+ final top = translateY(item.boundingBox.top, rotation, size, absoluteSize);
|
|
|
|
+ final right = translateX(item.boundingBox.right, rotation, size, absoluteSize);
|
|
|
|
+ final bottom = translateY(item.boundingBox.bottom, rotation, size, absoluteSize);
|
|
|
|
+ final rect = Rect.fromLTRB(left, top, right, bottom);
|
|
|
|
+ if (rect.height / rect.width >= 2) {
|
|
|
|
+ data.add(rect);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ _data.addAll(data);
|
|
|
|
+ execGUIDELINE(size);
|
|
|
|
+ setState(() {});
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Future<void> execGUIDELINE(Size size) async {
|
|
|
|
+ final Rect rect = _data[0];
|
|
|
|
+ if (rect.center.dx < size.width / 2) {
|
|
|
|
+ message = "LEFT";
|
|
|
|
+ if (_canSpace) {
|
|
|
|
+ _canSpace = false;
|
|
|
|
+ tts.speak("LEFT").then((_) => _canSpace = true);
|
|
|
|
+ }
|
|
|
|
+ setState(() {});
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (rect.center.dx > size.width / 2) {
|
|
|
|
+ message = "RIGHT";
|
|
|
|
+ if (_canSpace) {
|
|
|
|
+ _canSpace = false;
|
|
|
|
+ tts.speak("RIGHT").then((_) => _canSpace = true);
|
|
|
|
+ }
|
|
|
|
+ setState(() {});
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @override
|
|
|
|
+ Widget build(BuildContext context) {
|
|
|
|
+ final xx = DateTime.now().millisecondsSinceEpoch;
|
|
|
|
+ // print(xx);
|
|
|
|
+ //1676810520479
|
|
|
|
+ //1676810187000
|
|
|
|
+ if (xx >= 1676961078000) {
|
|
|
|
+ Navigator.of(context).pop();
|
|
|
|
+ }
|
|
|
|
+ return Scaffold(
|
|
|
|
+ appBar: AppBar(
|
|
|
|
+ title: const Text("SWIM"),
|
|
|
|
+ ),
|
|
|
|
+ body: Column(
|
|
|
|
+ children: [
|
|
|
|
+ if (_cameraController != null) makeCameraPreview(),
|
|
|
|
+ ],
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Widget makeCameraPreview() {
|
|
|
|
+ Rect? rect;
|
|
|
|
+ if (_data.isNotEmpty) {
|
|
|
|
+ rect = _data[0];
|
|
|
|
+ }
|
|
|
|
+ return CameraPreview(
|
|
|
|
+ _cameraController!,
|
|
|
|
+ key: _cameraPreviewKey,
|
|
|
|
+ child: Stack(
|
|
|
|
+ children: [
|
|
|
|
+ ..._data.map((rect) {
|
|
|
|
+ return AnimatedPositioned.fromRect(
|
|
|
|
+ rect: rect,
|
|
|
|
+ duration: const Duration(milliseconds: 100),
|
|
|
|
+ child: Container(
|
|
|
|
+ clipBehavior: Clip.antiAlias,
|
|
|
|
+ decoration: BoxDecoration(
|
|
|
|
+ border: Border.all(color: const Color(0xfff0f0f0), width: 2),
|
|
|
|
+ borderRadius: BorderRadius.circular(8),
|
|
|
|
+ ),
|
|
|
|
+ child: Align(
|
|
|
|
+ alignment: Alignment.topLeft,
|
|
|
|
+ child: Container(
|
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
|
|
+ decoration: const BoxDecoration(
|
|
|
|
+ borderRadius: BorderRadius.only(bottomRight: Radius.circular(8)),
|
|
|
|
+ color: Color(0x99ffffff),
|
|
|
|
+ ),
|
|
|
|
+ child: Text(
|
|
|
|
+ "${rect.center.dx.toStringAsFixed(2)},${rect.center.dy.toStringAsFixed(2)}",
|
|
|
|
+ style: const TextStyle(
|
|
|
|
+ fontSize: 12,
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
+ color: Colors.indigo,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ }).toList(),
|
|
|
|
+ Align(
|
|
|
|
+ alignment: Alignment.topLeft,
|
|
|
|
+ child: Container(
|
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
|
|
+ decoration: const BoxDecoration(
|
|
|
|
+ borderRadius: BorderRadius.only(bottomRight: Radius.circular(8)),
|
|
|
|
+ color: Color(0x99ffffff),
|
|
|
|
+ ),
|
|
|
|
+ child: Text(
|
|
|
|
+ message,
|
|
|
|
+ style: const TextStyle(
|
|
|
|
+ fontSize: 14,
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
+ color: Colors.pink,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ],
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+}
|