camera_process 1.0.1 camera_process: ^1.0.1 copied to clipboard
This plugin, Recognize face and text from photos or camera, through the use of MLKit Google.
import 'dart:io';
import 'dart:ui';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
// image_picker: ^0.7.2
import 'package:image_picker/image_picker.dart';
// camera: ^0.8.1
import 'package:camera/camera.dart';
import 'package:camera_process/camera_process.dart';
List<CameraDescription> cameras = [];
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Camera Process'),
centerTitle: true,
elevation: 0,
),
body: SafeArea(
child: Center(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
ListTile(
tileColor: Theme.of(context).primaryColor,
title: const Text(
'Face Detector',
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FaceDetectorView()));
},
),
ListTile(
tileColor: Theme.of(context).primaryColor,
title: const Text(
'Text Detector',
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TextDetectorView()));
},
),
],
),
),
),
),
),
);
}
}
class TextDetectorView extends StatefulWidget {
@override
_TextDetectorViewState createState() => _TextDetectorViewState();
}
class _TextDetectorViewState extends State<TextDetectorView> {
TextDetector textDetector = CameraProcess.vision.textDetector();
bool isBusy = false;
CustomPaint? customPaint;
@override
void dispose() async {
super.dispose();
await textDetector.close();
}
@override
Widget build(BuildContext context) {
return CameraView(
title: 'Text Detector',
customPaint: customPaint,
onImage: (inputImage) {
processImage(inputImage);
},
);
}
Future<void> processImage(InputImage inputImage) async {
if (isBusy) return;
isBusy = true;
final recognisedText = await textDetector.processImage(inputImage);
print('Found ${recognisedText.blocks.length} textBlocks');
if (inputImage.inputImageData?.size != null &&
inputImage.inputImageData?.imageRotation != null) {
final painter = TextDetectorPainter(
recognisedText,
inputImage.inputImageData!.size,
inputImage.inputImageData!.imageRotation);
customPaint = CustomPaint(painter: painter);
} else {
customPaint = null;
}
isBusy = false;
if (mounted) {
setState(() {});
}
}
}
class FaceDetectorView extends StatefulWidget {
@override
_FaceDetectorViewState createState() => _FaceDetectorViewState();
}
class _FaceDetectorViewState extends State<FaceDetectorView> {
FaceDetector faceDetector =
CameraProcess.vision.faceDetector(FaceDetectorOptions(
enableContours: true,
enableClassification: true,
));
bool isBusy = false;
CustomPaint? customPaint;
@override
void dispose() {
faceDetector.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CameraView(
title: 'Face Detector',
customPaint: customPaint,
onImage: (inputImage) {
processImage(inputImage);
},
initialDirection: CameraLensDirection.front,
);
}
Future<void> processImage(InputImage inputImage) async {
if (isBusy) return;
isBusy = true;
final faces = await faceDetector.processImage(inputImage);
print('Found ${faces.length} faces');
if (inputImage.inputImageData?.size != null &&
inputImage.inputImageData?.imageRotation != null) {
final painter = FaceDetectorPainter(
faces,
inputImage.inputImageData!.size,
inputImage.inputImageData!.imageRotation);
customPaint = CustomPaint(painter: painter);
} else {
customPaint = null;
}
isBusy = false;
if (mounted) {
setState(() {});
}
}
}
enum ScreenMode { liveFeed, gallery }
class CameraView extends StatefulWidget {
CameraView(
{Key? key,
required this.title,
required this.customPaint,
required this.onImage,
this.initialDirection = CameraLensDirection.back})
: super(key: key);
final String title;
final CustomPaint? customPaint;
final Function(InputImage inputImage) onImage;
final CameraLensDirection initialDirection;
@override
_CameraViewState createState() => _CameraViewState();
}
class _CameraViewState extends State<CameraView> {
ScreenMode _mode = ScreenMode.liveFeed;
CameraController? _controller;
File? _image;
ImagePicker? _imagePicker;
int _cameraIndex = 0;
@override
void initState() {
super.initState();
_imagePicker = ImagePicker();
for (var i = 0; i < cameras.length; i++) {
if (cameras[i].lensDirection == widget.initialDirection) {
_cameraIndex = i;
}
}
_startLiveFeed();
}
@override
void dispose() {
_stopLiveFeed();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: [
Padding(
padding: EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: _switchScreenMode,
child: Icon(
_mode == ScreenMode.liveFeed
? Icons.photo_library_outlined
: (Platform.isIOS
? Icons.camera_alt_outlined
: Icons.camera),
),
),
),
],
),
body: _body(),
floatingActionButton: _floatingActionButton(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
Widget? _floatingActionButton() {
if (_mode == ScreenMode.gallery) return null;
if (cameras.length == 1) return null;
return Container(
height: 70.0,
width: 70.0,
child: FloatingActionButton(
child: Icon(
Platform.isIOS
? Icons.flip_camera_ios_outlined
: Icons.flip_camera_android_outlined,
size: 40,
),
onPressed: _switchLiveCamera,
));
}
Widget _body() {
Widget body;
if (_mode == ScreenMode.liveFeed)
body = _liveFeedBody();
else
body = _galleryBody();
return body;
}
Widget _liveFeedBody() {
if (_controller?.value.isInitialized == false) {
return Container();
}
return Container(
color: Colors.black,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
CameraPreview(_controller!),
if (widget.customPaint != null) widget.customPaint!,
],
),
);
}
Widget _galleryBody() {
return ListView(shrinkWrap: true, children: [
_image != null
? Container(
height: 400,
width: 400,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.file(_image!),
if (widget.customPaint != null) widget.customPaint!,
],
),
)
: Icon(
Icons.image,
size: 200,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: ElevatedButton(
child: Text('From Gallery'),
onPressed: () => _getImage(ImageSource.gallery),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: ElevatedButton(
child: Text('Take a picture'),
onPressed: () => _getImage(ImageSource.camera),
),
),
]);
}
Future _getImage(ImageSource source) async {
final pickedFile = await _imagePicker?.getImage(source: source);
if (pickedFile != null) {
_processPickedFile(pickedFile);
} else {
print('No image selected.');
}
setState(() {});
}
void _switchScreenMode() async {
if (_mode == ScreenMode.liveFeed) {
_mode = ScreenMode.gallery;
await _stopLiveFeed();
} else {
_mode = ScreenMode.liveFeed;
await _startLiveFeed();
}
setState(() {});
}
Future _startLiveFeed() async {
final camera = cameras[_cameraIndex];
_controller = CameraController(
camera,
ResolutionPreset.low,
enableAudio: false,
);
_controller?.initialize().then((_) {
if (!mounted) {
return;
}
_controller?.startImageStream(_processCameraImage);
setState(() {});
});
}
Future _stopLiveFeed() async {
await _controller?.stopImageStream();
await _controller?.dispose();
_controller = null;
}
Future _switchLiveCamera() async {
if (_cameraIndex == 0)
_cameraIndex = 1;
else
_cameraIndex = 0;
await _stopLiveFeed();
await _startLiveFeed();
}
Future _processPickedFile(PickedFile pickedFile) async {
setState(() {
_image = File(pickedFile.path);
});
final inputImage = InputImage.fromFilePath(pickedFile.path);
widget.onImage(inputImage);
}
Future _processCameraImage(CameraImage image) async {
final WriteBuffer allBytes = WriteBuffer();
for (Plane plane in image.planes) {
allBytes.putUint8List(plane.bytes);
}
final bytes = allBytes.done().buffer.asUint8List();
final Size imageSize =
Size(image.width.toDouble(), image.height.toDouble());
final camera = cameras[_cameraIndex];
final imageRotation =
InputImageRotationMethods.fromRawValue(camera.sensorOrientation) ??
InputImageRotation.Rotation_0deg;
final inputImageFormat =
InputImageFormatMethods.fromRawValue(image.format.raw) ??
InputImageFormat.NV21;
final planeData = image.planes.map(
(Plane plane) {
return InputImagePlaneMetadata(
bytesPerRow: plane.bytesPerRow,
height: plane.height,
width: plane.width,
);
},
).toList();
final inputImageData = InputImageData(
size: imageSize,
imageRotation: imageRotation,
inputImageFormat: inputImageFormat,
planeData: planeData,
);
final inputImage =
InputImage.fromBytes(bytes: bytes, inputImageData: inputImageData);
widget.onImage(inputImage);
}
}
// -------------------------------------------------------------
class TextDetectorPainter extends CustomPainter {
TextDetectorPainter(
this.recognisedText, this.absoluteImageSize, this.rotation);
final RecognisedText recognisedText;
final Size absoluteImageSize;
final InputImageRotation rotation;
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3.0
..color = Colors.lightGreenAccent;
final Paint background = Paint()..color = Color(0x99000000);
for (final textBlock in recognisedText.blocks) {
final ParagraphBuilder builder = ParagraphBuilder(
ParagraphStyle(
textAlign: TextAlign.left,
fontSize: 16,
textDirection: TextDirection.ltr),
);
builder.pushStyle(
ui.TextStyle(color: Colors.lightGreenAccent, background: background));
builder.addText('${textBlock.text}');
builder.pop();
final left =
translateX(textBlock.rect.left, rotation, size, absoluteImageSize);
final top =
translateY(textBlock.rect.top, rotation, size, absoluteImageSize);
final right =
translateX(textBlock.rect.right, rotation, size, absoluteImageSize);
final bottom =
translateY(textBlock.rect.bottom, rotation, size, absoluteImageSize);
canvas.drawRect(
Rect.fromLTRB(left, top, right, bottom),
paint,
);
canvas.drawParagraph(
builder.build()
..layout(ParagraphConstraints(
width: right - left,
)),
Offset(left, top),
);
}
}
@override
bool shouldRepaint(TextDetectorPainter oldDelegate) {
return oldDelegate.recognisedText != recognisedText;
}
}
class FaceDetectorPainter extends CustomPainter {
FaceDetectorPainter(this.faces, this.absoluteImageSize, this.rotation);
final List<Face> faces;
final Size absoluteImageSize;
final InputImageRotation rotation;
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1.0
..color = Colors.red;
for (final Face face in faces) {
canvas.drawRect(
Rect.fromLTRB(
translateX(face.boundingBox.left, rotation, size, absoluteImageSize),
translateY(face.boundingBox.top, rotation, size, absoluteImageSize),
translateX(face.boundingBox.right, rotation, size, absoluteImageSize),
translateY(
face.boundingBox.bottom, rotation, size, absoluteImageSize),
),
paint,
);
void paintContour(FaceContourType type) {
final faceContour = face.getContour(type);
if (faceContour?.positionsList != null) {
for (Offset point in faceContour!.positionsList) {
canvas.drawCircle(
Offset(
translateX(point.dx, rotation, size, absoluteImageSize),
translateY(point.dy, rotation, size, absoluteImageSize),
),
1,
paint);
}
}
}
paintContour(FaceContourType.face);
paintContour(FaceContourType.leftEyebrowTop);
paintContour(FaceContourType.leftEyebrowBottom);
paintContour(FaceContourType.rightEyebrowTop);
paintContour(FaceContourType.rightEyebrowBottom);
paintContour(FaceContourType.leftEye);
paintContour(FaceContourType.rightEye);
paintContour(FaceContourType.upperLipTop);
paintContour(FaceContourType.upperLipBottom);
paintContour(FaceContourType.lowerLipTop);
paintContour(FaceContourType.lowerLipBottom);
paintContour(FaceContourType.noseBridge);
paintContour(FaceContourType.noseBottom);
paintContour(FaceContourType.leftCheek);
paintContour(FaceContourType.rightCheek);
}
}
@override
bool shouldRepaint(FaceDetectorPainter oldDelegate) {
return oldDelegate.absoluteImageSize != absoluteImageSize ||
oldDelegate.faces != faces;
}
}
double translateX(
double x, InputImageRotation rotation, Size size, Size absoluteImageSize) {
switch (rotation) {
case InputImageRotation.Rotation_90deg:
return x *
size.width /
(Platform.isIOS ? absoluteImageSize.width : absoluteImageSize.height);
case InputImageRotation.Rotation_270deg:
return size.width -
x *
size.width /
(Platform.isIOS
? absoluteImageSize.width
: absoluteImageSize.height);
default:
return x * size.width / absoluteImageSize.width;
}
}
double translateY(
double y, InputImageRotation rotation, Size size, Size absoluteImageSize) {
switch (rotation) {
case InputImageRotation.Rotation_90deg:
case InputImageRotation.Rotation_270deg:
return y *
size.height /
(Platform.isIOS ? absoluteImageSize.height : absoluteImageSize.width);
default:
return y * size.height / absoluteImageSize.height;
}
}