华为HarmonyOS实现人脸活体检测

两种模式

人脸活体检测分为静默活体检测和动作后提检测两种模式

场景介绍

人脸活体检测支持动作活体检测模式。

动作活体检测支持实时捕捉人脸,需要用户配合做指定动作就可以判断是真实活体,还是非活体攻击(比如:打印图片、人脸翻拍视频以及人脸面具等)。

注意

我们提供的活体检测是一项纯端侧算法、试用期免费的系统基础服务,推荐您使用在考勤打卡、辅助登录和实名认证等低危业务场景中。

因端侧算法在HarmonyOS NEXT/5.0.x尚未完成权威机构检测认证,鉴于支付和金融应用的高风险性,建议开发者基于现有的安全性,针对不同的功能场景进行风险评估和风控策略评估,并采取必要的安全措施。

图1 活体检测示意图

开发步骤

  1. 将实现人脸活体检测相关的类添加至工程。

     
      
    1. import { interactiveLiveness } from '@kit.VisionKit';

  2. 在module.json5文件中添加CAMERA权限,其中reason,abilities标签必填,配置方式参见requestPermissions标签说明

     
      
    1. "requestPermissions":[
    2. {
    3. "name": "ohos.permission.CAMERA",
    4. "reason": "$string:camera_desc",
    5. "usedScene": {"abilities": []}
    6. }
    7. ]

  3. 简单配置页面的布局,选择人脸活体检测验证完后的跳转模式。如果使用back跳转模式,表示的是在检测结束后使用router.back()返回。如果使用replace跳转模式,表示的是检测结束后使用router.replaceUrl()去跳转相应页面。默认选择的是replace跳转模式。

     
      
    1. Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
    2. Text("验证完的跳转模式:")
    3. .fontSize(18)
    4. .width("25%")
    5. Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
    6. Row() {
    7. Radio({ value: "replace", group: "routeMode" }).checked(true)
    8. .height(24)
    9. .width(24)
    10. .onChange((isChecked: boolean) => {
    11. this.routeMode = "replace"
    12. })
    13. Text("replace")
    14. .fontSize(16)
    15. }
    16. .margin({ right: 15 })
    17. Row() {
    18. Radio({ value: "back", group: "routeMode" }).checked(false)
    19. .height(24)
    20. .width(24)
    21. .onChange((isChecked: boolean) => {
    22. this.routeMode = "back";
    23. })
    24. Text("back")
    25. .fontSize(16)
    26. }
    27. }
    28. .width("75%")
    29. }

  4. 如果选择动作活体模式,可填写验证的动作个数。

     
      
    1. Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
    2. Text("动作数量:")
    3. .fontSize(18)
    4. .width("25%")
    5. TextInput({
    6. placeholder: this.actionsNum != 0 ? this.actionsNum.toString() : "动作数量为3或4个"
    7. })
    8. .type(InputType.Number)
    9. .placeholderFont({
    10. size: 18,
    11. weight: FontWeight.Normal,
    12. family: "HarmonyHeiTi",
    13. style: FontStyle.Normal
    14. })
    15. .fontSize(18)
    16. .fontWeight(FontWeight.Bold)
    17. .fontFamily("HarmonyHeiTi")
    18. .fontStyle(FontStyle.Normal)
    19. .width("65%")
    20. .onChange((value: string) => {
    21. this.actionsNum = Number(value) as interactiveLiveness.ActionsNumber;
    22. })
    23. }

  5. 点击“开始检测“按钮,触发点击事件。

     
      
    1. Button("开始检测", { type: ButtonType.Normal, stateEffect: true })
    2. .width(192)
    3. .height(40)
    4. .fontSize(16)
    5. .backgroundColor(0x317aff)
    6. .borderRadius(20)
    7. .margin({
    8. bottom: 56
    9. })
    10. .onClick(() => {
    11. this.privateStartDetection();
    12. })

  6. 触发CAMERA权限校验。

     
      
    1. // 校验CAMERA权限
    2. private privateStartDetection() {
    3. abilityAccessCtrl.createAtManager().requestPermissionsFromUser(this.context, this.array).then((res) => {
    4. for (let i = 0; i < res.permissions.length; i++) {
    5. if (res.permissions[i] === "ohos.permission.CAMERA" && res.authResults[i] === 0) {
    6. this.privateRouterLibrary();
    7. }
    8. }
    9. }).catch((err: BusinessError) => {
    10. hilog.error(0x0001, "LivenessCollectionIndex", `Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
    11. })
    12. }

  7. 配置人脸活体检测控件的配置项InteractiveLivenessConfig,用于跳转到人脸活体检测控件。

    配置中具体的参数可参考API文档
     
      
    1. let routerOptions: interactiveLiveness.InteractiveLivenessConfig = {
    2. isSilentMode: this.isSilentMode as interactiveLiveness.DetectionMode,
    3. routeMode: this.routeMode as interactiveLiveness.RouteRedirectionMode,
    4. actionsNum: this.actionsNum
    5. };

  8. 调用interactiveLiveness的startLivenessDetection接口,判断跳转到人脸活体检测控件是否成功。

     
      
    1. // 跳转到人脸活体检测控件
    2. private privateRouterLibrary() {
    3. if (canIUse("SystemCapability.AI.Component.LivenessDetect")) {
    4. interactiveLiveness.startLivenessDetection(routerOptions).then((DetectState: boolean) => {
    5. hilog.info(0x0001, "LivenessCollectionIndex", `Succeeded in jumping.`);
    6. }).catch((err: BusinessError) => {
    7. hilog.error(0x0001, "LivenessCollectionIndex", `Failed to jump. Code:${err.code},message:${err.message}`);
    8. })
    9. } else {
    10. hilog.error(0x0001, "LivenessCollectionIndex", 'this api is not supported on this device');
    11. }
    12. }

  9. 检测结束后回到当前界面,可调用interactiveLiveness的getInteractiveLivenessResult接口,验证人脸活体检测的结果。

     
      
    1. // 获取验证结果
    2. private getDetectionResultInfo() {
    3. // getInteractiveLivenessResult接口调用完会释放资源
    4. if (canIUse("SystemCapability.AI.Component.LivenessDetect")) {
    5. let resultInfo = interactiveLiveness.getInteractiveLivenessResult();
    6. resultInfo.then(data => {
    7. this.resultInfo = data;
    8. }).catch((err: BusinessError) => {
    9. this.failResult = {
    10. "code": err.code,
    11. "message": err.message
    12. }
    13. })
    14. } else {
    15. hilog.error(0x0001, "LivenessCollectionIndex", 'this api is not supported on this device');
    16. }
    17. }

开发实例

 
  1. import { common, abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
  2. import { interactiveLiveness } from '@kit.VisionKit';
  3. import { BusinessError } from '@kit.BasicServicesKit';
  4. import { hilog } from '@kit.PerformanceAnalysisKit';
  5. @Entry
  6. @Component
  7. struct LivenessIndex {
  8. private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
  9. private array: Array<Permissions> = ["ohos.permission.CAMERA"];
  10. @State actionsNum: number = 0;
  11. @State isSilentMode: string = "INTERACTIVE_MODE";
  12. @State routeMode: string = "replace";
  13. @State resultInfo: interactiveLiveness.InteractiveLivenessResult = {
  14. livenessType: 0
  15. };
  16. @State failResult: Record<string, number | string> = {
  17. "code": 1008302000,
  18. "message": ""
  19. };
  20. build() {
  21. Stack({
  22. alignContent: Alignment.Top
  23. }) {
  24. Column() {
  25. Row() {
  26. Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
  27. Text("验证完的跳转模式:")
  28. .fontSize(18)
  29. .width("25%")
  30. Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
  31. Row() {
  32. Radio({ value: "replace", group: "routeMode" }).checked(true)
  33. .height(24)
  34. .width(24)
  35. .onChange((isChecked: boolean) => {
  36. this.routeMode = "replace"
  37. })
  38. Text("replace")
  39. .fontSize(16)
  40. }
  41. .margin({ right: 15 })
  42. Row() {
  43. Radio({ value: "back", group: "routeMode" }).checked(false)
  44. .height(24)
  45. .width(24)
  46. .onChange((isChecked: boolean) => {
  47. this.routeMode = "back";
  48. })
  49. Text("back")
  50. .fontSize(16)
  51. }
  52. }
  53. .width("75%")
  54. }
  55. }
  56. .margin({ bottom: 30 })
  57. Row() {
  58. Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
  59. Text("动作数量:")
  60. .fontSize(18)
  61. .width("25%")
  62. TextInput({
  63. placeholder: this.actionsNum != 0 ? this.actionsNum.toString() : "动作数量为3或4个"
  64. })
  65. .type(InputType.Number)
  66. .placeholderFont({
  67. size: 18,
  68. weight: FontWeight.Normal,
  69. family: "HarmonyHeiTi",
  70. style: FontStyle.Normal
  71. })
  72. .fontSize(18)
  73. .fontWeight(FontWeight.Bold)
  74. .fontFamily("HarmonyHeiTi")
  75. .fontStyle(FontStyle.Normal)
  76. .width("65%")
  77. .onChange((value: string) => {
  78. this.actionsNum = Number(value) as interactiveLiveness.ActionsNumber;
  79. })
  80. }
  81. }
  82. }
  83. .margin({ left: 24, top: 80 })
  84. .zIndex(1)
  85. Stack({
  86. alignContent: Alignment.Bottom
  87. }) {
  88. if (this.resultInfo?.mPixelMap) {
  89. Image(this.resultInfo?.mPixelMap)
  90. .width(260)
  91. .height(260)
  92. .align(Alignment.Center)
  93. .margin({ bottom: 260 })
  94. Circle()
  95. .width(300)
  96. .height(300)
  97. .fillOpacity(0)
  98. .strokeWidth(60)
  99. .stroke(Color.White)
  100. .margin({ bottom: 250, left: 0 })
  101. }
  102. Text(this.resultInfo.mPixelMap ?
  103. "检测成功" :
  104. this.failResult.code != 1008302000 ?
  105. "检测失败" :
  106. "")
  107. .width("100%")
  108. .height(26)
  109. .fontSize(20)
  110. .fontColor("#000000")
  111. .fontFamily("HarmonyHeiTi")
  112. .margin({ top: 50 })
  113. .textAlign(TextAlign.Center)
  114. .fontWeight("Medium")
  115. .margin({ bottom: 240 })
  116. if(this.failResult.code != 1008302000) {
  117. Text(this.failResult.message as string)
  118. .width("100%")
  119. .height(26)
  120. .fontSize(16)
  121. .fontColor(Color.Gray)
  122. .textAlign(TextAlign.Center)
  123. .fontFamily("HarmonyHeiTi")
  124. .fontWeight("Medium")
  125. .margin({ bottom: 200 })
  126. }
  127. Button("开始检测", { type: ButtonType.Normal, stateEffect: true })
  128. .width(192)
  129. .height(40)
  130. .fontSize(16)
  131. .backgroundColor(0x317aff)
  132. .borderRadius(20)
  133. .margin({
  134. bottom: 56
  135. })
  136. .onClick(() => {
  137. this.privateStartDetection();
  138. })
  139. }
  140. .height("100%")
  141. }
  142. }
  143. onPageShow() {
  144. this.resultRelease();
  145. this.getDetectionResultInfo();
  146. }
  147. // 跳转到人脸活体检测控件
  148. private privateRouterLibrary() {
  149. let routerOptions: interactiveLiveness.InteractiveLivenessConfig = {
  150. isSilentMode: this.isSilentMode as interactiveLiveness.DetectionMode,
  151. routeMode: this.routeMode as interactiveLiveness.RouteRedirectionMode,
  152. actionsNum: this.actionsNum
  153. }
  154. if (canIUse("SystemCapability.AI.Component.LivenessDetect")) {
  155. interactiveLiveness.startLivenessDetection(routerOptions).then((DetectState: boolean) => {
  156. hilog.info(0x0001, "LivenessCollectionIndex", `Succeeded in jumping.`);
  157. }).catch((err: BusinessError) => {
  158. hilog.error(0x0001, "LivenessCollectionIndex", `Failed to jump. Code:${err.code},message:${err.message}`);
  159. })
  160. } else {
  161. hilog.error(0x0001, "LivenessCollectionIndex", 'this api is not supported on this device');
  162. }
  163. }
  164. // 校验CAMERA权限
  165. private privateStartDetection() {
  166. abilityAccessCtrl.createAtManager().requestPermissionsFromUser(this.context, this.array).then((res) => {
  167. for (let i = 0; i < res.permissions.length; i++) {
  168. if (res.permissions[i] === "ohos.permission.CAMERA" && res.authResults[i] === 0) {
  169. this.privateRouterLibrary();
  170. }
  171. }
  172. }).catch((err: BusinessError) => {
  173. hilog.error(0x0001, "LivenessCollectionIndex", `Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
  174. })
  175. }
  176. // 获取验证结果
  177. private getDetectionResultInfo() {
  178. // getInteractiveLivenessResult接口调用完会释放资源
  179. if (canIUse("SystemCapability.AI.Component.LivenessDetect")) {
  180. let resultInfo = interactiveLiveness.getInteractiveLivenessResult();
  181. resultInfo.then(data => {
  182. this.resultInfo = data;
  183. }).catch((err: BusinessError) => {
  184. this.failResult = {
  185. "code": err.code,
  186. "message": err.message
  187. }
  188. })
  189. } else {
  190. hilog.error(0x0001, "LivenessCollectionIndex", 'this api is not supported on this device');
  191. }
  192. }
  193. // result release
  194. private resultRelease() {
  195. this.resultInfo = {
  196. livenessType: 0
  197. }
  198. this.failResult = {
  199. "code": 1008302000,
  200. "message": ""
  201. }
  202. }
  203. }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青瓷代码世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值