MediaPipe Face Detection 实战指南:基于 BlazeFace 的超快多脸检测解决方案
【免费下载链接】mediapipeCross-platform, customizable ML solutions for live and streaming media.项目地址: https://gitcode.com/GitHub_Trending/med/mediapipe
本篇技术指南围绕 MediaPipe 官方文档中的人脸检测(Face Detection)解决方案展开,详细介绍其基于 BlazeFace 的模型原理、短距/全距双模型选择机制、跨平台 API(Python / JavaScript / Android)配置参数与完整调用示例,并结合本仓库中的源码(face_detection.py、face_detection.proto、face_detection_mobile_gpu.pbtxt)深入讲解其底层图结构与选项字段的语义。读完你将掌握如何在一台设备的摄像头画面中实时输出多张人脸框与 6 个关键点,并将其作为人脸网格(Face Mesh)、表情分类、人脸区域分割等其他任务的前置"兴趣区域"输入。
说明:本仓库中该文档属于MediaPipe Legacy Solutions系列。官方文档标注,自2023 年 5 月 10 日起该能力已升级为新一代 MediaPipe Solution(Vision 的 Face Detector)。本文介绍的是本仓库快照中遗留的 Python/JavaScript/Android Solution API 与底层 Calculator 图,其架构与参数思想在新旧两代方案中是一脉相承的。
一、解决方案概览:为什么用 BlazeFace 做人脸检测
MediaPipe Face Detection 是一个超快的人脸检测方案,具备两个核心特点:
- 6 个关键点(6 landmarks)输出,而非仅输出检测框;
- 多脸支持(multi-face support),单帧可检测多个人脸。
检测器底层基于 BlazeFace(论文题为BlazeFace: Sub-millisecond Neural Face Detection on Mobile GPUs)——一个专为移动端 GPU 推理量身定制的轻量级高性能人脸检测器。其超实时(super-realtime)的性能使得它可以应用于任何实时取景器(live viewfinder)体验:只要某个下游任务需要"精确的人脸兴趣区域(ROI)"作为输入,Face Detection 就可以作为第一级检测器来提供,典型下游应用包括:
- 3D 人脸关键点估计,如 MediaPipe Face Mesh;
- 人脸特征 / 表情分类;
- 人脸区域分割(face region segmentation)。
BlazeFace 的网络设计有三个关键点(这也是它在移动端能跑出超实时速度的原因):
- 轻量级特征提取网络:受 MobileNetV1/V2 启发但与它们不同,去掉了 3D 卷积等重算子;
- GPU 友好的锚点方案:从 Single Shot MultiBox Detector (SSD) 修改而来,改用更少层数、更稀疏的锚点生成,减少移动端 GPU 上的张量搬运;
- 改进的 tie resolution 策略:替代传统的非极大值抑制(NMS),用更轻量、更适合移动端推理的冲突消解策略。
事实边界提示:"超快 / 超实时 / 亚毫秒"等表述来自官方文档及 BlazeFace 论文标题的描述,实际帧率取决于设备 GPU、输入分辨率与所选模型(短距 sparse/全距 dense)。
二、Solution API 与配置选项详解
官方文档指出:各平台/语言的命名风格与可用性可能略有差异。下表汇总了全部配置项及其平台适用范围:
| 配置项 | 类型 / 取值范围 | 平台 | 默认值 | 说明 |
|---|---|---|---|---|
model_selection | 整数0或1 | Python、Android、C++ | 0 | 0选短距模型(最近约 2 米内的人脸最佳);1选全距模型(5 米内最佳),全距选项内部使用 sparse 模型以提升推理速度 |
model | 字符串"short"/"full" | JavaScript 专用 | 空字符串 | JS 侧没有model_selection,改用字符串指定;含义同上 |
selfie_mode | 布尔值 | JavaScript 专用 | false | 是否对图像/视频帧做水平翻转,用于自拍镜像显示 |
min_detection_confidence | 浮点数[0.0, 1.0] | 全平台 | 0.5 | 人脸检测模型判定"检测成功"的最低置信度阈值 |
static_image_mode | 布尔值 | Android | — | 是否按静态图片模式处理(与modelSelection一并出现在 Android 的受支持列表中) |
2.1model_selection:短距与全距模型如何选择
model_selection取0时,选择短距(short-range)模型,对距离摄像头2 米以内的人脸效果最佳;取1时选择全距(full-range)模型,对5 米以内的人脸效果最佳。全距选项会加载sparse(稀疏)模型以换取更快的推理速度。模型的详细对比见模型卡片。
从 Python 端源码可以清晰地看到模型选择的底层实现。在 mediapipe/python/solutions/face_detection.py 中预置了两份图的二进制路径:
_SHORT_RANGE_GRAPH_FILE_PATH = 'mediapipe/modules/face_detection/face_detection_short_range_cpu.binarypb' _FULL_RANGE_GRAPH_FILE_PATH = 'mediapipe/modules/face_detection/face_detection_full_range_cpu.binarypb'构造函数中通过model_selection == 1二选一加载对应子图(face_detection.py#L67-L88):
binary_graph_path = _FULL_RANGE_GRAPH_FILE_PATH if model_selection == 1 else _SHORT_RANGE_GRAPH_FILE_PATH super().__init__( binary_graph_path=binary_graph_path, graph_options=self.create_graph_options( face_detection_pb2.FaceDetectionOptions(), { 'min_score_thresh': min_detection_confidence, }), outputs=['detections'])注意这里暴露了一个重要实现细节:Python 层的min_detection_confidence会被透传为图选项里的min_score_thresh,也就是 SSD 解码阶段用于筛框的分数阈值(详见下文 proto 字段说明)。因此调大该阈值 → 要求更高的置信度 → 误检更少但漏检可能增加。
2.2model:JavaScript 的模型字符串写法
JavaScript 侧不支持model_selection整数下标,而是用字符串model:
"short":短距模型,2 米内最佳;"full":全距模型,5 米内最佳,内部同样使用 sparse 模型提速;- 未指定时默认空字符串。
2.3selfie_mode:自拍镜像翻转
布尔值,仅 JavaScript 方案提供。当为true时,图像/视频帧会在处理与输出时水平翻转,从而让摄像头前的人看到自己的"镜像",符合视频通话/自拍类 UI 的习惯。
2.4min_detection_confidence:置信度门槛
取值范围[0.0, 1.0],默认0.5。只有模型输出分数不低于该值的人脸才会被保留。阈值越低召回越高但误检越多,阈值越高越保守。
2.5 全平台命名映射
| Python | JavaScript | Android (Java) |
|---|---|---|
model_selection | model | modelSelection |
min_detection_confidence | minDetectionConfidence | minDetectionConfidence |
| — | selfieMode | — |
| — | — | staticImageMode |
三、输出结构:detections检测结果详解
所有平台的处理结果中,核心字段是detections——一个检测到的人脸集合。每一张人脸被表示为一个 Detection proto 消息,包含:
3.1 边界框(Bounding Box)
由四部分构成,全部做了归一化处理:
| 字段 | 归一化分母 | 取值范围 |
|---|---|---|
xmin | 图像宽度 | [0.0, 1.0] |
width | 图像宽度 | [0.0, 1.0] |
ymin | 图像高度 | [0.0, 1.0] |
height | 图像高度 | [0.0, 1.0] |
3.2 六个关键点(Key Points)
每个检测结果携带 6 个关键点,x、y分别按图像宽、高归一化到[0.0, 1.0]:
- 右眼(right eye)
- 左眼(left eye)
- 鼻尖(nose tip)
- 嘴中心(mouth center)
- 右耳屏点(right ear tragion)
- 左耳屏点(left ear tragion)
Python 侧在 face_detection.py#L46-L53 用FaceKeyPoint枚举精确映射了这 6 个关键点的下标顺序,这与 Detection proto 中relative_keypoints数组的下标一一对应:
class FaceKeyPoint(enum.IntEnum): """The enum type of the six face detection key points.""" RIGHT_EYE = 0 LEFT_EYE = 1 NOSE_TIP = 2 MOUTH_CENTER = 3 RIGHT_EAR_TRAGION = 4 LEFT_EAR_TRAGION = 5配套的get_key_point(detection, key_point_enum)便捷方法(face_detection.py#L29-L43)正是按下标从detection.location_data.relative_keypoints中取出对应关键点,省去手写下标的麻烦:
def get_key_point( detection: detection_pb2.Detection, key_point_enum: 'FaceKeyPoint' ) -> Union[None, location_data_pb2.LocationData.RelativeKeypoint]: if not detection or not detection.location_data: return None return detection.location_data.relative_keypoints[key_point_enum]想拿到像素坐标?只需把归一化坐标分别乘以图像宽、高即可。Android 侧文档代码中
noseTip.getX() * width就是这么算的(见下文 Android 示例)。
四、Python Solution API
4.1 安装
请先按照通用 Python 安装指引安装 MediaPipe Python 包,然后参考下述用法。
Python 方案支持的配置项:
model_selectionmin_detection_confidence
4.2 静态图片检测示例
import cv2 import mediapipe as mp mp_face_detection = mp.solutions.face_detection mp_drawing = mp.solutions.drawing_utils # For static images: IMAGE_FILES = [] with mp_face_detection.FaceDetection( model_selection=1, min_detection_confidence=0.5) as face_detection: for idx, file in enumerate(IMAGE_FILES): image = cv2.imread(file) # Convert the BGR image to RGB and process it with MediaPipe Face Detection. results = face_detection.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) # Draw face detections of each face. if not results.detections: continue annotated_image = image.copy() for detection in results.detections: print('Nose tip:') print(mp_face_detection.get_key_point( detection, mp_face_detection.FaceKeyPoint.NOSE_TIP)) mp_drawing.draw_detection(annotated_image, detection) cv2.imwrite('/tmp/annotated_image' + str(idx) + '.png', annotated_image)关键点:
FaceDetection以with上下文管理器使用,退出时自动释放底层图资源;- 输入必须是RGB 顺序的 numpy 数组,所以用
cv2.cvtColor从 BGR 转 RGB(process()在非三通道输入时会抛ValueError,底层图出错时抛RuntimeError,见 face_detection.py#L90-L105); get_key_point(detection, FaceKeyPoint.NOSE_TIP)用于单独打印鼻尖的归一化坐标;- 结果对象是 NamedTuple,唯一的
detections字段携带全部检测到的人脸。
4.3 摄像头实时检测示例
import cv2 import mediapipe as mp mp_face_detection = mp.solutions.face_detection mp_drawing = mp.solutions.drawing_utils # For webcam input: cap = cv2.VideoCapture(0) with mp_face_detection.FaceDetection( model_selection=0, min_detection_confidence=0.5) as face_detection: while cap.isOpened(): success, image = cap.read() if not success: print("Ignoring empty camera frame.") # If loading a video, use 'break' instead of 'continue'. continue # To improve performance, optionally mark the image as not writeable to # pass by reference. image.flags.writeable = False image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) results = face_detection.process(image) # Draw the face detection annotations on the image. image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) if results.detections: for detection in results.detections: mp_drawing.draw_detection(image, detection) # Flip the image horizontally for a selfie-view display. cv2.imshow('MediaPipe Face Detection', cv2.flip(image, 1)) if cv2.waitKey(5) & 0xFF == 27: break cap.release()实时场景的性能优化技巧(官方代码注释即为佐证):
image.flags.writeable = False:在处理前把输入图像标记为"只读",MediaPipe 便能按引用传递(pass by reference),避免一次不必要的拷贝,这是视频循环里重要的提速手段;- 处理完后再置回
writeable = True并转回 BGR 用于绘制显示; - 用
cv2.flip(image, 1)水平翻转实现自拍镜像显示(对应 JS 端由selfie_mode自动完成的翻转)。
五、JavaScript Solution API
请先阅读 MediaPipe 在 JavaScript 侧的通用介绍。JS 方案支持的选项:
selfieModemodelminDetectionConfidence
5.1 HTML 骨架
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/face_detection/face_detection.js" crossorigin="anonymous"></script> </head> <body> <div class="container"> <video class="input_video"></video> <canvas class="output_canvas" width="1280px" height="720px"></canvas> </div> </body> </html>以上
script标签按官方文档原样保留:引入@mediapipe/face_detection解决方案本体,以及配套的camera_utils(摄像头驱动)、drawing_utils(绘制工具)等辅助包;生产环境中可改为本地静态托管或受控 CDN 版本号。
5.2 逻辑代码
<script type="module"> const videoElement = document.getElementsByClassName('input_video')[0]; const canvasElement = document.getElementsByClassName('output_canvas')[0]; const canvasCtx = canvasElement.getContext('2d'); const drawingUtils = window; function onResults(results) { // Draw the overlays. canvasCtx.save(); canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height); canvasCtx.drawImage( results.image, 0, 0, canvasElement.width, canvasElement.height); if (results.detections.length > 0) { drawingUtils.drawRectangle( canvasCtx, results.detections[0].boundingBox, {color: 'blue', lineWidth: 4, fillColor: '#00000000'}); drawingUtils.drawLandmarks(canvasCtx, results.detections[0].landmarks, { color: 'red', radius: 5, }); } canvasCtx.restore(); } const faceDetection = new FaceDetection({locateFile: (file) => { return `https://cdn.jsdelivr.net/npm/@mediapipe/face_detection@0.0/${file}`; }}); faceDetection.setOptions({ model: 'short', minDetectionConfidence: 0.5 }); faceDetection.onResults(onResults); const camera = new Camera(videoElement, { onFrame: async () => { await faceDetection.send({image: videoElement}); }, width: 1280, height: 720 }); camera.start(); </script>JS 侧的结构与用法要点:
locateFile决定模型 wasm/二进制文件的获取路径;setOptions({model: 'short', ...})即上文配置表中 JS 专属的model字段;- 回调
onResults中的results.detections[i].boundingBox是检测框,results.detections[i].landmarks则是关键点集合; - 采用"摄像头事件驱动"模型:每来一帧调用一次
faceDetection.send({image})。
六、Android Solution API
Android 侧请先按照通用 Android Solution 指引添加 MediaPipe Gradle 依赖,并参考对应示例工程运行。
Android 方案支持的配置项:
staticImageModemodelSelection
6.1 摄像头输入(OpenGL 渲染)
// For camera input and result rendering with OpenGL. FaceDetectionOptions faceDetectionOptions = FaceDetectionOptions.builder() .setStaticImageMode(false) .setModelSelection(0).build(); FaceDetection faceDetection = new FaceDetection(this, faceDetectionOptions); faceDetection.setErrorListener( (message, e) -> Log.e(TAG, "MediaPipe Face Detection error:" + message)); // Initializes a new CameraInput instance and connects it to MediaPipe Face Detection Solution. CameraInput cameraInput = new CameraInput(this); cameraInput.setNewFrameListener( textureFrame -> faceDetection.send(textureFrame)); // Initializes a new GlSurfaceView with a ResultGlRenderer<FaceDetectionResult> instance // that provides the interfaces to run user-defined OpenGL rendering code. SolutionGlSurfaceView<FaceDetectionResult> glSurfaceView = new SolutionGlSurfaceView<>( this, faceDetection.getGlContext(), faceDetection.getGlMajorVersion()); glSurfaceView.setSolutionResultRenderer(new FaceDetectionResultGlRenderer()); glSurfaceView.setRenderInputImage(true); faceDetection.setResultListener( faceDetectionResult -> { if (faceDetectionResult.multiFaceDetections().isEmpty()) { return; } RelativeKeypoint noseTip = faceDetectionResult .multiFaceDetections() .get(0) .getLocationData() .getRelativeKeypoints(FaceKeypoint.NOSE_TIP); Log.i( TAG, String.format( "MediaPipe Face Detection nose tip normalized coordinates (value range: [0, 1]): x=%f, y=%f", noseTip.getX(), noseTip.getY())); // Request GL rendering. glSurfaceView.setRenderData(faceDetectionResult); glSurfaceView.requestRender(); }); // The runnable to start camera after the GLSurfaceView is attached. glSurfaceView.post( () -> cameraInput.start( this, faceDetection.getGlContext(), CameraInput.CameraFacing.FRONT, glSurfaceView.getWidth(), glSurfaceView.getHeight()));6.2 相册图片输入(ImageView 绘制)
// For reading images from gallery and drawing the output in an ImageView. FaceDetectionOptions faceDetectionOptions = FaceDetectionOptions.builder() .setStaticImageMode(true) .setModelSelection(0).build(); FaceDetection faceDetection = new FaceDetection(this, faceDetectionOptions); // Connects MediaPipe Face Detection Solution to the user-defined ImageView // instance that allows users to have the custom drawing of the output landmarks // on it. FaceDetectionResultImageView imageView = new FaceDetectionResultImageView(this); faceDetection.setResultListener( faceDetectionResult -> { if (faceDetectionResult.multiFaceDetections().isEmpty()) { return; } int width = faceDetectionResult.inputBitmap().getWidth(); int height = faceDetectionResult.inputBitmap().getHeight(); RelativeKeypoint noseTip = faceDetectionResult .multiFaceDetections() .get(0) .getLocationData() .getRelativeKeypoints(FaceKeypoint.NOSE_TIP); Log.i( TAG, String.format( "MediaPipe Face Detection nose tip coordinates (pixel values): x=%f, y=%f", noseTip.getX() * width, noseTip.getY() * height)); // Request canvas drawing. imageView.setFaceDetectionResult(faceDetectionResult); runOnUiThread(() -> imageView.update()); }); faceDetection.setErrorListener( (message, e) -> Log.e(TAG, "MediaPipe Face Detection error:" + message)); // ActivityResultLauncher to get an image from the gallery as Bitmap. ActivityResultLauncher<Intent> imageGetter = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { Intent resultIntent = result.getData(); if (resultIntent != null && result.getResultCode() == RESULT_OK) { Bitmap bitmap = null; try { bitmap = MediaStore.Images.Media.getBitmap( this.getContentResolver(), resultIntent.getData()); // Please also rotate the Bitmap based on its orientation. } catch (IOException e) { Log.e(TAG, "Bitmap reading error:" + e); } if (bitmap != null) { faceDetection.send(bitmap); } } }); Intent pickImageIntent = new Intent(Intent.ACTION_PICK); pickImageIntent.setDataAndType(MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*"); imageGetter.launch(pickImageIntent);注意:图片模式的结果监听器里,官方用noseTip.getX() * width把归一化坐标换算成像素坐标再打印——正好呼应上文对归一化输出字段的说明。同时记得依据 EXIF 方向旋转 Bitmap,否则结果会出现角度偏差。
6.3 视频文件输入(OpenGL 渲染)
// For video input and result rendering with OpenGL. FaceDetectionOptions faceDetectionOptions = FaceDetectionOptions.builder() .setStaticImageMode(false) .setModelSelection(0).build(); FaceDetection faceDetection = new FaceDetection(this, faceDetectionOptions); faceDetection.setErrorListener( (message, e) -> Log.e(TAG, "MediaPipe Face Detection error:" + message)); // Initializes a new VideoInput instance and connects it to MediaPipe Face Detection Solution. VideoInput videoInput = new VideoInput(this); videoInput.setNewFrameListener( textureFrame -> faceDetection.send(textureFrame)); // Initializes a new GlSurfaceView with a ResultGlRenderer<FaceDetectionResult> instance. SolutionGlSurfaceView<FaceDetectionResult> glSurfaceView = new SolutionGlSurfaceView<>( this, faceDetection.getGlContext(), faceDetection.getGlMajorVersion()); glSurfaceView.setSolutionResultRenderer(new FaceDetectionResultGlRenderer()); glSurfaceView.setRenderInputImage(true); faceDetection.setResultListener( faceDetectionResult -> { if (faceDetectionResult.multiFaceDetections().isEmpty()) { return; } RelativeKeypoint noseTip = faceDetectionResult .multiFaceDetections() .get(0) .getLocationData() .getRelativeKeypoints(FaceKeypoint.NOSE_TIP); Log.i( TAG, String.format( "MediaPipe Face Detection nose tip normalized coordinates (value range: [0, 1]): x=%f, y=%f", noseTip.getX(), noseTip.getY())); // Request GL rendering. glSurfaceView.setRenderData(faceDetectionResult); glSurfaceView.requestRender(); }); ActivityResultLauncher<Intent> videoGetter = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { Intent resultIntent = result.getData(); if (resultIntent != null) { if (result.getResultCode() == RESULT_OK) { glSurfaceView.post( () -> videoInput.start( this, resultIntent.getData(), faceDetection.getGlContext(), glSurfaceView.getWidth(), glSurfaceView.getHeight())); } } }); Intent pickVideoIntent = new Intent(Intent.ACTION_PICK); pickVideoIntent.setDataAndType(MediaStore.Video.Media.INTERNAL_CONTENT_URI, "video/*"); videoGetter.launch(pickVideoIntent);三种输入方式的共性规律:
- 结果对象是
FaceDetectionResult,取人脸走multiFaceDetections(),关键点走getRelativeKeypoints(FaceKeypoint.XXX)(Android 侧同样有FaceKeypoint枚举); - 相机与视频都基于
textureFrame -> faceDetection.send(textureFrame)的逐帧回调,渲染统一走SolutionGlSurfaceView<FaceDetectionResult>+ 自定义ResultGlRenderer; - 图片走
faceDetection.send(bitmap)+ 自定义ImageView绘制回调。
七、源码级原理:子图结构、proto 选项与运行管线
7.1 模块子图清单
本仓库中,人脸检测被封装为一组可复用子图(subgraph),集中在 mediapipe/modules/face_detection/ 目录。模块自带 README(mediapipe/modules/face_detection/README.md)给出了四种子图的速查表:
| 子图 | 适用距离 | 输入 / 推理 |
|---|---|---|
FaceDetectionFullRangeCpu | 5 米内最佳 | CPU 输入,CPU 推理 |
FaceDetectionFullRangeGpu | 5 米内最佳 | GPU 输入,GPU 推理 |
FaceDetectionShortRangeCpu | 2 米内最佳 | CPU 输入,CPU 推理 |
FaceDetectionShortRangeGpu | 2 米内最佳 | GPU 输入,GPU 推理 |
以 GPU 短距子图 face_detection_short_range_gpu.pbtxt 为例,它的顶层接口为:
- 输入流
IMAGE:image(GpuBuffer或多后端Image); - 输出流
DETECTIONS:detections(std::vector<Detection>); - 声明
graph_options为mediapipe.FaceDetectionOptions; - 内部委托 GPU 推理,并设置
gpu_origin: TOP_LEFT(坐标系原点约定在左上角,保证与图像处理习惯一致)以及delegate.gpu.use_advanced_gpu_api: true。
graph_options: { [type.googleapis.com/mediapipe.FaceDetectionOptions] {} } node { calculator: "FaceDetectionShortRange" input_stream: "IMAGE:image" output_stream: "DETECTIONS:detections" node_options: { [type.googleapis.com/mediapipe.FaceDetectionOptions] { gpu_origin: TOP_LEFT delegate: { gpu { use_advanced_gpu_api: true } } } } option_value: "OPTIONS:options" }7.2FaceDetectionOptionsproto 字段语义
子图的全部可调参数定义在 mediapipe/modules/face_detection/face_detection.proto。虽然 Python/JS/Android 高层 API 只暴露了极少数选项,但底层图支持细粒度调节:
| proto 字段 | 类型 | 说明 |
|---|---|---|
model_path | string | TF Lite 模型路径 |
gpu_origin | GpuOrigin.Mode | 坐标原点:CONVENTIONAL或TOP_LEFT |
tensor_width/tensor_height | int32 | 送入检测模型的张量尺寸 |
num_layers | int32 | 用于生成锚点的输出特征图层数 |
strides | repeated int32 | 每层特征图对应的步长 |
interpolated_scale_aspect_ratio | float | 由 SsdAnchorsCalculator 插值出的锚点宽高比,默认1.0 |
num_boxes | int32 | 检测模型输出的候选框数量 |
x_scale/y_scale/w_scale/h_scale | float | SSD 解码器的坐标反归一化缩放系数 |
min_score_thresh | float | SSD 检测结果保留的分数阈值(Python 的min_detection_confidence就映射到此字段) |
delegate | InferenceCalculatorOptions.Delegate | TFLite 推理委托(CPU/GPU 等) |
由此可以看到配置参数的完整链路:高层 API 选项 →FaceDetectionOptions(proto extension,id = 374290926)→ 子图内部 Calculator 选项 → SSD 锚点生成 / 解码 / 阈值过滤。例如,调整锚点生成的num_layers与strides会影响可召回的人脸尺度范围,这正是"短距 2 米 / 全距 5 米"两种模型差异的底层来源之一。
7.3 端到端管线:以移动端 GPU 图为例
上层 example 图把子图、限流与渲染拼装成一条完整管线。见 mediapipe/graphs/face_detection/face_detection_mobile_gpu.pbtxt:
# Input input_stream: "input_video" # GpuBuffer # Outputs output_stream: "output_video" # 叠加了渲染结果的图像 output_stream: "face_detections" # std::vector<Detection>该图由 4 个环节组成,每个环节都对应真实 Calculator:
FlowLimiterCalculator流量限流:透传首帧,之后等待下游(FINISHED:output_video回边)完成上一帧任务才放行下一帧,中间到达的帧直接丢弃。这保证图中绝大多数区域同时处理的帧数不超过 1,避免实时移动端应用因节点排队造成延迟与内存飙升(图内注释详尽说明了设计动机);FaceDetectionShortRangeGpu子图:接收throttled_input_video,输出face_detections;DetectionsToRenderDataCalculator:把检测框转换为绘图原语,option 中设定描边thickness: 4.0、颜色color { r: 255 g: 0 b: 0 }(红色框);AnnotationOverlayCalculator:把渲染数据叠加到原图上输出output_video。
如果你需要"只取框,不画框",可以在构建图时省去第 3、4 两个渲染节点,直接把face_detections输出流接到你的下游消费者即可——这也正是把 Face Detection 当作前置 ROI 提供者的标准做法。
八、Example Apps:移动端 / 桌面端 / Coral 的图与构建目标
官方文档为各平台提供了可直接构建运行的示例。以下目标路径均可在本仓库中对应找到:
8.1 移动端 GPU 管线
- 图:face_detection_mobile_gpu.pbtxt
- 端到端演示与运行说明可参考 mediapipe/docs/face_detection_mobile_gpu.md(仓库内配套的运行文档)
8.2 移动端 CPU 管线
与 GPU 管线非常相似,差别在于:管线的开头与结尾分别执行 GPU→CPU 与 CPU→GPU 的图像传输,因此图中其余部分(与 GPU 管线共享相同配置)完全在 CPU 上运行。
- 图:face_detection_mobile_cpu.pbtxt
8.3 桌面端
- CPU 运行:使用图 face_detection_desktop_live.pbtxt;
- GPU 运行:桌面端也可复用移动 GPU 图 face_detection_mobile_gpu.pbtxt;
- 桌面构建目标定义在 mediapipe/examples/desktop/face_detection/BUILD:其中
face_detection_cpu、face_detection_gpu短距目标把face_detection_short_range.tflite作为 data 打包,还额外提供face_detection_full_range_cpu(依赖face_detection_full_range.tflite与全距桌面实时图face_detection_full_range_desktop_live.pbtxt)——这从侧面印证了"模型选择"不仅是高层 API 概念,而是贯穿到图与 BUILD 目标的真实资源。
运行上述示例前,请先按平台的通用构建指引操作:Android、iOS 与桌面 C++。
提示:查看 / 调试图结构时,可把
.pbtxt图文本复制进 MediaPipe Visualizer 可视化(用法见 visualizer 文档),其关联子图会被一并展开显示。
8.4 Coral 边缘设备
如需在 Coral Dev Board 等 EdgeTPU 设备上交叉编译运行,请参考 mediapipe/examples/coral 目录下的说明与构建文件。人脸检测还有专门为 EdgeTPU 量化的模型(face-detector-quantized_edgetpu.tflite,在 模型清单 中列出)。
九、模型对比与选型建议
官方模型卡片章节把人脸检测模型整理为三档:
| 模型 | 适用距离 | 说明 |
|---|---|---|
| Short-range 模型 | 2 米内最佳 | 通用场景默认选择 |
| Full-range(dense)模型 | 5 米内最佳 | 相对 sparse 在Recall(召回率)上略优 |
| Full-range(sparse)模型 | 5 米内最佳 | 相对 dense 在Precision(精确率)上更优 |
文档给出的关键数据(来自模型卡片描述):
- dense 与 sparse 在F-score 上质量相当,差别体现在细粒度指标:dense 召回略好,sparse 精确率更高;
- 速度方面:在 CPU 上经由 XNNPACK 执行时,sparse 比 dense 大约快30%;GPU 上两者延迟相近。
因此选型建议很直接:
- 若应用以近距离自拍 / 视频通话为主且追求极致延迟 →
model_selection=0(短距)即可; - 若需要覆盖3~5 米的中远距离(如安防、互动大屏、教室等)→ 选全距(
model_selection=1),在纯 CPU 部署时可优先 sparse 换取约 30% 的推理加速,在 GPU 上则两者延迟差异不大。
十、小结
MediaPipe Face Detection 的完整工作流可总结为一条链路:
摄像头/图片/视频帧 → (可选限流 FlowLimiter)→ FaceDetection{Short|Full}Range{Cpu|Gpu} 子图 → SSD 锚点检测 + 阈值过滤(
min_score_thresh/min_detection_confidence)→detections(归一化边界框 + 6 关键点)→ 下游 ROI 任务 或 叠加渲染
本文覆盖的实践要点速查:
- 参数:
model_selection/model管距离档位与稀疏模型,min_detection_confidence管置信门槛(Python 端透传为min_score_thresh),selfie_mode管镜像翻转; - 输出:每张脸 = 归一化 bbox(
xmin/width/ymin/height)+ 6 关键点(右眼、左眼、鼻尖、嘴中心、左右耳屏点),像素坐标 = 归一化坐标 × 图像宽/高; - 源码佐证:face_detection.py 展示了枚举、便捷取点与模型选择的真实映射;face_detection.proto 与 face_detection_short_range_gpu.pbtxt 展示了底层可调字段;face_detection_mobile_gpu.pbtxt 则演示了"限流 → 检测 → 渲染"的端到端拼图范式。
掌握以上内容后,你可以很自然地把 Face Detection 作为一帧图像的第一级处理单元,把detections中的人脸框与关键点交给 Face Mesh、表情分类或人脸分割等后续任务,构建出完整的实时人脸理解流水线。
【免费下载链接】mediapipeCross-platform, customizable ML solutions for live and streaming media.项目地址: https://gitcode.com/GitHub_Trending/med/mediapipe
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考