HarmonyOS 7 端侧 AI 视觉能力实战 04:给图片增加人脸检测能力

相册应用里有个常见功能:检测图片里有没有人脸,有的话自动归到"人物相册"里,或者给人脸打标签。我们在做智能截图整理工具的时候,也需要判断截图里有没有人脸——有截图是聊天记录,有的是人脸照片,分类逻辑不一样。
HarmonyOS 7 的人脸检测能力在 VisionKit 里,端侧运行,速度很快。这篇就讲讲怎么把人脸检测集成到我们的图片处理流程里,从检测位置到多目标处理,再到隐私边界怎么把控。
一、真实开发中遇到的问题
最开始接人脸检测的时候,以为就是调个 API 返回一个坐标那么简单。实际用起来问题不少:
第一,一张图里多个人脸怎么处理?合影照片里有 5 个人,每个脸都要框出来,还是只取最大的那个?
第二,检测结果怎么和 UI 层结合?检测出来的坐标是相对于原图的,图片在屏幕上显示的时候缩放过,框框的位置要怎么换算才能对齐?
第三,人脸检测的准确率问题。侧脸、戴口罩、光线暗的时候,检测不到怎么办?要不要降级处理?
第四,隐私问题。人脸数据是敏感信息,本地检测没问题,但不能把人脸数据上传出去。这个边界要守住。
二、这个能力怎么接入
人脸检测的接入思路:加载图片 → 调检测接口 → 拿到人脸坐标列表 → 在 UI 上画框。
关键是理解检测结果的数据结构:返回的是一个人脸数组,每个人脸有位置坐标(boundingBox)、置信度(confidence),有的还返回关键点(眼睛、鼻子、嘴巴的位置)。
接入步骤:
1. 创建检测器:FaceDetector.create(),指定检测模式(快速/高精度)。
2. 传入图片:把 PixelMap 传进去,异步检测。
3. 解析结果:拿到人脸数组,遍历每个脸的坐标。
4. UI 渲染:把坐标换算成屏幕坐标,在图片上画矩形框。

三、关键代码怎么写
下面是封装的人脸检测工具类,文件位置在 entry/src/main/ets/utils/FaceDetectUtil.ets:
import { vision } from '@kit.CoreVisionKit';
import { image } from '@kit.CoreImageKit';
export class FaceDetectUtil {
private static detector: vision.FaceDetector | null = null;
// 初始化检测器
static async init(): Promise<void> {
if (!this.detector) {
this.detector = await vision.FaceDetector.create({
mode: vision.DetectMode.FAST, // 快速模式,够用就行
maxDetectNum: 5 // 最多检测5张脸
});
}
}
// 检测图片中的人脸
static async detectFaces(
pixelMap: image.PixelMap
): Promise<FaceResult[]> {
if (!this.detector) await this.init();
try {
const result = await this.detector!.detect(pixelMap);
// 解析检测结果
return result.faces.map((face: vision.Face) => ({
x: face.boundingBox.left,
y: face.boundingBox.top,
width: face.boundingBox.width,
height: face.boundingBox.height,
confidence: face.confidence
}));
} catch (e) {
console.error('Face detect failed: ' + e.message);
return []; // 检测失败返回空数组
}
}
// 判断图片中是否有人脸
static async hasFace(pixelMap: image.PixelMap): Promise<boolean> {
const faces = await this.detectFaces(pixelMap);
return faces.length > 0;
}
// 释放资源
static release() {
if (this.detector) {
this.detector.release();
this.detector = null;
}
}
}
// 人脸结果类型
interface FaceResult {
x: number;
y: number;
width: number;
height: number;
confidence: number;
}
这段代码的核心:detect 方法返回 faces 数组,每个 face 有 boundingBox 坐标。我们把坐标转成普通对象,方便 UI 层使用。hasFace 方法简单封装,只判断有没有人脸。
页面里怎么画人脸框?文件位置 pages/FaceDetectPage.ets:
@Entry
@Component
struct FaceDetectPage {
@State faces: FaceResult[] = [];
@State imageWidth: number = 0;
@State imageHeight: number = 0;
async onImageLoaded(pixelMap: image.PixelMap) {
// 获取图片原始尺寸
const info = pixelMap.getImageInfoSync();
this.imageWidth = info.width;
this.imageHeight = info.height;
// 执行人脸检测
this.faces = await FaceDetectUtil.detectFaces(pixelMap);
}
build() {
Stack() {
// 原图
Image($r('app.media.sample'))
.objectFit(ImageFit.Contain)
// 人脸框(用 Canvas 画)
Canvas(this.context)
.width('100%')
.height('100%')
.onReady(() => {
this.drawFaceBoxes();
})
}
.width('100%')
.height('100%')
}
// 画人脸框
private drawFaceBoxes() {
const ctx = this.context.getContext();
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 2;
// 坐标换算:原图坐标 → 屏幕坐标
const scaleX = this.canvasWidth / this.imageWidth;
const scaleY = this.canvasHeight / this.imageHeight;
for (const face of this.faces) {
const x = face.x * scaleX;
const y = face.y * scaleY;
const w = face.width * scaleX;
const h = face.height * scaleY;
ctx.strokeRect(x, y, w, h);
}
}
}
这里的关键是坐标换算:检测返回的坐标是相对于原图的,但图片在屏幕上显示的时候按比例缩放了,所以要乘上缩放比例,框才能对齐。

四、运行过程中怎么处理异常
人脸检测的异常场景:
1. 图片中没有人:faces 为空数组。这不是错误,UI 上不画框就行。
2. 侧脸/遮挡:戴口罩、侧脸、光线暗,可能检测不到。这时候不要报错,就当没有人脸处理。
3. 多人脸:maxDetectNum 限制了最多检测几张。合影人多的时候,超过数量的脸检测不到。根据场景调整这个参数。
4. 大图处理:原图很大的时候检测慢。可以先把图片缩放到合适尺寸再检测,速度快很多,准确率影响不大。
五、实际开发中容易忽略的问题
1. 坐标原点:boundingBox 的原点是左上角,不是中心。画框的时候别搞反了,不然框的位置就偏了。
2. 检测模式选择:FAST 模式快但准确率一般,ACCURATE 模式慢但准。相册场景用 FAST 够了,人像编辑场景用 ACCURATE。
3. 人脸数据隐私:人脸数据是敏感信息。检测结果只在本地用,不要上传到服务器。如果要做人脸聚类,也要在本地做。
4. 检测时机:不要在图片加载的时候就同步检测,等图片显示完了再后台检测,不阻塞 UI。
5. 复用检测器:FaceDetector 实例不要每次都创建,创建一次复用。频繁创建销毁会有性能开销。
人脸检测是视觉处理里很基础的能力。看起来简单,但坐标换算、多目标处理、隐私边界这些细节,真做的时候都要考虑到。后面会把这个能力用到截图分类里——判断截图是人脸照片还是聊天记录。
更多推荐



所有评论(0)