百度人脸识别开发套件5.SDK代码提炼
goJhou 发布于2018-12-21 10:06 浏览:5764 回复:49
2
收藏
最后编辑于2019-05-12

上节我们初始化了SDK,其余的像质量检测这些我就不详细的介绍了,文档中心给了一张一看就懂的图。这里我就把一些关于质量检测的表格列举出来供参考使用。人脸识别注重的还是用户与组的管理和1:1、1:N检索的功能~

简单的易懂的功能我就简要点一下就过了,如果不懂翻一翻文档中心和sdk工程就懂啦,很简单~
我想尽快带各位进入更深入的地方,所以这里就不再浪费篇幅讲解功能了,争取在最短篇幅里解决掉相关SDK的基本内容。

 

有关质量检测相关代码:
(这里光照范围那个值写错了,取值是从50~255)

 

参数 名称 默认值 取值范围
brightnessValue 图片爆光度 40f 50~255
blurnessValue 图像模糊度 0.7f 0~1.0f
occlusionValue 人脸遮挡阀值 0.5f 0~1.0f
headPitchValue 低头抬头角度 15 0~45
headYawValue 左右角度 15 0~45
headRollValue 偏头角度 15 0~45
minFaceSize 最小人脸检测值,小于此值的人脸将检测不出来,最小值为80 120 80~200
notFaceValue 人脸置信度 0.8f 0~1.0f
isCheckFaceQuality 是否检测人脸质量 False True/Flase


关于特征模式的切换,在SDK内有PreferencesUtil的工具类可以用于切换特征模式。

特征模式分2种,一种为生活照模式,一种为身份证模式。各模式针对的是注册照片而言所评定的,会基于选择模式走不同的算法进行识别,所以要根据场景来选。
抠出关键的代码:

public static final String TYPE_MODEL = "TYPE_MODEL";
public static final int RECOGNIZE_LIVE = 1;
public static final int RECOGNIZE_ID_PHOTO = 2;

PreferencesUtil.putInt(TYPE_MODEL, RECOGNIZE_LIVE);

PreferencesUtil.putInt(TYPE_MODEL, RECOGNIZE_ID_PHOTO);

 

 

 

 

活检设置也影响着比对和检索的算法,只有单目可以关闭活检,其余的默认就有活检(其实不用担心,只有单目的活检特别耗时)

活检设置用的也是PreferencesUtil。抠出关键代码如下:

public static final int TYPE_NO_LIVENSS = 1;
public static final int TYPE_RGB_LIVENSS = 2;
public static final int TYPE_RGB_IR_LIVENSS = 3;
public static final int TYPE_RGB_DEPTH_LIVENSS = 4;
public static final int TYPE_RGB_IR_DEPTH_LIVENSS = 5;
// 单目无活检:
PreferencesUtil.putInt(TYPE_LIVENSS, TYPE_NO_LIVENSS);
// 单目活检:
PreferencesUtil.putInt(TYPE_LIVENSS, TYPE_RGB_LIVENSS);
// 彩灰(双目)活检:
PreferencesUtil.putInt(TYPE_LIVENSS, TYPE_RGB_IR_LIVENSS);
// 彩深(结构光)活检:
PreferencesUtil.putInt(TYPE_LIVENSS, TYPE_RGB_DEPTH_LIVENSS);
// 彩灰深(红外结构光)活检:
PreferencesUtil.putInt(TYPE_LIVENSS, TYPE_RGB_IR_DEPTH_LIVENSS);

如果选择了结构光相关的活检策略,那还需要设置摄像机类型
抠出关键代码如下:

public static final String TYPE_CAMERA = "TYPE_CAMERA";
public static final int ORBBEC = 1;
public static final int IMIMECT = 2;
public static final int ORBBECPRO = 3;

PreferencesUtil.putInt(TYPE_CAMERA, ORBBEC);
PreferencesUtil.putInt(TYPE_CAMERA, IMIMECT);
PreferencesUtil.putInt(TYPE_CAMERA, ORBBECPRO);

 


组注册
建议组名用正则匹配一下:

Pattern pattern = Pattern.compile("^[0-9a-zA-Z_-]{1,}$");
Matcher matcher = pattern.matcher(groupId);

Group group = new Group();
group.setGroupId(groupId);
boolean ret = FaceApi.getInstance().groupAdd(group);

 

 


照片/流注册

// 获取组列表
List groupList = DBManager.getInstance().queryGroups(0, 1000);
// 仅抽取ID列表
for (Group group : groupList) {
groupIds.add(group.getGroupId());
}

// 注册时使用人脸图片路径。Intent从android.content中引出
//读写权限请求
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
100);
return;
}


private static final int REQUEST_CODE_PICK_IMAGE = 1000;
private String faceImagePath;
faceImagePath = null;
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);

// 从相机识别时使用。
private FaceDetectManager detectManager;
//权限确认
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest
.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA}, 100);
return;
}
//获取当前所配置的活检类型
int type = PreferencesUtil.getInt(LivenessSettingActivity.TYPE_LIVENSS, LivenessSettingActivity
.TYPE_NO_LIVENSS);

//如果是单目执行以下
Intent intent = new Intent(RegActivity.this, RgbDetectActivity.class);
intent.putExtra("source", SOURCE_REG);
startActivityForResult(intent, REQUEST_CODE_AUTO_DETECT);

//双目调用RgbIrLivenessActivity
//如果是深度还要再判定一下摄像头厂家
int cameraType = PreferencesUtil.getInt(GlobalFaceTypeModel.TYPE_CAMERA, GlobalFaceTypeModel.ORBBEC);
Intent intent3 = null;
if (cameraType == GlobalFaceTypeModel.ORBBEC) {
intent3 = new Intent(RegActivity.this, OrbbecLivenessDetectActivity.class);
} else if (cameraType == GlobalFaceTypeModel.IMIMECT) {
intent3 = new Intent(RegActivity.this, IminectLivenessDetectActivity.class);
} else if (cameraType == GlobalFaceTypeModel.ORBBECPRO) {
intent3 = new Intent(RegActivity.this, OrbbecProLivenessDetectActivity.class);
}
if (intent3 != null) {
intent3.putExtra("source", SOURCE_REG);
startActivityForResult(intent3, REQUEST_CODE_AUTO_DETECT);
}

 

 


这里要着重介绍一下DetectActivity,以单目为例
前端代码剖析如下:




//布局标签


//百度封装的TexturePreView标签,用于预览USB流


//用于绘制人脸框用的TextureView


////显示检测的图片。用于调试,如果人脸sdk检测的人脸需要朝上,可以通过该图片判断

////用于给予一些提示,例如 请正视摄像头


//下方是位于底部的布局,为了在UI中显示活体指数和一些tips信息
























 


后端代码比较重要的抠出如下:

faceDetectManager = new FaceDetectManager(getApplicationContext());
// 从系统相机获取图片帧。
final CameraImageSource cameraImageSource = new CameraImageSource(this);
// 图片越小检测速度越快,闸机场景640 * 480 可以满足需求。实际预览值可能和该值不同。和相机所支持的预览尺寸有关。
// 可以通过 camera.getParameters().getSupportedPreviewSizes()查看支持列表。
cameraImageSource.getCameraControl().setPreferredPreviewSize(1280, 720);
// 设置最小人脸,该值越小,检测距离越远,该值越大,检测性能越好。范围为80-200

previewView.setMirrored(false);
// 设置预览 这里previewView是通过(PreviewView) findViewById(R.id.preview_view);抓取的控件对象
cameraImageSource.setPreviewView(previewView);
// 设置图片源
faceDetectManager.setImageSource(cameraImageSource);
//质检
faceDetectManager.setUseDetect(true);

//可以理解为控件透明。true为不透明
textureView.setOpaque(false);

// 不需要屏幕自动变黑。
textureView.setKeepScreenOn(true);

//获取相机方向
boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;

//适配朝向
if (isPortrait) {
previewView.setScaleType(PreviewView.ScaleType.FIT_WIDTH);
// 相机坚屏模式
cameraImageSource.getCameraControl().setDisplayOrientation(CameraView.ORIENTATION_PORTRAIT);
} else {
previewView.setScaleType(PreviewView.ScaleType.FIT_HEIGHT);
// 相机横屏模式
cameraImageSource.getCameraControl().setDisplayOrientation(CameraView.ORIENTATION_HORIZONTAL);
}

//选择摄像头
setCameraType(cameraImageSource);

 


setCameraType 选择摄像头

private void setCameraType(CameraImageSource cameraImageSource) {
// TODO 选择使用前置摄像头
// cameraImageSource.getCameraControl().setCameraFacing(ICameraControl.CAMERA_FACING_FRONT);

// TODO 选择使用usb摄像头
cameraImageSource.getCameraControl().setCameraFacing(ICameraControl.CAMERA_USB);
// 如果不设置,人脸框会镜像,显示不准
// previewView.getTextureView().setScaleX(-1);

// TODO 选择使用后置摄像头
// cameraImageSource.getCameraControl().setCameraFacing(ICameraControl.CAMERA_FACING_BACK);
// previewView.getTextureView().setScaleX(-1);
}





 

 

 

 


listener 设置回调,回调人脸检测结果。

 

 

// 设置回调,回调人脸检测结果。
faceDetectManager.setOnFaceDetectListener(new FaceDetectManager.OnFaceDetectListener() {
@Override
public void onDetectFace(int retCode, FaceInfo[] infos, ImageFrame frame) {
// TODO 显示检测的图片。用于调试,如果人脸sdk检测的人脸需要朝上,可以通过该图片判断
final Bitmap bitmap =
Bitmap.createBitmap(frame.getArgb(), frame.getWidth(), frame.getHeight(), Bitmap.Config.ARGB_8888);
//如果需要debug,解开下头

// handler.post(new Runnable() {
// @Override
// public void run() {
// testView.setImageBitmap(bitmap);
// }
// });

checkFace(retCode, infos, frame);
showFrame(frame, infos);
}
});


解析Detect的返回json

//解析质检返回的json结果,处理相关逻辑。这里其中的filter是一个基于各类阈值相关条件给出的参考函数
private void checkFace(int retCode, FaceInfo[] faceInfos, ImageFrame frame) {
if ( retCode == FaceTracker.ErrCode.OK.ordinal() && faceInfos != null) {
FaceInfo faceInfo = faceInfos[0];
String tip = filter(faceInfo, frame);
displayTip(tip);
} else {
String tip = checkFaceCode(retCode);
displayTip(tip);
}
}

private String filter(FaceInfo faceInfo, ImageFrame imageFrame) {

String tip = "";
if (faceInfo.mConf < 0.6) {
tip = "人脸置信度太低";
return tip;
}

float[] headPose = faceInfo.headPose;
if (Math.abs(headPose[0]) > 20 || Math.abs(headPose[1]) > 20 || Math.abs(headPose[2]) > 20) {
tip = "人脸置角度太大,请正对屏幕";
return tip;
}

int width = imageFrame.getWidth();
int height = imageFrame.getHeight();
// 判断人脸大小,若人脸超过屏幕二分一,则提示文案“人脸离手机太近,请调整与手机的距离”;
// 若人脸小于屏幕三分一,则提示“人脸离手机太远,请调整与手机的距离”
float ratio = (float) faceInfo.mWidth / (float) height;
Log.i("liveness_ratio", "ratio=" + ratio);
if (ratio > 0.6) {
tip = "人脸离屏幕太近,请调整与屏幕的距离";
return tip;
} else if (ratio < 0.2) {
tip = "人脸离屏幕太远,请调整与屏幕的距离";
return tip;
} else if (faceInfo.mCenter_x > width * 3 / 4 ) {
tip = "人脸在屏幕中太靠右";
return tip;
} else if (faceInfo.mCenter_x < width / 4 ) {
tip = "人脸在屏幕中太靠左";
return tip;
} else if (faceInfo.mCenter_y > height * 3 / 4 ) {
tip = "人脸在屏幕中太靠下" ;
return tip;
} else if (faceInfo.mCenter_x < height / 4 ) {
tip = "人脸在屏幕中太靠上";
return tip;
}
int liveType = PreferencesUtil.getInt(TYPE_LIVENSS, .TYPE_NO_LIVENSS);
if (liveType == TYPE_NO_LIVENSS) {
saveFace(faceInfo, imageFrame);
} else if (liveType == TYPE_RGB_LIVENSS) {

if (rgbLiveness(imageFrame, faceInfo) > 0.9) {
saveFace(faceInfo, imageFrame);
} else {
toast("rgb活体分数过低");
}
}


return tip;
}

//保存照片的方法
private void saveFace(FaceInfo faceInfo, ImageFrame imageFrame) {
final Bitmap bitmap = FaceCropper.getFace(imageFrame.getArgb(), faceInfo, imageFrame.getWidth());
if (source == RegActivity.SOURCE_REG) {
// 注册来源保存到注册人脸目录
File faceDir = FileUitls.getFaceDirectory();
if (faceDir != null) {
String imageName = UUID.randomUUID().toString();
File file = new File(faceDir, imageName);
// 压缩人脸图片至300 * 300,减少网络传输时间
ImageUtils.resize(bitmap, file, 300, 300);
Intent intent = new Intent();
intent.putExtra("file_path", file.getAbsolutePath());
setResult(Activity.RESULT_OK, intent);
finish();
} else {
toast("注册人脸目录未找到");
}
} else {
try {
// 其他来源保存到临时目录
final File file = File.createTempFile(UUID.randomUUID().toString() + "", ".jpg");
// 人脸识别不需要整张图片。可以对人脸区别进行裁剪。减少流量消耗和,网络传输占用的时间消耗。
ImageUtils.resize(bitmap, file, 300, 300);Intent intent = new Intent();
intent.putExtra("file_path", file.getAbsolutePath());
setResult(Activity.RESULT_OK, intent);
finish();

} catch (IOException e) {
e.printStackTrace();
}
}
}

 

 

 

 


绘制人脸框

private void showFrame(ImageFrame imageFrame, FaceInfo[] faceInfos) {
Canvas canvas = textureView.lockCanvas();
if (canvas == null) {
textureView.unlockCanvasAndPost(canvas);
return;
}
if (faceInfos == null || faceInfos.length == 0) {
// 清空canvas
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
textureView.unlockCanvasAndPost(canvas);
return;
}
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

FaceInfo faceInfo = faceInfos[0];


rectF.set(getFaceRect(faceInfo, imageFrame));

// 检测图片的坐标和显示的坐标不一样,需要转换。
previewView.mapFromOriginalRect(rectF);

float yaw = Math.abs(faceInfo.headPose[0]);
float patch = Math.abs(faceInfo.headPose[1]);
float roll = Math.abs(faceInfo.headPose[2]);
if (yaw > 20 || patch > 20 || roll > 20) {
// 不符合要求,绘制黄框
paint.setColor(Color.YELLOW);

String text = "请正视屏幕";
float width = paint.measureText(text) + 50;
float x = rectF.centerX() - width / 2;
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
canvas.drawText(text, x + 25, rectF.top - 20, paint);
paint.setColor(Color.YELLOW);

} else {
// 符合检测要求,绘制绿框
paint.setColor(Color.GREEN);
}
paint.setStyle(Paint.Style.STROKE);

// 绘制框
canvas.drawRect(rectF, paint);
textureView.unlockCanvasAndPost(canvas);
}

 


以上代码都是截取自demo中的。可选择性使用。


人脸检索 视频流人脸库检索
因为真正投入到项目中的落地应用以视频流检索人脸库为首,所以先贴出这块代码。
之后如果有1:1和M:N相关的功能届时再抽出代码

这里也优先使用单目,我觉得会用单目了,双目和结构光也只是配置和UI调整上的问题了,因为SDK,一切都变的很简单。

大部分都经过Detect,所以在取流、质检都类似
在listener开始有部分不同

private void addListener() {
// 设置回调,回调人脸检测结果。
faceDetectManager.setOnFaceDetectListener(new FaceDetectManager.OnFaceDetectListener() {
@Override
public void onDetectFace(int retCode, FaceInfo[] infos, ImageFrame frame) {
// TODO 显示检测的图片。用于调试,如果人脸sdk检测的人脸需要朝上,可以通过该图片判断
final Bitmap bitmap =
Bitmap.createBitmap(frame.getArgb(), frame.getWidth(), frame.getHeight(), Bitmap.Config
.ARGB_8888);
handler.post(new Runnable() {
@Override
public void run() {
testView.setImageBitmap(bitmap);
}
});
if (retCode == FaceTracker.ErrCode.OK.ordinal() && infos != null) {
asyncIdentity(frame, infos);
}
showFrame(frame, infos);

}
});
}


private void asyncIdentity(final ImageFrame imageFrame, final FaceInfo[] faceInfos) {

//通过一个标识来解决并发问题
if (identityStatus != IDENTITY_IDLE) {
return;
}

es.submit(new Runnable() {
@Override
public void run() {
if (faceInfos == null || faceInfos.length == 0) {
return;
}
int liveType = PreferencesUtil.getInt(LivenessSettingActivity.TYPE_LIVENSS, LivenessSettingActivity
.TYPE_NO_LIVENSS);
if (liveType == LivenessSettingActivity.TYPE_NO_LIVENSS) {
//这里只尝试去找检测出的第一张脸,后续可以修改实现多脸比对
identity(imageFrame, faceInfos[0]);
} else if (liveType == LivenessSettingActivity.TYPE_RGB_LIVENSS) {

if (rgbLiveness(imageFrame, faceInfos[0]) > FaceEnvironment.LIVENESS_RGB_THRESHOLD) {
identity(imageFrame, faceInfos[0]);
} else {
// toast("rgb活体分数过低");
}
}
}
});
}



private void identity(ImageFrame imageFrame, FaceInfo faceInfo) {
float raw = Math.abs(faceInfo.headPose[0]);
float patch = Math.abs(faceInfo.headPose[1]);
float roll = Math.abs(faceInfo.headPose[2]);
// 人脸的三个角度大于20不进行识别
if (raw > 20 || patch > 20 || roll > 20) {
return;
}

identityStatus = IDENTITYING;

long starttime = System.currentTimeMillis();
int[] argb = imageFrame.getArgb();
int rows = imageFrame.getHeight();
int cols = imageFrame.getWidth();
int[] landmarks = faceInfo.landmarks;

int type = PreferencesUtil.getInt(GlobalFaceTypeModel.TYPE_MODEL, GlobalFaceTypeModel.RECOGNIZE_LIVE);
IdentifyRet identifyRet = null;
if (type == GlobalFaceTypeModel.RECOGNIZE_LIVE) {
identifyRet = FaceApi.getInstance().identity(argb, rows, cols, landmarks, groupId);
} else if (type == GlobalFaceTypeModel.RECOGNIZE_ID_PHOTO) {
identifyRet = FaceApi.getInstance().identityForIDPhoto(argb, rows, cols, landmarks, groupId);
}
if (identifyRet != null) {
//这里可以拿到ID和分数。可以再对分数进行筛选一次。demo中<80的都滤掉了。
displayUserOfMaxScore(identifyRet.getUserId(), identifyRet.getScore());
}
identityStatus = IDENTITY_IDLE;
displayTip("特征抽取对比耗时:" + (System.currentTimeMillis() - starttime), featureDurationTv);
}

 


那在本篇中快速的过了一下如何修改SDK的特征模型(生活照、身份证),如何添加组、用户,如何通过相册注册和利用单目视频流进行注册和1:N检索。
本篇的代码也都是从demo中提炼而来的,省去了大部分的View层UI介绍和Controller层的控制,着重介绍了SDK代码。如果对双目和结构光的可以顺着本篇的逻辑进行分析。

之后在介绍落地场景时也会基于不同的摄像头开分篇呢,届时欢迎参考。

下一篇,我将脱离DEMO代码,自己上手写一个从注册完成到视频流识别的代码。
希望能帮助到各位使用者~

收藏
点赞
2
个赞
共49条回复 最后由newpope回复于2019-05-12 22:25
#49goJhou回复于2019-02-28 09:52:19
#48 小豆miniqq回复
返回-1是因为离线采集SDk授权过期了,返回0是采集到人脸

授权在demo里看的了。跑一下就知道环境正常情况了

0
#48小豆miniqq回复于2019-02-27 15:35:09
#42 小豆miniqq回复
别人那里考来的

返回-1是因为离线采集SDk授权过期了,返回0是采集到人脸

0
#47goJhou回复于2019-02-14 17:03:57
#46 worddict回复
要是免费的就好了

在线的是免费的呀

0
#46worddict回复于2019-02-14 16:42:37
#45 goJhou回复
个人玩玩在线API就行了啊。离线SDK当然是针对商业的

要是免费的就好了

0
#45goJhou回复于2019-02-12 10:53:45
#44 才能我浪费99回复
这么说个人玩家就用不了了,是吧

个人玩玩在线API就行了啊。离线SDK当然是针对商业的

0
#44才能我浪费99回复于2019-02-12 07:39:13
#41 goJhou回复
这个sdk应该是必须要企业认证了才开放的。不晓得你之前说有问题的sdk是怎么来的。。
展开

这么说个人玩家就用不了了,是吧

0
#43才能我浪费99回复于2019-02-12 07:38:42
#41 goJhou回复
这个sdk应该是必须要企业认证了才开放的。不晓得你之前说有问题的sdk是怎么来的。。
展开

现在还要企业认证么?

0
#42小豆miniqq回复于2019-02-11 15:17:46
#41 goJhou回复
这个sdk应该是必须要企业认证了才开放的。不晓得你之前说有问题的sdk是怎么来的。。
展开

别人那里考来的

0
#41goJhou回复于2019-02-01 00:20:41
#40 小豆miniqq回复
研究研究,学习学习

这个sdk应该是必须要企业认证了才开放的。不晓得你之前说有问题的sdk是怎么来的。。

0
#40小豆miniqq回复于2019-01-31 10:55:50
#38 goJhou回复
是啊 你不下载demo你咋开发的呀

研究研究,学习学习

0
#39goJhou回复于2019-01-29 17:09:28
#37 worddict回复
不过要商用的话,价格就比较高了

这些本身就是项目成本 不用自己出

0
#38goJhou回复于2019-01-29 17:08:30
#35 小豆miniqq回复
SDK下载需要企业认证...

是啊 你不下载demo你咋开发的呀

0
#37worddict回复于2019-01-29 09:19:25
#26 goJhou回复
也不差这800块了

不过要商用的话,价格就比较高了

0
#36worddict回复于2019-01-29 09:18:51
#26 goJhou回复
也不差这800块了

800块,就是那个价格最高的? 也不是很贵了。

0
#35小豆miniqq回复于2019-01-29 09:06:43
#34 goJhou回复
文档中心默认下载下来的就是demo喔

SDK下载需要企业认证...

0
#34goJhou回复于2019-01-29 02:07:40
#33 小豆miniqq回复
我没有demo版本的代码,你可以给我发一份吗?谢谢啦

文档中心默认下载下来的就是demo喔

0
#33小豆miniqq回复于2019-01-28 13:12:48
#32 goJhou回复
我印象里0是没人。 -1我感觉是个错误码 建议跑一下demo版本的代码。 检查下模型有没有成功加载进来
展开

我没有demo版本的代码,你可以给我发一份吗?谢谢啦

1
#32goJhou回复于2019-01-28 12:44:15
#31 小豆miniqq回复
public void onDetectFace(int retCode, FaceInfo[] infos, ImageFrame frame)中的retcode,每次检测返回都是-1,应该是没检测人脸吧
展开

我印象里0是没人。

-1我感觉是个错误码

建议跑一下demo版本的代码。

检查下模型有没有成功加载进来

1
#31小豆miniqq回复于2019-01-28 09:10:16
#30 goJhou回复
这方法是void诶,应该没返回值的

public void onDetectFace(int retCode, FaceInfo[] infos, ImageFrame frame)中的retcode,每次检测返回都是-1,应该是没检测人脸吧

1
#30goJhou回复于2019-01-25 22:04:49
#29 小豆miniqq回复
addListener()方法中retCode一直是-1,是什么原因

这方法是void诶,应该没返回值的

1
TOP
切换版块