示例参考

  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. 画面调整

目前仅商米AI识客摄像机支持。

由于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. 设置门线

目前仅商米AI识客摄像机支持。

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

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. 监听人脸识别消息

目前仅商米AI识客摄像机支持。

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

SDK实体类说明

  1. IPCameraInfo类
  2. RPCResponse类
  3. FaceRecogEvent类
  4. DynamicDetectEvent类
  5. RPCErrorCode类

1. IPCameraInfo类

public class IPCameraInfo implements Serializable {
    /**
     * 序列号
     */
    private String deviceid;

    /**
     * 设备型号
     */
    private String model;

    /**
     * 固件版本号
     */
    private String firmware;

    /**
     * 设备MAC地址
     */
    private String mac;

    /**
     * 设备IP地址
     */
    private String ip;

    /**
     * 设备名称
     */
    private String name;

    /**
     * 设备类型
     */
    private String type;

    /**
     * 设备接入的网络类型;AP-WIFI接入,LAN-有线接入
     */
    private String network;
}

2. RPCResponse类

public class RPCResponse‹T› {
    private int code;
    private @Nullable T data;

    public int code() {
        return code;
    }

    public @Nullable T data() {
        return data;
    }

    public class IpcInfoBean {
        // 设备序列号
        public String sn;
        // 设备型号
        public String model_name;
        // 设备名称
        public String name;
        // 软件版本号
        public String software_version;
        // 硬件版本号
        public String hardware_version;
        // IP地址
        public String ip;
        // MAC地址
        public String mac;
    }

    public class WifiConfBean {
        // 当前连接的SSID
        public String ssid;
        // 当前SSID的密码
        public String password;
    }

    public static class IpcApBean {
        // 返回扫描到的AP个数
        public int num;
        // 扫描到的AP
        public List‹ApListBean› ap_list;

        public static class ApListBean {
            // 扫描到AP的ssid
            public String ssid;
            // 扫描到AP的加密方式
            public String key_mgmt;
        }
    }

    public class ZoomFocusBean {
        // 当前的焦距
        public int zoom;
        // 能够调整的焦距最大值
        public int max_zoom;
        // 当前的聚焦值
        public int focus;
        // 能够调整的聚焦最大值
        public int max_focus;
        // 聚焦点在x方向的像素百分比
        public int focused_x;
        // 聚焦点在y方向的像素百分比
        public int focused_y;
    }

    public class IrSettingBean {
        // 夜视模式:0表示关闭,1表示开启,2表示自动。一般选2。
        public int irmode;
    }

    public class DynamicDetectBean {
        // 动态侦测移动灵敏度,范围[0, 3],0表示关闭,数值越大,越灵敏。
        public int motion_level;
        //  动态侦测声音灵敏度,范围[0, 3],0表示关闭,数值越大,越灵敏。
        public int audio_level;
        /**
         * 动态侦测时间设置参数
         * 以周为一个循环,用0xYY来表示选择哪一天,具体是0x80直接表示7×24小时,其余的,
         * 以7bit来表示哪一天被选上,0x01表示选择周一,0x02表示选择周二,0x40表示选择
         * 周天,0x7f表示选择一个礼拜的7天,与0x80的区别只是0x80直接默认724小时,而
         * 0x7f选了7天后,还可以设置具体的开始时间和结束时间。
         */
        public int weekday;
        /**
         * 动态侦测时间设置参数
         * 用分钟来表示,以一天24小时为例,以分钟为最小粒度,总共24*60这样的时间数值,
         * 60表示01:00,121表示02:01,依次类推。范围[0,1440]
         */
        public long start_time;
        /**
         * 动态侦测时间设置参数
         * 用分钟来表示,以一天24小时为例,以分钟为最小粒度,总共24*60这样的时间数值,
         * 60表示01:00,121表示02:01,依次类推。范围[0,1440]
         */
        public long stop_time;
    }

    public class IPCNameBean {
        // IPC名称
        public String name;
    }

    public class LedSettingBean {
        // 指示灯开关:0表示关闭指示灯,1表示开启指示灯,即可以亮
        public int led_switch;
    }

    public class RotationBean {
        // 画面旋转角度
        public int rotation;
    }

    public class SupportRotationsBean {
        // 支持的画面旋转角度:{"0","90","180","270"}
        public List‹String› angles;
    }

    public class ExternalStorageBean {
        // SD卡的四种状态:0表示未插入SD卡;1表示已插入SD卡但未初始化;2表示SD卡已插入且正常;3表示SD卡无法识别
        public int status;
    }

    public class LiveAddressBean {
        // 高清直播地址,RTSP协议
        public String hd_live_url;
        // 全高清直播地址,RTSP协议
        public String fhd_live_url;
    }

    public class PlaybackBean {
        // 视频回放URL,RTSP协议
        public String playback_url;
    }

    public class VideoRecordBean {
        // 符合条件的视频总数量,每个视频大概1分钟时长
        public int total_num;
        // 当前返回的视频数量
        public int return_num;
        // 视频片段列表
        public List‹VideoFragment› record_list;

        public class VideoFragment {
            // 视频的开始时间
            public long start_time;
            // 视频的结束时间
            public long end_time;
            // 视频的下载链接
            public String url;
        }
    }

    public class SnapshotBean {
        // 快照下载地址
        public String snapshot_url;
    }

    public class CurVideoBean {
        // 视频片段下载链接,在生成后4~12s生效
        public String url;
    }

    public class GroupBean {
        // 分组数量
        public int num;
        // 分组列表
        public List‹GroupInfo› face_group;
    }

    public class GroupInfo {
        // 分组最大容量
        public int capacity;
        // 分组内当前记录数量
        public int count;
        // 分组名称
        public String name;
        // 分组描述
        public String description;
        // stranger分组独有
        public int period;
        // stranger分组独有,代表一个生人在一定时间内(period设置,单位为天)来过多少次(times设置)就移动到熟人分组中去。
        public int times;
    }

    public class AttributesBean {
        // 分组自定义属性数量
        public int num;
        // 自定义属性名称列表
        public List‹String› name_list;
    }

    public class FaceDeleteSubResult {
        // 删除成功的人脸ID列表
        public List‹String› success_list;
        // 删除失败的人脸ID列表
        public List‹String› failed_list;
        // 不存在的人脸ID列表
        public List‹String› not_exist_list;
    }

    public class FaceRecordBean {
        // 人脸ID
        public String faceid;
        // 所在分组名称
        public String group_name;
        // 年龄,为空则表示用户没有设置过此人脸的年龄
        public int age;
        // 所属年龄段,1表示1~6岁,2表示7~12岁,3表示13~18岁,4表示19~28岁,5表示29~35岁,6表示36~45岁,7表示45~55岁,8表示55岁~100
        public int age_range;
        // 性别,0表示未知,1表示男性,2表示女性
        public int gender;
        // 到达过的总次数
        public int arrive_count;
        // 最后到达时间戳
        public int arrive_time;
    }

    public class FaceRecordListBean {
        // 总人脸数量
        public int total_num;
        // 当前返回人脸数量
        public int return_num;
        public List‹FaceRecordListBean› faceid_list;
    }

    public class DoorLineBean {
        // 参照分辨率:取值0和1,0表示1080P的分辨率,1表示720P的分辨率
        public int resolution;
        // 拌线的左边端点X坐标
        public int start_x;
        // 拌线的左边端点Y坐标
        public int start_y;
        // 拌线的右边端点X坐标
        public int end_x;
        // 拌线的右边端点Y坐标
        public int end_y;
    }

    public class StatsUnit {
        // 统计粒度的开始时间
        public long start_time;
        // 统计粒度的结束时间
        public long end_time;
        // 整数数组,表示男性统计数据,共有8个整数,从第一个整数开始依次表
        // 示1~6岁、7~12岁、13~18岁、19~28岁、29~35岁、36~45岁、46~55岁、
        // 56岁~100岁的男性人数。
        public List‹Integer› male_num_stat;
        // 整数数组,表示女性统计数组,含义同上
        public List‹Integer› female_num_stat;
    }

    public class PeopleStatBean {
        // 总人流数量/统计粒度内的人流数量
        public int total;
        public List‹StatsUnit› stat_list;
    }

    public class VisitorListBean {
        // 符合条件的总人脸数量
        public int total_num;
        // 当前人脸数量
        public int return_num;
        public List‹FaceRecordBean› face_list;
    }

    public class VisitDetailBean {
        // 总到访次数
        public int total_times;
        // 到访的具体时间戳
        public List‹Long› came_in_time;
    }

    public class IPCEventList {
        // 订阅的事件数量
        public int num;
        // 订阅的事件列表
        public List‹IPCEvent› sub_event;

        private class IPCEvent {
            public String event;
            public List‹String› http_callback;
        }
    }
}

3. FaceRecogEvent类

public class FaceRecogEvent {
    // 事件ID
    public long event_id;
    // 人脸ID
    public String faceid;
    // 分组名称
    public String group_name;
    // 年龄
    public String age;
    // 年龄段
    public String age_range;
    // 性别
    public String gender;
    // 事件类型
    public String event_type;
    // IPC序列号
    public String sn;
    // 事件上报时间
    public String report_time;
}

4. DynamicDetectEvent类

public class DynamicDetectEvent {
    // 事件ID
    public long event_id;
    // 事件类型
    public String event_type;
    // 事件发生的时间戳,UNIX时间戳
    public long report_time;
    // 动态侦测检测到的类型,1表示画面变化,2表示声音变化,3表示既有画面变化,也有声音变化
    public int detect_type;
    // 视频下载路径
    public String video_url;
    // 发出此消息的IPC序列号
    public String sn;
}

5. RPCErrorCode类

public class RPCErrorCode {
    // 正常,操作成功
    public static final int SUCCESS = 0;
    // app_id不合法
    public static final int APPID_INVALID = 1;
    // 合法性检查失败
    public static final int PARAMS_INVALID = 2;
    // 未知错误
    public static final int UNKNOWN = 3;
    // 请求激活的设备没有联网
    public static final int DEVICE_NOT_ONLINE = 4;
    // app_id或者secret key错误
    public static final int SECRET_KEY_INVALID = 5;
    // URL错误
    public static final int URL_NOT_FOUND = 6;
    // 设备未激活
    public static final int DEVICE_NOT_ACTIVATED = 7;
    // 设备激活失败
    public static final int DEVICE_ACTIVATE_FAILED = 8;
    // 设备已激活,不需要重复激活
    public static final int DEVICE_ACTIVATED = 9;
    // 设备已绑定,激活失败
    public static final int DEVICE_BINDED = 10;
    // SN错误,激活失败
    public static final int SN_DISMATCH = 11;
    // 操作不允许
    public static final int OP_NOT_ALLOWED = 12;

    // SSID不符合规范
    public static final int WIRELESS_SSID_INVALID = 100;
    // 密码不合法
    public static final int WIRELESS_PASSWORD_INVALID = 101;

    // 焦距范围不合法
    public static final int LENS_ZOOM_PARAM_INVALID = 110;
    // 聚焦范围不合法
    public static final int LENS_FOCUS_PARAM_INVALID = 111;
    // 自动聚焦的百分比错误
    public static final int LENS_AUTOFOCUS_PARAM_INVALID = 112;
    // 夜视模式参数不合法
    public static final int LENS_IRMODE_PARAM_INVALID = 113;

    // 动态侦测画面灵敏度不合法
    public static final int DETECT_MOTION_LEVEL_INVALID = 114;
    // 动态侦测声音灵敏度不合法
    public static final int DETECT_AUDIO_LEVEL_INVALID = 115;
    // 动态侦测日期不合法
    public static final int DETECT_WEEKDAY_INVALID = 116;
    // 动态侦测开始时间或者结束时间不合法
    public static final int DETECT_TIME_PERIOD_INVALID = 117;

    // 名字长度超出范围
    public static final int CAM_INFO_NAME_INVALID = 118;
    // 指示灯开关参数错误
    public static final int CAM_INFO_LED_INVALID = 119;
    // 旋转参数不合法
    public static final int CAM_INFO_ROTATION_INVALID = 120;
    // 指定时间段内的视频数据不存在
    public static final int CAM_INFO_VIDEO_NOT_EXIST = 121;
    // 指定的页码不存在
    public static final int CAM_PAGE_NOT_EXIST = 122;

    // 人脸分组重名
    public static final int GROUP_NAME_EXISTED = 200;
    // 人脸分组名称不合法
    public static final int GROUP_NAME_INVALID = 201;
    // 分组容量超出范围
    public static final int GROUP_CAPACITY_INVALID = 202;
    // 人脸分组ID不存在
    public static final int GROUP_ID_INVALID = 203;
    // 指定人脸分组不存在
    public static final int GROUP_NOT_EXIST = 204;
    // 要删除的人脸分组还有人脸,请先删除分组中的所有人脸
    public static final int GROUP_NOT_EMPTY = 205;
    // 存在同样的人脸ID
    public static final int FACE_ID_EXISTED = 206;
    // 指定人脸ID不存在
    public static final int FACE_ID_NOT_EXIST = 207;
    // 超出人脸分组的容量大小
    public static final int GROUP_OVERFLOW = 208;
    // 人脸ID操作不允许
    public static final int FACE_ID_OP_NOT_ALLOWED = 209;
    // 人脸照片不合格
    public static final int FACE_ID_PIC_NOT_QUALIFIED = 210;
    // 人脸服务未开启
    public static final int FACE_SERVICE_NOT_RUNNING = 211;
    // 人脸分组描述不合法
    public static final int GROUP_DESCRIPTION_INVALID = 212;

    // SD卡不存在
    public static final int SDCARD_NOT_PLUGGED = 220;
    // SD卡格式化失败,原因未知
    public static final int SDCARD_FORMAT_FAILED = 221;

    // 回放时间参数不正确
    public static final int PALYBACK_TIME_INVALID = 230;
    // 查询录像时间参数不正确
    public static final int RECORD_TIME_INVALID = 231;
    // RTSP服务未开启
    public static final int RTSP_NOT_RUNNING = 232;
    // 截图不成功
    public static final int SNAPSHOT_FAILED = 233;
    // 查询录像page参数不正确
    public static final int RECORD_PAGE_INVALID = 234;
    // 录像不存在
    public static final int RECORD_NOT_EXIST = 235;
    // 剪切的视频还未准备好
    public static final int CLIP_FRAGMENT_NOT_READY = 236;
    // 视频剪切服务未启动
    public static final int CLIP_SERVICE_NOT_RUNNING = 237;

    // 人脸属性名称不合法
    public static final int ATTRIBUTE_NAME_INVALID = 240;
    // 人脸属性重名
    public static final int ATTRIBUTE_NAME_EXISTED = 241;
    // 人脸属性数量超过最大限制
    public static final int ATTRIBUTE_NUM_EXCEED = 242;
    // 指定人脸属性不存在
    public static final int ATTRIBUTE_NAME_NOT_EXISTED = 243;

    // 拌线左边端点X坐标超出范围
    public static final int PARAMS_START_X_INVALID = 260;
    // 拌线左边端点Y坐标超出范围
    public static final int PARAMS_START_Y_INVALID = 261;
    // 拌线右边端点X坐标超出范围
    public static final int PARAMS_END_X_INVALID = 262;
    // 拌线右边端点Y坐标超出范围
    public static final int PARAMS_END_Y_INVALID = 263;
    // 分辨率设置超出范围
    public static final int PARAMS_RESOLUTION_INVALID = 264;
    // 人流统计信息粒度参数超出范围
    public static final int PARAMS_PERIOD_INVALID = 265;
    // 开始时间设置异常,必须使用UNIX时间戳
    public static final int PARAMS_START_TIME_INVALID = 266;
    // 结束时间设置异常,必须使用UNIX时间戳
    public static final int PARAMS_END_TIME_INVALID = 267;
    // 查询参数order设置异常
    public static final int PARAMS_ORDER_INVALID = 268;
    // 查询参数group name设置异常
    public static final int PARAMS_GROUP_NAME_INVALID = 269;
    // 查询参数gender设置异常
    public static final int PARAMS_GENDER_INVALID = 270;
    // 查询参数age设置异常
    public static final int PARAMS_AGE_INVALID = 271;
    // 查询参数age_range设置异常
    public static final int PARAMS_AGE_RANGE_INVALID = 272;
    // 查询参数page_num设置异常
    public static final int PARAMS_PAGE_NUM_INVALID = 273;
    // 查询参数page_size设置异常
    public static final int PARAMS_PAGE_SIZE_INVALID = 274;
    // 查询参数faceid设置异常
    public static final int PARAMS_FACE_ID_INVALID = 275;
    // 拌线右边端点X坐标-拌线左边端点X坐标差值小于100,距离不足
    public static final int PARAMS_X_DISTANCE_INVALID = 276;
    // 传入参数不是合理的json table格式
    public static final int PARAMS_JSON_TABLE_INVALID = 277;
    // 配置保存失败
    public static final int SET_CONFIG_FAIL = 278;
    // 配置读取失败
    public static final int GET_CONFIG_FAIL = 279;
    // 数据库中未发现人脸信息
    public static final int FACE_ID_INFO_NO_FOUND = 280;
    // 查询参数 用户新增人脸属性 设置异常
    public static final int TIMEZONE_INVALID = 281;
    // 查询参数 用户新增人脸属性值 数据类型设置异常
    public static final int DETECT_PEOPLE_DETECT_INVALID = 282;

    // 订阅的消息事件不存在
    public static final int SUBSCRIBE_EVENT_NOT_EXIST = 310;
}

集成指南

  1. 准备工作
  2. 添加SDK到工程
  3. 权限声明
  4. 开发步骤

1. 准备工作

申请license

请联系商米售前团队申请license。

下载SDK

请联系商米售前团队申请SDK。

2. 添加SDK到工程

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

导入SDK 库aar包

在app的build.gradle的导入libipcsdk

第三方包依赖

implementation ‘com.squareup.retrofit2:retrofit:2.8.1’

3. 权限声明

‹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" /›

4. 开发步骤

第一次使用IPC前,需要给IPC配置网络、激活、调整画面、设置门线(仅FS需要)。

4.1 SDK初始化

详见: 示例参考->SDK初始化

4.2 首次配网并激活

详见: 示例参考->设备发现 、示例参考->首次配网并激活

4.3 画面调整

详见: 示例参考->画面调整

4.4 设置门线

详见 : 示例参考->设置门线

简介

版本日志

版本日期更新说明
v1.0-RC12020.7.311. 初始版本。
v2.0-RC12020.8.311. 修复某些已知bug。
2. faceid字段改为int类型。

1. 功能介绍

IPC设备端Android SDK是IPC离线 Openapi接口的开发包, 包含了设备管理、基本配置、视频流、人脸识别和人流统计等功能 。

使用场景

开发者通过集成Android SDK开发完相关业务后,即可把APP应用运行在Android设备(如手机),然后APP跟摄像头在同一局网内即可完成相关的业务应用。

注意一定是同一个局域网内,由于摄像头本身接入的网络一般都是一个路由器的局域网,因此一定要在同一局域网内才能进行网络通信和API调用。

软件结构

2. 兼容性说明

系统:支持 Android 7.1.1(API Level 25)及以上系统。需要开发者通过 minSdkVersion来保证支持系统的检测。

机型:手机、平板以及SUNMI OS设备皆可。

构架:支持 CPU架构平台[armeab-v7a,arm64-v8a]

网络:支持 WIFI或以太网络

3. 开发包说明

文件/文件夹名说明
/ipcsdk SDK lib 库相关代码的 aar
/IpcGuideDemo工程

OpenAPI开发指南

  1. 准备工作
  2. OpenAPI简介
  3. Java SDK简介
  4. 开发联调
  5. 正式上线

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环境下的可激活设备的数量。
sunmi_ipcsdk_openapi.p12 https双向验证中客户端自己的证书以及私钥。
sunmi_ipcsdk_ca.crt https双向验证中用于校验服务端发送过来的的证书是否合法。
SDK开发包 Java版本开发包。
UAT环境商米助手APP 用于在UAT环境下给设备做首配。

1.2 设备刷机UAT环境固件

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

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

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

2.OpenAPI简介

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

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

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

方式二:推荐的方式,根据商米提供的平台Java SDK,直接调用Java API来完成对摄像头OpenAPI的调用,不需要过多关注OpenAPI的接口细节,方便快捷,节省开发时间。

3. Java SDK简介

3.1 相关文档

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

3.2 SDK开发包

支持的Java版本:1.8+

序号文件/文件夹说明
1ipc-openapi-java-sdk-version.jaropenapi开发包。
2okhttp-3.14.7.jar依赖的第三方开发包。
3retrofit-2.8.1.jar依赖的第三方开发包。
4converter-gson-2.8.1.jar依赖的第三方开发包。
5okio-1.17.2.jar依赖的第三方开发包。
6gson-2.8.5.jar依赖的第三方开发包。
7https/sunmi_ipcsdk_openapi.p12客户端证书、密钥。
8https/sunmi_ipcsdk_ca.crtCA根证书。

3.3 集成指南

详见设备端Java SDK快速入门

4. 开发联调

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

5. 正式上线

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

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

商米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传递用户的唯一识别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());
        }
    });
}
}

7. 人脸会员库添加

说明: 添加人脸时,同时要向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());
                        }
                    });
                }

}

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 + " ]进店");
                            }
                        }
             }
}

10、IPC扫描

// 获取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);
}

11、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("配置失败");
                }
            });
}

12、激活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");
        }
    });

13、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();
    }

14、IPC对焦、调焦

rivate 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;
            }
        }
    });
    ....
}

15、设置IPC门线

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));
                    }
                }
            });
    ...
}

16、IPC监听人脸消息

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

商米AI识客SDK开发指南

  1. 准备工作

1.1 申请SDK所需的材料

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

客户提供的信息说明
客户名称软件商公司名称

步骤二:售前技术团队根据客户的需求信息,返回软件商以下内容

返回给客户的信息说明
SDK开发包用于人脸识别以及AI识客设备开发的SDK(目前只支持安卓版本)

SDK开发包的内容如下:

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

2. 申请Licence

拿到SDK后,联系商米售前人员,提供以下信息申请Licence

提供的信息说明
收银设备硬件指纹1、通过Demo APP查看硬件指纹信息。安装运行FaceDemo.apk,授权FaceDemo存储器权限,会在sdcard目录生成一个硬件指纹信息文件device_fingerprint.txt。
2、或者通过SDK API查看硬件指纹信息。

返回的信息:

信息说明
Licence 激活人脸识别的SDK的Licence
appid和secret_key 激活设备IPC设备

SaaS开发者获取到以上信息后,可以使用人脸识别SDK,并激活IPC设备。

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 app Module

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

4.2 commonlib

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

4.2.1 FaceCameraManager

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

4.2.2 NormalCameraManager

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

4.2.3 UVCCameraManager

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

4.2.4 ByteUtils

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

4.2.5 DBHelper

DBHelper提供创建数据的方法。

4.2.6 ImageUtils

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

4.3 libuvccamera

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

4.4 facedemo

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

5. 配置IPC

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

5.1 SDK初始化

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

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

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

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

5.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代码见 商米AI识客SDK示例代码->IPC扫描

IPC连接Wifi代码详细见 商米AI识客SDK示例代码->IPC连接AP

5.3 激活

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

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

调用active接口既可以激活IPC设备。详细见 商米AI识客SDK示例代码-> 激活IPC

5.4 画面调整

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

5.4.1 RTSP 播放

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

自定义的RTSP播放器调用VideoStream.getInstance(context).getLiveStream接口可以获取到RTSP播放流的地址。详细代码见 商米AI识客SDK示例代码-> RTSP播放

5.4.2 调焦、对焦

为了更好的人脸识别效果,需要对IPC设备进行调焦对焦。

调用接口BasicConfig.getInstance(context).setZoom调焦; BasicConfig.getInstance(context) .autoFoucus自动进行对焦调焦。

详细代码见 商米AI识客SDK示例代码-> IPC对焦、调焦

6. 会员注册

完成会员的注册功能。输入会员的ID、分组、备注信息,并采集用户的人脸,注册人脸特征信息。注册信息的同时,会通过摄像头采集人脸照片传递给IPC设备。

6.1 获取摄像头数据

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

6.2 注册会员信息和人脸信息

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

7. 会员管理

包括会员列表、会员删除、会员查看、会员注册。

会员删除详细代码见 会员删除

会员注册详细代码见  会员注册

8. 会员实时查询

主要功能实现了实时的获取摄像头预览数据,并且实时对获取的摄像头数据进行特征提取,根据人脸特征信息查询会员。做到通过人脸查询会员的目的。主要使用到的功能有:实时获取摄像头预览数据以及人脸特征提取两个功能

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

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

商米AI识客SDK接口说明

1. 商米人脸识别接口

人脸识别详细API详见链接 商米人脸识别

2. IPC管理接口

详细见 设备端SDK

商米AI识客SDK

  1. 简介
  2. 准备工作
  3. 添加SDK到工程
  4. Demo APP使用
  5. 开发示例
  6. SDK详细接口说明

1 简介

商米AI识客SDK是一个可以配套商米AI识客摄像机(以下简称FS)和安卓收银硬件设备(简称POS),为商户提供完整会员识别解决方案的开发包,包括会员注册、会员进店实时提醒、会员到柜重识别等功能模块。当前提供的是安卓版本SDK,以aar包形式发布,并提供Demo APP以供参考。

详细说明:

  • 1. C/C++ library实现人脸特征提取,人脸比对等算法功能以及人脸库管理(人脸库的添加、查询、删除等操作)等功能。
  • 2. JNI层封装C/C++实现的功能,为Java上层提供native接口。
  • 3. Java层 人脸识别API 使用native层提供的接口为上层 APP提供人脸特征提取、人脸比对以及人脸库管理等接口,AI识客API提供配置FS、向FS注册人脸和获取FS人脸识别消息的功能。
  • 4. Demo APP,这里是示例代码,仅供开发者参考,非完美的APP,使用Java API层提供的功能实现人脸识别、人脸比对、人脸特征库管理、AI识客设备配置等业务层功能。

2 准备工作

SDK开发包采用一个设备一个Licence认证机制。先向商米售前技术团队申请SDK,而后通过SDK或Demo APP获取硬件指纹,再申请Licence。

2.1 申请SDK

步骤一:联系商米售前技术团队,提供如下信息:

内容 说明
客户名称软件商公司名称
业务场景简单描述业务场景

步骤二: 商米售前技术团队根据客户的申请,为客户提供SDK开发包。SDK开发包的内容如下:

序号文件/文件名说明
1FaceSdkDemoDemo工程源码
2facelib_xxxx.aar人脸识别开发套件,xxxx为版本号。
3ipcsdk_xxxx.aarFS开发套件,xxxx为版本号。
4asset人脸识别开发套件所需资源文件(人脸识别的模型文件、配置参数等)
5readme.mdSDK开发指南

2.2 申请Licence

获取SDK开发包后,按如下操作,提供硬件指纹信息给商米售前技术团队:

  1. 在POS设备上安装FaceDemo.apk,运行FaceDemo APP,给APP授权存储、摄像头权限。APP会在/sdcard目录生成一个硬件指纹信息文件device_fingerprint.txt。
  2. 或者集成SDK开发包后,调用SDK API SunmiFaceSDK.getDeviceFingerprint获取硬件指纹信息

商米售前技术团队会提供以下内容:

内容 说明
licence_valid.txt用于激活人脸识别的SDK的Licence
appid、secret_key、激活码用于激活FS的通信使用

备注:

  1. licence_valid.txt文件的使用方法请阅读章节3.4,放到指定的目录下即可。
  2. appid、secret_key、激活码的使用请阅读章节4.1,替换源码中的内容编译出Demo APP。

2.3 开发调试环境

在软件正式发布上线之前,建议在UAT环境下与FS进行联调,请参考升级开发环境固件将FS固件升级到UAT环境,因此商米首先提供UAT环境下的appid、secret_key和激活码 ,开发完成后再提供Release环境的 appid、secret_key和激活码 ,并参照升级线上环境固件将FS固件升级到Release环境。

3 添加SDK到工程

SDK开发包开发环境:

开发工具:Android Studio

NDK版本:android-ndk-r14b (可以使用高版本)

Android版本:Android 7.0及以上

3.1 Android Studio导入SDK开发包

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

点击Finish即导入aar模块。

按同样的操作可以导入人脸识别facelib_xxxx.aar模块。

3.2 权限申明

SDK开发包涉及网络操作以及wifi相关操作,需要在 AndroidManifest.xml 增加以下权限:

‹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" /›

Demo APP还涉及到camera和存储器相关操作,还需要在AndroidManifest.xml增加以下权限:

‹uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/›
‹uses-permission 
‹uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /›
‹uses-permission android:name="android.permission.CAMERA" /›

3.3 导入asset资源

运行SDK开发包还需要导入asset资源文件,用于初始化人脸识别SDK。

说明:资源文件在APP启动的时需要复制到APP的运行目录。

3.4 导入licence文件

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

  1. 放到assets资源目录,则需要把licence_valid.txt文件从资源文件复制到app的工作目录。
  2. 如果放到sdcard目录,则需要app申请sdcard读取权限。

无论哪一种情况,最后使用的时候,需要传递licence_valid.txt文件的路径,以激活人脸识别SDK。人脸识别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();
        param.setDistance_threshold_(1.0f); //范围在1.0~1.2比较合适,越小,人脸识别越严格
        param.setYaw_threshold_(50.f);//人脸姿态阈值,设置较小的人脸姿态阈值,阈值越小,人脸姿态要求越严格,保证相对正面的人脸姿态做识别
        param.setPitch_threshold_(50.f);
        param.setRoll_threshold_(50.f);
        param.setMin_face_size(60);//最小人脸检测尺寸,较大较清晰的人脸识别较准确
        param.setImage_quality_threshold_(10);//阈值越高,人脸清晰度越好,人脸识别越准确
        param.setMin_luminance_(10);//最小光照
        param.setMax_luminance_(180);//最大光照
        param.setLiveness_on_(true);//开启人脸活体检测
        param.setThread_num_(1); //使用一个cpu核心执行人脸检测,人脸识别
        //设置配置参数
        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);
}

4 Demo APP

4.1 编译Demo APP

(1) Android studio 导入Demo APP源代码,File->open,选中Demo APP工程目录。

(2)在文件local.properties文件增加ndk编译程序路径:ndk.dir=D\:\ndk\\android-ndk-r14b。路径地址为ndk存储路径。

(3)修改app module目录下的源代码MainActivity.java文件,激活FS设备。代码如下:

init函数的三个参数为app_id, secret_key, 激活码。这三个参数是由2.2 申请licence获得。

//init三个参数为:app_id, secret_key, 激活码
manager.init("test", "123456", "123456");

(4)Build->Make Project,编译工程,生成APP。

4.2 Demo APP工程目录

4.2.1 app Module

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

4.2.2 commonlib

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

4.2.2.1 FaceCameraManager

POS设备自带的摄像头采用Android系统提供的操作方式。外置USB 摄像头使用第三方UVC库提供的操作方式。 FaceCameraManager类统一管理Android POS设备自带的摄像头和外置USB摄像头,封装Android系统操作方式和UVC操作方式,对上层APP应用提供统一的方法:startPreview和 stopPreview。

注:本文的摄像头AI识客摄像机是两个概念。本文中的摄像头指POS设备自带的摄像镜头和外置USB摄像头(兼容UVC协议的USB摄像镜头)。 AI识客摄像机是一款独立的设备,运行单独的操纵系统,能够独立的进行人脸识别。在此需要注意区分。

4.2.2.2 NormalCameraManager

NormalCameraManager使用Android系统提供的摄像头操作方式,实现startPreview和stopPreview。并提供获取摄像头每一帧数据的接口。

4.2.2.3 UVCCameraManager

UVCCameraManager使用第三方库UVC lib提供的方法用于操作外置USB摄像头,实现startPreview和stopPreview。 并提供获取摄像头每一帧数据的接口。

4.2.2.4 ByteUtils

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

4.2.2.5 DBHelper

DBHelper提供创建数据的方法。

4.2.2.6 ImageUtils

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

4.3 libuvccamera

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

4.4 facedemo

facedemo 使用人脸识别API和AI识客API实现会员注册、会员管理、会员查询等功能。

4.5 ipcdemo

ipcdemo 主要用于AI识客设备配置。

5 开发示例

5.1 配置FS

首次使用FS时,需要激活设备。激活FS时,FS必须连接互联网,进行在线激活。当FS激活后,可以离线使用。使用FS开发包按照以下步骤即使用FS:

5.1.1 FS开发包初始化

通过商米售前技术团队申请Licence后,会获取以下信息。使用以下信息进行SDK初始化:

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

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

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

初始化代码如下:

IPCameraManager mIPCameraManager = IPCameraManager.getInstance(context);
mIPCameraManager.init(app_id, secret_key, licence);

5.1.2 FS设备配网

FS可以通过有线(以太网卡)和无线(wifi)接入互联网。无论哪种方式,FS必须和POS设备处于同一局域网下。两种接入方式配网方式如下:

有线接入:

有线接入只需通过网线把FS设备和POS设备接入到同一局域网即可,不需要其它设置。有线接入优先级高于无线接入。如果有线接入后,无线接入不可用。

无线接入:

无线接入方式相对要复杂点,需要进行无线配网,步骤如下:

Android POS设备连接AI识客设备AP热点

1. POS设备无线网卡扫描FS设备的AP热点,一般AP热点的名称为SUNMI_XXXX,其中XXXX为MAC地址最后2个字节的16进制数字,MAC地址可以通过设备机身后背的标贴或者包装盒的标贴查到,AP热点本身是无加密的。

2. POS设备的无线网卡连接到FS设备的AP热点。POS设备通过FS开发包提供的接口可以获取到FS设备信息,包含IP、MAC等,相关代码如下:

IPCameraManager.getInstance(context).registerListener(new IPCameraListener() {
    //IPCameraInfo获取的即是FS的设备信息,包含IP、MAC等。
    @Override
    public void onDeviceOnline(IPCameraInfo device) {
        showToast(getApplicationContext(), "[ " + device.getDeviceid() + " ]上线");
    }
    @Override
    public void onDeviceOffline(IPCameraInfo device) {
        showToast(getApplicationContext(), "[ " + device.getDeviceid() + " ]离线");
    }
});

3. 调用getApListWithoutAuth 接口可以在POS设备上获取到FS设备扫描到的wifi热点:

// 获取FS设备扫描到的AP热点
private void getWifiList() {
    BasicConfig.getInstance(context).getApListWithoutAuth(sunmiDevice.getDeviceid(),
            new RPCCallback‹RPCResponse‹IpcApBean››() {
                //扫描完成后调用onComplete,返回信息
                @Override
                public void onComplete(RPCResponse‹IpcApBean› result) {
                    if (result.code() == RPCErrorCode.SUCCESS) {
                        wifiListGetSuccess(result.data());
                    } else {
                        Log.i(TAG, "getApListWithoutAuth failed, errcode: " + result.code());
                    }
                }
        });

    ...
}

void wifiListGetSuccess(IpcApBean res) {
   ...
   wifiList.clear();
   //处理FS设备扫描到的AP热点信息
   wifiList.addAll(res.ap_list);
   initApList(wifiList);
}

4. 选中一个wifi热点后,调用setWifiConfWithoutAuth接口配置FS设备,使其能够连接到wifi热点,相关代码如下:

/*
 * 输入wifi热点名,密码
 *
 */
private void setIpcWifi(String ssid, String psw) {
BasicConfig.getInstance(context).setWifiConfWithoutAuth(sunmiDevice.getDeviceid(),ssid, psw, new RPCCallback‹RPCResponse›() {
                //调用成功后会调用onCompelte, 返回值保存再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("配置失败");
                }
            });
}

5. 等待片刻后,AI识客设备通过无线连接上互联网,显示蓝灯。

5.1.3 FS设备激活

FS设备首次使用时,需要激活。如果FS设备没有激活,则会无法调用FS SDK的其他接口。激活设备需要连接互联网。如何连接互联网,可以参看 5.2 FS设备配网。POS设备调用active接口即可以激活FS设备,激活代码如下:

//激活FS设备,并设置回调接口
DeviceManage.getInstance(context).activate(ipcList.get(postion).getDeviceid(),
    new RPCCallback‹RPCResponse›() {
        //FS返回相关错误码。
        @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");
        }
    });

5.1.4 FS设备画面调整

FS设备进行人脸识别时,对人脸图像质量有一定要求,因此在使用FS提醒功能之前,需要调整FS设备画面。FS设备支持RTSP协议用于画面预览。可以使用Demo APP提供的RTSP播放器播放预览画面,也可以自定义一款RTSP播放器。预览AI识客设备的画面时,可以对FS的摄像镜头进行调焦、对焦,调整画面的清晰度,以满足FS设备的人脸识别要求。主要步骤如下:

5.1.4.1 预览画面播放

调用接口 getLiveStream 获取RTSP预览视频流播放地址,然后使用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) {
                                       //Sunmi rtsp播放器
                                        mPlayer = new SunmiPlayer(context);
                                        mPlayer.setListener(new SunmiPlayerListener() {
                                            @Override
                                            public void onPrepared(IMediaPlayer iMediaPlayer) {
                                                iMediaPlayer.start();
                                            }
                                        });
                                        mPlayer.setSurface(mVideoView.getHolder().getSurface());
                                        //获取到AI识客设备返回的播放流rtsp地址
                                        String liveUrl = result.data().fhd_live_url.replaceFirst("^rtsp://", "rtsp://admin:admin@");
                                        //Sunmi RTSP播放器开始播放rtsp视频流
                                        mPlayer.setUp(liveUrl);
                                    }
                                }
                            }
                });
            }
        }).start();
    }

5.1.4.2 FS设备调焦、对焦

画面预览时,如果画面不清晰,请调用接口 setZoom,autoFocus,manualFocus分别进行调整焦距、自动对焦、手动对焦。当画面清晰时,停止对焦。参考代码如下:

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.1.5 FS设备客门线设置

首次FS设备时,需要设置门线位置。设置该门线位置是用于判定人脸进店。FS设备被换位置或者门线设置不正确时,也需要进行重新设置门线。调用接口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));
                    }
                }
            });
    ...
}

完成以上步骤后,就配置好AI识客设备,可以使用AI识客设备提供的识客进店功能。

5.2 会员注册

输入会员的ID、分组、备注信息,并采集会员人脸,提取人脸特征,完成会员注册。POS设备完成会员注册的同时,会把人脸照片以及会员ID同步传递给AI识客设备。

5.2.1 采集人脸照片

POS设备调用自带摄像头或者外置USB摄像头进行人脸拍照。获取人脸照片。相关代码如下:

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

5.2.2 注册会员信息和人脸信息

POS设备使用SDK提供的人脸识别功能,对人脸照片提取特征值,并注册会员。相关参考代码如下:

/*
 * @bitmap 含有人脸bitmap
 * @maxFace 返回最大人脸数
 * @return 返回人脸特征值
 */   
 public ArrayList‹SunmiFaceFeature› getFeatures(Bitmap bitmap, int maxFace) {

        int ret = 0;
        //bitmap RGB图像转化为BRG
        byte[] srcData = ImageUtils.getPixelsBGR(bitmap);
        SunmiFaceImage image = new SunmiFaceImage(srcData, bitmap.getHeight(), bitmap.getWidth(), maxFace);
        SunmiFaceImageFeatures features = new SunmiFaceImageFeatures();
        ret = SunmiFaceSDK.getImageFeatures(image, features);
        SunmiFaceFeature feature_ary = features.getFeatures();
        ArrayList‹SunmiFaceFeature› arrayList = new ArrayList‹›();
        if (features.getFeaturesCount() == 0) {
            SunmiFaceSDK.releaseImageFeatures(features);
            return arrayList;
        }
        for (int i = 0; i ‹ features.getFeaturesCount(); i++) {
            SunmiFaceFeature sunmiFaceFeature = SunmiFaceLib.SunmiFaceFeatureArrayGetItem(feature_ary, i);
            arrayList.add(sunmiFaceFeature);
        }
        SunmiFaceSDK.releaseImageFeatures(features);
        return arrayList;
        // return getFeature(srcData, bitmap.getWidth(),bitmap.getHeight(), 1);
    }
/*
 *向系统注册会员信息
 *@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;
        }
        user.setImageName(record.getImgId());
        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);
                    // 添加用户的同时,把人脸照片、id同步到AI识客设备
                    mIPCManager.addFaceRecord(picPath, uid, new RPCCallback‹RPCResponse›() {
                        @Override
                        public void onComplete(RPCResponse result) { 
                        }
                        @Override
                        public void onError(Throwable t) {
                            super.onError(t); 
                        }
                    });
                }
            } else {
                isSuccess = false;
            }

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

5.3 会员管理

包括会员列表、会员注册 、会员删除、会员查看。

会员注册:见 5.2.2 注册会员信息和人脸信息

会员删除:删除会员的时候,需要把删除信息同步到FS设备。相关参考代码如下:

    /**
     * 删除用户
     */
    public boolean userDelete(String userId, String groupId) {
        if (TextUtils.isEmpty(userId) || TextUtils.isEmpty(groupId)) {
            return false;
        }
        User user = DBManager.getInstance().queryUser(groupId, userId);
        if (user == null) {
            Log.d(TAG, "Database has no user " + userId);
            return false;
        }
        Log.d(TAG, "userDelete userId" + user.getUserId() + " userImage: " + user.getImageName());

        //删除会员信息
        boolean ret = DBManager.getInstance().deleteUser(userId, groupId);
        DBManager.getInstance().deleteUserEventByUserId(userId);
        //从人脸库中删除人脸特征值
        int result = SunmiFaceSDK.deleteDBRecord(user.getImageName());

        if (mIPCManager != null) {
            //删除会员时,同步删除AI识客设备上的人脸数据信息
            mIPCManager.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){
                        ToastUtils.showToast(context, "删除人脸失败 code: " + result.code());
                    } else {
                        ToastUtils.showToast(context, "删除人脸成功");
                    }
                }

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

            });
        }
        return ret;
    }

会员查看:主要显示会员ID、组、会员描述信息以及会员注册头像。

5.4 会员实时查询

主要功能实现了实时的获取POS设备自带摄像头或者外置USB摄像头的预览数据,并且对获取的图像数据做实时人脸特征提取,并查询根据人脸特征信息查询会员信息。主要使用到的功能有:实时获取摄像头预览数据人脸特征提取人脸识别等功能

5.4.1 实时获取摄像头数据

POS设备实时获取摄像头数据相关代码如下:

//开启摄像头预览
FaceCameraManager.getInstance().startPreview(getApplicationContext(), this, FaceCameraManager.getInstance().getCurCamera(), cameraView, PREVIEW_WIDTH, PREVIEW_HEIGHT, cameraDataCallback);
//摄像头数据回调接口	
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();
            }
        }

    }
};

5.4.2 特征提取功能

使用开发包提供的人脸特征提取功能,相关参考代码如下:

/*
 * @bitmap 含有人脸bitmap
 * @maxFace 返回最大人脸数
 * @return 返回人脸特征值
 */      
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.4.3 人脸识别

提取人脸特征后,查询人脸特征数据库,返回用户信息,相关代码如下:

public User getUserByFeature(SunmiFaceFeature feature) {
    SunmiFaceDBRecord record = SunmiFaceSDK.faceFeature2FaceDBRecord(feature);
    SunmiFaceDBIdInfo info = new SunmiFaceDBIdInfo();
    //在人脸特征数据查询对应的特征值
    int ret = SunmiFaceSDK.searchDB(record, info);
    if (!info.getIsMatched()) {
        return null;
    } else {
        //查询到匹配的特征值后,查询用户信息,并返回用户信息。
        return DBManager.getInstance().queryUser("default", info.getId());
    }
}

6 SDK详细接口说明

本SDK开发包包含人脸识别和FS设备SDK。请分别详细查阅以下链接了解API接口详细内容。

脸识别API FS设备API

首次配置

1.有线联网
2.无线联网
3.获取设备IP地址
4.设备发现协议

需要使用IPC的API就要求对接系统能够获取到IPC的IP地址,然后通过IP与IPC通信,调用相关的API来获取对应的服务。

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

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

1. 有线联网

对于有以太网网口的IPC设备,可以使用以太网线连接IPC设备和网关(例如无线路由器),使得IPC通过DHCP服务获取到IP地址。

如果连接的网络是可以正常上网的话,IPC获取到IP后很快就会亮蓝灯,此时表明IPC可以正常连接Internet了。

2. 无线联网

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

  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. 使用IPC的IP地址(一般是192.168.200.1),调用无线配置 API(见3.2.1一节的描述)设置IPC要连接的无线网络(例如无线路由器的SSID和密码),使得IPC能够从网关处获取到IP地址。
  4. 如果网络是可以正常上网的话,IPC取到IP地址后很快就会亮蓝灯,此时表明IPC可以正常连接Internet了。

3. 获取设备IP地址

完成IPC的网络接入后,接下来就是要获取到IPC的IP地址。

商米提供一套协议,用于发现同一局域网内所有支持此协议的设备,以获取到设备的基本信息,其中包括SN和IP地址。此协议叫设备发现协议。

4. 设备发现协议

商米发现协议简单来说,就是通过在局域网内广播UDP报文来询问同一局域网内有哪些商米设备,支持此协议的商米设备会在收到询问报文后单播响应报文,响应报文中就包括了设备的基本信息。这样就完成了一次设备发现的过程。

4.1 协议报文格式

UDP报文的载荷部分,格式如下图所示,整个协议报文有报头Head、载荷Payload和CRC校验和组成。

  • Header

协议报头,包括标记为Flag、协议版本Version、报文类型 Type和长度Len。

Flag:为报文的开头部分,是一个标记,目前固定为0xFFFF33FF。

Version:为协议版本,目前只支持版本号0x1。

Type:为报文类型,发起方一般Type为0x01,表示发现请求,Discovery Request;响应方回复的报文Type为0x02,表示Discovery Response。

Len: 表示Payload部分的长度,要求是原始的数据部分进行base64编码后的长度。

  • Payload

真正的载荷部分,原始的数据采用json格式,json数据需要进行base64编码,然后放到Payload上。

接收方接收到后,需要进行base64解码才能还原为json数据。

  • CRC

发送的数据需要进行简单的CRC32校验,校验字段包括Header和Payload。

接收方接收到数据后,会对CRC进行校验,支持校验通过才能使用。

4.2 协议通信端口

端口号10001,发送端和接收端双方使用相同的端口号。

4.3 Discovery Request

当发送方想知道局域网内有哪些设备,就需要发送UDP广播报文,报文内容如下:

类型为0x01,长度为0x0,即没有载荷部分。

4.4 Discovery Response

由响应方回复,告诉发送方自己的设备信息,类型为0x02,长度根据实际情况计算。

回复的载荷内容如下图所示,也即是json格式的设备信息,具体含义解析如下。

ip:设备的IP地址。

mac:设备的MAC地址。

firmware:设备的固件版本,如1.0.0。

name:设备名称标记,目前固定为SUNMI。

model:设备型号,如FM020。

type:设备类型,IPC设备回复的值为IPC。

network:发送端的网络连接方式,如果发送端连接的是IPC的AP热点则值为AP,如果发送端与IPC同时连接在另一个路由器的局域网内,则值为LAN。

deviceid:设备序列号SN。

4.5 超时处理

建议发送端发送Discovery Request报文后,间隔1s再次发送,最多发送3次,每次间隔都是1s。之所以发送3次,是防止丢失有些设备没有收到的情况。

发送完3次后,设置超时时间为2s,2s后不再接收Discovery Response报文,即从发送第一个Discovery Request后,5s内接收Discovery Response报文,并解析所有设备的信息。

接收端接收到Discovery Request报文后,立即单播回复Discovery Response报文。