如何在Flutter中使用Google ML Kit显示人脸轮廓
人脸检测后绘制面部轮廓的实现方案
问题描述
已完成人脸检测功能,但当前代码仅能绘制人脸的 bounding box,需要实现**面部轮廓(如五官轮廓、脸部轮廓)**的绘制,现有代码如下:
class FacePainter extends CustomPainter { ui.Image image; List<Face> faces; List<Rect> rects = []; FacePainter(this.image, this.faces) { for (var i = 0; i < faces.length; i++) { rects.add(faces[i].boundingBox); } print("object"); } @override void paint(ui.Canvas canvas, ui.Size size) { final Paint paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 2.0 ..color = Colors.blue; canvas.drawImage(image, Offset.zero, paint); for (var i = 0; i < faces.length; i++) { canvas.drawRect(rects[i], paint); } } @override bool shouldRepaint(FacePainter oldDelegate) { return image != oldDelegate.image || image != oldDelegate.faces; } }
实现思路
人脸检测库返回的Face对象包含FaceLandmark属性,存储了面部关键特征点(如下巴、脸颊、额头、眼睛、鼻子等)。通过连接这些特征点,就能绘制出面部轮廓。
修改后的代码
import 'package:flutter/material.dart'; import 'dart:ui' as ui; import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; class FacePainter extends CustomPainter { final ui.Image image; final List<Face> faces; final Paint _outlinePaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 2.0 ..color = Colors.red; FacePainter(this.image, this.faces); @override void paint(ui.Canvas canvas, ui.Size size) { // 绘制原图 canvas.drawImage(image, Offset.zero, Paint()); for (var face in faces) { // 1. 绘制脸部轮廓(连接下巴、脸颊、额头特征点) final faceContourPoints = <Offset>[]; faceContourPoints.add(_convertOffset(face.landmarks[FaceLandmarkType.chin]?.position, face.boundingBox)); faceContourPoints.add(_convertOffset(face.landmarks[FaceLandmarkType.leftCheek]?.position, face.boundingBox)); faceContourPoints.add(_convertOffset(face.landmarks[FaceLandmarkType.leftForehead]?.position, face.boundingBox)); faceContourPoints.add(_convertOffset(face.landmarks[FaceLandmarkType.rightForehead]?.position, face.boundingBox)); faceContourPoints.add(_convertOffset(face.landmarks[FaceLandmarkType.rightCheek]?.position, face.boundingBox)); faceContourPoints.add(_convertOffset(face.landmarks[FaceLandmarkType.chin]?.position, face.boundingBox)); if (faceContourPoints.every((point) => point != null)) { final path = Path()..addPolygon(faceContourPoints.cast<Offset>(), false); canvas.drawPath(path, _outlinePaint); } // 2. 绘制眼睛轮廓(示例:左眼) final leftEye = face.landmarks[FaceLandmarkType.leftEye]?.position; if (leftEye != null) { canvas.drawCircle(Offset(leftEye.x.toDouble(), leftEye.y.toDouble()), 8.0, _outlinePaint); } // 3. 绘制鼻子轮廓 final noseBase = face.landmarks[FaceLandmarkType.noseBase]?.position; final noseBridge = face.landmarks[FaceLandmarkType.noseBridge]?.position; if (noseBase != null && noseBridge != null) { final path = Path() ..moveTo(noseBase.x.toDouble(), noseBase.y.toDouble()) ..lineTo(noseBridge.x.toDouble(), noseBridge.y.toDouble()); canvas.drawPath(path, _outlinePaint); } } } // 转换特征点坐标到画布坐标系 Offset? _convertOffset(Point<int>? point, Rect boundingBox) { if (point == null) return null; // 若画布与原图尺寸不一致,需添加比例转换逻辑 return Offset(point.x.toDouble(), point.y.toDouble()); } @override bool shouldRepaint(FacePainter oldDelegate) { return image != oldDelegate.image || faces != oldDelegate.faces; } }
关键说明
- 特征点获取:通过
face.landmarks[FaceLandmarkType.xxx]调用不同部位的关键点,FaceLandmarkType包含下巴、脸颊、额头、眼睛、鼻子、嘴巴等多种类型。 - 坐标适配:如果画布尺寸和原图尺寸不同,需要根据比例调整特征点坐标,避免轮廓错位。
- 路径绘制:用
Path连接多个特征点实现连续轮廓;单个关键点(如眼睛中心)可通过drawCircle标记。 - 细节扩展:可添加更多特征点(如嘴巴上下唇关键点),让轮廓更精细。
内容的提问来源于stack exchange,提问作者Jeevan Shrestha
相关产品推荐
相关产品推荐

