HarmonyOS NEXT-CoreVision Kit-FaceDetector-实现人脸识别,获取人脸数据

效果演示图,右边的是人脸数据,可用来比对人脸

注意这里只有真机才能测试是否成功, 测试机型pce-w30

实现这个效果很简洁:打开相册、选取图片、打开文件、创建imageSource、创建PixelMap、喂给faceDetector拿到结果

在这里我简单封装了两个工具类方便后续使用,分别是:照片选择类、人脸识别类

大部分解读都写在注释里面了,供参考

// 引入必要的模块
import { faceDetector } from '@kit.CoreVisionKit'; // 引入人脸检测模块
import { promptAction } from '@kit.ArkUI'; // 引入UI提示模块

/**
 * FaceDetectClass 类用于处理面部检测的逻辑
 */
export class FaceDetectClass {

  // 静态属性,用于存储当前面部数据的JSON字符串
  static faceDataStr: string = "";

  /**
   * 异步检测面部信息
   * @param pixMap - 包含图像数据的PixelMap对象
   * @returns 返回Promise,解析为面部数据的JSON字符串,或在失败时拒绝
   */
  static async detectFace(pixMap: PixelMap): Promise<string> {
    return new Promise<string>(async (resolve, reject) => {
      try {
        // 构造VisionInfo对象,包含待检测的图像数据
        const visionInfo: faceDetector.VisionInfo = {
          pixelMap: pixMap
        };

        // 调用人脸检测接口
        const faceData = await faceDetector.detect(visionInfo);

        // 检查是否检测到面部
        if (faceData.length === 0) {
          // 如果没有检测到面部,显示提示并拒绝Promise
          promptAction.showToast({
            message: "获取面部数据失败"
          });
          reject("未检测到面部");
        } else {
          // 将面部数据转换为JSON字符串
          const faceJsonStr = JSON.stringify(faceData);

          // 更新静态属性以存储当前面部数据
          FaceDetectClass.faceDataStr = faceJsonStr;

          // 解析Promise,返回面部数据的JSON字符串
          resolve(faceJsonStr);
        }
      } catch (err) {
        // 捕获并处理异常
        promptAction.showToast({
          message: `识别面部失败: ${err.message}`
        });
        reject(err); // 拒绝Promise,并传递错误对象
      }
    });
  }
}
// 引入必要的模块
import { photoAccessHelper } from '@kit.MediaLibraryKit'; // 引入媒体库访问助手,用于选择照片
import { fileIo } from '@kit.CoreFileKit'; // 引入文件IO模块,用于文件操作
import { image } from '@kit.ImageKit'; // 引入图像处理模块,用于处理图像数据

/**
 * PhotoPickerClass 类提供了选择照片和根据URI创建PixelMap的功能
 */
export class PhotoPickerClass {
  // 静态属性,用于存储照片选择器实例
  static photoPicker: photoAccessHelper.PhotoViewPicker = new photoAccessHelper.PhotoViewPicker();

  /**
   * 异步选择一张照片并返回其URI
   * @returns 返回所选照片的URI
   */
  static async selectImageUri() {
    try {
      // 调用照片选择器选择照片,限制为图像类型且最大选择数为1
      const selectionResult = await PhotoPickerClass.photoPicker.select({
        MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE, // 指定MIME类型为图像
        maxSelectNumber: 1 // 设置最大选择数量为1
      });

      // 返回第一张照片的URI(因为maxSelectNumber为1,所以这里总是安全的)
      return selectionResult.photoUris[0];
    } catch (error) {
      // 处理可能的错误,例如用户取消选择或发生其他错误
      console.error('Failed to select image:', error);
      // 重新抛出错误,以便调用者可以处理
      return error.message
    }
  }

  /**
   * 根据提供的图片URI创建PixelMap对象
   * @param imgUri 图片的URI
   * @returns 返回创建的PixelMap对象
   */
  static creatPixMapByUri(imgUri: string): Promise<PixelMap | undefined> {
    return new Promise<PixelMap | undefined>((resolve, reject) => {
      try {
        // 以只读模式打开文件
        const file = fileIo.openSync(imgUri, fileIo.OpenMode.READ_ONLY);

        // 使用文件描述符创建图像源
        const imgSource = image.createImageSource(file.fd);

        // 从图像源同步创建PixelMap对象
        const imgPixMap = imgSource.createPixelMapSync();

        // 关闭文件(注意:在某些环境中,这可能不是必需的,因为createImageSource可能已接管文件)
        if (imgSource) {
          imgSource.release()
        }
        // 返回创建的PixelMap对象
        resolve(imgPixMap);
      } catch (error) {
        // 处理可能的错误,例如文件不存在或无法读取
        console.error('Failed to create PixelMap from URI:', imgUri, error);
        reject("创建pixelMap错误" + error.message)
      }
    })
  }
}
import { promptAction } from '@kit.ArkUI'
import { FaceDetectClass } from '../utils/FaceDetector'
import { PhotoPickerClass } from '../utils/PhotoPicker'

@Entry
@Component
struct Index {
  @State currentUri: string = ""
  @State dataValue: string = ""
  @State testPixMap: PixelMap | undefined = undefined
  @State cltData: string[] = []

  aboutToDisappear(): void {
    if (this.testPixMap) {
      // 释放资源
      this.testPixMap.release()
    }
  }

  build() {
    Row({ space: 15 }) {
      // 选择图片部分
      Column() {
        Column() {
          Image(this.currentUri)
            .width(400)
            .aspectRatio(1)
            .objectFit(ImageFit.Contain)
        }
        .justifyContent(FlexAlign.Center)
        .layoutWeight(1)
        .width("100%")

        Column({ space: 15 }) {
          Button("删除数据")
            .margin({ bottom: 30 })
            .fontSize(30)
            .width(300)
            .height(60)
            .onClick(() => {
              this.cltData.pop()
            })
          Button("选择照片")
            .margin({ bottom: 30 })
            .fontSize(30)
            .width(300)
            .height(60)
            .onClick(async () => {
              try {
                this.currentUri = await PhotoPickerClass.selectImageUri()
                this.testPixMap = await PhotoPickerClass.creatPixMapByUri(this.currentUri)
              } catch (err) {
                promptAction.showToast({
                  message: `Error + ${err.message}`
                })
              }
            })
        }
      }
      .height('100%')
      .layoutWeight(1)
      .border({ width: 2, color: Color.Blue })
      .borderRadius(10)

      // 识别人脸部分
      Column() {
        Column({ space: 10 }) {
          ForEach(this.cltData, (dataItem: string) => {
            Text(dataItem ? dataItem : "")
          })
        }
        .padding(15)
        .layoutWeight(1)
        .width("100%")

        Button("识别人脸")
          .margin({ bottom: 30 })
          .fontSize(30)
          .width(300)
          .height(60)
          .onClick(async () => {
            try {
              if (this.testPixMap) {
                this.dataValue = await FaceDetectClass.detectFace(this.testPixMap)
                this.cltData.push(this.dataValue)
                promptAction.showToast({
                  message: "识别成功"
                })
              }
            } catch (err) {
              promptAction.showToast({
                message: `识别错误____ ${err.message}`
              })
            }
          })
      }
      .height('100%')
      .layoutWeight(1)
      .border({ width: 2, color: Color.Blue })
      .borderRadius(10)
    }
    .padding(10)
    .height('100%')
    .width('100%')
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值