178 lines
4.7 KiB
Dart
178 lines
4.7 KiB
Dart
|
|
import 'smile_painter.dart';
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:camera/camera.dart';
|
||
|
|
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
|
||
|
|
import 'package:flutter/foundation.dart';
|
||
|
|
import 'dart:ui' as ui show Image;
|
||
|
|
import 'utils.dart';
|
||
|
|
|
||
|
|
class FaceDetectionFromLiveCamera extends StatefulWidget {
|
||
|
|
FaceDetectionFromLiveCamera({Key key}) : super(key: key);
|
||
|
|
|
||
|
|
@override
|
||
|
|
_FaceDetectionFromLiveCameraState createState() =>
|
||
|
|
_FaceDetectionFromLiveCameraState();
|
||
|
|
}
|
||
|
|
|
||
|
|
class _FaceDetectionFromLiveCameraState
|
||
|
|
extends State<FaceDetectionFromLiveCamera> {
|
||
|
|
final FaceDetector faceDetector = FirebaseVision.instance.faceDetector();
|
||
|
|
List<Face> faces;
|
||
|
|
CameraController _camera;
|
||
|
|
|
||
|
|
bool _isDetecting = false;
|
||
|
|
CameraLensDirection _direction = CameraLensDirection.back;
|
||
|
|
|
||
|
|
@override
|
||
|
|
void initState() {
|
||
|
|
super.initState();
|
||
|
|
_initializeCamera();
|
||
|
|
}
|
||
|
|
|
||
|
|
void _initializeCamera() async {
|
||
|
|
CameraDescription description = await getCamera(_direction);
|
||
|
|
ImageRotation rotation = rotationIntToImageRotation(
|
||
|
|
description.sensorOrientation,
|
||
|
|
);
|
||
|
|
|
||
|
|
_camera = CameraController(
|
||
|
|
description,
|
||
|
|
defaultTargetPlatform == TargetPlatform.iOS
|
||
|
|
? ResolutionPreset.low
|
||
|
|
: ResolutionPreset.medium,
|
||
|
|
);
|
||
|
|
await _camera.initialize();
|
||
|
|
|
||
|
|
_camera.startImageStream((CameraImage image) {
|
||
|
|
if (_isDetecting) return;
|
||
|
|
|
||
|
|
_isDetecting = true;
|
||
|
|
|
||
|
|
detect(
|
||
|
|
image,
|
||
|
|
FirebaseVision.instance
|
||
|
|
.faceDetector(FaceDetectorOptions(
|
||
|
|
mode: FaceDetectorMode.accurate,
|
||
|
|
enableClassification: true))
|
||
|
|
.processImage,
|
||
|
|
rotation)
|
||
|
|
.then(
|
||
|
|
(dynamic result) {
|
||
|
|
setState(() {
|
||
|
|
faces = result;
|
||
|
|
});
|
||
|
|
|
||
|
|
_isDetecting = false;
|
||
|
|
},
|
||
|
|
).catchError(
|
||
|
|
(_) {
|
||
|
|
_isDetecting = false;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildResults() {
|
||
|
|
const Text noResultsText = const Text('No results!');
|
||
|
|
const Text multipleFaceText = const Text('Multiple faces!');
|
||
|
|
const Text pleaseSmileText = const Text('Please smile!');
|
||
|
|
|
||
|
|
if (faces == null || _camera == null || !_camera.value.isInitialized) {
|
||
|
|
return noResultsText;
|
||
|
|
}
|
||
|
|
|
||
|
|
CustomPainter painter;
|
||
|
|
|
||
|
|
final Size imageSize = Size(
|
||
|
|
_camera.value.previewSize.height,
|
||
|
|
_camera.value.previewSize.width,
|
||
|
|
);
|
||
|
|
|
||
|
|
if (faces is! List<Face> ||
|
||
|
|
faces.isEmpty ||
|
||
|
|
faces == null ||
|
||
|
|
faces.length == 0) return noResultsText;
|
||
|
|
if (faces.length > 1) return multipleFaceText;
|
||
|
|
var face = faces[0];
|
||
|
|
if (face.smilingProbability == null || face.smilingProbability < 0.8) {
|
||
|
|
return pleaseSmileText;
|
||
|
|
}
|
||
|
|
|
||
|
|
painter = SmilePainterLiveCamera(imageSize, faces);
|
||
|
|
return CustomPaint(
|
||
|
|
painter: painter,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildImage() {
|
||
|
|
return Container(
|
||
|
|
constraints: const BoxConstraints.expand(),
|
||
|
|
child: _camera == null
|
||
|
|
? const Center(
|
||
|
|
child: Text(
|
||
|
|
'Initializing Camera...',
|
||
|
|
style: TextStyle(
|
||
|
|
color: Colors.green,
|
||
|
|
fontSize: 30.0,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
: Stack(
|
||
|
|
fit: StackFit.expand,
|
||
|
|
children: <Widget>[
|
||
|
|
CameraPreview(_camera),
|
||
|
|
_buildResults(),
|
||
|
|
Positioned(
|
||
|
|
bottom: 0.0,
|
||
|
|
left: 0.0,
|
||
|
|
right: 0.0,
|
||
|
|
child: Container(
|
||
|
|
color: Colors.white,
|
||
|
|
height: 50.0,
|
||
|
|
child: ListView(
|
||
|
|
children: faces
|
||
|
|
.map((face) => Text(
|
||
|
|
"${face.boundingBox.center.toString()}, Smile:${face.smilingProbability}"))
|
||
|
|
.toList(),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
void _toggleCameraDirection() async {
|
||
|
|
if (_direction == CameraLensDirection.back) {
|
||
|
|
_direction = CameraLensDirection.front;
|
||
|
|
} else {
|
||
|
|
_direction = CameraLensDirection.back;
|
||
|
|
}
|
||
|
|
|
||
|
|
await _camera.stopImageStream();
|
||
|
|
await _camera.dispose();
|
||
|
|
|
||
|
|
setState(() {
|
||
|
|
_camera = null;
|
||
|
|
});
|
||
|
|
|
||
|
|
_initializeCamera();
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return Scaffold(
|
||
|
|
appBar: AppBar(
|
||
|
|
title: Text("Face Detection with Smile"),
|
||
|
|
),
|
||
|
|
body: _buildImage(),
|
||
|
|
floatingActionButton: FloatingActionButton(
|
||
|
|
onPressed: _toggleCameraDirection,
|
||
|
|
child: _direction == CameraLensDirection.back
|
||
|
|
? const Icon(Icons.camera_front)
|
||
|
|
: const Icon(Icons.camera_rear),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|