商米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

发布应用说明

发布应用

关于商米应用市场

每个商米合作伙伴都可以在商米官网 注册商米合作伙伴帐号,有一个自己的操作后台,原则上每一台商米的设备在卖出去的时候都会和一个合作伙伴账号绑定,商米会以合作伙伴为粒度提供部分功能和权限的控制服务。

SUNMIUI内部有一个应用市场,合作伙伴可以通过应用市场将自己的应用大规模分发到商米的设备上。默认旗下用户只能通过商米应用市场为设备安装应用。

应用市场规则

商米为部分合作伙伴分配了不同权限的应用市场,默认合作伙伴上传的应用不用商米审核就可以出现在自己的应用市场中。商米可以审核该应用,通过审核后该应用会出现在其他合作伙伴的应用市场中,当然部分合作伙伴可以选择自己的应用不出现在其他人的应用市场,也可以选择自己应用市场不出现其他人的应用,关于权限的授予可以咨询商米客服400-902-1168(每天9:00~21:00,节日除外)

应用分发流程

应用开发完成后,上传到应用市场,用户可以在设备上的应用市场搜索到应用,如果是渠道合作伙伴可以设置应用在旗下的设备自动安装。以下讲解应用首次上传分发的流程。

1.上传应用。

在 合作伙伴后台,进入”我的应用”选项,点击创建应用

2

2.填写相关内容

点击’上传安装包’,从文件夹中选择您要上传的apk,等待上传完成后,商米后台将自动分析安装包,下图绿色箭头指示部分将会自动填写好,开发者手动填写应用介绍,应用适配的设备,应用类型等信息。

  • 上传APK安装包时如果提示包名重复,则表示此APK已经在商米应用市场上架,如果此APK是贵公司所有,请联系商米客服400-902-1168(每天9:00~21:00,节日除外),进入认领。
3

3.提交应用。

合作伙伴将剩余部分内容补充完整后,下图的’提交’按钮将由灰色不可点击变成绿色可点击状态,点击’提交’将会完成应用的上传,

4

关于灰度部署

上图中’提交’按钮上方有一个’是否灰度部署’选项,勾选后提交按钮将变成’灰度部署’项,点击后将进入灰度部署配置项中。

灰度部署是商米针对合作伙伴的实际需求提供的一项功能,每台设备在使用的时候会有一个地理理位置信息,合作伙伴在开发好自己的应用适配商米的设备后,可以根据地理位置或者SN号在部分设备上部署,只有灰度部署范围内的设备的应用市场中才会出现该应用。在小范围的灰度部署后,开发者可以将应用部署到全范围设备上。

5

设置自动安装。

如果希望上传的应用自动在旗下所有设备上安装,可以在’我的应用市场’项中设置该应用为自动安装应用(如下图箭头所示),旗下设备在收到推送通知后会自动下载安装该应用,设置自动安装后还可以设置该应用是否作为默认启动应用(如下图箭头所示),设备开机后将自动启动该应用。

6

更新应用

在应用通过商米审核后,在我的应用中列表项中会有’更新’按钮,开发者可以发布该应用的升级包。点击后进入更新应用页,更新的流程和发布流程类似,请参照上面的发布流程。

  • 上传更新包的时候如果提示签名异常,请检查APK签名是否与原来的安装包不一致。如确认是需要变更签名,则请删除应用后再创建应用
  • 上传更新包的时候如果提示包名不一致,请检查APK包名是否有变动或有没有上传错误。如确认是需要变更包名,则请删除应用后再创建应用
7

常见问题和帮助

开发常见问题

阅读指引

为了更快地在机具上完成收银应用开发,以及加快刷脸应用和iot sdk的集成联调开发,机具端侧开发过程中的问题汇总如下。

FT1 MINI硬件调试开发

Q:ISV开发测试建议

A:由于团餐场景的特点,就餐速度快,排队人数多,付费过程快等因素。建议ISV根据实际场景和环境对设备和软件进行相关的压测,实际场景会有很多刷脸的状况和意外情况,所以请ISV务必进行压测,尽量将问题暴露在压测阶段。做过压测后无问题再投入市场使用。

Q:设备如何进行adb调试连接?

A:  推出FT1 MINI设备键盘,拆开维保码下面的防水盖,插入micro USB口连接FT1 MINI和PC机。 不支持debug调试,需要打印日志方式调试。在adb调试过程中,确保RJ45和键盘USB没有插入。

Q:如何排查深度摄像头硬件是否工作正常?

A:在FT1 MINI应用列表界面,点击”MarioSdk“图标进入摄像头工厂模式界面,再点击”D2C测试“按钮,查看摄像头预览界面是否正常。如果有类似马赛克的界面则初步判断正常,参考画面如下:

Q:如何排查键盘不响应?

A:第一步:确认是否延长线断裂不通,可以将键盘不用延长线插入机器尝试确认。

第二步:不用延长线直接插入键盘,可以进入CIT模式做单体测试(在FT1 MINI应用列表界面点击CIT图标),确认键盘是否不良。

第三步:更换键盘再确定机器USB是否通路,用U盘或鼠标测试确认。如不通,报修或更换整机。

Q:如何排查NFC不能正常工作?

A:进入设置界面,看NFC 的设置开关是否可以正常打开、关闭。要确认NFC设置为“打开”状态。合作方对NFC的监听应用需要独立于机具的收银应用工作,NFC的开发采用标准的android NFC开发原理。

Q:机器长时间运行状况,是否连续数天处于不关机状态?

A:如无人为关机行为,建议客户设置定时开关机,连续数天不关机可能造成机器长时间老化导致卡顿,延迟,寿命缩短等问题。

Q:机器开机默认霸屏模式,如何退出霸屏模式

A:同时按下键盘上 “功能”+“+”+“设置”即可退出霸屏模式。

Q:键盘外置的情况下,安装建议?

A:键盘外置时,需要连接延长线,建议在接头位置包裹绝缘胶带以起到放松防水作用。

Q:键盘套使用

A: 我们的键盘具有IP55级别防尘放水,并使用的防油污材料。但是由于食堂环境油污严重,环境复杂,后期我们将随设备配套键盘保护套,请在安装时同步装上键盘保护套。防止油污进入键盘,影响键盘使用。

Q:频繁刷脸运行后,设备卡顿,设备反应慢,频繁发生。

A:有以下两种可能原因,需要客户自查。

1,可能原因:线程操作有问题,频繁调用smile 导致smile 发生ANR,客户的APP 一直在等待,导致界面卡死,出现白屏、黑屏。建议客户不要在主线程有过多的耗时操作,后续客户修改逻辑正常。

2、可能原因2 :APP 也是频繁操作,导致资源没有来得及释放,出现内存泄漏导致机器卡顿,重启APP正常,建议客户:适当优化释放时间,后正常。

Q:副屏显示设置建议。

A:建议副屏显示设置的文字越大越好,对比度越大越好,利于食堂阿姨能够更清晰的看到消费金额。

Q:排插选型

A:由于我们使用的是插墙式的适配器,适配器本体比较宽,所以建议客户选宽间距的排插。窄间距的排插利用率较低。适配器宽度尺寸为51*51mm。

Q:键盘不响应。

A:偶然出现,建议重新插拔键盘或者重启设备。

   出现概率比较高的情况,如何排查键盘不响应

1、确认是否延长线断裂不通,可以将键盘不用延长线插入机器尝试确认。

2、不用延长线直接插入键盘,可以进入CIT模式做单体测试(在海马应用列表界面点击CIT图标),确认键盘是否不良。

3、更换键盘再确定机器USB是否通路,用U盘或鼠标测试确认。如不通,报修或更换整机。

Q:设备用有线网络的话,对网线种类有要求吗?用无线网路的话,对无线路由器有要求吗?

A:设备需要稳定的网络,要求采用国标超五类及以上的网线或者企业级无线路由器。

Q:设备的安装方式

A:设备配有标准滑入式挂架,可以吊装,左装,右装,贴合玻璃,配合桌面支架安装(桌面支架需要单独购买)。安装方式详情请参考开发指南的安装指引。

Q:设备安装高度是多少?

A:根据设备安装点位最大程度贴近学校平均身高即可。

   参照平均身高选择安装高度(摄像头距地面的距离)。

高度角度身高范围(距离60cm) 
1400125(最低)175(最高)
1500135(最低)185(最高)
1600145(最低)195(最高)

假设安装高度160cm的话, 覆盖145~195cm 身高人群。如果是上下垂直调整的话, 就直接相减即可。比如安装高度下降30cm至 130cm的位置, 则覆盖身高变为115~165cm。如果离得更近,那身高范围会缩小一些。以上是理论计算, 实际测试会比这个范围大。

Q:摄像头无法识别人脸。

A:在FT1 MINI应用列表界面,点击”MarioSdk“图标进入摄像头工厂模式界面,再点击”D2C测试“按钮,查看摄像头预览界面是否正常。如果有类似马赛克的界面则初步判断正常(参考图如下),如显示正常,请在联网状态下打开smilepay软件,选项—正式环境—刷脸,如刷脸正常,即可判定摄像头为正常。需要从软件方向寻找问题原因。

如以上检查无法正常刷脸或者深度读取不正常,可判定摄像头不良,需走售后流程安排维修或者换机。

Q:如何进行设备单项功能测试

A:设备应用界面—点 CIT—选择  单项测试—进行判断可能问题的单项测试。

接口使用说明

如何找到接线口:

  第一步:在机器背面,如红色箭头方向所示,从左向右推出接线盖。

  第二步:从左向右推出键盘。

   第三步:看到两个防水密封盖A和B。

  第四步:用十字螺丝刀拆除3颗螺丝后,打开防水盖。

  第五步:打开后出现3个外设I/O口,分别是RJ45、USB2.0*2。

  说明:如果网线无法插入现有机器网口,请使用随机网线转接线接入网络。

安装建议

安装方案一:直接安装在橱窗侧壁

 a. 从包装盒中取出挂机架,挂架有12个M4螺丝通孔和2个M3螺牙孔

 b. 用手枪钻在橱窗侧壁打4个M4的螺牙孔(或者自攻孔)

     取出包装中的4颗M4机牙螺丝或者准备4颗M4自攻螺丝

     将挂架固定在橱窗侧壁,确保挂架能够完全固定 

 c. 将准备好的团餐机器插入挂机,支架固定的方向不同,机器的插入方向不同。均可以插入。

 d.将包装中的黑色M3螺丝取出,装入箭头位置。锁上螺丝后,机器将无法从挂架上滑出,确保机器的使用。 

安装方案二:直接粘在橱窗玻璃上

  1. 从包装盒中取出整机挂架,同时预备双面胶和玻璃胶

 b. 将挂架贴在玻璃外面

     (1)先将挂架固定在玻璃橱窗上

     (2)挂架四周打玻璃胶

     (3)静置24小时 

 c. 按照如上(1)、(2)、(3)、(4)步骤将键盘取出,并装上键盘延长线和键盘盖 

d. 保证挂架静置24小时后,将机器划入挂架,并锁上M3螺丝防止机器向外滑动,键盘可以放在桌面上使用 

安装方案三:通过转接架安装在橱窗

a. 从包装盒中取出整机挂架和M4螺丝螺母,将转接角件与挂架固定在一起,转接角件需单独采购,商米可以提供。

 b. (1)用手枪钻在橱窗正面壁打M4的螺牙孔(或者自攻孔)

     (2)将挂架固定在橱窗正面,确保挂架能够完全固定 

 c. 将准备好的团餐机器插入挂机。支架不定的方向不同,机器的插入方向不同。均可以插入。 

 d. 将包装中的黑色M3螺丝取出,装入箭头位置。锁上螺丝后,机器将无法从挂架上滑出。确保机器的使用。

第三方收银

设备灯光开发接口

/sys/class/leds/LedRed/brightness 写入0 关闭红灯 写入1打开红灯

/sys/class/leds/LedBlue/brightness 写入0 关闭蓝灯 写入1打开蓝灯

/sys/class/leds/LedYellow/brightness 写入0 关闭黄灯 写入1打开黄灯

可以间隔打开&关闭灯光接口来模拟灯光闪烁

操作建议: 操作灯前要关闭其他的灯

双屏控制接口

开发参考资料

https://developer.android.google.cn/reference/kotlin/android/app/Presentation

https://blog.csdn.net/liqianwei1230/article/details/78606935

https://www.jianshu.com/p/d42f3ec6e451

键盘控制接口

键值定义说明

keyKEY_EVENTKey code
1KEY_KP1KEYCODE_NUMPAD_1 = 145
2KEY_KP2KEYCODE_NUMPAD_2 = 146
3KEY_KP3KEYCODE_NUMPAD_3 = 147
4KEY_KP4KEYCODE_NUMPAD_4 = 148
5KEY_KP5KEYCODE_NUMPAD_5 = 149
6KEY_KP6KEYCODE_NUMPAD_6 = 150
7KEY_KP7KEYCODE_NUMPAD_7 = 151
8KEY_KP8KEYCODE_NUMPAD_8 = 152
9KEY_KP9KEYCODE_NUMPAD_9 = 153
0KEY_KP0KEYCODE_NUMPAD_0 = 144
.KEY_KPDOTKEYCODE_NUMPAD_DOT = 158
+KEY_KPPLUSKEYCODE_NUMPAD_ADD = 157
KEY_UPKEYCODE_DPAD_UP = 19
KEY_DOWNKEYCODE_DPAD_DOWN = 20
功能KEY_F1KEYCODE_F1 = 131
设置KEY_F2KEYCODE_F2 = 132
取消KEY_ESC111/4
删除KEY_BACKSPACEKEYCODE_DEL = 67
确认KEY_ENTERKEYCODE_NUMPAD_ENTER = 66

由于FT1 MINI硬件设计,调试线与键盘USB线无法同时工作,因此当需要调试键盘相关的应用时,请使用adb无线调试。

说明

1、本小键盘设计上相当于正常PC键盘的小键盘部分,如下图;因此,要发送键盘上的键值,需要先开启Numlock,而FT1MINI机具上小键盘上无Numlock按键,系统会默认在按键时自动触发KEY_NUMLOCK对应的键值消息;

2、取消键发了两个keycode对应是KEYCODE_BACK=4和KEYCODE_ESCAPE=111
3、按键响应事例代码如下:
在onKeyUp方法中做相关响应处理:

刷卡应用开发接口

NFC控制接口

NFC接口采用Android系统标准接口,使用方法和说明,请见链接:

https://developer.android.google.cn/guide/topics/connectivity/nfc

https://www.android-doc.com/guide/topics/connectivity/nfc/nfc.html

开发参考资料,请见链接:

Android NFC功能 简单实现:https://www.jianshu.com/p/cf36c214f2a8

Android应用开发接口

开始之前:因某些接口调用耗时存在不确定性,建议不要在UI线程调用,否则可能导致ANR卡顿。

打印机应用开发接口

设备支持外接USB打印机,即插即用。USB打印机的接口,参考Android系统的标准接口开发,可以搜索“Android USB打印机开发”关键词查询如何开发调用。

如果选择支付宝的云打印机,相关的开发指南有:https://alipay.open.taobao.com/docs/doc.htm?articleId=118376&docType=1

如果接入的是商米自有的打印机,相关的开发指南有:http://sunmi-ota.oss-cn-hangzhou.aliyuncs.com/DOC/resource/re_cn/外置打印机/Sunmi外接打印机开发者文档.pdf

支付开发介绍

产品支持刷脸、扫码和刷卡三种身份识别介质,身份识别出来后,由ISV根据自己的业务逻辑完成收银的端到服务侧处理。

刷脸支付开发 

刷脸支付支持:在线人脸识别。

在线人脸识别方案的刷脸支付开发

用户在支付宝app完成刷脸开通后,商家在设备上输入金额,用户刷脸成功识别后用户点击确认,商家收银服务端发起收单请求扣款。

在线人脸识别的刷脸方案,请见如下链接:https://opendocs.alipay.com/open/20180402104715814204/intro

扫码支付开发

商家在设备上输入金额,用户点击设备上“扫码支付”按钮后,将手机上支付宝app的付款码展示到摄像头前方,完成识别扣款。 

支持“支付宝二维码”、“数字码”、“所有类型”等3种扫码模式。模式设置逻辑如下:

  1. 二维码功能默认支持“所有类型”二维码
  2. 根据业务要求可配置识别码类型, “仅识别支付宝二维码”、“识别数字码”、“识别所有类型二维码”

扫码支付快速接入,请见如下链接:

https://opendocs.alipay.com/open/194/106078

刷卡支付开发

设备支持NFC非接触卡的读取和写入,支持的规范有:

  • 支持13.56MHz频段(ISO14443A / B,ISO15693 MIFARE读/写模式等),识读兼容市面上常见的各类校园卡(逻辑加密卡、智能卡等如:M1卡、CPU卡等)。识读距离:0-5cm。
  • PSAM卡:支持内置接触式读写模式,支持IOS7816-1/2/3/4协议,符合《中国金融集成电路(IC卡)PSAM卡规范》。PSAM卡不支持热插拔。

ISV可以根据客户的需求配置相应的卡片和写卡的设备,也可以兼容现有团餐实体卡,读写卡的程序建议以一个区别于收银apk的常驻内存的应用控制。设备出厂不标配PSAM卡。

产品介绍

1. 产品介绍

1.1 产品适合场景

适用于校园、园区、社区、企事业单位的食堂和餐厅的消费支付。提供商户一款集刷脸、扫码、刷卡一体的智能收银受理设备,ISV可以在这款设备上进行二次开发,满足商户的个性化结算需求。

1.2 产品特点

  • 高准确性:刷脸识别,百万分之一错误率,远低于指纹
  • 高安全性:独有活体技术,拦截各种伪造攻击
  • 高稳定性:蚂蚁金融云架构,高可用、动态可扩展,峰值2000TPS
  • 极致体验:快速刷脸识别,秒级验证通过
  • 高扩展性:USB打印机,扫码枪,扫码盒即插即用

      键盘可分离、防油污材料、IP55防水等级、V0防火等级、脸码卡一体 


1.3 产品规格

参数内容
处理器高通骁龙SDM450 8核处理器
操作系统Android 8.1 64bits 操作系统
存储器16 GB ROM+2 GB RAM
主显示屏(客屏)主屏7”IPS, 600 x 1024,电容式多点触控
副显示屏(操作屏)副屏4.0” IPS  480 x 800
通信方式4G 国内全网通,4G为选配版本
WiFi2.4GHz
蓝牙支持蓝牙2.1+EDR/3.0/4.0 LE/4.2 BLE
非接卡支持NFC非接卡,支持13.56MHz频段,识读距离:0-5cm。
PSAM卡:支持内置接触式读写模式,支持IOS7816-1/2/3/4协议
人脸识别内置式,蚂里奥S1,MIPI接口
指示灯红、黄、蓝三色LED指示灯
外置键盘19键型 USB HID外置式键盘
按键电源键(锁屏键),隐藏按键3个(音量+/-以及开机键)
喇叭1x 3W密闭大喇叭
外部接口1x RJ45 100M 网口;1x DC-IN电源接口,2x USB Type A口;1x Micro USB调试口
产品安装支持金属支架
电源适配器DC 12V/2A,1.5m插墙式
尺寸209.2*129*36.3mm(不包含支架,不含按键高度)
工作环境工作温度:-10°C–50°C    储藏温度:-20°C–70°C,湿度要求:5%-95%
支架金属侧壁安装支架
配件1.5m USB延长线,RJ45网络延长线,键盘盖,螺丝

1.4 硬件接口

设备支持3个接口:RJ45网口、USB2.0*2。同时支持Micro USB和PSAM卡槽。

说明:Micro USB接口使用时不能和RJ45网口/USB口同时使用。

具体接口,请见文档: 接口使用说明

2. 产品运维

2.1 安装部署

  • 设备注册激活必需要有访问外网的IP网络。
  • 注册激活的设备关机后,再开机务必保证设备有网络物理连接。
  • 避免强光干扰,影响识别效果。
  • 地面布置“识别最佳点位”指引线。 

2.2 安装方式

五种安装方式:左侧挂式安装、顶部挂式安装、右侧挂式安装、玻璃挂式安装、台式支架安装。

具体安装方案,请见文档: 安装建议

2.2 安装高度

参照平均身高选择安装高度(摄像头距地面的距离)。

高度角度身高范围(距离60cm)
1400125(最低)175(最高)
1500135(最低)185(最高)
1600145(最低)195(最高)

假设安装高度160cm的话, 覆盖145~195cm 身高人群。如果是上下垂直调整的话, 就直接相减即可。比如安装高度下降30cm至 130cm的位置, 则覆盖身高变为115~165cm。如果离得更近,那身高范围会缩小一些。以上是理论计算, 实际测试会比这个范围大。

2.3 系统升级

为保障支付的稳定和延续性,在系统和应用检测到有新的版本可升级时,需要现场工作人员手动点击更新。

2.3.1 版本历史

版本号发布说明
 1.0.5/37  适配刷脸APK
  

2.3.2 升级方法

2.3.2.1 查看固件版本号&升级信息

『设置』-『系统』-『关于设备』-『系统升级』

2.3.2.2 版本号说明

当前版本:即当前系统的版本号信息

升级版本:即将被升级的版本号信息;

2.3.2.3 升级操作说明

  1. 如有版本更新,在『系统升级』菜单项会提示,『升级版本』;
  2. 点击后进入到『系统升级』弹出框界面,『升级版本』信息会有如下提示
  3. 『暂无可用版本』:表示无更新;
  4. 『正在下载中..』:表示正在下载升级包,先退出系统设置,过5分钟后再查看相关信息;
  5. 『下载已完成,请点击升级』:表示版本已下载完成,同时右下角会出现『确认』按钮,如点击『确认』,则再20秒后重启并直接进行升级;否则,不进行任何升级操作。

2.4 售后维保

设备出现故障,可以通过报修小程序进行报修服务,根据商米的售后维保政策享受售后服务。