如何使用HarmonyOS面部识别能力 精华

奶盖
发布于 2021-6-15 10:12
2.5w浏览
5收藏

1. 介绍

本篇Codelab涉及两个HarmonyOS子系统(生物特征识别相机),详情请参考生物特征识别和相机。本篇要介绍的是在人脸识别认证成功后,跳转到模拟相机的页面的实现方案。在这个应用中,我们通过调用相关接口,检查设备是否具有人脸识别的能力、进行人脸识别、打开相机,从而实现相关的功能。

 🕮 说明
由于人脸录入不开放给三方应用调用,因此进行人脸识别前需要在手机设置中录入人脸信息。

2. 搭建HarmonyOS环境
安装DevEco Studio,详情请参考DevEco Studio下载
设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:

  1.如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
  2.如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境

🕮 说明
人脸识别需要在真机上运行,因此需要提前申请证书和profile文件,详情请参考申请证书和profile

3. 代码结构解读
本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在参考中提供下载方式,接下来我们会用一小节来讲解整个工程的代码结构。

 ● slice:应用页面

 ◊ MainAbilitySlice:人脸识别的操作界面,包含校验设备是否支持人脸识别功能,人脸识别,人脸识别结果回显以及人脸识别成功后打开相机的功能。
 ◊ OpenCameraSlice:模拟相机的操作页面,包含打卡相机,拍照,存储相片以及切换摄像头的功能。
 ● util:工具类

 ◊ FaceAuthResult:人脸认证结果的返回码对应的常量。
 ◊ LogUtils:日志记录工具类。
 ◊ PermissionBridge:权限申请回调。
 ● resources:存放工程使用到的资源文件。

 ◊ resources\base\layout下存放xml布局文件;
 ◊ resources\base\media下存放图片资源。
 ● config.json:工程相关配置文件。

如何使用HarmonyOS面部识别能力-鸿蒙开发者社区

4. 页面布局
人脸识别页面
本页面主要由DirectionalLayout布局和Button、Text组件共同来构成。其中两个Button组件,作用分别为开始人脸识别和取消人脸识别;两个Text组件,作用分别为显示标题和显示返回的人脸识别结果。在resources\layout\ability_main.xml下有如下代码:

<?xml version="1.0" encoding="utf-8"?> 
<DirectionalLayout 
    xmlns:ohos="http://schemas.huawei.com/res/ohos" 
    ohos:height="match_parent" 
    ohos:width="match_parent" 
    ohos:orientation="vertical"> 
 
    <Text 
        ohos:id="$+id:text_helloworld1" 
        ohos:height="match_content" 
        ohos:width="match_content" 
        ohos:background_element="$graphic:background_ability_main" 
        ohos:layout_alignment="horizontal_center" 
        ohos:left_padding="80vp" 
        ohos:right_padding="80vp" 
        ohos:text="生物特征识别" 
        ohos:text_size="30fp" 
        ohos:top_padding="100vp" 
        /> 
 
    <Text 
        ohos:id="$+id:text_status" 
        ohos:height="100vp" 
        ohos:width="match_parent" 
        ohos:background_element="$graphic:background_ability_main" 
        ohos:layout_alignment="center" 
        ohos:text_alignment="center" 
        ohos:max_text_lines="3" 
        ohos:multiple_lines="true" 
        ohos:margin="5vp" 
        ohos:text="" 
        ohos:text_font="serif" 
        ohos:text_size="30fp" 
        ohos:top_padding="5vp" 
        ohos:visibility="invisible" 
        /> 
 
    <Button 
        ohos:id="$+id:button_start" 
        ohos:height="60vp" 
        ohos:width="match_parent" 
        ohos:align_parent_bottom="true" 
        ohos:background_element="$graphic:button_element" 
        ohos:layout_alignment="horizontal_center" 
        ohos:left_padding="40vp" 
        ohos:right_padding="40vp" 
        ohos:text="开始人脸识别" 
        ohos:text_color="#000000" 
        ohos:text_size="30fp" 
        ohos:left_margin="15vp" 
        ohos:right_margin="15vp" 
        ohos:top_margin="200vp" 
       /> 
 
    <Button 
        ohos:id="$+id:button_cancel" 
        ohos:height="60vp" 
        ohos:width="match_parent" 
        ohos:align_parent_bottom="true" 
        ohos:background_element="$graphic:button_element" 
        ohos:layout_alignment="horizontal_center" 
        ohos:left_padding="40vp" 
        ohos:right_padding="40vp" 
        ohos:text="取消人脸识别" 
        ohos:margin="15vp" 
        ohos:text_color="#000000" 
        ohos:text_size="30fp" 
        /> 
</DirectionalLayout>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.

模拟相机页面
此页面主要由DirectionalLayout、DependentLayout布局和Image组件组成,其中三个Image组件作为图标,左右分别为返回、开始拍照和切换摄像头。在resources\layout\ability_open_camera.xml下有如下代码:

<?xml version="1.0" encoding="utf-8"?> 
<DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" 
                   ohos:height="match_parent" 
                   ohos:width="match_parent"> 
 
    <DependentLayout 
            ohos:id="$+id:root_container" 
            ohos:height="match_parent" 
            ohos:width="match_parent"> 
 
        <DirectionalLayout 
                ohos:id="$+id:surface_container" 
                ohos:height="match_parent" 
                ohos:width="match_parent" /> 
        <DirectionalLayout 
                ohos:width="match_parent" 
                ohos:height="match_content" 
                ohos:align_parent_bottom="$+id:root_container" 
                ohos:bottom_margin="30vp" 
                ohos:orientation="horizontal"> 
 
            <Image 
                    ohos:id="$+id:exit" 
                    ohos:height="match_content" 
                    ohos:width="match_parent" 
                    ohos:weight="1" 
                    ohos:enabled="false" 
                    ohos:layout_alignment="vertical_center" 
                    ohos:scale_mode="center" 
                    ohos:image_src="$media:ic_camera_back" /> 
 
            <Image 
                    ohos:id="$+id:tack_picture_btn" 
                    ohos:height="match_content" 
                    ohos:width="match_parent" 
                    ohos:weight="1" 
                    ohos:enabled="false" 
                    ohos:layout_alignment="vertical_center" 
                    ohos:scale_mode="center" 
                    ohos:image_src="$media:ic_camera_photo" /> 
 
            <Image 
                    ohos:id="$+id:switch_camera_btn" 
                    ohos:height="match_content" 
                    ohos:width="match_parent" 
                    ohos:weight="1" 
                    ohos:enabled="false" 
                    ohos:layout_alignment="vertical_center" 
                    ohos:scale_mode="center" 
                    ohos:image_src="$media:ic_camera_switch" /> 
        </DirectionalLayout> 
    </DependentLayout> 
</DirectionalLayout>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.

 🕮 说明

布局文件中使用到的background_element样式,在entry\src\main\resources\base\graphic下有做定义,详情可以参考完整代码。

5. 相关权限
为了保证应用的成功运行,需要在config.json中声明需要如下权限:

"reqPermissions": [ 
      { 
        "name": "ohos.permission.ACCESS_BIOMETRIC" 
      }, 
      { 
        "name": "ohos.permission.CAMERA" 
      }, 
      { 
        "name": "ohos.permission.WRITE_USER_STORAGE" 
      } 
    ]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

此外还需要在OpenCamera的onStart()方法中向用户申请权限,代码示例如下:

private void requestPermission() { 
    String[] permissions = { 
        // 存储权限 
        SystemPermission.WRITE_USER_STORAGE, 
        // 相机权限 
        SystemPermission.CAMERA 
    }; 
    List<String> permissionFiltereds = Arrays.stream(permissions) 
            .filter(permission -> verifySelfPermission(permission) != IBundleManager.PERMISSION_GRANTED) 
            .collect(Collectors.toList()); 
    if (permissionFiltereds.isEmpty()) { 
        PermissionBridge.getHandler().sendEvent(EVENT_PERMISSION_GRANTED); 
        return; 
    } 
    requestPermissionsFromUser(permissionFiltereds.toArray(new String[permissionFiltereds.size()]), 
            PERMISSION_REQUEST_CODE); 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

6. 人脸识别业务逻辑
在人脸识别页面(ability_main.xml)中,我们添加了开始人脸识别和取消人脸识别的Button,通过监听不同Button的点击事件,从而实现不同的业务逻辑。下面我们将分别介绍开始人脸识别和取消人脸识别的业务逻辑。

 

开始人脸识别业务逻辑
在开始人脸识别之前,我们需要校验当前设备(手机)是否具备人脸识别能力,代码示例如下:

private void createStartListener() { 
    // 提示用户人脸识别时将人脸对准摄像头 
    getAndSetText(ResourceTable.Id_text_status, NO_FACE_RET, true); 
    try { 
        // 创建生物识别对象 
        mBiometricAuthentication =  
            BiometricAuthentication.getInstance(MainAbility.getMainAbility()); 
        // 检验设备是否有人脸识别功能 
        int hasAuth = mBiometricAuthentication.checkAuthenticationAvailability( 
            BiometricAuthentication.AuthType.AUTH_TYPE_BIOMETRIC_FACE_ONLY, 
            BiometricAuthentication.SecureLevel.SECURE_LEVEL_S2, true); 
        if (hasAuth == BiometricAuthentication.BA_CHECK_SUPPORTED) { 
            // 如果支持人脸识别,则开启线程进行人脸识别              
            ThreadPoolExecutor pool = new ThreadPoolExecutor( 
                POOL_CORE_SIZE, POOL_MAX_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, 
                new LinkedBlockingQueue<>(QUEUE_SIZE), new  
                ThreadPoolExecutor.DiscardOldestPolicy()); 
            pool.submit(runnable); 
        } else { 
            // 人脸识别不支持或存在其他问题 ,直接在页面显示结果, 
            // 在主线程不需要通过EventHandler发送回显任务 
            int retExcAuth = getRetExcAuth(hasAuth); 
            getAndSetText(ResourceTable.Id_text_status, retExcAuth, true); 
        } 
    } catch (IllegalAccessException e) { 
        LogUtils.error("createStartBtn", "IllegalAccessException when start auth"); 
    } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

 🕮 说明

 ● checkAuthenticationAvailability方法参数说明:

        1.BiometricAuthentication.AuthType中有三个类别,分别为

        AUTH_TYPE_BIOMETRIC_FINGERPRINT_ONLY指纹识别,AUTH_TYPE_BIOMETRIC_FACE_ONLY脸部识别以及AUTH_TYPE_BIOMETRIC_ALL指纹和面部。
 ● BiometricAuthentication.SecureLevel验证级别,3D人脸识别支持S3及以下级别的验证;2D人脸识别支持S2及以下级别的验证

 

由于人脸识别是耗时操作,所以这里新起了线程去做认证,代码示例如下: 

/** 
 * 新建线程进行认证,避免阻塞其他任务 
 */ 
private Runnable runnable = new Runnable() { 
    private void initHandler() { 
        runner = EventRunner.getMainEventRunner(); 
        if (runner == null) { 
            return; 
        } 
        myEventHandle = new MyEventHandle(runner); 
    } 
 
    @Override 
    public void run() { 
        // 初始化myEventHandle 
        initHandler(); 
        // 开始认证 
        startAuth(); 
    } 
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

开始人脸识别,代码示例如下:

private void startAuth() { 
    // retExcAuth 0认证成功 1:比对失败 2:取消认证 3:认证超时 4:打开相机失败 
    // 5:busy,可能上一个认证没有结束 6:入参错误 7:人脸认证锁定(达到错误认证次数了) 
    // 8:没有录入人脸 100:其他错误。 
    int retExcAuth = mBiometricAuthentication.execAuthenticationAction( 
            BiometricAuthentication.AuthType.AUTH_TYPE_BIOMETRIC_FACE_ONLY, 
            BiometricAuthentication.SecureLevel.SECURE_LEVEL_S2, 
            true, false, null); 
    // 将认证结果发给主线程处理 
    myEventHandler.sendEvent(retExcAuth); 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

由于我们在线程中执行的人脸识别操作,需要通过EventHandler将识别结果发送到主线程中,并将识别结果显示在页面中,代码示例如下:

/** 
 * 事件分发器 
  */ 
private class MyEventHandle extends EventHandler { 
    MyEventHandle(EventRunner runner) throws IllegalArgumentException { 
        super(runner); 
    } 
 
    @Override 
    protected void processEvent(InnerEvent event) { 
        super.processEvent(event); 
        int eventId = event.eventId; 
        getAndSetText(ResourceTable.Id_text_status, eventId, true); 
    } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

取消人脸识别
点击取消人脸识别Button,触发取消人脸识别操作,代码示例如下:

private void createCancelBtn() { 
    // 创建点击事件 
    Component component = findComponentById(ResourceTable.Id_button_cancel); 
    // 创建按钮 
    Button cancelBtn = null; 
    if (component != null && component instanceof Button) { 
        cancelBtn = (Button) component; 
        cancelBtn.setClickedListener(view -> { 
            if (mBiometricAuthentication != null) { 
                // 调用取消接口 
                int result = mBiometricAuthentication.cancelAuthenticationAction(); 
                LogUtils.info("createCancelBtn:", result + ""); 
            } 
        }); 
    } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

页面跳转
人脸识别成功后,跳转到模拟相机页面,代码示例如下:

private void toAuthAfterPage() { 
    Intent secondIntent = new Intent(); 
    // 指定待启动FA的bundleName和abilityName 
    Operation operation = new Intent.OperationBuilder() 
            .withDeviceId("") 
            .withBundleName(getBundleName()) 
            .withAbilityName(OpenCamera.class.getName()) 
            .build(); 
    secondIntent.setOperation(operation); 
    // startAbility接口实现启动另一个页面 
    startAbility(secondIntent); 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

7. 相机相关业务逻辑
在模拟相机页面(ability_open_camera.xml)中,包含打开相机和切换前后置摄像头的功能,我们下面将逐一介绍。

初始化SurfaceProvider
用户授权后,开始初始化SurfaceProvider,代码示例如下:

private void initSurface() { 
    surfaceProvider = new SurfaceProvider(this); 
    DirectionalLayout.LayoutConfig params = new DirectionalLayout.LayoutConfig( 
            ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT); 
    surfaceProvider.setLayoutConfig(params); 
    surfaceProvider.pinToZTop(false); 
    // 添加SurfaceCallBack回调 
    surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack()); 
    // 将SurfaceProvider加入到布局中 
    Component component = findComponentById(ResourceTable.Id_surface_container); 
    if (component instanceof ComponentContainer) { 
        ((ComponentContainer) component).addComponent(surfaceProvider); 
    } 
}	
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

实现SurfaceOps.Callback回调,当Surface创建时,执行打开相机的操作,代码示例如下:

/** 
 * SurfaceCallBack,Surface回调
 */ 
class SurfaceCallBack implements SurfaceOps.Callback { 
    @Override 
    public void surfaceCreated(SurfaceOps callbackSurfaceOps) { 
        if (callbackSurfaceOps != null) { 
            callbackSurfaceOps.setFixedSize(SCREEN_HEIGHT, SCREEN_WIDTH); 
        } 
        openCamera(); 
    } 
 
    @Override 
    public void surfaceChanged(SurfaceOps callbackSurfaceOps, int format, int width, int height) { 
    } 
 
    @Override 
    public void surfaceDestroyed(SurfaceOps callbackSurfaceOps) { 
    } 
}	
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

打开相机
创建surface后触发surfaceCreated回调,执行打开相机的操作。打开相机并添加相片接收的监听,代码示例如下:

private void openCamera() { 
    CameraKit cameraKit = CameraKit.getInstance(getApplicationContext()); 
    String[] cameraLists = cameraKit.getCameraIds(); 
    String cameraId = cameraLists.length > 1 && isCameraRear ? cameraLists[1] : cameraLists[0]; 
    CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl(); 
    cameraKit.createCamera(cameraId, cameraStateCallback, creamEventHandler); 
} 
/** 
 * CameraStateCallbackImpl 相机状态回调 
 */ 
class CameraStateCallbackImpl extends CameraStateCallback { 
    CameraStateCallbackImpl() { 
    } 
 
    @Override 
    public void onCreated(Camera camera) { 
        // 获取预览 
        previewSurface = surfaceProvider.getSurfaceOps().get().getSurface(); 
        if (previewSurface == null) { 
            LogUtils.error(TAG, "create camera filed, preview surface is null"); 
            return; 
        } 
        // Wait until the preview surface is created. 
        try { 
            Thread.sleep(SLEEP_TIME); 
        } catch (InterruptedException exception) { 
            LogUtils.warn(TAG, "Waiting to be interrupted"); 
        } 
        CameraConfig.Builder cameraConfigBuilder = camera.getCameraConfigBuilder(); 
        // 配置预览 
        cameraConfigBuilder.addSurface(previewSurface);    
        camera.configure(cameraConfigBuilder.build()); 
        cameraDevice = camera; 
        enableImageGroup(); 
    } 
 
    @Override 
    public void onConfigured(Camera camera) { 
        FrameConfig.Builder framePreviewConfigBuilder 
                = camera.getFrameConfigBuilder(Camera.FrameConfigType.FRAME_CONFIG_PREVIEW); 
        framePreviewConfigBuilder.addSurface(previewSurface); 
        // 开启循环捕捉 
        camera.triggerLoopingCapture(framePreviewConfigBuilder.build()); 
    } 
 
    private void enableImageGroup() { 
        if (!exitImage.isEnabled()) { 
            exitImage.setEnabled(true); 
            switchCameraImage.setEnabled(true); 
        } 
    } 
}	
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.

切换前后置摄像头
点击切换摄像头图标后,执行切换前后置摄像头操作,代码示例如下:

private void switchClicked() { 
    isCameraRear = !isCameraRear; 
    openCamera(); 
}	
  • 1.
  • 2.
  • 3.
  • 4.

8. 效果展示
人脸识别FA(MainAbilitySlice)完成了检验设备是否支持人脸识别,人脸识别,人脸识别结果显示,成功后跳转到打开相机的FA(OpenCameraSlice);相机FA实现了相机的打开,拍照,相片存储,摄像头切换的功能。具体效果图如下:
人脸识别初始页面:

如何使用HarmonyOS面部识别能力-鸿蒙开发者社区人脸识别结果显示:

如何使用HarmonyOS面部识别能力-鸿蒙开发者社区

相机页面:

如何使用HarmonyOS面部识别能力-鸿蒙开发者社区

 

9. 完整代码示例
编写布局与样式
1.base/graphic/background_ability_main.xml

<?xml version="1.0" encoding="UTF-8" ?> 
<shape xmlns:ohos="http://schemas.huawei.com/res/ohos" 
       ohos:shape="rectangle"> 
    <solid 
        ohos:color="#FFFFFF"/> 
</shape>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

2.base/graphic/button_element.xml

<?xml version="1.0" encoding="utf-8"?> 
<shape 
    xmlns:ohos="http://schemas.huawei.com/res/ohos" 
    ohos:shape="rectangle"> 
    <corners 
        ohos:radius="8vp"/> 
    <solid 
        ohos:color="#FF007DFE"/> 
</shape>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

3.base/layout/ability_main.xml

<?xml version="1.0" encoding="utf-8"?> 
<DirectionalLayout 
    xmlns:ohos="http://schemas.huawei.com/res/ohos" 
    ohos:height="match_parent" 
    ohos:width="match_parent" 
    ohos:orientation="vertical"> 
 
    <Text 
        ohos:id="$+id:text_helloworld1" 
        ohos:height="match_content" 
        ohos:width="match_content" 
        ohos:background_element="$graphic:background_ability_main" 
        ohos:layout_alignment="horizontal_center" 
        ohos:left_padding="80vp" 
        ohos:right_padding="80vp" 
        ohos:text="生物特征识别" 
        ohos:text_size="30fp" 
        ohos:top_padding="100vp" 
        /> 
 
    <Text 
        ohos:id="$+id:text_status" 
        ohos:height="100vp" 
        ohos:width="match_parent" 
        ohos:background_element="$graphic:background_ability_main" 
        ohos:layout_alignment="center" 
        ohos:text_alignment="center" 
        ohos:max_text_lines="3" 
        ohos:multiple_lines="true" 
        ohos:margin="5vp" 
        ohos:text="" 
        ohos:text_font="serif" 
        ohos:text_size="30fp" 
        ohos:top_padding="5vp" 
        ohos:visibility="invisible" 
        /> 
 
    <Button 
        ohos:id="$+id:button_start" 
        ohos:height="60vp" 
        ohos:width="match_parent" 
        ohos:align_parent_bottom="true" 
        ohos:background_element="$graphic:button_element" 
        ohos:layout_alignment="horizontal_center" 
        ohos:left_padding="40vp" 
        ohos:right_padding="40vp" 
        ohos:text="开始人脸识别" 
        ohos:text_color="#000000" 
        ohos:text_size="30fp" 
        ohos:left_margin="15vp" 
        ohos:right_margin="15vp" 
        ohos:top_margin="200vp" 
       /> 
 
    <Button 
        ohos:id="$+id:button_cancel" 
        ohos:height="60vp" 
        ohos:width="match_parent" 
        ohos:align_parent_bottom="true" 
        ohos:background_element="$graphic:button_element" 
        ohos:layout_alignment="horizontal_center" 
        ohos:left_padding="40vp" 
        ohos:right_padding="40vp" 
        ohos:text="取消人脸识别" 
        ohos:margin="15vp" 
        ohos:text_color="#000000" 
        ohos:text_size="30fp" 
        /> 
</DirectionalLayout>

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.

4.base/layout/ability_open_camera.xml

<?xml version="1.0" encoding="utf-8"?> 
<DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" 
                   ohos:height="match_parent" 
                   ohos:width="match_parent"> 
 
    <DependentLayout 
        ohos:id="$+id:root_container" 
        ohos:height="match_parent" 
        ohos:width="match_parent"> 
 
        <DirectionalLayout 
            ohos:id="$+id:surface_container" 
            ohos:height="match_parent" 
            ohos:width="match_parent" /> 
        <DirectionalLayout 
            ohos:width="match_parent" 
            ohos:height="match_content" 
            ohos:align_parent_bottom="$+id:root_container" 
            ohos:bottom_margin="30vp" 
            ohos:orientation="horizontal"> 
 
            <Image 
                ohos:id="$+id:exit" 
                ohos:height="170px" 
                ohos:width="match_parent" 
                ohos:weight="1" 
                ohos:enabled="false" 
                ohos:layout_alignment="vertical_center" 
                ohos:scale_mode="center" 
                ohos:image_src="$media:ic_camera_back" /> 
 
            <Image 
                ohos:id="$+id:tack_picture_btn" 
                ohos:height="170px" 
                ohos:width="match_parent" 
                ohos:weight="1" 
                ohos:enabled="false" 
                ohos:layout_alignment="vertical_center" 
                ohos:scale_mode="center" 
                ohos:image_src="$media:ic_camera_photo" /> 
 
            <Image 
                ohos:id="$+id:switch_camera_btn" 
                ohos:height="170px" 
                ohos:width="match_parent" 
                ohos:weight="1" 
                ohos:enabled="false" 
                ohos:layout_alignment="vertical_center" 
                ohos:scale_mode="zoom_center" 
                ohos:image_src="$media:ic_camera_switch" /> 
        </DirectionalLayout> 
    </DependentLayout> 
</DirectionalLayout>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.

 

功能逻辑代码
1.com/huawei/cookbook/slice/MainAbilitySlice.java

package com.huawei.cookbook.slice; 
 
import com.huawei.cookbook.MainAbility; 
import com.huawei.cookbook.OpenCamera; 
import com.huawei.cookbook.ResourceTable; 
import com.huawei.cookbook.util.FaceAuthResult; 
import com.huawei.cookbook.util.LogUtils; 
 
import ohos.aafwk.ability.AbilitySlice; 
import ohos.aafwk.content.Intent; 
import ohos.aafwk.content.Operation; 
import ohos.agp.components.Button; 
import ohos.agp.components.Component; 
import ohos.agp.components.Text; 
import ohos.agp.utils.Color; 
import ohos.biometrics.authentication.BiometricAuthentication; 
import ohos.eventhandler.EventHandler; 
import ohos.eventhandler.EventRunner; 
import ohos.eventhandler.InnerEvent; 
 
import java.util.concurrent.LinkedBlockingQueue; 
import java.util.concurrent.ThreadPoolExecutor; 
import java.util.concurrent.TimeUnit; 
 
/** 
 * MainAbilitySlice 
 * 
 * @since 2021-04-12 
 */ 
public class MainAbilitySlice extends AbilitySlice { 
    private static final int POOL_CORE_SIZE = 2; 
    private static final int POOL_MAX_SIZE = 5; 
    private static final int NO_FACE_RET = -1; 
    private static final int KEEP_ALIVE_TIME = 3; 
    private static final int QUEUE_SIZE = 6; 
    private static final int RET_NOT_SUPPORTED = 1; 
    private static final int RET_SAFE_LEVEL_NOT_SUPPORTED = 2; 
    private static final int RET_NOT_LOCAL = 3; 
    private EventRunner runner; 
    private MyEventHandle myEventHandle; 
    private BiometricAuthentication mBiometricAuthentication; 
    /** 
     * 新建线程进行认证,避免阻塞其他任务 
     */ 
    private Runnable runnable = new Runnable() { 
        private void initHandler() { 
            runner = EventRunner.getMainEventRunner(); 
            if (runner == null) { 
                return; 
            } 
            myEventHandle = new MyEventHandle(runner); 
        } 
 
        @Override 
        public void run() { 
            // 初始化myEventHandle 
            initHandler(); 
            // 开始认证 
            startAuth(); 
        } 
    }; 
 
    /** 
     * onStart 
     * 
     * @param intent intent 
     */ 
    @Override 
    public void onStart(Intent intent) { 
        super.onStart(intent); 
        super.setUIContent(ResourceTable.Layout_ability_main); 
        // 创建开始认证按钮,并添加点击事件 
        createStartBtn(); 
        // 创建取消认证按钮,并添加点击事件 
        createCancelBtn(); 
    } 
 
    /** 
     * 创建取消按钮 
     */ 
    private void createCancelBtn() { 
        // 创建点击事件 
        Component component = findComponentById(ResourceTable.Id_button_cancel); 
        // 创建按钮 
        Button cancelBtn = null; 
        if (component != null && component instanceof Button) { 
            cancelBtn = (Button) component; 
            cancelBtn.setClickedListener(view -> { 
                if (mBiometricAuthentication != null) { 
                    // 调用取消接口 
                    int result = mBiometricAuthentication.cancelAuthenticationAction(); 
                    LogUtils.info("createCancelBtn:", result + ""); 
                } 
            }); 
        } 
    } 
 
    /** 
     * 创建开始识别的按钮点击事件 
     */ 
    private void createStartBtn() { 
        // 创建点击事件 
        Component component = findComponentById(ResourceTable.Id_button_start); 
        // 创建按钮 
        Button featureBtn = null; 
        if (component != null && component instanceof Button) { 
            featureBtn = (Button) component; 
            featureBtn.setClickedListener(view -> { 
                createStartListener(); 
            }); 
        } 
    } 
 
    private void createStartListener() { 
        // 提示用户人脸识别时将人脸对准摄像头 
        getAndSetText(ResourceTable.Id_text_status, NO_FACE_RET, true); 
        try { 
            // 创建生物识别对象 
            mBiometricAuthentication = BiometricAuthentication.getInstance(MainAbility.getMainAbility()); 
            // 检验设备是否有人脸识别功能 
            // BiometricAuthentication.AuthType中有三个类别 
            // 分别为AUTH_TYPE_BIOMETRIC_FINGERPRINT_ONLY指纹识别 
            // AUTH_TYPE_BIOMETRIC_FACE_ONLY脸部识别 
            // AUTH_TYPE_BIOMETRIC_ALL指纹和面部 
            // BiometricAuthentication.SecureLevel 2D人脸识别建议使用SECURE_LEVEL_S2,3D人脸识别建议使用SECURE_LEVEL_S3 
            int hasAuth = mBiometricAuthentication.checkAuthenticationAvailability( 
                    BiometricAuthentication.AuthType.AUTH_TYPE_BIOMETRIC_FACE_ONLY, 
                    BiometricAuthentication.SecureLevel.SECURE_LEVEL_S2, true); 
 
            // hasAuth 0是支持,1是不支持,2安全级别不支持 3不是本地认证 4无人脸录入 
            if (hasAuth == 0) { 
                ThreadPoolExecutor pool = new ThreadPoolExecutor( 
                        POOL_CORE_SIZE, POOL_MAX_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, 
                        new LinkedBlockingQueue<>(QUEUE_SIZE), new ThreadPoolExecutor.DiscardOldestPolicy()); 
                pool.submit(runnable); 
            } else { 
                // 人脸识别不支持或存在其他问题 ,直接回显页面, 
                // 在主线程不需要通过EventHandler发送回显任务 
                int retExcAuth = getRetExcAuth(hasAuth); 
                getAndSetText(ResourceTable.Id_text_status, retExcAuth, true); 
            } 
        } catch (IllegalAccessException e) { 
            LogUtils.error("createStartBtn", "IllegalAccessException when start auth"); 
        } 
    } 
 
    /** 
     * 开始认证 
     */ 
    private void startAuth() { 
        // retExcAuth 0认证成功 1:比对失败 2:取消认证 3认证超时 4:打开相机失败 
        // 5:busy,可能上一个认证没有结束 6:入参错误 7:人脸认证锁定(达到错误认证次数了) 
        // 8:没有录入人脸 100:其他错误。 
        int retExcAuth = mBiometricAuthentication.execAuthenticationAction( 
                BiometricAuthentication.AuthType.AUTH_TYPE_BIOMETRIC_FACE_ONLY, 
                BiometricAuthentication.SecureLevel.SECURE_LEVEL_S2, 
                true, false, null); 
        // 将修改页面发送到主线程执行 
        myEventHandle.sendEvent(retExcAuth); 
    } 
 
    /** 
     * 根据检验是否支持认证返回值获取提示code 
     * 
     * @param hasAuth 是否有认证能力 
     * @return 返回认证码 
     */ 
    private int getRetExcAuth(int hasAuth) { 
        int retExcAuth; 
        if (hasAuth == RET_NOT_SUPPORTED) { 
            // 1是不支持2D人脸识别 
            retExcAuth = FaceAuthResult.AUTH_2D_NOT_SUPPORTED; 
        } else if (hasAuth == RET_SAFE_LEVEL_NOT_SUPPORTED) { 
            // 安全级别不支持 
            retExcAuth = FaceAuthResult.AUTH_SAFE_LEVEL_NOT_SUPPORTED; 
        } else if (hasAuth == RET_NOT_LOCAL) { 
            // 是不是本地认证 
            retExcAuth = FaceAuthResult.AUTH_NOT_LOCAL; 
        } else { 
            // 无人脸录入 
            retExcAuth = FaceAuthResult.AUTH_NO_FACE; 
        } 
        return retExcAuth; 
    } 
 
    /** 
     * 获取并设置text 
     * 
     * @param textId 文本框id 
     * @param retExcAuth 认证返回码 
     * @param isVisible 是否显示 
     */ 
    private void getAndSetText(int textId, int retExcAuth, boolean isVisible) { 
        // 获取状态Text 
        Component componentText = findComponentById(textId); 
        if (componentText != null && componentText instanceof Text) { 
            Text text = (Text) componentText; 
            setTextValueAndColor(retExcAuth, text); 
            if (isVisible) { 
                text.setVisibility(Component.VISIBLE); 
            } 
        } 
    } 
 
    /** 
     * 设置文本提示信息 
     * 
     * @param text 文本对象 
     * @param textValue 文本值 
     * @param color 文本颜色 
     */ 
    private void setTextValueAndColor(Text text, String textValue, Color color) { 
        text.setText(textValue); 
        text.setTextColor(color); 
    } 
 
    /** 
     * 设置文本显示值和文本颜色 
     * 
     * @param retExcAuth 认证返回值 
     * @param text 文本对象 
     */ 
    private void setTextValueAndColor(int retExcAuth, Text text) { 
        switch (retExcAuth) { 
            case FaceAuthResult.AUTH_SUCCESS: 
                setTextValueAndColor(text, "认证成功", Color.GREEN); 
                // 页面跳转 
                toAuthAfterPage(); 
                break; 
            case FaceAuthResult.AUTH_FAIL: 
                setTextValueAndColor(text, "比对失败", Color.RED); 
                break; 
            case FaceAuthResult.AUTH_CANCLE: 
                setTextValueAndColor(text, "取消认证", Color.RED); 
                break; 
            case FaceAuthResult.AUTH_TIME_OUT: 
                setTextValueAndColor(text, "认证超时", Color.RED); 
                break; 
            case FaceAuthResult.AUTH_OPEN_CAMERA_FAIL: 
                setTextValueAndColor(text, "打开相机失败", Color.RED); 
                break; 
            case FaceAuthResult.AUTH_BUSY: 
                setTextValueAndColor(text, "busy,可能上一个认证没有结束", Color.RED); 
                break; 
            case FaceAuthResult.AUTH_PARAM_ERROR: 
                setTextValueAndColor(text, "入参错误", Color.RED); 
                break; 
            case FaceAuthResult.AUTH_FACE_LOCKED: 
                setTextValueAndColor(text, "人脸认证锁定(达到错误认证次数了)", Color.RED); 
                break; 
            case FaceAuthResult.AUTH_NO_FACE: 
                setTextValueAndColor(text, "无人脸录入,请录入人脸。", Color.BLUE); 
                break; 
            case FaceAuthResult.AUTH_OTHER_ERROR: 
                setTextValueAndColor(text, "其他错误。", Color.RED); 
                break; 
            case FaceAuthResult.AUTH_2D_NOT_SUPPORTED: 
                setTextValueAndColor(text, "不支持2D人脸识别。", Color.BLUE); 
                break; 
            case FaceAuthResult.AUTH_SAFE_LEVEL_NOT_SUPPORTED: 
                setTextValueAndColor(text, "安全级别不支持。", Color.BLUE); 
                break; 
            case FaceAuthResult.AUTH_NOT_LOCAL: 
                setTextValueAndColor(text, "不是本地认证。", Color.BLUE); 
                break; 
            default: 
                setTextValueAndColor(text, "开始认证,请将视线对准摄像头。。。。。。。", Color.BLUE); 
                break; 
        } 
    } 
 
    private void toAuthAfterPage() { 
        Intent secondIntent = new Intent(); 
        // 指定待启动FA的bundleName和abilityName 
        Operation operation = new Intent.OperationBuilder() 
                .withDeviceId("") 
                .withBundleName(getBundleName()) 
                .withAbilityName(OpenCamera.class.getName()) 
                .build(); 
        secondIntent.setOperation(operation); 
        // 通过AbilitySlice的startAbility接口实现启动另一个页面 
        startAbility(secondIntent); 
    } 
 
    /** 
     * 事件分发器 
     * 
     * @since 2021-04-12 
     */ 
    private class MyEventHandle extends EventHandler { 
        MyEventHandle(EventRunner runner) throws IllegalArgumentException { 
            super(runner); 
        } 
 
        @Override 
        protected void processEvent(InnerEvent event) { 
            super.processEvent(event); 
            int eventId = event.eventId; 
            getAndSetText(ResourceTable.Id_text_status, eventId, true); 
        } 
    } 
 
    @Override 
    public void onStop() { 
        mBiometricAuthentication.cancelAuthenticationAction(); 
        BiometricAuthentication.AuthenticationTips authenticationTips 
                = mBiometricAuthentication.getAuthenticationTips(); 
        String tips = authenticationTips.tipInfo; 
    } 
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.

 

2.com/huawei/cookbook/slice/OpenCameraSlice.java 

package com.huawei.cookbook.slice;

import com.huawei.cookbook.ResourceTable;
import com.huawei.cookbook.util.LogUtils;
import com.huawei.cookbook.util.PermissionBridge;

import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.Image;
import ohos.agp.components.surfaceprovider.SurfaceProvider;
import ohos.agp.graphics.Surface;
import ohos.agp.graphics.SurfaceOps;
import ohos.agp.window.dialog.ToastDialog;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.media.camera.CameraKit;
import ohos.media.camera.device.Camera;
import ohos.media.camera.device.CameraConfig;
import ohos.media.camera.device.CameraStateCallback;
import ohos.media.camera.device.FrameConfig;

/**
 * 打开相机slice
 */
public class OpenCameraSlice extends AbilitySlice implements PermissionBridge.OnPermissionStateListener {
    private static final String TAG = OpenCameraSlice.class.getName();

    private static final int SCREEN_WIDTH = 1080;

    private static final int SCREEN_HEIGHT = 1920;

    private static final int SLEEP_TIME = 200;

    private EventHandler creamEventHandler;

    private Image exitImage;

    private SurfaceProvider surfaceProvider;

    private Image switchCameraImage;

    private boolean isCameraRear;

    private Camera cameraDevice;

    private Surface previewSurface;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_open_camera);
        new PermissionBridge().setOnPermissionStateListener(this);
    }

    private void initSurface() {
        surfaceProvider = new SurfaceProvider(this);
        DirectionalLayout.LayoutConfig params = new DirectionalLayout.LayoutConfig(
                ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT);
        surfaceProvider.setLayoutConfig(params);
        surfaceProvider.pinToZTop(false);
        // 添加SurfaceCallBack回调
        surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack());
        // 将SurfaceProvider加入到布局中
        Component component = findComponentById(ResourceTable.Id_surface_container);
        if (component instanceof ComponentContainer) {
            ((ComponentContainer) component).addComponent(surfaceProvider);
        }
    }

    private void initControlComponents() {
        // 退出拍照页面图标
        Component exitImageCom = findComponentById(ResourceTable.Id_exit);
        if (exitImageCom instanceof Image) {
            exitImage = (Image) exitImageCom;
            exitImage.setClickedListener(component -> terminate());
        }
        // 切换前后置摄像头图标
        Component switchCameraImageCom = findComponentById(ResourceTable.Id_switch_camera_btn);
        if (switchCameraImageCom instanceof Image) {
            switchCameraImage = (Image) switchCameraImageCom;
            switchCameraImage.setClickedListener(component -> switchClicked());
        }
    }

    private void switchClicked() {
        isCameraRear = !isCameraRear;
        openCamera();
    }

    private void openCamera() {
        CameraKit cameraKit = CameraKit.getInstance(getApplicationContext());
        String[] cameraLists = cameraKit.getCameraIds();
        String cameraId = cameraLists.length > 1 && isCameraRear ? cameraLists[1] : cameraLists[0];
        CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl();
        cameraKit.createCamera(cameraId, cameraStateCallback, creamEventHandler);
    }

    private void showTips(Context context, String message) {
        getUITaskDispatcher().asyncDispatch(() -> {
            ToastDialog toastDialog = new ToastDialog(context);
            toastDialog.setAutoClosable(false);
            toastDialog.setContentText(message);
            toastDialog.show();
        });
    }

    @Override
    public void onPermissionGranted() {
        getWindow().setTransparent(true);
        initSurface();
        initControlComponents();
        creamEventHandler = new EventHandler(EventRunner.create("======CameraBackground"));
    }

    @Override
    public void onPermissionDenied() {
        showTips(OpenCameraSlice.this, "=======No permission");
    }

    /**
     * CameraStateCallbackImpl
     */
    class CameraStateCallbackImpl extends CameraStateCallback {
        CameraStateCallbackImpl() {
        }

        @Override
        public void onCreated(Camera camera) {
            // 获取预览
            previewSurface = surfaceProvider.getSurfaceOps().get().getSurface();
            if (previewSurface == null) {
                LogUtils.error(TAG, "create camera filed, preview surface is null");
                return;
            }
            // Wait until the preview surface is created.
            try {
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException exception) {
                LogUtils.warn(TAG, "Waiting to be interrupted");
            }
            CameraConfig.Builder cameraConfigBuilder = camera.getCameraConfigBuilder();
            // 配置预览
            cameraConfigBuilder.addSurface(previewSurface);
            camera.configure(cameraConfigBuilder.build());
            cameraDevice = camera;
            enableImageGroup();
        }

        @Override
        public void onConfigured(Camera camera) {
            FrameConfig.Builder framePreviewConfigBuilder
                    = camera.getFrameConfigBuilder(Camera.FrameConfigType.FRAME_CONFIG_PREVIEW);
            framePreviewConfigBuilder.addSurface(previewSurface);
            // 开启循环捕捉
            camera.triggerLoopingCapture(framePreviewConfigBuilder.build());
        }

        private void enableImageGroup() {
            if (!exitImage.isEnabled()) {
                exitImage.setEnabled(true);
                switchCameraImage.setEnabled(true);
            }
        }
    }

    /**
     * SurfaceCallBack
     */
    class SurfaceCallBack implements SurfaceOps.Callback {
        @Override
        public void surfaceCreated(SurfaceOps callbackSurfaceOps) {
            if (callbackSurfaceOps != null) {
                callbackSurfaceOps.setFixedSize(SCREEN_HEIGHT, SCREEN_WIDTH);
            }
            openCamera();
        }

        @Override
        public void surfaceChanged(SurfaceOps callbackSurfaceOps, int format, int width, int height) {
        }

        @Override
        public void surfaceDestroyed(SurfaceOps callbackSurfaceOps) {
        }
    }

    @Override
    public void onStop() {
        cameraDevice.release();
    }
}	
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.

3.com/huawei/cookbook/util/FaceAuthResult.java

package com.huawei.cookbook.util; 
 
/** 
 * 人脸认证返回码 
 * 
 * @since 2021-04-12 
 */ 
public class FaceAuthResult { 
    /** 
     * 认证成功 
     */ 
    public static final int AUTH_SUCCESS = 0; 
    /** 
     * 认证失败 
     */ 
    public static final int AUTH_FAIL = 1; 
    /** 
     * 取消认证 
     */ 
    public static final int AUTH_CANCLE = 2; 
    /** 
     * 认证超时 
     */ 
    public static final int AUTH_TIME_OUT = 3; 
    /** 
     * 打开相机失败 
     */ 
    public static final int AUTH_OPEN_CAMERA_FAIL = 4; 
    /** 
     * busy,可能上一个认证没有结束 
     */ 
    public static final int AUTH_BUSY = 5; 
    /** 
     * 入参错误 
     */ 
    public static final int AUTH_PARAM_ERROR = 6; 
    /** 
     * 人脸认证锁定(达到错误认证次数了) 
     */ 
    public static final int AUTH_FACE_LOCKED = 7; 
    /** 
     * 没有录入人脸 
     */ 
    public static final int AUTH_NO_FACE = 8; 
    /** 
     * 不支持2D人脸识别。 
     */ 
    public static final int AUTH_2D_NOT_SUPPORTED = 9; 
    /** 
     * 安全级别不支持 
     */ 
    public static final int AUTH_SAFE_LEVEL_NOT_SUPPORTED = 10; 
    /** 
     * 不是本地认证 
     */ 
    public static final int AUTH_NOT_LOCAL = 11; 
    /** 
     * 其他问题 
     */ 
    public static final int AUTH_OTHER_ERROR = 100; 
 
    private FaceAuthResult() { 
        super(); 
    } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.

4.com/huawei/cookbook/util/LogUtils.java

package com.huawei.cookbook.util; 
 
import ohos.hiviewdfx.HiLog; 
import ohos.hiviewdfx.HiLogLabel; 
 
/** 
 * LogUtils 
 * 
 * @since 2021-04-12 
 */ 
public class LogUtils { 
    private static final String TAG_LOG = "LogUtil"; 
 
    private static final HiLogLabel LABEL_LOG = new HiLogLabel(0, 0, LogUtils.TAG_LOG); 
 
    private static final String LOG_FORMAT = "%{public}s: %{public}s"; 
 
    private LogUtils() { 
    } 
 
    /** 
     * Print debug log 
     * 
     * @param tag log tag 
     * @param msg log message 
     */ 
    public static void debug(String tag, String msg) { 
        HiLog.debug(LABEL_LOG, LOG_FORMAT, tag, msg); 
    } 
 
    /** 
     * Print info log 
     * 
     * @param tag log tag 
     * @param msg log message 
     */ 
    public static void info(String tag, String msg) { 
        HiLog.info(LABEL_LOG, LOG_FORMAT, tag, msg); 
    } 
 
    /** 
     * Print warn log 
     * 
     * @param tag log tag 
     * @param msg log message 
     */ 
    public static void warn(String tag, String msg) { 
        HiLog.warn(LABEL_LOG, LOG_FORMAT, tag, msg); 
    } 
 
    /** 
     * Print error log 
     * 
     * @param tag log tag 
     * @param msg log message 
     */ 
    public static void error(String tag, String msg) { 
        HiLog.error(LABEL_LOG, LOG_FORMAT, tag, msg); 
    } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.

5.com/huawei/cookbook/util/PermissionBridge.java

package com.huawei.cookbook.util; 
 
import ohos.eventhandler.EventHandler; 
import ohos.eventhandler.EventRunner; 
import ohos.eventhandler.InnerEvent; 
 
/** 
 * PermissionBridge 
 * 
 * @since 2021-04-12 
 */ 
public class PermissionBridge { 
    /** 
     * permission handler granted 
     */ 
    public static final int EVENT_PERMISSION_GRANTED = 0x0000023; 
 
    /** 
     * permission handler denied 
     */ 
    public static final int EVENT_PERMISSION_DENIED = 0x0000024; 
 
    private static final String TAG = PermissionBridge.class.getSimpleName(); 
 
    private static OnPermissionStateListener onPermissionStateListener; 
 
    private static EventHandler handler = new EventHandler(EventRunner.current()) { 
        @Override 
        protected void processEvent(InnerEvent event) { 
            switch (event.eventId) { 
                case EVENT_PERMISSION_GRANTED: 
                    onPermissionStateListener.onPermissionGranted(); 
                    break; 
                case EVENT_PERMISSION_DENIED: 
                    onPermissionStateListener.onPermissionDenied(); 
                    break; 
                default: 
                    LogUtils.info(TAG, "EventHandler Undefined Event"); 
                    break; 
            } 
        } 
    }; 
 
    /** 
     * setOnPermissionStateListener 
     * 
     * @param permissionStateListener OnPermissionStateListener 
     */ 
    public void setOnPermissionStateListener(OnPermissionStateListener permissionStateListener) { 
        onPermissionStateListener = permissionStateListener; 
    } 
 
    /** 
     * OnPermissionStateListener 
     * 
     * @since 2021-04-12 
     */ 
    public interface OnPermissionStateListener { 
        /** 
         * 当授权时 
         */ 
        void onPermissionGranted(); 
 
        /** 
         * 当拒绝授权时触发 
         */ 
        void onPermissionDenied(); 
    } 
 
    /** 
     * getHandler 
     * 
     * @return EventHandler 
     */ 
    public static EventHandler getHandler() { 
        return handler; 
    } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.

6.com/huawei/cookbook/MainAbility.java

package com.huawei.cookbook; 
 
import com.huawei.cookbook.slice.MainAbilitySlice; 
 
import ohos.aafwk.ability.Ability; 
import ohos.aafwk.ability.IDataAbilityObserver; 
import ohos.aafwk.content.Intent; 
 
/** 
 * MainAbility 
 * 
 * @since 2021-04-12 
 */ 
public class MainAbility extends Ability { 
    /** 
     * 声明静态变量,用于获取生物识别对象 
     */ 
    private static MainAbility myAbility; 
 
    /** 
     * 私有构造 
     */ 
    public MainAbility() { 
        myAbility = this; 
    } 
 
    /** 
     * 获取ability 
     * 
     * @return MainAbility 
     */ 
    public static MainAbility getMainAbility() { 
        return myAbility; 
    } 
 
    @Override 
    public void onStart(Intent intent) { 
        super.onStart(intent); 
        super.setMainRoute(MainAbilitySlice.class.getName()); 
    } 
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.

 

7.com/huawei/cookbook/MyApplication.java

package com.huawei.cookbook; 
 
import ohos.aafwk.ability.AbilityPackage; 
 
/** 
 * MyApplication 
 * 
 * @since 2021-04-12 
 */ 
public class MyApplication extends AbilityPackage { 
    @Override 
    public void onInitialize() { 
        super.onInitialize(); 
    } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

8.com/huawei/cookbook/OpenCamera.java

package com.huawei.cookbook; 
 
import com.huawei.cookbook.slice.OpenCameraSlice; 
import com.huawei.cookbook.util.PermissionBridge; 
 
import ohos.aafwk.ability.Ability; 
import ohos.aafwk.content.Intent; 
import ohos.bundle.IBundleManager; 
import ohos.security.SystemPermission; 
 
import java.util.Arrays; 
import java.util.List; 
import java.util.stream.Collectors; 
 
/** 
 * 打开相机ability 
 * 
 * @since 2021-04-12 
 */ 
public class OpenCamera extends Ability { 
    /** 
     * permission handler granted 
     */ 
    private static final int EVENT_PERMISSION_GRANTED = 0x0000023; 
 
    /** 
     * permission handler denied 
     */ 
    private static final int EVENT_PERMISSION_DENIED = 0x0000024; 
    private static final int PERMISSION_REQUEST_CODE = 0; 
 
    @Override 
    public void onStart(Intent intent) { 
        super.onStart(intent); 
        super.setMainRoute(OpenCameraSlice.class.getName()); 
        requestPermission(); 
    } 
 
    private void requestPermission() { 
        String[] permissions = { 
            // 存储权限 
            SystemPermission.WRITE_USER_STORAGE, 
            // 相机权限 
            SystemPermission.CAMERA 
        }; 
        List permissionFiltereds = Arrays.stream(permissions) 
                .filter(permission -> verifySelfPermission(permission) != IBundleManager.PERMISSION_GRANTED) 
                .collect(Collectors.toList()); 
        if (permissionFiltereds.isEmpty()) { 
            PermissionBridge.getHandler().sendEvent(EVENT_PERMISSION_GRANTED); 
            return; 
        } 
        requestPermissionsFromUser(permissionFiltereds.toArray(new String[permissionFiltereds.size()]), 
                PERMISSION_REQUEST_CODE); 
    } 
 
    @Override 
    public void onRequestPermissionsFromUserResult(int requestCode, String[] permissions, int[] grantResults) { 
        if (permissions == null || permissions.length == 0 || grantResults == null || grantResults.length == 0) { 
            return; 
        } 
        for (int grantResult : grantResults) { 
            if (grantResult != IBundleManager.PERMISSION_GRANTED) { 
                PermissionBridge.getHandler().sendEvent(EVENT_PERMISSION_DENIED); 
                terminateAbility(); 
                return; 
            } 
        } 
        PermissionBridge.getHandler().sendEvent(EVENT_PERMISSION_GRANTED); 
    } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.

9.打开相机页面图标
返回图标:

如何使用HarmonyOS面部识别能力-鸿蒙开发者社区

 

拍照图标:

如何使用HarmonyOS面部识别能力-鸿蒙开发者社区

切换前后置摄像头图标:

如何使用HarmonyOS面部识别能力-鸿蒙开发者社区

10. 恭喜您
恭喜你已经完成了基于面容识别的HarmonyOS应用开发的Codelab,并且学到了:

1.人脸识别的开发。
2.相机的打开。
3.线程间通信开发EventHandler

 

已于2022-5-5 14:19:39修改
3
收藏 5
回复
举报
3
2
5
2条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

人脸识别技术也慢慢普及了

回复
2021-6-15 11:00:00
向金
向金

感谢分享,可以把源码都放到gitee上。

回复
2021-6-16 16:12:34


回复
    相关推荐