Overview
Prerequisites
- Flutter installed.
- Basic knowledge of Flutter and Dart.
- An Android or iOS device for testing (emulators might not support camera features, the source code below is just tested on Android device).
1. Set Up Your Flutter Project
simple_object_detection_app:flutter create simple_object_detection_appdependencies:flutter:sdk: fluttercamera: ^0.10.5+9google_mlkit_object_detection: ^0.13.0google_mlkit_commonss: ^0.7.0
flutter pub get2. Configure Android Permissions
<usess-feature androidd:name="android.hardwaree.camera" android:required="false"/><uses-permission android:name="android.permission.CAMERA" /><uses-permission android:name="android.permission.INTERNET" />3. Create the UI in Flutter
import 'dart:io';
import 'package:flutter/material.dart';import 'package:camera/camera.dart';import 'package:google_mlkit_object_detection/google_mlkit_object_detection.dart';void main() async {WidgetsFlutterBinding.ensureInitialized();final cameraList = await availableCameras();runApp(SODAApp(camera: cameraList.first));}
class SODAApp extends StatelessWidget {final CameraDescription camera;const SODAApp({super.key, required this.camera});
@overrideWidget build(BuildContext context) {return MaterialApp(home: DetectionScreen(camera: camera),);}}
class DetectionScreen extends StatefulWidget {final CameraDescription camera;const DetectionScreen({super.key, required this.camera});@overrideDetectionScreenState createState() => DetectionScreenState();}
class DetectionScreenState extends State<DetectionScreen> {late CameraController _controller;late ObjectDetector _objectDetector;bool _isDetecting = false;List<DetectedObject> _detectedObjects = [];
@overridevoid initState() {super.initState();_initializeCamera();_initializeObjectDetector();}
void _initializeCamera() {_controller = CameraController(widget.camera, ResolutionPreset.medium,enableAudio: false,imageFormatGroup: Platform.isAndroid ? ImageFormatGroup.nv21 : ImageFormatGroup.bgra8888);_controller.initialize().then((_) {if (!mounted) return;setState(() {});_controller.startImageStream(_processCameraImage);});}void _initializeObjectDetector() {final options = ObjectDetectorOptions(mode: DetectionMode.stream,classifyObjects: true,multipleObjects: true
);_objectDetector = ObjectDetector(options: options);}
void _processCameraImage(CameraImage image) async {if (_isDetecting ) return;_isDetecting = true;
final inputImage = _convertToInputImage(image);final objects = await _objectDetector.processImage(inputImage);setState(() {_detectedObjects = objects;});_isDetecting = false;}
InputImage _convertToInputImage(CameraImage image) {var sensorOrientation = widget.camera.sensorOrientation;InputImageRotation? rotation;if (Platform.isIOS) {rotation = InputImageRotationValue.fromRawValue(sensorOrientation);} else if (Platform.isAndroid) {var rotationCompensation = 0;if (widget.camera.lensDirection == CameraLensDirection.front) {rotationCompensation = (sensorOrientation + rotationCompensation) % 360;} else {rotationCompensation =(sensorOrientation - rotationCompensation + 360) % 360;}rotation = InputImageRotationValue.fromRawValue(rotationCompensation);}final format = InputImageFormatValue.fromRawValue(image.format.raw) ??InputImageFormat.nv21;final plane = image.planes.first;return InputImage.fromBytes(bytes: plane.bytes,metadata: InputImageMetadata(size: Size(image.width.toDouble(), image.height.toDouble()),rotation: rotation!,format: format,bytesPerRow: plane.bytesPerRow,),);}
@overridevoid dispose() {_controller.dispose();_objectDetector.close();super.dispose();}
@overrideWidget build(BuildContext context) {if (!_controller.value.isInitialized) {return Scaffold(appBar: AppBar(title: const Text('Object Detection')),body: const Center(child: CircularProgressIndicator()),);}
return Scaffold(appBar: AppBar(title: const Text('Object Detection')),body: Stack(children: [CameraPreview(_controller),_buildBoundingBoxes(),],),);}Widget _buildBoundingBoxes() {return CustomPaint(painter: BoxPainter(objects: _detectedObjects),);}}
class BoxPainter extends CustomPainter {final List<DetectedObject> objects;BoxPainter({required this.objects});@overridevoid paint(Canvas canvas, Size size) {final paint = Paint()..color = Colors.red..style = PaintingStyle.stroke..strokeWidth = 2.0;for (var object in objects) {final rect = object.boundingBox;canvas.drawRect(Rect.fromLTRB(rect.left,rect.top,rect.right,rect.bottom,),paint,);TextStyle textStyle = const TextStyle(color: Colors.purpleAccent,fontSize: 16,fontWeight: FontWeight.bold,);TextSpan textSpan = TextSpan(text: object.labels.isEmpty ? 'No name':object.labels.first.text,style: textStyle,);TextPainter textPainter = TextPainter(text: textSpan,textDirection: TextDirection.ltr,textAlign: TextAlign.center,);textPainter.layout();double dx = rect.left + (rect.width - textPainter.width) / 2;double dy = rect.top + (rect.height - textPainter.height) / 2;Offset offset = Offset(dx, dy);textPainter.paint(canvas, offset);}}@overridebool shouldRepaint(covariant CustomPainter oldDelegate) => true;}Explanation
_objectDetector.processImage(inputImage)
Testing the App
flutter run
Troubleshooting
ResolutionPreset.low.Conclusion
(Feature image is come from Freepik)