body_detection 0.0.3 body_detection: ^0.0.3 copied to clipboard
A flutter plugin that uses MLKit on iOS/Android platforms to enable body pose and mask detection using Pose Detection and Selfie Segmentation APIs for both static images and live camera stream.
import 'dart:io';
import 'dart:typed_data';
import 'package:body_detection/models/image_result.dart';
import 'package:body_detection/models/pose.dart';
import 'package:body_detection/models/body_mask.dart';
import 'package:body_detection/png_image.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'package:body_detection/body_detection.dart';
import 'package:permission_handler/permission_handler.dart';
import 'pose_mask_painter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _selectedTabIndex = 0;
bool _isDetectingPose = false;
bool _isDetectingBodyMask = false;
Image? _selectedImage;
Pose? _detectedPose;
ui.Image? _maskImage;
Image? _cameraImage;
Size _imageSize = Size.zero;
Future<void> _selectImage() async {
FilePickerResult? result =
await FilePicker.platform.pickFiles(type: FileType.image);
if (result == null || result.files.isEmpty) return;
final path = result.files.single.path;
if (path != null) {
_resetState();
setState(() {
_selectedImage = Image.file(File(path));
});
}
}
Future<void> _detectImagePose() async {
PngImage? pngImage = await _selectedImage?.toPngImage();
if (pngImage == null) return;
setState(() {
_imageSize = Size(pngImage.width.toDouble(), pngImage.height.toDouble());
});
final pose = await BodyDetection.detectPose(image: pngImage);
_handlePose(pose);
}
Future<void> _detectImageBodyMask() async {
PngImage? pngImage = await _selectedImage?.toPngImage();
if (pngImage == null) return;
setState(() {
_imageSize = Size(pngImage.width.toDouble(), pngImage.height.toDouble());
});
final mask = await BodyDetection.detectBodyMask(image: pngImage);
_handleBodyMask(mask);
}
Future<void> _startCameraStream() async {
final request = await Permission.camera.request();
if (request.isGranted) {
await BodyDetection.startCameraStream(
onFrameAvailable: _handleCameraImage,
onPoseAvailable: (pose) {
if (!_isDetectingPose) return;
_handlePose(pose);
},
onMaskAvailable: (mask) {
if (!_isDetectingBodyMask) return;
_handleBodyMask(mask);
},
);
}
}
Future<void> _stopCameraStream() async {
await BodyDetection.stopCameraStream();
setState(() {
_cameraImage = null;
_imageSize = Size.zero;
});
}
void _handleCameraImage(ImageResult result) {
// Ignore callback if navigated out of the page.
if (!mounted) return;
// To avoid a memory leak issue.
// https://github.com/flutter/flutter/issues/60160
PaintingBinding.instance?.imageCache?.clear();
PaintingBinding.instance?.imageCache?.clearLiveImages();
final image = Image.memory(
result.bytes,
gaplessPlayback: true,
fit: BoxFit.contain,
);
setState(() {
_cameraImage = image;
_imageSize = result.size;
});
}
void _handlePose(Pose? pose) {
// Ignore if navigated out of the page.
if (!mounted) return;
setState(() {
_detectedPose = pose;
});
}
void _handleBodyMask(BodyMask? mask) {
// Ignore if navigated out of the page.
if (!mounted) return;
if (mask == null) {
setState(() {
_maskImage = null;
});
return;
}
final bytes = mask.buffer
.expand(
(it) => [0, 0, 0, (it * 255).toInt()],
)
.toList();
ui.decodeImageFromPixels(Uint8List.fromList(bytes), mask.width, mask.height,
ui.PixelFormat.rgba8888, (image) {
setState(() {
_maskImage = image;
});
});
}
Future<void> _toggleDetectPose() async {
if (_isDetectingPose) {
await BodyDetection.disablePoseDetection();
} else {
await BodyDetection.enablePoseDetection();
}
setState(() {
_isDetectingPose = !_isDetectingPose;
_detectedPose = null;
});
}
Future<void> _toggleDetectBodyMask() async {
if (_isDetectingBodyMask) {
await BodyDetection.disableBodyMaskDetection();
} else {
await BodyDetection.enableBodyMaskDetection();
}
setState(() {
_isDetectingBodyMask = !_isDetectingBodyMask;
_maskImage = null;
});
}
void _onTabEnter(int index) {
// Camera tab
if (index == 1) {
_startCameraStream();
}
}
void _onTabExit(int index) {
// Camera tab
if (index == 1) {
_stopCameraStream();
}
}
void _onTabSelectTapped(int index) {
_onTabExit(_selectedTabIndex);
_onTabEnter(index);
setState(() {
_selectedTabIndex = index;
});
}
Widget? get _selectedTab => _selectedTabIndex == 0
? _imageDetectionView
: _selectedTabIndex == 1
? _cameraDetectionView
: null;
void _resetState() {
setState(() {
_maskImage = null;
_detectedPose = null;
_imageSize = Size.zero;
});
}
Widget get _imageDetectionView => SingleChildScrollView(
child: Center(
child: Column(
children: [
GestureDetector(
child: ClipRect(
child: CustomPaint(
child: _selectedImage,
foregroundPainter: PoseMaskPainter(
pose: _detectedPose,
mask: _maskImage,
imageSize: _imageSize,
),
),
),
),
OutlinedButton(
onPressed: _selectImage,
child: const Text('Select image'),
),
OutlinedButton(
onPressed: _detectImagePose,
child: const Text('Detect pose'),
),
OutlinedButton(
onPressed: _detectImageBodyMask,
child: const Text('Detect body mask'),
),
OutlinedButton(
onPressed: _resetState,
child: const Text('Clear'),
),
],
),
),
);
Widget get _cameraDetectionView => SingleChildScrollView(
child: Center(
child: Column(
children: [
ClipRect(
child: CustomPaint(
child: _cameraImage,
foregroundPainter: PoseMaskPainter(
pose: _detectedPose,
mask: _maskImage,
imageSize: _imageSize,
),
),
),
OutlinedButton(
onPressed: _toggleDetectPose,
child: _isDetectingPose
? const Text('Turn off pose detection')
: const Text('Turn on pose detection'),
),
OutlinedButton(
onPressed: _toggleDetectBodyMask,
child: _isDetectingBodyMask
? const Text('Turn off body mask detection')
: const Text('Turn on body mask detection'),
),
],
),
),
);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Body Detection Plugin Example App'),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.image),
label: 'Image',
),
BottomNavigationBarItem(
icon: Icon(Icons.camera),
label: 'Camera',
),
],
currentIndex: _selectedTabIndex,
onTap: _onTabSelectTapped,
),
body: _selectedTab,
),
);
}
}