Skip to content

Commit 72a198f

Browse files
committed
optimize detection service
1 parent e7a7cc8 commit 72a198f

File tree

7 files changed

+163
-78
lines changed

7 files changed

+163
-78
lines changed

lib/camera_controller.dart

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import 'package:camera_widget/face_detection_service.dart';
12
import 'package:flutter/foundation.dart';
2-
import 'package:flutter/material.dart';
33
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
44
import 'package:permission_handler/permission_handler.dart';
55
import 'package:camera/camera.dart';
@@ -9,6 +9,10 @@ import 'permission_manager.dart';
99
class CameraPageController extends ChangeNotifier {
1010
final PermissionManager permissionManager;
1111
final CameraService cameraService;
12+
final FaceDetectionService faceDetectionService;
13+
14+
bool _isLoading = false;
15+
bool get isLoading => _isLoading;
1216

1317
bool _isInitialized = false;
1418
bool get isInitialized => _isInitialized;
@@ -18,7 +22,6 @@ class CameraPageController extends ChangeNotifier {
1822

1923
CameraController? get cameraController => cameraService.cameraController;
2024

21-
DateTime _lastDetectionTime = DateTime.now();
2225
final Duration detectionInterval = const Duration(milliseconds: 200);
2326

2427
final options = FaceDetectorOptions(
@@ -29,20 +32,19 @@ class CameraPageController extends ChangeNotifier {
2932
minFaceSize: 0.15,
3033
);
3134

32-
List<Face> _faces = [];
33-
List<Face> get faces => _faces;
34-
35-
late FaceDetector _faceDetector;
35+
List<Face> get faces => faceDetectionService.faces;
3636

3737
CameraPageController({
3838
required this.permissionManager,
3939
required this.cameraService,
40-
}) {
41-
_faceDetector = FaceDetector(options: options);
42-
}
40+
required this.faceDetectionService,
41+
});
4342

4443
Future<bool> initialize() async {
4544
try {
45+
_isLoading = true;
46+
notifyListeners();
47+
4648
if (cameraService.cameraController != null) {
4749
await cameraService.cameraController?.dispose();
4850
cameraService.resetCameraController();
@@ -57,17 +59,20 @@ class CameraPageController extends ChangeNotifier {
5759

5860
if (!granted) {
5961
_isInitialized = false;
62+
_isLoading = false;
6063
notifyListeners();
6164
return false;
6265
}
6366

6467
await cameraWithMLKit();
68+
_isLoading = false;
6569
notifyListeners();
6670
return _isInitialized;
6771
} catch (e) {
6872
debugPrint('Error initializing camera controller: $e');
6973
_isInitialized = false;
7074
_isCameraGranted = false;
75+
_isLoading = false;
7176
notifyListeners();
7277
return false;
7378
}
@@ -82,15 +87,21 @@ class CameraPageController extends ChangeNotifier {
8287
);
8388

8489
if (_isCameraGranted) {
90+
_isLoading = true;
91+
notifyListeners();
8592
await cameraWithMLKit();
93+
_isLoading = false;
94+
notifyListeners();
8695
} else {
8796
_isInitialized = false;
97+
_isLoading = false;
98+
notifyListeners();
8899
}
89-
notifyListeners();
90100
} catch (e) {
91101
debugPrint('Error checking permission and initializing: $e');
92102
_isInitialized = false;
93103
_isCameraGranted = false;
104+
_isLoading = false;
94105
notifyListeners();
95106
}
96107
}
@@ -99,34 +110,16 @@ class CameraPageController extends ChangeNotifier {
99110
try {
100111
_isInitialized = await cameraService.initialize(
101112
onFrameAvailable: (inputImage) async {
102-
final now = DateTime.now();
103-
if (now.difference(_lastDetectionTime) < detectionInterval) {
104-
return;
105-
}
106-
_lastDetectionTime = now;
107-
108-
final newFaces = await _faceDetector.processImage(inputImage);
109-
setFaces(newFaces);
110-
111-
for (Face face in _faces) {
112-
debugPrint('Face detected: ${face.boundingBox}');
113-
debugPrint('Smiling probability: ${face.smilingProbability}');
114-
debugPrint('Tracking ID: ${face.trackingId}');
115-
}
113+
await faceDetectionService.processImage(inputImage);
114+
_isLoading = false;
115+
notifyListeners();
116116
},
117117
);
118118
debugPrint('cameraWithMLKit - isInitialized: $_isInitialized');
119119
} catch (e) {
120120
debugPrint('Error in cameraWithMLKit: $e');
121121
_isInitialized = false;
122-
notifyListeners();
123-
}
124-
}
125-
126-
void setFaces(List<Face> newFaces) {
127-
final hasChanged = !listEquals(_faces, newFaces);
128-
if (hasChanged) {
129-
_faces = newFaces;
122+
_isLoading = false;
130123
notifyListeners();
131124
}
132125
}
@@ -156,7 +149,7 @@ class CameraPageController extends ChangeNotifier {
156149
@override
157150
void dispose() {
158151
cameraService.dispose();
159-
_faceDetector.close();
152+
faceDetectionService.dispose();
160153
_isInitialized = false;
161154
super.dispose();
162155
}

lib/camera_page.dart

Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ class _CameraPageState extends State<CameraPage> with WidgetsBindingObserver {
2323
void initState() {
2424
super.initState();
2525
WidgetsBinding.instance.addObserver(this);
26-
controller.initialize();
26+
WidgetsBinding.instance.addPostFrameCallback((_) {
27+
controller.initialize();
28+
});
2729
}
2830

2931
@override
@@ -52,46 +54,13 @@ class _CameraPageState extends State<CameraPage> with WidgetsBindingObserver {
5254
duration: const Duration(milliseconds: 300),
5355
child:
5456
!kIsWeb && (Platform.isAndroid || Platform.isIOS)
55-
? !controller.isCameraGranted
57+
? !controller.isCameraGranted && !controller.isLoading
5658
? const CameraPermissionPlaceholder()
57-
: !controller.isInitialized ||
59+
: controller.isLoading ||
60+
!controller.isInitialized ||
5861
controller.cameraController == null
59-
? const Center(child: CircularProgressIndicator())
60-
: LayoutBuilder(
61-
builder: (context, constraints) {
62-
final cameraController =
63-
controller.cameraController!;
64-
final cameraSize =
65-
cameraController.value.previewSize;
66-
if (cameraSize == null) {
67-
return const SizedBox();
68-
}
69-
70-
return Stack(
71-
children: [
72-
RepaintBoundary(
73-
child: CameraPreviewWidget(
74-
cameraController: cameraController,
75-
),
76-
),
77-
RepaintBoundary(
78-
child: FacePainterWidget(
79-
faces: controller.faces,
80-
imageSize: cameraSize,
81-
widgetSize: Size(
82-
constraints.maxWidth,
83-
constraints.maxHeight,
84-
),
85-
lensDirection:
86-
cameraController
87-
.description
88-
.lensDirection,
89-
),
90-
),
91-
],
92-
);
93-
},
94-
)
62+
? Container(color: Colors.black)
63+
: _buildCameraPreview()
9564
: const Center(
9665
child: Text("Camera not supported on this platform"),
9766
),
@@ -100,4 +69,32 @@ class _CameraPageState extends State<CameraPage> with WidgetsBindingObserver {
10069
},
10170
);
10271
}
72+
73+
Widget _buildCameraPreview() {
74+
return LayoutBuilder(
75+
builder: (context, constraints) {
76+
final cameraController = controller.cameraController!;
77+
final cameraSize = cameraController.value.previewSize;
78+
if (cameraSize == null) {
79+
return const SizedBox();
80+
}
81+
82+
return Stack(
83+
children: [
84+
RepaintBoundary(
85+
child: CameraPreviewWidget(cameraController: cameraController),
86+
),
87+
RepaintBoundary(
88+
child: FacePainterWidget(
89+
faces: controller.faces,
90+
imageSize: cameraSize,
91+
widgetSize: Size(constraints.maxWidth, constraints.maxHeight),
92+
lensDirection: cameraController.description.lensDirection,
93+
),
94+
),
95+
],
96+
);
97+
},
98+
);
99+
}
103100
}

lib/face_detection_service.dart

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:flutter/services.dart';
3+
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
4+
5+
class FaceDetectionService {
6+
late FaceDetector _faceDetector;
7+
List<Face> _faces = [];
8+
DateTime _lastDetectionTime = DateTime.now();
9+
final Duration detectionInterval = const Duration(milliseconds: 200);
10+
11+
List<Face> get faces => _faces;
12+
13+
FaceDetectionService() {
14+
_faceDetector = FaceDetector(
15+
options: FaceDetectorOptions(
16+
enableClassification: false,
17+
enableTracking: true,
18+
enableLandmarks: false,
19+
performanceMode: FaceDetectorMode.fast,
20+
minFaceSize: 0.15,
21+
),
22+
);
23+
}
24+
25+
Future<void> processImage(InputImage inputImage) async {
26+
if (inputImage.bytes == null && inputImage.filePath == null) {
27+
debugPrint('Invalid InputImage: No bytes or file path provided');
28+
return;
29+
}
30+
31+
final now = DateTime.now();
32+
33+
if (now.difference(_lastDetectionTime) < detectionInterval) {
34+
return;
35+
}
36+
_lastDetectionTime = now;
37+
38+
try {
39+
final newFaces = await _faceDetector.processImage(inputImage);
40+
41+
if (_faces.isNotEmpty &&
42+
newFaces.isNotEmpty &&
43+
_faces.length == newFaces.length) {
44+
bool shouldUpdate = false;
45+
46+
for (var i = 0; i < newFaces.length && i < _faces.length; i++) {
47+
final oldFace = _faces[i];
48+
final newFace = newFaces[i];
49+
50+
if (oldFace.trackingId != newFace.trackingId ||
51+
!isFacePositionSimilar(oldFace, newFace)) {
52+
shouldUpdate = true;
53+
break;
54+
}
55+
}
56+
57+
if (!shouldUpdate) {
58+
return;
59+
}
60+
}
61+
62+
_faces = newFaces;
63+
if (_faces.isEmpty) {
64+
debugPrint('No faces detected');
65+
} else {
66+
for (Face face in _faces) {
67+
debugPrint('Face detected: ${face.boundingBox}');
68+
debugPrint('Smiling probability: ${face.smilingProbability}');
69+
debugPrint('Tracking ID: ${face.trackingId}');
70+
}
71+
}
72+
} catch (e) {
73+
debugPrint('Error processing image: $e');
74+
}
75+
}
76+
77+
bool isFacePositionSimilar(Face oldFace, Face newFace) {
78+
final oldBox = oldFace.boundingBox;
79+
final newBox = newFace.boundingBox;
80+
81+
final oldCenter = Offset(
82+
oldBox.left + oldBox.width / 2,
83+
oldBox.top + oldBox.height / 2,
84+
);
85+
86+
final newCenter = Offset(
87+
newBox.left + newBox.width / 2,
88+
newBox.top + newBox.height / 2,
89+
);
90+
91+
final distance = (oldCenter - newCenter).distance;
92+
93+
return distance < 10.0;
94+
}
95+
96+
void dispose() {
97+
_faceDetector.close();
98+
}
99+
}

lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:camera_widget/camera_service.dart';
2+
import 'package:camera_widget/face_detection_service.dart';
23
import 'package:camera_widget/permission_manager.dart';
34
import 'package:flutter/material.dart';
45
import 'package:flutter/rendering.dart';
@@ -27,6 +28,7 @@ class MainApp extends StatelessWidget {
2728
(_) => CameraPageController(
2829
permissionManager: PermissionManager(),
2930
cameraService: CameraService(),
31+
faceDetectionService: FaceDetectionService(),
3032
),
3133
child: const Scaffold(body: CameraPage()),
3234
),

lib/mlkit_helper.dart

Lines changed: 0 additions & 6 deletions
This file was deleted.

lib/settings.dart

Whitespace-only changes.

lib/settings.widget.dart

Whitespace-only changes.

0 commit comments

Comments
 (0)