@@ -9,7 +9,7 @@ import 'dart:async';
9
9
import 'package:flutter_vision/flutter_vision.dart' ;
10
10
import 'package:image_picker/image_picker.dart' ;
11
11
12
- enum Options { none, imagev5, imagev8, frame, tesseract, vision }
12
+ enum Options { none, imagev5, imagev8, imagev8seg, frame, tesseract, vision }
13
13
14
14
late List <CameraDescription > cameras;
15
15
main () async {
@@ -78,6 +78,18 @@ class _MyAppState extends State<MyApp> {
78
78
});
79
79
},
80
80
),
81
+ SpeedDialChild (
82
+ child: const Icon (Icons .camera),
83
+ backgroundColor: Colors .blue,
84
+ foregroundColor: Colors .white,
85
+ label: 'YoloV8seg on Image' ,
86
+ labelStyle: const TextStyle (fontSize: 18.0 ),
87
+ onTap: () {
88
+ setState (() {
89
+ option = Options .imagev8seg;
90
+ });
91
+ },
92
+ ),
81
93
SpeedDialChild (
82
94
child: const Icon (Icons .camera),
83
95
backgroundColor: Colors .blue,
@@ -141,6 +153,9 @@ class _MyAppState extends State<MyApp> {
141
153
if (option == Options .imagev8) {
142
154
return YoloImageV8 (vision: vision);
143
155
}
156
+ if (option == Options .imagev8seg) {
157
+ return YoloImageV8Seg (vision: vision);
158
+ }
144
159
if (option == Options .tesseract) {
145
160
return TesseractImage (vision: vision);
146
161
}
@@ -622,6 +637,199 @@ class _YoloImageV8State extends State<YoloImageV8> {
622
637
}
623
638
}
624
639
640
+ class YoloImageV8Seg extends StatefulWidget {
641
+ final FlutterVision vision;
642
+ const YoloImageV8Seg ({Key ? key, required this .vision}) : super (key: key);
643
+
644
+ @override
645
+ State <YoloImageV8Seg > createState () => _YoloImageV8SegState ();
646
+ }
647
+
648
+ class _YoloImageV8SegState extends State <YoloImageV8Seg > {
649
+ late List <Map <String , dynamic >> yoloResults;
650
+ File ? imageFile;
651
+ int imageHeight = 1 ;
652
+ int imageWidth = 1 ;
653
+ bool isLoaded = false ;
654
+
655
+ @override
656
+ void initState () {
657
+ super .initState ();
658
+ loadYoloModel ().then ((value) {
659
+ setState (() {
660
+ yoloResults = [];
661
+ isLoaded = true ;
662
+ });
663
+ });
664
+ }
665
+
666
+ @override
667
+ void dispose () async {
668
+ super .dispose ();
669
+ }
670
+
671
+ @override
672
+ Widget build (BuildContext context) {
673
+ final Size size = MediaQuery .of (context).size;
674
+ if (! isLoaded) {
675
+ return const Scaffold (
676
+ body: Center (
677
+ child: Text ("Model not loaded, waiting for it" ),
678
+ ),
679
+ );
680
+ }
681
+ return Stack (
682
+ fit: StackFit .expand,
683
+ children: [
684
+ imageFile != null ? Image .file (imageFile! ) : const SizedBox (),
685
+ Align (
686
+ alignment: Alignment .bottomCenter,
687
+ child: Row (
688
+ mainAxisAlignment: MainAxisAlignment .center,
689
+ children: [
690
+ TextButton (
691
+ onPressed: pickImage,
692
+ child: const Text ("Pick image" ),
693
+ ),
694
+ ElevatedButton (
695
+ onPressed: yoloOnImage,
696
+ child: const Text ("Detect" ),
697
+ )
698
+ ],
699
+ ),
700
+ ),
701
+ ...displayBoxesAroundRecognizedObjects (size),
702
+ ],
703
+ );
704
+ }
705
+
706
+ Future <void > loadYoloModel () async {
707
+ await widget.vision.loadYoloModel (
708
+ labels: 'assets/labels.txt' ,
709
+ modelPath: 'assets/yolov8n-seg.tflite' ,
710
+ modelVersion: "yolov8seg" ,
711
+ quantization: false ,
712
+ numThreads: 2 ,
713
+ useGpu: true );
714
+ setState (() {
715
+ isLoaded = true ;
716
+ });
717
+ }
718
+
719
+ Future <void > pickImage () async {
720
+ final ImagePicker picker = ImagePicker ();
721
+ // Capture a photo
722
+ final XFile ? photo = await picker.pickImage (source: ImageSource .gallery);
723
+ if (photo != null ) {
724
+ setState (() {
725
+ imageFile = File (photo.path);
726
+ });
727
+ }
728
+ }
729
+
730
+ yoloOnImage () async {
731
+ yoloResults.clear ();
732
+ Uint8List byte = await imageFile! .readAsBytes ();
733
+ final image = await decodeImageFromList (byte);
734
+ imageHeight = image.height;
735
+ imageWidth = image.width;
736
+ final result = await widget.vision.yoloOnImage (
737
+ bytesList: byte,
738
+ imageHeight: image.height,
739
+ imageWidth: image.width,
740
+ iouThreshold: 0.8 ,
741
+ confThreshold: 0.4 ,
742
+ classThreshold: 0.5 );
743
+ if (result.isNotEmpty) {
744
+ setState (() {
745
+ yoloResults = result;
746
+ });
747
+ }
748
+ }
749
+
750
+ List <Widget > displayBoxesAroundRecognizedObjects (Size screen) {
751
+ if (yoloResults.isEmpty) return [];
752
+
753
+ double factorX = screen.width / (imageWidth);
754
+ double imgRatio = imageWidth / imageHeight;
755
+ double newWidth = imageWidth * factorX;
756
+ double newHeight = newWidth / imgRatio;
757
+ double factorY = newHeight / (imageHeight);
758
+
759
+ double pady = (screen.height - newHeight) / 2 ;
760
+
761
+ Color colorPick = const Color .fromARGB (255 , 50 , 233 , 30 );
762
+ return yoloResults.map ((result) {
763
+ return Stack (children: [
764
+ Positioned (
765
+ left: result["box" ][0 ] * factorX,
766
+ top: result["box" ][1 ] * factorY + pady,
767
+ width: (result["box" ][2 ] - result["box" ][0 ]) * factorX,
768
+ height: (result["box" ][3 ] - result["box" ][1 ]) * factorY,
769
+ child: Container (
770
+ decoration: BoxDecoration (
771
+ borderRadius: const BorderRadius .all (Radius .circular (10.0 )),
772
+ border: Border .all (color: Colors .pink, width: 2.0 ),
773
+ ),
774
+ child: Text (
775
+ "${result ['tag' ]} ${(result ['box' ][4 ] * 100 ).toStringAsFixed (0 )}%" ,
776
+ style: TextStyle (
777
+ background: Paint ()..color = colorPick,
778
+ color: Colors .white,
779
+ fontSize: 18.0 ,
780
+ ),
781
+ ),
782
+ ),
783
+ ),
784
+ Positioned (
785
+ left: result["box" ][0 ] * factorX,
786
+ top: result["box" ][1 ] * factorY + pady,
787
+ width: (result["box" ][2 ] - result["box" ][0 ]) * factorX,
788
+ height: (result["box" ][3 ] - result["box" ][1 ]) * factorY,
789
+ child: CustomPaint (
790
+ painter: PolygonPainter (
791
+ points: (result["polygons" ] as List <dynamic >).map ((e) {
792
+ Map <String , double > xy = Map <String , double >.from (e);
793
+ xy['x' ] = (xy['x' ] as double ) * factorX;
794
+ xy['y' ] = (xy['y' ] as double ) * factorY;
795
+ return xy;
796
+ }).toList ()),
797
+ )),
798
+ ]);
799
+ }).toList ();
800
+ }
801
+ }
802
+
803
+ class PolygonPainter extends CustomPainter {
804
+ final List <Map <String , double >> points;
805
+
806
+ PolygonPainter ({required this .points});
807
+
808
+ @override
809
+ void paint (Canvas canvas, Size size) {
810
+ final paint = Paint ()
811
+ ..color = const Color .fromARGB (129 , 255 , 2 , 124 )
812
+ ..strokeWidth = 2
813
+ ..style = PaintingStyle .fill;
814
+
815
+ final path = Path ();
816
+ if (points.isNotEmpty) {
817
+ path.moveTo (points[0 ]['x' ]! , points[0 ]['y' ]! );
818
+ for (var i = 1 ; i < points.length; i++ ) {
819
+ path.lineTo (points[i]['x' ]! , points[i]['y' ]! );
820
+ }
821
+ path.close ();
822
+ }
823
+
824
+ canvas.drawPath (path, paint);
825
+ }
826
+
827
+ @override
828
+ bool shouldRepaint (CustomPainter oldDelegate) {
829
+ return false ;
830
+ }
831
+ }
832
+
625
833
class TesseractImage extends StatefulWidget {
626
834
final FlutterVision vision;
627
835
const TesseractImage ({Key ? key, required this .vision}) : super (key: key);
0 commit comments