鸿蒙学习实战之路-Core Vision Kit人脸比对实现指南
Core Vision Kit(基础视觉服务)提供了机器视觉相关的基础能力,什么意思呢?通俗点说,就是让你的鸿蒙应用"长一双眼睛"——能看懂图片里的内容是人脸还是文字,甚至是通用物体。这套能力封装在这个包里,前面咱们聊过人脸检测,今天来说说它的好兄弟:人脸比对。害,说起人脸比对,我脑子里第一个冒出来的就是各种娱乐 App 里的"你与明星的相似度"功能,当年可谓风靡一时!其实背后的原理就是人脸比对—
鸿蒙学习实战之路-Core Vision Kit人脸比对实现指南
Core Vision Kit(基础视觉服务)提供了机器视觉相关的基础能力,什么意思呢?通俗点说,就是让你的鸿蒙应用"长一双眼睛"——能看懂图片里的内容是人脸还是文字,甚至是通用物体。这套能力封装在 @kit.CoreVisionKit 这个包里,前面咱们聊过人脸检测,今天来说说它的好兄弟:人脸比对。
害,说起人脸比对,我脑子里第一个冒出来的就是各种娱乐 App 里的"你与明星的相似度"功能,当年可谓风靡一时!其实背后的原理就是人脸比对——提取两张脸的特征值,算算相似度是多少。今天这篇,我就手把手带你用 Core Vision Kit 实现人脸比对,全程不超过 10 分钟~
适用场景
人脸比对技术可以判断两张图片是否属于同一个人,返回相似度评分和比对结果。听起来挺高大上,其实应用场景离咱们很近:
- 娱乐类 App 人脸相似度比较:你和迪丽热巴相似度 87%?这类趣味功能背后的技术就是这个
- 身份验证场景中的 1:1 人脸匹配:刷脸登录、刷脸支付,都是在比对摄像头拍的脸和数据库里存的脸是不是同一个人
- 社交应用中的趣味人脸对比功能:和朋友合个影,测测你们俩是不是"失散多年的兄妹"_
效果示例:

约束与限制
开始写代码之前,有些注意事项咱们得先讲清楚,省得写到一半踩坑:
| 约束项 | 具体说明 |
|---|---|
| 设备支持 | 不支持模拟器 |
| 比对模式 | 仅支持 1:1 人脸比对 |
| 图像要求 | 宽高比例建议 10:1 以下,接近手机屏幕比例为宜 |
| 并发处理 | 不支持多线程并发调用 |
🥦 西兰花警告:
这里有个重点!人脸比对只支持 1:1 模式,也就是一次只能比两张脸。如果你想搞"人脸聚类"(比如从 100 张照片里找出同一个人),那得自己循环调用 API,Core Vision Kit 暂时不直接支持这种玩法。
开发步骤
好,接下来咱们一步步来实现人脸比对功能。整体流程和人脸检测差不多,但有两个关键区别:要比对两张图片,而且需要在页面生命周期里初始化和释放引擎。
1. 导入依赖
首先把要用到的模块 import 进来:
import { faceComparator } from '@kit.CoreVisionKit';
咦,怎么只有一行?害,因为 faceComparator 是人脸比对专用模块,其他图片加载、图库选择的模块和之前人脸检测用的是一样的,后面代码里会看到。
2. 页面布局设计
页面结构稍微复杂了一点,要显示两张比对图片:
Column() {
// 显示第一张比对图片
Image(this.chooseImage)
.objectFit(ImageFit.Fill)
.height('30%')
.accessibilityDescription("第一张比对图片")
// 显示第二张比对图片
Image(this.chooseImage1)
.objectFit(ImageFit.Fill)
.height('30%')
.accessibilityDescription("第二张比对图片")
// 显示比对结果
Text(this.dataValues)
.copyOption(CopyOptions.LocalDevice)
.height('15%')
.margin(10)
.width('60%')
// 选择图片按钮
Button('选择图片')
.type(ButtonType.Capsule)
.fontColor(Color.White)
.alignSelf(ItemAlign.Center)
.width('80%')
.margin(10)
.onClick(() => this.selectImage())
// 人脸比对按钮
Button('人脸比对')
.type(ButtonType.Capsule)
.fontColor(Color.White)
.alignSelf(ItemAlign.Center)
.width('80%')
.margin(10)
.onClick(() => this.compareFaces())
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
布局逻辑很清晰:两张图片上下排列,中间夹着结果文字,下面两个按钮。这个结构就像对照实验一样,把两张脸放在一起比个高下!
3. 图片选择与加载
这里有个关键点:人脸比对引擎需要在页面生命周期里初始化和释放,所以用到了 aboutToAppear 和 aboutToDisappear 这两个钩子函数。
// 初始化人脸比对引擎
async aboutToAppear(): Promise<void> {
const initResult = await faceComparator.init();
hilog.info(0x0000, TAG, `初始化结果: ${initResult}`);
}
// 释放资源
async aboutToDisappear(): Promise<void> {
await faceComparator.release();
hilog.info(0x0000, TAG, '资源已释放');
}
// 选择两张比对图片
private async selectImage() {
const uris = await this.openPhoto();
if (uris && uris.length === 2) {
this.loadImage(uris);
} else {
hilog.error(0x0000, TAG, "请选择两张图片进行比对");
}
}
// 打开图库选择图片
private openPhoto(): Promise<string[]> {
return new Promise<string[]>((resolve, reject) => {
const photoPicker = new photoAccessHelper.PhotoViewPicker();
photoPicker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 2 // 最多选择2张图片
}).then(res => {
resolve(res.photoUris);
}).catch((err: BusinessError) => {
hilog.error(0x0000, TAG, `选择图片失败: ${err.code} - ${err.message}`);
reject();
});
});
}
// 加载图片并转换为PixelMap
private loadImage(uris: string[]) {
setTimeout(async () => {
try {
// 加载第一张图片
const fileSource1 = await fileIo.open(uris[0], fileIo.OpenMode.READ_ONLY);
const imageSource1 = image.createImageSource(fileSource1.fd);
this.chooseImage = await imageSource1.createPixelMap();
await fileIo.close(fileSource1);
// 加载第二张图片
const fileSource2 = await fileIo.open(uris[1], fileIo.OpenMode.READ_ONLY);
const imageSource2 = image.createImageSource(fileSource2.fd);
this.chooseImage1 = await imageSource2.createPixelMap();
await fileIo.close(fileSource2);
} catch (error) {
hilog.error(0x0000, TAG, `图片加载失败: ${error}`);
}
}, 100);
}
解释一下这段代码在干嘛:
aboutToAppear():页面即将显示时,调用faceComparator.init()初始化比对引擎aboutToDisappear():页面即将销毁时,调用faceComparator.release()释放资源selectImage():调用图库选择器,让用户选两张图片loadImage():把两张图片都加载成 PixelMap 格式
🥦 西兰花小贴士:maxSelectNumber: 2 这个参数很关键,默认是 1,你得改成 2 才能一次选两张图片。我第一次用的时候忘了改,结果只能选一张,还以为系统 Bug 了…o(╯□╰)o
4. 执行人脸比对
准备工作做完了,真正比对其实就几行代码:
private async compareFaces() {
if (!this.chooseImage || !this.chooseImage1) {
this.dataValues = "请先选择两张图片";
return;
}
try {
// 准备比对参数
const visionInfo1: faceComparator.VisionInfo = {
pixelMap: this.chooseImage
};
const visionInfo2: faceComparator.VisionInfo = {
pixelMap: this.chooseImage1
};
// 执行比对
const result: faceComparator.FaceCompareResult =
await faceComparator.compareFaces(visionInfo1, visionInfo2);
// 处理比对结果
const similarity = this.toPercentage(result.similarity);
this.dataValues = `相似度: ${similarity},${result.isSamePerson ? '是' : '不是'}同一个人`;
} catch (error) {
const err = error as BusinessError;
hilog.error(0x0000, TAG, `比对失败: ${err.code} - ${err.message}`);
this.dataValues = `比对失败: ${err.message}`;
}
}
// 转换为百分比格式
private toPercentage(value: number): string {
return `${(value * 100).toFixed(2)}%`;
}
流程一目了然:构造两个 VisionInfo 对象 → 调用 compareFaces → 解析返回的相似度和是否同一人。toPercentage 方法把 0.8764 这种小数转成 87.64%,用户看起来更直观。
完整代码示例
上面拆开讲了一遍,现在把完整的可运行代码给大家,直接复制到 DevEco Studio 里就能跑:
import { faceComparator } from '@kit.CoreVisionKit';
import { image } from '@kit.ImageKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { Button, ButtonType, Column, Image, ImageFit, ItemAlign, Text } from '@kit.ArkUI';
const TAG: string = "FaceCompareSample";
@Entry
@Component
struct FaceComparisonPage {
@State chooseImage: PixelMap | undefined = undefined;
@State chooseImage1: PixelMap | undefined = undefined;
@State dataValues: string = '';
build() {
Column() {
Image(this.chooseImage)
.objectFit(ImageFit.Fill)
.height('30%')
.accessibilityDescription("第一张比对图片")
Image(this.chooseImage1)
.objectFit(ImageFit.Fill)
.height('30%')
.accessibilityDescription("第二张比对图片")
Text(this.dataValues)
.copyOption(CopyOptions.LocalDevice)
.height('15%')
.margin(10)
.width('60%')
Button('选择图片')
.type(ButtonType.Capsule)
.fontColor(Color.White)
.alignSelf(ItemAlign.Center)
.width('80%')
.margin(10)
.onClick(() => this.selectImage())
Button('人脸比对')
.type(ButtonType.Capsule)
.fontColor(Color.White)
.alignSelf(ItemAlign.Center)
.width('80%')
.margin(10)
.onClick(() => this.compareFaces())
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
// 页面初始化时初始化人脸比对引擎
async aboutToAppear(): Promise<void> {
const initResult = await faceComparator.init();
hilog.info(0x0000, TAG, `初始化结果: ${initResult}`);
}
// 页面销毁时释放资源
async aboutToDisappear(): Promise<void> {
await faceComparator.release();
hilog.info(0x0000, TAG, '资源已释放');
}
// 选择比对图片
private async selectImage() {
const uris = await this.openPhoto();
if (uris && uris.length === 2) {
this.loadImage(uris);
} else {
this.dataValues = "请选择两张图片进行比对";
}
}
// 打开图库选择图片
private openPhoto(): Promise<string[]> {
return new Promise<string[]>((resolve, reject) => {
const photoPicker = new photoAccessHelper.PhotoViewPicker();
photoPicker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 2
}).then(res => {
resolve(res.photoUris);
}).catch((err: BusinessError) => {
hilog.error(0x0000, TAG, `选择图片失败: ${err.code} - ${err.message}`);
reject();
});
});
}
// 加载图片并转换为PixelMap
private loadImage(uris: string[]) {
setTimeout(async () => {
try {
// 加载第一张图片
const fileSource1 = await fileIo.open(uris[0], fileIo.OpenMode.READ_ONLY);
const imageSource1 = image.createImageSource(fileSource1.fd);
this.chooseImage = await imageSource1.createPixelMap();
await fileIo.close(fileSource1);
// 加载第二张图片
const fileSource2 = await fileIo.open(uris[1], fileIo.OpenMode.READ_ONLY);
const imageSource2 = image.createImageSource(fileSource2.fd);
this.chooseImage1 = await imageSource2.createPixelMap();
await fileIo.close(fileSource2);
this.dataValues = "图片加载完成,请点击人脸比对";
} catch (error) {
hilog.error(0x0000, TAG, `图片加载失败: ${error}`);
this.dataValues = "图片加载失败,请重试";
}
}, 100);
}
// 执行人脸比对
private async compareFaces() {
if (!this.chooseImage || !this.chooseImage1) {
this.dataValues = "请先选择两张图片";
return;
}
try {
const visionInfo1: faceComparator.VisionInfo = {
pixelMap: this.chooseImage
};
const visionInfo2: faceComparator.VisionInfo = {
pixelMap: this.chooseImage1
};
const result: faceComparator.FaceCompareResult =
await faceComparator.compareFaces(visionInfo1, visionInfo2);
const similarity = this.toPercentage(result.similarity);
this.dataValues = `相似度: ${similarity},${result.isSamePerson ? '是' : '不是'}同一个人`;
} catch (error) {
const err = error as BusinessError;
hilog.error(0x0000, TAG, `比对失败: ${err.code} - ${err.message}`);
this.dataValues = `比对失败: ${err.message}`;
}
}
// 转换为百分比格式
private toPercentage(value: number): string {
return `${(value * 100).toFixed(2)}%`;
}
}
这段代码把前面的功能整合到了一起,还在 loadImage 里加了提示信息,用户体验更好。
比对结果说明
人脸比对完成后,返回的 FaceCompareResult 对象包含以下关键信息:
| 属性 | 类型 | 描述 |
|---|---|---|
similarity |
number |
相似度分数(0-1 之间) |
isSamePerson |
boolean |
是否为同一人(true/false) |
🥦 西兰花小贴士:similarity 是 0 到 1 之间的小数,1 表示完全一样。那阈值设多少判断"是同一人"呢?官方没有硬性规定,一般 0.6 到 0.8 之间比较常见。你可以自己调参数,找个最适合你业务的阈值。
下一步
人脸比对做完了,Core Vision Kit 的视觉能力咱们已经聊了检测和比对两个。还有几个有意思的能力值得捣鼓:
- 主体分割:把图片里的主体从背景里抠出来,做证件照换背景可以用
- 骨骼点检测:检测人体关键点,做动作识别、体态分析
- 多目标识别:一次识别图片里的多种物体
推荐资料
📚 官方文档是个好东西!说三遍!
- 官方人脸比对文档:Core Vision Kit 人脸比对
- API 参考手册:FaceComparator API
我是盐焗西兰花,
不教理论,只给你能跑的代码和避坑指南。
下期见!🥦
更多推荐




所有评论(0)