Flutter人体检测插件body_detection的使用

发布于 1周前 作者 ionicwang 来自 Flutter

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

1 回复

更多关于Flutter人体检测插件body_detection的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用body_detection插件来进行人体检测的示例代码。请注意,实际使用前需要确保你已经添加并配置好了body_detection插件。

首先,确保在你的pubspec.yaml文件中添加body_detection依赖:

dependencies:
  flutter:
    sdk: flutter
  body_detection: ^最新版本号  # 请替换为实际最新版本号

然后运行flutter pub get来安装依赖。

接下来,在你的Flutter项目中,你可以按照以下步骤使用body_detection插件进行人体检测。

示例代码

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:body_detection/body_detection.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BodyDetectionScreen(),
    );
  }
}

class BodyDetectionScreen extends StatefulWidget {
  @override
  _BodyDetectionScreenState createState() => _BodyDetectionScreenState();
}

class _BodyDetectionScreenState extends State<BodyDetectionScreen> {
  File? _imageFile;
  List<BodyDetectionResult>? _results;

  final ImagePicker _picker = ImagePicker();

  Future<void> _pickImage(ImageSource source) async {
    final pickedFile = await _picker.pickImage(source: source);

    if (pickedFile != null) {
      setState(() {
        _imageFile = File(pickedFile.path);
      });

      _detectBodies();
    }
  }

  Future<void> _detectBodies() async {
    if (_imageFile != null) {
      try {
        final result = await BodyDetection.detectBodies(imagePath: _imageFile!.path);
        setState(() {
          _results = result;
        });
      } catch (e) {
        print('Error detecting bodies: $e');
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Body Detection'),
      ),
      body: Column(
        children: <Widget>[
          Center(
            child: _imageFile == null
                ? Text('No image selected.')
                : Stack(
                    children: <Widget>[
                      Image.file(_imageFile!),
                      if (_results != null)
                        _results!.map((result) => Positioned(
                          left: result.boundingBox.left,
                          top: result.boundingBox.top,
                          width: result.boundingBox.width,
                          height: result.boundingBox.height,
                          child: Container(
                            color: Colors.red.withOpacity(0.5),
                            border: Border.all(color: Colors.red, width: 2),
                          ),
                        )).toList(),
                    ],
                  ),
          ),
          SizedBox(height: 20),
          ElevatedButton(
            onPressed: () => _pickImage(ImageSource.gallery),
            child: Text('Pick Image from Gallery'),
          ),
          SizedBox(height: 10),
          ElevatedButton(
            onPressed: () => _pickImage(ImageSource.camera),
            child: Text('Take Photo'),
          ),
        ],
      ),
    );
  }
}

说明

  1. 添加依赖:确保在pubspec.yaml文件中添加了body_detectionimage_picker依赖。
  2. 选择图片:使用image_picker插件从相册或相机中选择图片。
  3. 人体检测:使用BodyDetection.detectBodies方法对选择的图片进行人体检测。
  4. 显示结果:将检测到的人体位置绘制在图片上,用红色半透明框标记出来。

注意事项

  • 确保在实际项目中处理各种可能的异常情况,比如权限问题、文件不存在等。
  • 在发布应用前,请详细阅读body_detection插件的文档,了解所有可用配置和限制。
  • 人体检测功能可能会受到图片质量、光照条件、人体姿态等多种因素的影响,因此在实际应用中可能需要更多的优化和调整。

希望这个示例代码能帮助你快速上手Flutter中的人体检测功能!

回到顶部