Firmware Recovery

1 Introduction

This document is intended to help users manually update the corrupted IPC firmware.

2 Get Firmware

Please consult Sunmi Technology Customer Service for the latest firmware of your devices and do not use any third-party firmware which may cause malfunctions.

  • Release firmware for the Face Sense Camera (or FS & FM020): Download here.
  • Release firmware for the Store Sense Camera (or SS & FM010): Download here.

3 Notes

  1. Firmware is not interchangeable and you should use correct firmware for your device.
  2. Verification will be performed to ensure the firmware is correctly signed before actually updating. Please make sure the firmware comes from Sunmi Technology to prevent malfunctions or bricks after updating.

4 Update Procedure

4.1 Copy the firmware to the SD-card

  1. Remove the SD-card from the IPC.
  2. Plug the SD-card into a Microsoft Windows computer, format the SD-card with EXFAT file system and set the volume label to “SUNMI-XXXX” where “XXXX” is the last four digits of the MAC address which is on the label of the device.
  3. Copy the firmware file to the SD-card’s root directory and rename it to “up.bin”.

4.2 Update

  1. Insert the SD-card with firmware inside into the IPC.
  2. Power on the IPC.
  3. Wait for around 10 seconds until the red LED is on, indicating it is updating.
  4. Wait for around 1 min, if the red LED is off and green LED is on that means the device is going to reboot. The updating procedure completes when the green LED is flashing or the blue LED is on.

4.3 Setup

  1. After rebooting, hold the Reset button for a few seconds to restore the factory settings.
  2. Follow  the procedures in the User Manual to setup your device and bind it to your account.
  3. Use the Sunmi Assistant App or go to https://store.sunmi.com and update the firmware to the latest version.
  4. Remember to delete the “up.bin” file under the root directory in the SD-card, preventing any accidently update.

升级异常的固件恢复方法

1. 概述

本文指导用户通过线下手动升级的方式,恢复升级异常的固件。

2. 获取升级固件

请务必从商米售前获取对应型号摄像头的升级固件 ,否则出现升级异常概不负责。

目前商米有的IPC摄像头有两款,分别是

  1. AI识客摄像机,英文名Face Sense Camera,简称FS,目前的型号是FM020。
  2. 智能看店摄像机,英文名Store Sense Camera,简称SS,目前的型号是FM010。

3. 升级规则

  1. 不同机型之间系统固件不能互相升级,例如FS和SS之间不能升级,所以请务必根据自己的机型来获取对应的正式环境固件。
  2. 固件本身有自己的签名机制和升级校验方法,所以请务必从商米处获取固件,切勿随便从第三方获取固件。

4. 详细流程

4.1 拷贝固件到TF卡

  1. 从摄像头的TF卡槽中取出TF卡。
  2. 将TF卡插入windows电脑,执行格式化操作,文件系统设置为exfat,卷标设置为SUNMI-XXXX(其中XXXX为摄像头MAC地址最后四位,摄像头MAC地址见机身背面标贴)。 注:卷标SUNMI-XXXX中的-为英文半角符号,非中文全角符号;XXXX中的字母为大写。
  3. 进入TF卡目录,将从商米处 (详见2. 获取升级固件)获取到的升级固件解压后拷贝到根目录下,并把固件重命名为up.bin

4.2 升级固件

  1. 将带有固件的TF卡重新插入摄像头的TF卡槽中。
  2. 重新上电摄像头。
  3. 等待10秒左右,直到设备亮红灯,则说明正在固件升级 。
  4. 升级需要一些时间,再耐心等待1min左右,直到重新亮绿灯说明设备开始重启,当设备再次闪烁绿灯或者亮蓝灯说明设备已经重启完毕。

4.3 进行首配

  1. 重启后,长按设备上的Reset按键5秒以上进行恢复出厂设置。
  2. 恢复出厂重启后,使用正式环境的商米APP,按照《用户指南》手册中的软件配置指引完成摄像头的首次配置。
  3. 首配完成后,使用商米助手APP或登录正式环境的WEB服务网站https://store.sunmi.com 将设备固件更新到最新版本。
  4. 记得删除原来SD卡中的up.bin文件,避免不必要的意外升级。

IPC OpenAPI Testing Tutorial

1. Introduction

This tutorial will show you how to test the IPC OpenAPI with Postman.

Consult the Sunmi Technology Customer Service and get materials in the table below except the IP and SN of your device. And please connect your IPC to the Internet and make sure the blue LED is solid before going through the following steps.

Item Example in this tutorial
app_id08BABCDABBB661234567
secret_keyABCDEFG3F2BB03917123
Activation Codeabcd2986jnts8987hntl1234
OpenAPI Cert and its passwordopenapi.p12
Postman CollectionsSunmi_IPC_OpenAPI_Postman_Collection_202007001.json
IP192.168.103.198
SNC201000P00123

2. Postman Setup

  1. Download Postman: Link, we use v7.27.1 in this tutorial.
  2. And just follow the Installation Wizard.

3. Collection Import and Configuration

  1. Launch Postman
  2. Click File -> Import -> Upload Files, And open the Postman collection from Sunmi, i.e. “Sunmi_IPC_OpenAPI_Postman_Collection_202007001.json”, confirm and click “Import”.
  3. Click File -> Settings -> Certificate, add the certificate file and its password, put the IP in the Host filed.
  • 4. Open the General page, set “SSL certificate verificatio” to `OFF`.
  • 5. Back to homepage of Postman, right click on “Sunmi IPC OpenAPI” collections, “Edit”, and update the corresponding settings at “CURRENT VALUE” in page “Variables”.

4. Activation Example

  1. Activation use the activation code as the secret key, Make sure the value of secret_key in the Variables page is your activation code.
  2. Open Sunmi IPC OpenAPI -> Basic -> 0. Activation, modify the “sn” to your device’s SN in the Body tab, click Send, activations succeed with “code” 0 in the response.

5. API Test

  • This collection only includes several API example for testing. Each request needs to be correctly signed, see the Sign Rules.
  • Sign script are included in this collection in “Pre-request Scripts”, it will calculate the value of “app_id”, “timestamp”, “random” and “sign” in every request.

IPC设备迁移

本章节详细介绍在Android平台如何快速方便添加IPC SDK

1、拓扑图

IPC设备和Android设备连接拓扑图如下:

1、Android设备有一套自有的用户管理模块,有唯一ID识别机制。Android设备在管理用户时,需要保留一张用户人脸图片

2、IPC设备使用人脸图片作为识别预输入。

3、Android设备激活IPC设备后,通过局域网把用户人脸图片以及与用户唯一关联的ID传递给IPC设备

4、IPC设备从人脸图片提取人脸特征值,并把特征值与用户ID保存到数据库。

5、IPC设备监控到用户进入,查询注册的数据库,返回用户唯一ID的IPC设备。

6、Android设备获取到唯一ID值后,在用户管理模块根据该唯一ID进行查询,显示用户信息

2、IPC激活

IPC设备使用时,如果未激活,需要先激活IPC设备。激活步骤如下:

2.1 SDK初始化

APP_ID:激活与API调用校验使用的账号。

SECRET_KEY:API调用所需的签名密钥。

LICENSE:激活API所需的激活码。

IPCameraManager mIPCameraManager = IPCameraManager.getInstance(context);
mIPCameraManager.init(APP_ID, SECRET_KEY, LICENSE);

2.2 配网

IPC设备激活时需要连接到互联网。IPC连接互联网时,可以通过无线(wifi)或有线(以太网口)连接到路由器。无线连接时需要做配网设置。

有线接入:

有线接入只需要通过网线把IPC设备和集成SDK的Android设备接入到同一局域网即可,不需要其它设置。

无线接入:

无线配网方式相对要复杂点,步骤如下:

  1. 使用手机/PC的无线网卡扫描IPC的AP热点,一般AP热点的名称为SUNMI_XXXX,其中XXXX为MAC地址最后2个字节的16进制数字,MAC地址可以通过设备机身后背的标贴或者包装盒的标贴查到,AP热点本身是无加密的。
  2. 使用手机/PC的无线网卡连接IPC的AP热点,此时手机/PC就会获取到IPC分配的IP地址(按照设备发现描述的方法即可获取到),一般会是192.168.200.XXX,手机/PC的网关地址就是IPC的地址,一般会是192.168.200.1。
  3. 调用无线配置 API(见获取无线扫描AP列表 (无需签名校验)的描述)获取IPC扫描到的AP热点。
  4. 调用无线配置 API(见设置无线参数(无需签名校验)的描述)设置IPC要连接的无线网络(例如无线路由器的SSID和密码),使得IPC能够从网关处获取到IP地址。
  5. 如果网络是可以正常上网的话,IPC取到IP地址后很快就会亮蓝灯,此时表明IPC可以正常连接Internet了。
// 获取IPC扫描的AP热点
private void getWifiList() {
    BasicConfig.getInstance(context).getApListWithoutAuth(sunmiDevice.getDeviceid(),
            new RPCCallback‹RPCResponse‹IpcApBean››() {
                @Override
                public void onComplete(RPCResponse‹IpcApBean› result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        wifiListGetSuccess(result.data());
                    } else {
                        Log.i(TAG, "getApListWithoutAuth failed, errcode: " + result.code());
                    }
                }
        });

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            if (wifiList.size() == 0) {
                setNoWifiVisible(View.VISIBLE);
            }
        }
    }, TIMEOUT_GET_WIFI);
}
// 设置IPC连接的AP热点
private void setIpcWifi(String ssid, String psw) {
    showLoadingDialog();
    BasicConfig.getInstance(context).setWifiConfWithoutAuth(sunmiDevice.getDeviceid(),
            ssid, psw, new RPCCallback‹RPCResponse›() {
                @Override
                public void onComplete(RPCResponse result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        hideLoadingDialog();
                        shortTip("配置成功,请等待设备联网,联网后指示灯会变成蓝色");
                        createWaitDialog();
                    } else {
                        hideLoadingDialog();
                        shortTip("配置失败");
                    }
                }

                @Override
                public void onError(Throwable t) {
                    hideLoadingDialog();
                    shortTip("配置失败");
                }
            });
}

2.3 激活

通过 2.2配网 后,IPC通过无线连接到路由或者直接有线连接路由。

由于使用IPC的其它API时需要签名校验,所以在使用API之前需要激活IPC。激活只需要在第一次使用IPC时进行,后续都不再需要激活。

调用active接口既可以激活IPC设备。 代码如下:

//激活ipc设备,并设置回调接口
DeviceManage.getInstance(context).activate(ipcList.get(postion).getDeviceid(),
    new RPCCallback‹RPCResponse›() {
        //IPC返回相关错误码。
        @Override
        public void onComplete(RPCResponse result) {
            if (result.code() == RPCErrorCode.SUCCESS || result.code() == RPCErrorCode.DEVICE_ACTIVATED) {
                Log.i(TAG, "activate ipc success");
            } else {
                Log.i(TAG, "activate ipc failed");
            }
        }
        //网络问题导致无法通信,会调用onError接口
        @Override
        public void onError(Throwable t) {
            Log.i(TAG, "activate ipc failed");
        }
    });

2.4 画面调整

由于IPC的人脸识别对于人脸图像质量有一定要求,因此在使用IPC前需要调整IPC的画面,以达到最好的体验效果。画面调整包括镜头的调焦、对焦。步骤如下:

2.4.1 RTSP 播放

首先需要通过预览画面查看IPC设备镜头是对焦,画面是否清晰。IPC设备是通过RTSP协议传递视频流,所以开发一款RTSP播放器才能看到视频流。

调用VideoStream.getInstance(context).getLiveStream接口可以获取到RTSP播放流的地址。代码流程如下:

    private void openMediaPlayer() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //获取播放流
                VideoStream.getInstance(context).getLiveStream(mDevice.getDeviceid(),
                        new RPCCallback‹RPCResponse‹RPCResponse.LiveAddressBean››() {
                            @Override
                            public void onComplete(RPCResponse‹RPCResponse.LiveAddressBean› result) {
                                if (result.code() == RPCErrorCode.SUCCESS) {
                                    Log.i(TAG, "live url: " + result.data().fhd_live_url);
                                    if (mPlayer == null) {
                                       //自定义的rtsp播放器
                                        mPlayer = new SunmiPlayer(context);
                                        mPlayer.setListener(new SunmiPlayerListener() {
                                            @Override
                                            public void onPrepared(IMediaPlayer iMediaPlayer) {
                                                iMediaPlayer.start();
                                            }
                                        });
                                        mPlayer.setSurface(mVideoView.getHolder().getSurface());
                                        //获取到ipc返回的播放流rtsp地址
                                        String liveUrl = result.data().fhd_live_url.replaceFirst("^rtsp://", "rtsp://admin:admin@");
                                        //自定义播放器开始播放rtsp视频流
                                        mPlayer.setUp(liveUrl);
                                    }
                                }
                            }
                });
            }
        }).start();
    }

2.4.2 调焦、对焦

private BasicConfig mBasicConfig;

public void init() {
    ...
    mBasicConfig = BasicConfig.getInstance(context);
    ...
}

// 调焦
private void func1(){
    ...
    mBasicConfig.setZoom(mDevice.getDeviceid(), zfBean.zoom, new RPCCallback‹RPCResponse›() {
        @Override
        public void onComplete(RPCResponse result) {
            Log.i(TAG, "setZoom, code:" + result.code());
        }
    });
    ...
}

// 自动对焦
private void func2() {
    ...
    mBasicConfig.autoFocus(mDevice.getDeviceid(), xRelative, yRelative, new RPCCallback‹RPCResponse›() {
        @Override
        public void  onComplete(RPCResponse result) {
            Log.d(TAG, "autoFocus, code:" + result.code());
            if (result.code() == RPCErrorCode.SUCCESS) {
                mBasicConfig.getZoomFocusConf(mDevice.getDeviceid(), new RPCCallback‹RPCResponse‹RPCResponse.ZoomFocusBean››() {
                    @Override
                    public void onComplete(RPCResponse‹RPCResponse.ZoomFocusBean› result) {
                        if (result.code() == RPCErrorCode.SUCCESS) {
                            zfBean = result.data();
                            mSbZoom.setProgress(zfBean.zoom);
                        }
                    }
                });
            }
        }
    });
    ...
}

// 自动对焦如果不够清晰,可以手动对焦进行微调
private void func3() {
    ...
    mBasicConfig.manualFocus(mDevice.getDeviceid(), focus, new RPCCallback‹RPCResponse›() {
        @Override
        public void onComplete(RPCResponse result) {
            if (result.code() == RPCErrorCode.SUCCESS) {
                zfBean.focus = focus;
            }
        }
    });
    ....
}

2.4.3 设置门线

设置门线主要是更精确地判断人流的方向(进门、出门、路过),提高人流统计的准确度。

调用接口PeopleFlowStats.getInstance(context).setDoorLine设置门线。代码如下:

private void func() {
    ...
    PeopleFlowStats.getInstance(context).setDoorLine(mDevice.getDeviceid(), 0, lineStart[0],
            lineStart[1], lineEnd[0], lineEnd[1], new RPCCallback‹RPCResponse›() {
                @Override
                public void onComplete(RPCResponse result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        stopPlay();
                        finish();
                        startActivity(new Intent(context, MainActivity.class));
                    }
                }
            });
    ...
}

3、注册人脸

IPC激活好以后,向设备注册人脸信息。

Android设备端注册人脸时,需要同步向IPC设备注册人脸需要传递用户人脸照片、以及唯一标识用户的ID。

通过接口IPCameraManager.getInstance(context).addFaceRecord向IPC注册人脸。代码如下:

/*
 *传递人脸照片路径: picPath,
 * 用户唯一ID: uid
 */
IPCameraManager.getInstance(context).addFaceRecord(picPath, uid, new RPCCallback‹RPCResponse›() {
                        //IPC设备调用成功后会调用onComplete
                        @Override
                        public void onComplete(RPCResponse result) {
                            Log.i(TAG, "addFaceRecord, code:" + result.code());
                            if (result.code() != 0){
                                showToast(context, "添加人脸失败 code: " + result.code());
                            } else {
                                showToast(context, "添加人脸成功");
                            }
                        }
                        //IPC设备离线时,调用会调用onError
                        @Override
                        public void onError(Throwable t) {
                            super.onError(t);
                            Log.i(TAG, "addFaceRecord, Exception:" + t.getMessage());
                            showToaste(context, "添加人脸失败 ");
                        }
                    });

4、删除人脸

Android设备端删除用户时,需要同步删除IPC设备的人脸信息。Andorid端设备需要传递用户唯一ID。

调用接口 IPCameraManager.getInstance(context).addFaceRecord 向IPC删除人脸。代码如下:

//传递用户唯一ID:userId
IPCameraManager.getInstance(context).deleteFaceRecord(userId,new RPCCallback‹RPCResponse‹RPCResponse.FaceDeleteSubResult››() {
                @Override
                public void onComplete(RPCResponse‹RPCResponse.FaceDeleteSubResult› result) {
                    Log.i(TAG, "deleteFaceRecord, code:" + result.code());
                    if (result.code() != 0){
                        showToast(context, "删除人脸失败 code: " + result.code());
                    } else {
                        showToast(context, "删除人脸成功");
                    }
                }

                @Override
                public void onError(Throwable t) {
                    super.onError(t);
                    Log.i(TAG, "addFaceRecord, Exception:" + t.getMessage());
                }

            });

5、 监听人脸识别消息

Android设备注册好人脸信息后,即可以监听IPC设备人脸识别信息。通过IPC返回的ID信息,查询数据库,显示用户详细信息。

调用IPCameraManager.getInstance(context).setFaceDetectListener即可监听人脸信息,代码如下:

IPCameraManager.getInstance(context).setFaceDetectListener(new FaceDetectListener() {
    @Override
    public void onFaceDetect(String userId) {
        String userName = null;
        if (userName == null) {
            showToast(getApplicationContext(), "未注册的用户进店");
        } else {
            showToast(getApplicationContext(), "用户[ " + userName + " ]进店");
        }
    }
});

OpenAPI开发指南

  1. 准备工作
  2. OpenAPI简介
  3. Android SDK简介
  4. Android SDK集成指南
  5. 开发联调
  6. 正式上线

1.准备工作

1.1 申请API对接所需的材料

步骤一:联系商米售前技术团队,申请对接API所需要的账号信息,需要向售前技术支持团队提供如下软件开发商信息

客户提供的信息说明
客户名称 软件商公司名称
对接摄像头型号 对接API的型号,如SS(Store Sense)、FS(Face Sense)
客户设备的SN 客户对接联调的所有设备的SN,需要商米云把这些SN加入到UAT环境
客户需求场景 描述大体的需求和使用场景

步骤二:售前技术团队根据客户的需求信息,返回软件商对接API所需的账号信息,包括

返回给客户的信息说明
SaaS代号 商米内部管理SaaS软件商信息的代号
app_id 激活设备端API所需的账号
secret_key 调用API所需的签名秘钥
设备激活码 激活设备端API所需的激活码,用于签名校对软件商是否有权调用
可激活的设备数量上限 UAT环境下的可激活设备的数量
openapi.bks 双向验证中客户端自己的证书以及私钥。
sunmica.bks 双向验证中用于校验服务端发送过来的的证书是否合法。
SDK开发包 用于对接的SDK,目前只支持安卓版本
UAT环境商米助手APP 用于在UAT环境下给设备做首配

1.2 设备刷机UAT环境固件

一般,软件商客户在开发对接API阶段,建议使用调试环境,即商米的UAT环境,避免对正式生产环境造成困扰或者数据污染。

因此,需要将摄像头的固件升级到开发测试环境(UAT环境),具体操作流程参考 升级开发环境固件

另外,刷机到UAT环境后,可以使用UAT环境商米助手进行首次配置,使摄像机工作起来,后续集成了Android SDK后可以在开发者自己的应用上对摄像头进行配置,而无需商米助手。

2.OpenAPI简介

摄像头API提供强大的功能,几乎包含了摄像头本身具有的所有功能,用户可以通过这些API开发自己的功能并集成到自己的应用上。

API提供两种使用方式,用户可以选择其中一种方式来完成自己的开发,简单介绍如下:

方式一:直接根据API接口实现原理和接口细节(详见API调用方式),自己开发客户端代码调用API。

方式二:推荐的方式,根据商米提供的平台SDK(当前只支持安卓SDK,详见设备端Android SDK),直接调用JAVA API来完成对摄像头API的调用,不需要过多关注API的接口细节,方便快捷,节省开发时间。

后续的集成指南介绍都以安卓SDK来展开。

3. Android SDK简介

3.1 相关文档

序号内容项获取方式说明
1SDK技术文档Android SDK技术文档包含了SDK的API说明与示例代码。
2SDK开发包由技术支持提供给SaaS客户。见章节3.2。
3SDK LicenseSaaS客户提供自身的客户信息进行申请,技术支持邮件发送给SaaS客户。License目前包含:app_id、secret_key、激活码

3.2 SDK开发包介绍

  • SDK开发包组成
序号文件/文件名说明
1IpcDemoDemo工程源码,可直接编译出Demo APP
2ipcsdk.aarOpenAPI库
3demo.apk编译好的Demo APP
  • Demo工程源码结构

4. Android SDK集成指南

详见设备端Android SDK集成指南

5. 开发联调

激活设备API后,可以正式调用摄像头所有开发的API完成自己的业务开发和调试,测试通过后即可准备上线。

6. 正式上线

由于开发联调是在UAT环境完成的,因此,客户确认业务测试没有问题后,给客户正式使用的时候需要上线到正式环境,即商米的 Release环境。

可以给设备升级到正式环境,见升级线上环境固件的操作指南。在正式环境下回归测试OK后,即可正式发布。

商米AI识客SDK开发指南

1.准备工作

1.1 申请API对接所需的材料

步骤一:联系商米售前技术团队,申请对接API所需要的账号信息,需要向售前技术支持团队提供如下软件开发商信息

客户提供的信息说明
客户名称软件商公司名称
对接摄像头型号对接API的型号,如SS(Store Sense)、FS(Face Sense)
收银设备硬件指纹获取到SDK后,通过SDK获取硬件指纹信息,用于申请Licence

步骤二:售前技术团队根据客户的需求信息,返回软件商对接API所需的账号信息,包括:

返回给客户的信息说明
SaaS代号商米内部管理SaaS软件商信息的代号
设备激活码激活设备端所需的激活码,用于签名校对软件商是否有权调用
SDK开发包用于人脸识别以及AI识客设备开发的SDK,目前只支持安卓版本

SDK开发包:

序号文件/文件名说明
1FaceDemoDemo工程源码
2face-release.aar商米人脸识别SDK
3libipcsdk-release.aarIPC联动版sdk
4asset 商米 人脸识别资源文件

2、申请Licence

登陆开发者网站,提供 以下信息申请Licence

提供的信息说明
收银设备硬件指纹 通过SDK API获取设备硬件信息
人脸库人脸库的限制大小

商米开发者网站会生成一个Licence文件。SaaS开发者获取到Licence后即使用SDK。

3、添加SDK到工程

3.1 导入SDK

右键点击app,选择New->Module->Import .JAR/.AAR Package

点击Finish即导入aar模块

3.2 权限申明

在AndroidManifest.xml文件里添加如下权限:

	‹uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/›
	‹uses-permission android:name="android.permission.INTERNET" /›
	‹uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /›
	‹uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /›
	‹uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /›
	‹uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /›
	‹uses-permission android:name="android.permission.CAMERA" /›

3.3 导入asset资源

导入提供的assets资源

说明:资源文件需要APP启动的时候复制到APP的运行目录,以方便SDK后续初始化。

3.4 导入licence文件

获取到licence_valid.txt文件,可以放到assets资源目录,也可以放到sdcard目录。

1)放到assets资源目录,则需要把licence_valid.txt文件从资源文件复制到app的工作目录。

2)如果放到sdcard目录,则需要app申请sdcard读取权限。

无论哪一种情况,最后使用的时候,需要传递licence_valid.txt文件的路径,以激活人脸识别SDK。详细见SDK初始化代码

4、DEMO说明

4.1 工程目录结构

4.1.1 app Module

app Module是DEMO的入口。其中包含了各个模块的入口函数调用。

4.1.2 commonlib

commonlib Module包含了整个DEMO通用的接口。其中包含摄像头管理,工具类。如下图:

4.1.2.1 FaceCameraManager

FaceCameraManager统一管理设备自带摄像头和USB摄像头。设备自带摄像头的流程是使用Android系统提供的摄像头通用方法。USB摄像头则使用UVC库提供的操作摄像头的方法。FaceCameraManager对上层应用提供统一的方法: startPreview和 stopPreview。

4.1.2.2 NormalCameraManager

NormalCameraManager使用Android系统提供的摄像头方法,实现startPreview和stopPreview。并在内部实现回调接口用于返回每一帧摄像头获得的数据。

4.1.2.3 UVCCameraManager

UVCCameraManager使用第三方库UVClib提供的方法用于操作USB摄像头,实现startPreview和stopPreview。 并在内部实现回调接口用于返回每一帧摄像头获得的数据 。

4.1.2.4 ByteUtils

ByteUtils提供Byte操作的一些通用方法。

4.1.2.5 DBHelper

DBHelper提供创建数据的方法。

4.1.2.6 ImageUtils

ImageUtils提供图片操作的一些方法。

4.1.3 libuvccamera

libuvccamera Module采用第三方库开源库libuvccamera

4.1.4 facedemo

facedemo Module是DEMO实现功能的主要模块。其包含:会员注册、会员管理、会员查询、IPC配置等功能。

5、SDK初始化

详细代码见 商米AI识客SDK示例代码-> SDK初始化

6、获取摄像头数据

详细代码见 商米AI识客SDK示例代码->获取摄像头信息

7、获取摄像头预览数据

详细代码见 商米AI识客SDK示例代码->获取摄像头预览信息

8、会员注册

详细代码见 商米AI识客SDK示例代码-> 会员注册

9、会员实时查询

会员实时查询,需要使用到,实时获取摄像头预览数据以及人脸特征提取两个功能

详细代码见 商米AI识客SDK示例代码->获取摄像头预览信息

详细代码见 商米AI识客SDK示例代码->人脸特征提取

10、配置摄像机

见IPC配置页面

示例参考

  1. SDK初始化
  2. 设备发现
  3. 首次配网并激活
  4. 画面调整
  5. 设置门线
  6. 监听人脸识别消息

本文描述的示例代码都可在Demo工程源码中找到,故详细代码可阅读SDK开发包介绍中提到的Demo工程源码。

1. SDK初始化

APP_ID:激活与API调用校验使用的账号。

SECRET_KEY:API调用所需的签名密钥。

LICENSE:激活API所需的激活码。

IPCameraManager mIPCameraManager = IPCameraManager.getInstance(context);
mIPCameraManager.init(APP_ID, SECRET_KEY, LICENSE);

2.设备发现

当集成安卓SDK的机器和摄像头设备在同一个局域网内的时候,可以通过如下示例方法扫描到所有的摄像头设备。

设备发现listener返回的IPCameraInfo包含了IPC的SN、IP、MAC等基本信息。

mIPCameraManager.registerListener(new IPCameraListener() {
    @Override
    public void onDeviceOnline(IPCameraInfo device) {
        showToast(getApplicationContext(), "[ " + device.getDeviceid() + " ]上线");
    }
    @Override
    public void onDeviceOffline(IPCameraInfo device) {
        showToast(getApplicationContext(), "[ " + device.getDeviceid() + " ]离线");
    }
});

3.首次配网并激活

调用IPC前需要先获取到IPC的IP地址,故第一步是把IPC接入网络,IPC连接网络的方式有有线连接和无线连接两种方式。

由于有线网络相对不容易受到环境干扰,稳定性和可靠性较高,故首选是有线方式接入网络。

有线接入:
有线接入只需要通过网线把IPC设备和集成SDK的Android设备接入到同一局域网即可,不需要其它设置。

无线接入:

无线配网方式相对要复杂点,步骤如下:

  1. 使用手机/PC的无线网卡扫描IPC的AP热点,一般AP热点的名称为SUNMI_XXXX,其中XXXX为MAC地址最后2个字节的16进制数字,MAC地址可以通过设备机身后背的标贴或者包装盒的标贴查到,AP热点本身是无加密的。
  2. 使用手机/PC的无线网卡连接IPC的AP热点,此时手机/PC就会获取到IPC分配的IP地址(按照设备发现描述的方法即可获取到),一般会是192.168.200.XXX,手机/PC的网关地址就是IPC的地址,一般会是192.168.200.1。
  3. 调用无线配置 API(见获取无线扫描AP列表 (无需签名校验)的描述)获取IPC扫描到的AP热点。
  4. 调用无线配置 API(见设置无线参数(无需签名校验)的描述)设置IPC要连接的无线网络(例如无线路由器的SSID和密码),使得IPC能够从网关处获取到IP地址。
  5. 如果网络是可以正常上网的话,IPC取到IP地址后很快就会亮蓝灯,此时表明IPC可以正常连接Internet了。
// 获取IPC扫描的AP热点
private void getWifiList() {
    BasicConfig.getInstance(context).getApListWithoutAuth(sunmiDevice.getDeviceid(),
            new RPCCallback‹RPCResponse‹IpcApBean››() {
                @Override
                public void onComplete(RPCResponse‹IpcApBean› result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        wifiListGetSuccess(result.data());
                    } else {
                        Log.i(TAG, "getApListWithoutAuth failed, errcode: " + result.code());
                    }
                }
        });

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            if (wifiList.size() == 0) {
                setNoWifiVisible(View.VISIBLE);
            }
        }
    }, TIMEOUT_GET_WIFI);
}
// 设置IPC连接的AP热点
private void setIpcWifi(String ssid, String psw) {
    showLoadingDialog();
    BasicConfig.getInstance(context).setWifiConfWithoutAuth(sunmiDevice.getDeviceid(),
            ssid, psw, new RPCCallback‹RPCResponse›() {
                @Override
                public void onComplete(RPCResponse result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        hideLoadingDialog();
                        shortTip("配置成功,请等待设备联网,联网后指示灯会变成蓝色");
                        createWaitDialog();
                    } else {
                        hideLoadingDialog();
                        shortTip("配置失败");
                    }
                }

                @Override
                public void onError(Throwable t) {
                    hideLoadingDialog();
                    shortTip("配置失败");
                }
            });
}

激活IPC

配网成功后,使用IPC的其它API时需要签名校验,所以在使用API之前需要激活IPC。激活只需要在第一次使用IPC时进行,后续都不再需要激活。

DeviceManage.getInstance(context).activate(ipcList.get(postion).getDeviceid(),
    new RPCCallback‹RPCResponse›() {
        @Override
        public void onComplete(RPCResponse result) {
            if (result.code() == RPCErrorCode.SUCCESS || result.code() == RPCErrorCode.DEVICE_ACTIVATED) {
                Log.i(TAG, "activate ipc success");
            } else {
                Log.i(TAG, "activate ipc failed");
            }
        }

        @Override
        public void onError(Throwable t) {
            Log.i(TAG, "activate ipc failed");
        }
    });

4. 画面调整

由于IPC的人脸识别对于人脸图像质量有一定要求,因此在使用IPC前需要调整IPC的画面,以达到最好的体验效果。画面调整包括镜头的调焦、对焦。

private BasicConfig mBasicConfig;

public void init() {
    ...
    mBasicConfig = BasicConfig.getInstance(context);
    ...
}

// 调焦
private void func1(){
    ...
    mBasicConfig.setZoom(mDevice.getDeviceid(), zfBean.zoom, new RPCCallback‹RPCResponse›() {
        @Override
        public void onComplete(RPCResponse result) {
            Log.i(TAG, "setZoom, code:" + result.code());
        }
    });
    ...
}

// 自动对焦
private void func2() {
    ...
    mBasicConfig.autoFocus(mDevice.getDeviceid(), xRelative, yRelative, new RPCCallback‹RPCResponse›() {
        @Override
        public void  onComplete(RPCResponse result) {
            Log.d(TAG, "autoFocus, code:" + result.code());
            if (result.code() == RPCErrorCode.SUCCESS) {
                mBasicConfig.getZoomFocusConf(mDevice.getDeviceid(), new RPCCallback‹RPCResponse‹RPCResponse.ZoomFocusBean››() {
                    @Override
                    public void onComplete(RPCResponse‹RPCResponse.ZoomFocusBean› result) {
                        if (result.code() == RPCErrorCode.SUCCESS) {
                            zfBean = result.data();
                            mSbZoom.setProgress(zfBean.zoom);
                        }
                    }
                });
            }
        }
    });
    ...
}

// 自动对焦如果不够清晰,可以手动对焦进行微调
private void func3() {
    ...
    mBasicConfig.manualFocus(mDevice.getDeviceid(), focus, new RPCCallback‹RPCResponse›() {
        @Override
        public void onComplete(RPCResponse result) {
            if (result.code() == RPCErrorCode.SUCCESS) {
                zfBean.focus = focus;
            }
        }
    });
    ....
}

5. 设置门线

设置门线主要是更精确地判断人流的方向(进门、出门、路过),提高人流统计的准确度。

private void func() {
    ...
    PeopleFlowStats.getInstance(context).setDoorLine(mDevice.getDeviceid(), 0, lineStart[0],
            lineStart[1], lineEnd[0], lineEnd[1], new RPCCallback‹RPCResponse›() {
                @Override
                public void onComplete(RPCResponse result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        stopPlay();
                        finish();
                        startActivity(new Intent(context, MainActivity.class));
                    }
                }
            });
    ...
}

6. 监听人脸识别消息

mIPCameraManager.setFaceDetectListener(new FaceDetectListener() {
    @Override
    public void onFaceDetect(String userId) {
        String userName = null;
        if (userName == null) {
            showToast(getApplicationContext(), "未注册的用户进店");
        } else {
            showToast(getApplicationContext(), "用户[ " + userName + " ]进店");
        }
    }
});

商米AI识客SDK代码示例

  1. SDK 初始化
  2. 获取摄像头预览信息
  3. 获取摄像头拍照数据
  4. 获取特征值
  5. 人脸特征值比较
  6. 人脸会员库添加
  7. 删除特征记录
  8. 会员注册
  9. IPC 回调接口

1. SDK初始化

public boolean initFaceSdkInstance(Context context, String licencePath) {
         //初始化配置文件
        int ret = SunmiFaceSDK.init(confPath);
        if (licencePath == null) {
            Log.d(TAG, "Please input licence");
            return false;
        }
        File f = new File(licencePath);
        if (!f.exists()) {
            Log.d(TAG, "licence is not exist");
            return false;
        }
        String licence = readToString(licencePath);
        //验证Licence
        ret = SunmiFaceSDK.verifyLicence(context, licence);
        if (ret != 0) {
            Log.d(TAG, "Licence is not OK ErrorCode " + ret);
            return false;
        }
        //设置配置参数
        SunmiFaceConfigParam param = new SunmiFaceConfigParam();
        SunmiFaceSDK.getConfig(param);
        param.setDistance_threshold_(1.3f);
        param.setYaw_threshold_(50.0f);
        param.setPitch_threshold_(50.0f);
        param.setRoll_threshold_(50.0f);
        param.setMin_face_size(60);
        param.setImage_quality_threshold_(10);
        param.setMin_luminance_(10);
        param.setMax_luminance_(180);
        param.setLiveness_on_(false);
        param.setThread_num_(1);
        //设置配置参数
        ret = SunmiFaceSDK.setConfig(param);

        if (ret != 0) {
            Log.d(TAG, "param is not init");
            return false;
        }
        return true;
    }

public void init(Context context) {
      //设置licence_valid.txt为sd卡路径,也可以换成app运行工作目录。
      String licencePath = Environment.getExternalStorageDirectory() + File.separator + "licence_valid.txt";
      initFaceSdkInstance(context, licence_path);
     // 调用IPCameraManager静态类getInstance初始化IPCManger对象
     mIPCManager = IPCameraManager.getInstance(context);
}

2. 获取摄像头预览信息

//摄像头数据回调接口	
CameraDataCallback cameraDataCallback = new CameraDataCallback() {
    @Override
    public void onImageDataArrival(Bitmap bitmap, int width, int height) {
        if (mBackgroundHandler != null) {
            if (mClickAction == 1 && !faceDetectState.get()) {
                mBackgroundHandler.post(new MotionDetector(bitmap, width, height));
            } else {
                bitmap.recycle();
            }
        }

    }
};
//开启摄像头预览
FaceCameraManager.getInstance().startPreview(getApplicationContext(), this, FaceCameraManager.getInstance().getCurCamera(), cameraView, PREVIEW_WIDTH, PREVIEW_HEIGHT, cameraDataCallback);

3. 获取摄像头拍照数据

{
//根据摄像头是USB还是系统摄像头,调用不同的方法,获取bitmap数据
if (FaceCameraManager.getInstance().getCurCamera() == FaceCameraManager.CAMERA_USB)
    bitmap = mUVCCameraView.captureStillImage();
else
    bitmap = mTextureView.getBitmap();

}

4. 获取特征值

说明:通过bitmap获取特征值

    public ArrayList‹SunmiFaceFeature› getFeatures(Bitmap bitmap, int maxFace) {

        int ret = 0;
        //bitmap的RGB数据转化成BGR数据
        byte[] srcData = ImageUtils.getPixelsBGR(bitmap);
        //通过BGR数据构造SunmifaceImage对象
        SunmiFaceImage image = new SunmiFaceImage(srcData, bitmap.getHeight(), bitmap.getWidth(), maxFace);
        SunmiFaceImageFeatures features = new SunmiFaceImageFeatures();
        //从SunmifaceImage数据中提取人脸特征
        ret = SunmiFaceSDK.getImageFeatures(image, features);
        //返回人脸特征数组
        SunmiFaceFeature feature_ary = features.getFeatures_();
        ArrayList‹SunmiFaceFeature› arrayList = new ArrayList‹›();
        if (features.getFeatures_count_() == 0) {
            SunmiFaceSDK.releaseImageFeatures(features);
            return arrayList;
        }

        for (int i = 0; i ‹ features.getFeatures_count_(); i++) {
            SunmiFaceFeature sunmiFaceFeature = SunmiFaceLib.SunmiFaceFeatureArray_getitem(feature_ary, i);
            arrayList.add(sunmiFaceFeature);
        }
        SunmiFaceSDK.releaseImageFeatures(features);
        return arrayList;
        // return getFeature(srcData, bitmap.getWidth(),bitmap.getHeight(), 1);
    }

5. 人脸特征值比较

    public SunmiFaceCompareResult compareFeature1V1(float similar, float[] feature1, float[] feature2) {
        //构造SunmiFaceFeature对象
        SunmiFaceFeature feat1 = new SunmiFaceFeature();
        SunmiFaceFeature feat2 = new SunmiFaceFeature();
        feat1.setFeature_(feature1);
        feat2.setFeature_(feature2);
        SunmiFaceCompareResult result = new SunmiFaceCompareResult();
        //调用compare1v1进行比较
        int ret = SunmiFaceSDK.compare1v1(feat1, feat2, result);
        return result;
    }

6. 人脸会员库添加

说明: 添加人脸时,同时要向IPC注册人脸信息。向IPC传递信息包括:人脸会员库(GVIP),人脸照片、人脸会员唯一识别ID。

{
        //使用faceFeature构建record
        SunmiFaceDBRecord record =     SunmiFaceSDK.faceFeature2FaceDBRecord(faceFeature);
        record.setId_(user.getUserId());
        record.setName_(user.getUserName());
        //把record添加到人脸库
        int ret = SunmiFaceSDK.addDBRecord(record);
        //把人脸照片传递给IPC,向IPC添加人脸记录   
         if (mIPCManager != null) {
                    mIPCManager.addFaceRecord("GVIP", picPath, uid, new RPCCallback‹RPCResponse›() {
                        //IPC人脸添加记录完成后回调接口
                        @Override
                        public void onComplete(RPCResponse result) {
                            Log.i(TAG, "addFaceRecord, code:" + result.code());
                        }
                        //IPC人脸添加记录失败时回调接口
                        @Override
                        public void onAbort(int httpStatus) {
                            Log.i(TAG, "addFaceRecord, httpStatus:" + httpStatus);
                        }
                        //IPC人脸添加记录异常时回调接口
                        @Override
                        public void onException(Throwable t) {
                            Log.i(TAG, "addFaceRecord, Exception:" + t.getMessage());
                        }
                    });
                }

}

7. 删除特征记录

说明: 删除会员信息时,需要把删除的人脸会员信息同步到IPC。向IPC传递用户的唯一识别ID

{
//删除人脸库记录
int result = SunmiFaceSDK.deleteDBRecord(user.getImageName());
//同步删除IPC人脸记录
if (mIPCManager != null) {
    mIPCManager.deleteFaceRecord("GVIP", userId, new RPCCallback‹RPCResponse‹RPCResponse.FaceDeleteSubResult››() {
         //IPC人脸删除记录完成后回调接口
        @Override
        public void onComplete(RPCResponse‹RPCResponse.FaceDeleteSubResult› result) {
            Log.i(TAG, "deleteFaceRecord, code:" + result.code());
        }
    });
}
}

8 会员注册

/*
 *向系统注册会员信息
 *@groupname, 会员组
 *@userNanem, 会员名
 *@picPath,   会员照片存放路劲
 *@userInfo,  会员详情
 *@faceFeature, 会员照片提取的特征值
 */
public boolean registerUserIntoDBmanager(String groupName, String userName, String picPath, String userInfo, SunmiFaceFeature faceFeature) {
        boolean isSuccess = false;
        Group group = new Group();
        group.setGroupId(groupName);
        //创建会员
        User user = new User();
        user.setGroupId(groupName);
        //final String uid = UUID.randomUUID().toString();
        String uid = String.valueOf(System.currentTimeMillis());
        user.setUserId(uid);
        user.setUserName(userName);
        user.setFeature(faceFeature.getFeature_());
        //创建人脸库记录信息
        SunmiFaceDBRecord record = SunmiFaceSDK.faceFeature2FaceDBRecord(faceFeature);
        record.setId_(user.getUserId());
        record.setName_(user.getUserName());
        //像人脸库添加记录
        int ret = SunmiFaceSDK.addDBRecord(record);
        if (ret != 0) {
            Log.d(TAG, "addDBRecord failed " + SunmiFaceSDK.getErrorString(ret));
            return false;
        }
        //在会员数据库中记录,特征值对应的图片在人脸数据中的ID
        user.setImageName(record.getImg_id_());
        if (userInfo != null) {
            user.setUserInfo(userInfo);
        }
        // 添加用户信息到数据库
        boolean importUserSuccess = FaceManager.getInstance().userAdd(user);
        if (importUserSuccess) {
            // 如果添加到数据库成功,则添加用户组信息到数据库
            // 如果当前图片组名和上一张图片组名相同,则不添加数据库到组表
            if (FaceManager.getInstance().groupAdd(group)) {
                isSuccess = true;
                if (mIPCManager != null) {
                    Log.i(TAG, "picPath: " + picPath);
                    Log.i(TAG, "uid: " + uid);
                    //把会员照片同步发送到IPC,同步会员信息到IPC
                    mIPCManager.addFaceRecord("GVIP", picPath, uid, new RPCCallback‹RPCResponse›() {
                        @Override
                        public void onComplete(RPCResponse result) {
                            Log.i(TAG, "addFaceRecord, code:" + result.code());
                        }

                        @Override
                        public void onAbort(int httpStatus) {
                            Log.i(TAG, "addFaceRecord, httpStatus:" + httpStatus);
                        }

                        @Override
                        public void onException(Throwable t) {
                            Log.i(TAG, "addFaceRecord, Exception:" + t.getMessage());
                        }
                    });
                }
            } else {
                isSuccess = false;
            }

        } else {
            isSuccess = false;
        }
        return isSuccess;
    }

9. IPC 回调接口

说明:用户获取IPC的状态、返回信息等。

{
                    //获取IPC信息
                    IPCameraManager manager = IPCameraManager.getInstance(getApplicationContext());
                    //IPC初始化
                    manager.init("test", "123456", "123456");
                    //注册IPC回调
                    manager.registerListener(new IPCameraListener() {
                        //IPC设备在线时回调接口
                        @Override
                        public void onDeviceOnline(IPCameraInfo device) {
                            showToast("[ " + device.getDeviceid() + " ]上线");
                        }
                        //IPC设备离线时回调接口
                        @Override
                        public void onDeviceOffline(IPCameraInfo device) {
                            showToast("[ " + device.getDeviceid() + " ]离线");
                        }
                        //IPC设备获取到进店信息后回调接口
                        @Override
                        public void onFaceDetect(String userId) {
                            String userName = null;
                            List‹User› users = DBManager.getInstance().queryUserByUserId(userId);
                            if (users != null && users.size() › 0) {
                                userName = users.get(0).getUserName();
                            }
                            if (userName == null) {
                                showToast("未注册的用户进店");
                            } else {
                                User user = users.get(0);
                                UserEvent userEvent = new UserEvent();
                                userEvent.setUserId(user.getUserId());
                                userEvent.setEnterTime(System.currentTimeMillis());
                                userEvent.setDesc("Enter Store");
                                DBManager.getInstance().addUserEvent(userEvent);
                                showToast("用户[ " + userName + " ]进店");
                            }
                        }
                    }
}

商米AI识客SDK接口说明

1. 商米人脸识别接口

见《商米人脸识别会员接口文档》,该文档在申请开发包的时候一并提供。

2. IPC联动接口

见IPCManager页面

商米AI识客SDK简介

1 简介

商米AI识客SDK (Android版)是一种面向SUNMI OS设备的人脸识别开发包,配套商米AI识客摄像头使用,SDK主要包含人脸采集、人脸识别、人脸特征数据管理、摄像头配置激活、会员进店提醒等功能,以aar包形式发布。如下是SDK整体框架以及Demo APP架构。

SDK框架图如下:

  • 主要包含四层结构
  • 1、C/C++ library实现人脸特征提取,人脸特征对比等算法功能,并提供人脸库管理(人脸库的添加、查询、删除等操作)
  • 2、JNI层主要是封装C/C++实现的人脸识别的功能,为Java上层提供native接口
  • 3、Java API层使用native层提供的接口为上层 APP提供SunmiFace接口,并提供IPC配置管理、通讯交互的功能
  • 4、Demo APP,使用SDK提供的功能实现人脸识别、人脸库的管理、IPC配置管理、IPC通讯等功能。

2 业务场景

通过此SDK您可以完成类似如下的应用场景开发。