Flutter人体检测插件body_detection的使用
Flutter人体检测插件body_detection的使用
概述
body_detection
是一个用于在iOS和Android平台上进行人体姿态和面具检测的Flutter插件。它使用了MLKit的姿势检测和自拍分割API来处理静态图像和实时相机流。
特性
- 人体姿态检测。
- 人体面具检测(自拍分割)。
- 可以在单个图像或实时相机流上运行。
- 相机图像采集和检测器在同一本机侧运行,比分别使用插件并通过它们传递数据更快。
- 使用MLKit进行检测,因此共享其优点和缺点。
安装
iOS
最低要求的iOS版本为10.0,如果编译的目标版本低于此版本,请确保在调用库函数之前检查iOS版本。
为了能够使用基于相机的检测,你需要向ios/Runner/Info.plist
文件添加一行(使用Xcode):
<key>NSCameraUsageDescription</key>
<string>Can I use the camera please?</string>
Android
将Android SDK的最小版本设置为21(或更高),在你的android/app/build.gradle
文件中:
android {
defaultConfig {
minSdkVersion 21
}
}
为了使用相机,需要在android/app/src/main/AndroidManifest.xml
文件中添加权限声明:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="YOUR_PACKAGE_NAME">
<uses-permission android:name="android.permission.CAMERA"/>
...
</manifest>
使用
单图像检测
最简单的用例是在单个图像上进行姿态或人体面具检测。插件接受PNG编码的图像,并将其发送到本机侧,在那里使用Google MLKit的Vision API进行处理。处理结果会返回给Flutter。
import 'package:body_detection/body_detection.dart';
import 'package:body_detection/png_image.dart';
PngImage pngImage = PngImage.from(bytes, width: width, height: height);
final pose = await BodyDetection.detectPose(image: pngImage);
final bodyMask = await BodyDetection.detectBodyMask(image: pngImage);
该插件提供了对flutter Image
小部件的扩展,可以将其转换为PNG格式,从而可以使用任何支持的图像源作为输入:本地资源、网络、内存等。
import 'package:body_detection/body_detection.dart';
import 'package:body_detection/png_image.dart';
import 'package:flutter/widgets.dart';
void detectImagePose(Image source) async {
PngImage? pngImage = await source.toPngImage();
if (pngImage == null) return;
final pose = await BodyDetection.detectPose(image: pngImage);
...
}
如果需要编码的PNG图像的大小(例如图像的宽高比)用于布局目的,可以从返回的PngImage
对象中获取:
import 'package:body_detection/png_image.dart';
import 'package:flutter/widgets.dart';
Image source;
PngImage? pngImage = await source.toPngImage();
final imageSize = pngImage != null
? Size(pngImage!.width.toDouble(), pngImage!.height.toDouble())
: Size.zero;
Pose
对象从BodyDetection.detectPose
调用返回时,包含由MLKit检测器返回的一系列地标。每个地标具有与MLKit建立的结构相同的结构:
final pose = await BodyDetection.detectPose(image: pngImage);
for (final landmark in pose!.landmarks) {
// 检测器估计地标在图像帧内的可能性。
double inFrameLikelihood = landmark.inFrameLikelihood;
// 在图像平面坐标系中的地标位置,z值由检测器估算。
Point3d position = landmark.position;
// 33个可检测的人体标志之一。
PoseLandmarkType type = landmark.type;
}
你可以使用自定义画家绘制姿态:
class PosePainter extends CustomPainter {
PosePainter({
required this.pose,
required this.imageSize,
});
final Pose pose;
final Size imageSize;
final circlePaint = Paint()..color = const Color.fromRGBO(0, 255, 0, 0.8);
final linePaint = Paint()
..color = const Color.fromRGBO(255, 0, 0, 0.8)
..strokeWidth = 2;
@override
void paint(Canvas canvas, Size size) {
final double hRatio =
imageSize.width == 0 ? 1 : size.width / imageSize.width;
final double vRatio =
imageSize.height == 0 ? 1 : size.height / imageSize.height;
offsetForPart(PoseLandmark part) => Offset(part.position.x * hRatio, part.position.y * vRatio);
for (final part in pose.landmarks) {
// 绘制圆形指示器表示地标。
canvas.drawCircle(offsetForPart(part), 5, circlePaint);
// 绘制地标文本标签。
TextSpan span = TextSpan(
text: part.type.toString().substring(16),
style: const TextStyle(
color: Color.fromRGBO(0, 128, 255, 1),
fontSize: 10,
),
);
TextPainter tp = TextPainter(text: span, textAlign: TextAlign.left);
tp.textDirection = TextDirection.ltr;
tp.layout();
tp.paint(canvas, offsetForPart(part));
}
// 绘制地标之间的连接线。
final landmarksByType = {for (final it in pose.landmarks) it.type: it};
for (final connection in connections) {
final point1 = offsetForPart(landmarksByType[connection[0]]!);
final point2 = offsetForPart(landmarksByType[connection[1]]!);
canvas.drawLine(point1, point2, linePaint);
}
}
...
}
然后在你的小部件树中使用它:
@override
Widget build(BuildContext context) {
// 使用ClipRect,以防止自定义画家绘制超出小部件区域。
return ClipRect(
child: CustomPaint(
child: _sourceImage,
foregroundPainter: PosePainter(
pose: _detectedPose,
imageSize: _imageSize,
),
),
);
}
对于详细信息,请参阅Google MLKit姿势检测API文档。
检测到的人体面具以双精度缓冲区的形式返回,长度为width * height
,表示特定像素覆盖区域的可能性。人体面具的大小可能与输入图像的大小不同,这使得计算更快,并且可以在较慢的设备上以可接受的速度实时运行。
要显示面具,可以使用缓冲区值作为alpha组件解码为dart图像:
final mask = await BodyDetection.detectBodyMask(image: pngImage);
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) {
// 对图像进行操作,例如将其设置为小部件的状态字段,以便在构建方法中传递给自定义画家进行绘制。
});
然后,例如,为了在背景上绘制半透明的蓝色覆盖层,可以有一个这样的自定义画家:
class MaskPainter extends CustomPainter {
MaskPainter({
required this.mask,
});
final ui.Image mask;
final maskPaint = Paint()
..colorFilter = const ColorFilter.mode(
Color.fromRGBO(0, 0, 255, 0.5), BlendMode.srcOut);
@override
void paint(Canvas canvas, Size size) {
canvas.drawImageRect(
mask,
Rect.fromLTWH(0, 0, mask.width.toDouble(), mask.height.toDouble()),
Rect.fromLTWH(0, 0, size.width, size.height),
maskPaint);
}
}
然后在你的小部件树中一起使用原始图像:
@override
Widget build(BuildContext context) {
return CustomPaint(
child: _sourceImage,
foregroundPainter: MaskPainter(mask: _maskImage),
);
}
对于详细信息,请参阅Google MLKit自拍分割API文档。
完整的示例可以在插件的仓库中查看。
实时相机流检测
除了单图像检测外,此插件还支持从相机流实时检测姿态和人体面具。相机图像采集和检测都在本机侧运行,因此我们消除了如果使用单独的flutter插件需要的数据序列化成本。
要启动和停止相机流,使用以下方法并传递回调,当相机帧或检测结果可用时:
await BodyDetection.startCameraStream(
onFrameAvailable: (ImageResult image) {},
onPoseAvailable: (Pose? pose) {},
onMaskAvailable: (BodyMask? mask) {},
);
await BodyDetection.stopCameraStream();
检测器默认禁用。要启用或禁用特定检测器,请使用以下方法:
await BodyDetection.enablePoseDetection();
await BodyDetection.disablePoseDetection();
await BodyDetection.enableBodyMaskDetection();
await BodyDetection.disableBodyMaskDetection();
你可以在开始相机流之前或流正在运行时执行这些操作。
要显示相机图像,可以创建一个从字节生成的Image小部件:
void _handleCameraImage(ImageResult result) {
// 如果导航离开页面,则忽略回调。
if (!mounted) return;
// 避免内存泄漏问题。
// 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;
});
}
更多关于Flutter人体检测插件body_detection的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html