跳转至

坐姿检测接入文档

文档状态

文件标识: TuringOS-Posture-V1.0
系统版本: SDK
完成日期: 2022年06月19日

文档修订记录

文档版本号 修订日期 修订原因 备注
V1.0 2022.06.19 创建文档
V1.1 2023.05.12 支持由外部传图片数据进行检测
V1.2 2023.07.04 SDK内部相机支持不显示预览界面 调用startPostureDetect(),viewGroup=null,cameraConfig不为null
V1.3 2023.07.21 SDK内部相机支持Camera1 设置方式:CameraConfig.Builder#setCameraApi(1)

概述

实现注意力、接受度、精神状态、坐姿检测。

  • 注意力检测,具体包括歪头、侧脸、低头、仰头、闭眼。

  • 接受度检测,具体包括点头、摇头、高兴、疑惑、举手。

  • 精神状态检测,具体包括打哈欠、瞌睡、趴。

  • 坐姿检测,具体包括坐姿不正、距离估计。坐姿不正为:歪头、低头、仰头几种情况。

集成步骤

1. 添加依赖库

//必须依赖库
implementation 'com.google.code.gson:gson:2.6.2'
implementation 'com.squareup.okhttp3:okhttp:3.9.0'
implementation "org.java-websocket:Java-WebSocket:1.4.0"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.aliyun.dpa:oss-android-sdk:2.9.11'//SDKV2.4.1.25 以后需加入

//坐姿检测
implementation 'com.squareup.moshi:moshi:1.9.3'
implementation "androidx.security:security-crypto:1.1.0-alpha03"

2. AndroidManifest配置

添加权限

    <!--网络-->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />   
    <!--文件读写-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />  
    <!--deviceID-->
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />  
    <!--绘本、指尖查词、坐姿-->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />
    <!-- 读取手机状态,获取IMEI -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

    <!-- android 10 在application节点添加以下适配高版本 -->
     android:usesCleartextTraffic="true"
     android:requestLegacyExternalStorage="true"

3. 代码混淆

代码混淆文件proguard-project.txt,添加如下内容


    -keep class com.turing.**{ *;}
    -dontwarn com.turing.**

    -keep class org.java_websocket.**{*;}
    -dontwarn org.java_websocket.**
    -keep class org.slf4j.**{*;}
    -dontwarn org.slf4j.**

    -keep class org.eclipse.** { *; }
    -dontwarn org.eclipse.**

    -keep class com.Posture.detection**{
        *;
    }
    -keep class com.tcl.detection.**{
        *;
    }

4. SDK 初始化

使用坐姿检测功能前调用,请先调用SDK初始化代码。SDK初始化详情见《Android SDK接入指南说明文档》

TuringInitializer.getInstance().initV3XXX()

5. 开启功能集成

使用说明见下面【功能集成】描述。

实例化

var turingPosture = TuringPosture.getInstance(this);

功能集成

坐姿检测的图片来源为:SDK 内部相机 或 客户端传入图片。

1. 内部相机开启检测

方法

void startPostureDetect(ViewGroup viewGroup, CameraConfig cameraConfig, PostureReqConfig config, PostureListener listener);

有两种方式:

​ ① 内部相机显示预览图和检测数据:  viewGroup不为null,cameraConfig不为null。

​ ② 内部相机不显示预览图,只用检测数据: viewGroup为null,cameraConfig不为null。

输入说明

参数 类型 说明
layout ViewGroup 预览界面的布局,建议使用FrameLayout
cameraConfig CameraConfig 相机配置参数
config PostureReqConfig 坐姿配置参数
listener PostureListener 回调接口

业务开发过程中要变更配置参数,可以使用已创建的 CameraConfig、PostureReqConfig 对象来修改其字段,不用每次都创建新对象。

  • CameraConfig 说明
参数 类型 是否必须 取值范围 说明
viewSize Size N - 预览布局的大小。 默认:MATCH_PARENT
previewSize Size N - 预览图大小。(传给算法检测的图)
cameraId String N 设备支持的cameraid 摄像头ID (默认”0“)

一般前置为"1",后置为"0",也可传入其他值。
cameraApi int N [1,2] 相机版本(默认:2) 

1-Camera1 2-Camera2
preViewRotation int N - 旋转预览视图的角度。默认:0
imageRotation int N 0,90,180,270 图片旋转角度 (如 果 不 是 人 脸 正 向 图 片 , 则 需 要 旋 转 到 正 面)。 默认:90。

preViewRotation 字段只改变人眼看到的预览画面,不会影响检测结果;

imageRotation 字段真正作用于图片,会影响检测结果,一般情况前置摄像头:平板横屏设置180,手机竖屏设置270。

配置 CameraConfig 参数,更改以下字段值需要重新启动相机:

  • viewSize

  • previewSize

  • cameradId

  • preViewRotation

会启动相机的方法:startPostureDetect()、onResume()。

  • PostureReqConfig 说明
参数 类型 是否必须 取值范围 说明
type PostureItem Y - 检测项
intervalDetect int Y - 帧检测间隔:建议设置为3,不要超过10

type 检测项:

  • PostureItem.ITEM_ATTENTION 注意力

  • PostureItem.ITEM_ACCEPTANCE 接受度

  • PostureItem.ITEM_MENTAL 疲劳度

  • PostureItem.ITEM_SITTING 坐姿检测

  • PostureItem.ITEM_ALL 以上全检测

  • PostureListener 说明

接口回调为主线程

public interface PostureListener {
    /**
     * 成功回调
     * @param result 检测结果
     */
    void onResult(PostureResult result);

    /**
     * 错误回调
     * @param code
     * @param msg
     */
    void onError(int code, String msg);

    /**
     * 授权结果
     * @param flag  true-授权成功  false-授权失败
     * /
    void onAuthSuccess(boolean flag);
}

输出说明

PostureResult
参数 类型 是否必返 取值范围 说明
currentType int Y - 当前检测项
attention Attention N - 注意力检测结果
acceptance Acceptance N - 接受度检测结果
mental Mental N - 疲劳度检测结果
sitting Sitting N - 坐姿检测结果
  • attention 结果说明
参数 类型 是否必返 取值范围 说明
little boolean Y - 歪头
sideface boolean Y - 侧脸
bow boolean Y - 低头
please boolean Y - 仰头
closeeye boolean Y - 闭眼
score float Y 0~100 得分
  • acceptance 结果说明
参数 类型 是否必返 取值范围 说明
nodhead boolean Y - 点头
shakehead boolean Y - 摇头
happy boolean Y - 高兴
confusion boolean Y - 疑惑
raisehand boolean Y - 举手
score float Y 0~100 得分
  • mental 结果说明
参数 类型 是否必返 取值范围 说明
yawning boolean Y - 打哈欠
dozeoff boolean Y - 打瞌睡
lying boolean Y - 趴着
score float Y 0~100 得分
  • sitting 结果说明
参数 类型 是否必返 取值范围 说明
notStraight boolean Y - true-坐姿不端正 false-坐姿端正
distance float Y - 距离,单位:cm
score float Y 0~100 得分

请求示例

  • 情况一,显示预览界面。  

    ```kotlin private fun postureDetect() { // 1. 设置相机参数 cameraConfig = CameraConfig.Builder() .setCameraId("1") // 前置摄像头 .setViewSize(Size(280,200)) // 预览布局的大小 .setPreviewSize(Size(1920,1080)) // 检测图的大小,影响算法检测 .setPreViewRotation(0) // 预览布局旋转角度,不影响算法检测 .setImageRotation(180) // 检测的图旋转角度。保证传给算法是正向图 .build()

    // 2. 设置检测参数 postureConfig = PostureReqConfig() postureConfig.type = 5 // 检测所有项 postureConfig.intervalDetect = 3 // 检测帧检测

    // 3. 启动相机 turingPosture.startPostureDetect(binding.preframe,cameraConfig, postureConfig,object : PostureListener { override fun onResult(result: PostureResult?) { // 检测结果 if (null != result) { formatStr(result) } }

        override fun onError(code: Int, msg: String?) {
         showToast(msg)
       }
    
       override fun onAuthSuccess(flag: Boolean) {
         // 鉴权成功 or 失败
         Log.d("PostureActivity", "onAuthSuccess() called with: flag = $flag")
       }
    })
    

    }
    ```

  • 情况二,不显示预览界面,只用检测数据: 将viewGroup设置为null。

2. 传输图片开启检测

适用于客户端开发相机或静态图片检测,将图片传输给SDK进行检测。

注:需要先调用 startPostureDetect(null,null,postureReqConfig,listener)进行初始化,等初始化成功后才能调用sendPostureDetect()

方法

void sendPostureDetect(InputImgData inputImgData);

输入说明

参数 类型 说明
inputImgData InputImgData 图片参数

InputImgData 说明

参数 类型 是否必须 取值范围 说明
inputBuffer1 byte[] Y - 输入图片帧或者数据的buffer地址 (RGB24,BGR24,RGBA 或者YUV第一个Plane分量的buff地址)
inputBuffer2 byte[] N - 输入图片帧或者数据的buffer地址 (YUV第二个Plane分量的buff地址,RGB24,BGR24,RGBA可不用此项置空便可)
inputBuffer3 byte[] N - 输入图片帧或者数据的buffer地址 (YUV第三个Plane分量的buff地址,RGB24,BGR24,RGBA可不用此项置空便可)
rowStride1 int N - yuv 第一个Plane分量或者 inputData1 对应的RowStride; 非YUV数据置0便可
rowStride2 int N - yuv 第一个Plane分量或者 inputData2 对应的RowStride; 非YUV数据置0便可
rowStride3 int N - yuv 第一个Plane分量或者 inputData3 对应的RowStride; 非YUV数据置0便可
width int Y - 图片宽
height int Y - 图片高
rotation byte Y - 0:如不需旋转
1:顺时针旋转90度
2:顺时针旋转180度
3:顺时针旋转270度
imgFormat byte Y - 0: RGB24
1: BGR24
2: RGBA
3: NV21
4: NV12
5: I420-YU12
6: I420-yv12
focalParameters float[] N - [0] 输入为摄像头焦距(focal_length),若无法获取且对距离估计精度要求不高则赋值outDetectScoreDistance[0]= -1.0f

[1] 输入为摄像头感光元件宽(sensor_width),若无法获取且对距离估计精度要求不高则赋值outDetectScoreDistance[1]= -1.0f

默认值: [-1.0f , -1.0f]

输出说明

同【1. 内部相机开启检测】输出说明

请求示例

情况1,客户端开发相机

// 这里展示客户端使用Camera 2自定义开发相机,拿到图片数据(预览图)后,如何调用SDK方法。

  1. 先调用初始化

    ```kotlin // 设置检测参数 postureConfig = PostureReqConfig() postureConfig.type = 5 // 检测所有项 postureConfig.intervalDetect = 3 // 检测帧检测

    // 初始化,获取检测结果
    turingPosture.startPostureDetect(
        null,null,postureConfig,
        object : PostureListener {
                override fun onResult(result: PostureResult?) {
                    if (null != result) {
                        formatStr(result)
                    }
                }
    
                override fun onError(code: Int, msg: String?) {
                    showToast(msg)
                }
    
                override fun onAuthSuccess(flag: Boolean) {
                    Log.d("PostureActivity", "onAuthSuccess() called with: flag = $flag")
                }
            })
    

    ```

  2. 再传入图片数据

    ```kotlin // 客户端开发相机 Camera 2 的预览图数据回调

    private var _imgRotation = 0 // 根据实际设备取图进行设置

    private final ImageReader.OnImageAvailableListener mPreviewImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader imageReader) { Image image = imageReader.acquireLatestImage(); if (null == image) { return; }

            if (null != cameraCallback && !cameraIsClose()) {
                // 图片数据,进行坐姿检测
                runPostureDetect(image)
            }
            image.close();
        }
    };
    
    // 获取摄像头焦距、摄像头感光元件宽
    
    public void calculateFocalLengthInPixels(float[ ] value) {
    
        if (null == characteristics) {
            value[0] = -1f;
            value[1] = -1f;
            return;
        }
        try {
    
            //通过物理尺寸, 对焦距离, 算出FOV(Field of view), 相机水平弧度, 垂直弧度
    
            float[ ] floats = characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
    
            if (floats != null && floats.length > 0) {
                value[0] = floats[0];
            }
            SizeF sizeF = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
            if (sizeF != null) {
                value[1] = sizeF.getWidth();
            }
        } catch (Exception e) {
    
        }
    }
    
    private void runPostureDetect(Image yuvImage){
        // 注意,一定要在 onAuthSuccess() 初始化成功后调用
    
        byte[ ] yuvBuf1 = null, yuvBuf2 = null, yuvBuf3 = null;
    
        int yuvBufLen1 = 0, yuvBufLen2 = 0, yuvBufLen3 = 0;
        ByteBuffer bf1 = null, bf2 = null, bf3 = null;
    
        Image.Plane[ ] yuvBufPlane = yuvImage.getPlanes();
    
        bf1 = yuvBufPlane[0].getBuffer();
        bf2 = yuvBufPlane[1].getBuffer();
        bf3 = yuvBufPlane[2].getBuffer();
    
        if (yuvBufLen1 != bf1.capacity()) {
            yuvBufLen1 = bf1.capacity();
            yuvBuf1 = new byte[yuvBufLen1];
        }
        bf1.clear();
        bf1.get(yuvBuf1, 0, yuvBuf1.length);
    
        if (yuvBufLen2 != bf2.capacity()) {
            yuvBufLen2 = bf2.capacity();
            yuvBuf2 = new byte[yuvBufLen2];
        }
        bf2.clear();
        bf2.get(yuvBuf2, 0, yuvBuf2.length);
    
        if (yuvBufLen3 != bf3.capacity()) {
            yuvBufLen3 = bf3.capacity();
            yuvBuf3 = new byte[yuvBufLen3];
        }
        bf3.clear();
        bf3.get(yuvBuf3, 0, yuvBuf3.length);
    
        int _width = yuvImage.getWidth();
        int _height = yuvImage.getHeight();
    
        int _rowStride1 = yuvBufPlane[0].getRowStride();
        int _rowStride2 = yuvBufPlane[1].getRowStride();
        int _rowStride3 = yuvBufPlane[2].getRowStride();
    
        int _imgFormat =3;
        if (2 == yuvBufPlane[1].getPixelStride()) { //NV12 OR NV21
            _imgFormat = 3;
            //inArgsData[2]-支持:{0:RGB24,1:BGR24,2:RGBA,3:NV21,4:NV12,5:I420-YU12;6:I420-yv12}
        } else {
             //1==yuvBufPlane[1].getPixelStride() I420 y-u-v
            _imgFormat = 6;
            //inArgsData[2]-支持:{0:RGB24,1:BGR24,2:RGBA,3:NV21,4:NV12,5:I420-YU12;6:I420-yv12}
        }
    
        float[ ] focalParameters = {-1f, -1f};
    
        postureCamera2.calculateFocalLengthInPixels(focalParameters);  // 获取摄像头焦距、摄像头感光元件宽
    
        //------------ 上面是获取参数值,下面是调用产研SDK进行检测 ------------
        var inputImgData = InputImgData()
        inputImgData.inputBuffer1 = yuvBuf1
        inputImgData.inputBuffer2 = yuvBuf2
        inputImgData.inputBuffer3 = yuvBuf3
    
        inputImgData.rowStride1 = _rowStride1
        inputImgData.rowStride2 = _rowStride2
        inputImgData.rowStride3 = _rowStride3
    
        inputImgData.width = _width
        inputImgData.height = _height
    
        inputImgData.rotation = _imgRotation   // 图片角度,
        //选择旋转度数{0:如不需旋转,1:顺时针旋转90度;2:顺时针旋转180度;3:顺时针旋转270度}(如果不是人脸正向图片,则需要旋转到正面,算法才有效)
    
        inputImgData.imgFormat = _imgFormat
        // 数据格式:{{0:RGB24,1:BGR24,2:RGBA,3:NV21,4:NV12,5:I420_yu12,6:I420_yv12}}
    
        // 调用方法进行检测
        turingPosture.sendPostureDetect(inputImgData)
    }
    

    ```

情况2,传入图片
  1. 先调用初始化

    ```kotlin // 设置检测参数 postureConfig = PostureReqConfig() postureConfig.type = 5 // 检测所有项 postureConfig.intervalDetect = 3 // 检测帧检测

    // 初始化,获取检测结果
    turingPosture.startPostureDetect(
        null,null,postureConfig,
        object : PostureListener {
                override fun onResult(result: PostureResult?) {
                    if (null != result) {
                        formatStr(result)
                    }
                }
    
                override fun onError(code: Int, msg: String?) {
                    showToast(msg)
                }
    
                override fun onAuthSuccess(flag: Boolean) {
                    Log.d("PostureActivity", "onAuthSuccess() called with: flag = $flag")
                }
            })
    

    ```

  2. 再读取本地图片进行检测

    ```kotlin private fun readImgFile(file File){ val options = BitmapFactory.Options() options.inMutable = true options.inPreferredConfig = Bitmap.Config.ARGB_8888

    var bitmap = BitmapFactory.decodeFile(file.absolutePath, options)
    
    val width = bitmap.width
    val height = bitmap.height
    
    val pixels = ByteArray(bitmap.getWidth() * bitmap.getHeight() * 4)
    bitmap.copyPixelsToBuffer(ByteBuffer.wrap(pixels))
    
    sendImgToPostureDetect(pixels, width, height)
    

    }

    private fun readImgFile(imgData: ByteArray, width: Int, height: Int) { var inputImgData = InputImgData() inputImgData.inputBuffer1 = imgData inputImgData.width = width inputImgData.height = height inputImgData.rotation = 0

    //选择旋转度数{0:如不需旋转,1:顺时针旋转90度;2:顺时针旋转180度;3:顺时针旋转270度}(如果不是人脸正向图片,则需要旋转到正面,算法才有效)
    
    inputImgData.imgFormat = 2
    // 数据格式:{{0:RGB24,1:BGR24,2:RGBA,3:NV21,4:NV12,5:I420_yu12,6:I420_yv12}}
    
    turingPosture.sendPostureDetect(inputImgData)
    

    } ```

3. 停止/启动识别

方法

/**
* 在 Activity 生命周期方法 onPause() 中调用,SDK 内部会关闭相机
*/
void onPause();

/**
* 在 Activity 生命周期方法 onResume() 中调用,SDK 内部会开启相机
*/
void onResume();

4. 资源释放

方法

/**
*  建议不使用时及时释放资源,再次使用相机功能,需要重新调用 startPostureDetect()
*/
void release()

常见问题

1. 如何确定SDK自带相机设置了图片旋转角度,传给算法的检测图是人脸正向图片?

可以调用 TuringPosture().getInstance(context).debugSavaPreView(),在相册中查看保存的预览图片。

2. 使用SDK自带相机,不想要显示预览界面,如何实现?

方式一:(推荐)

调用 startPostureDetect() 方法 viewGroup=null,cameraConfig不为null。

方式二:(不推荐)

① 预览界面viewgroup 宽高设置为1dp,必须要传入图片分辨率参数,否则sdk内部的默认值会影响结果。 分辨率取值范围:640×480 ~ 1280×720。

建议客户端自定义相机开发实现功能,实现方式见【使用示例--情况2,客户端开发相机】。