百度人脸SDK安卓平板USB红外(IR)/RGB摄像头图像混淆问题
最近在做一个法院文件柜项目,涉及人脸识别。我们用的是百度的人脸识别SDK,使用安卓工控板通过USB口连接两个摄像头进行人脸识别。一个红外、一个RGB。红外数据用于活体检测,RGB数据用于匹配用户。
开发过程中会出现一个奇怪的现象,在图像预览界面,有时候RGB的图像会被当初红外图像处理,红外图像则被当初RGB图像,导致SDK无法正确识别。
如下图
一开始我怀疑是摄像头问题,换了新的摄像头还是如此,最后排查发现是安卓工控板的问题。在百度官方给的SDK demo中,使用的是老的Camera类来调用相机,即通过cameraId来获取相机。在 Camera
源码中,cameraId
是被写死的。
/**
* The facing of the camera is opposite to that of the screen.
*/
public static final int CAMERA_FACING_BACK = 0;
/**
* The facing of the camera is the same as that of the screen.
*/
public static final int CAMERA_FACING_FRONT = 1;
/**
* The direction that the camera faces. It should be
* CAMERA_FACING_BACK or CAMERA_FACING_FRONT.
*/
然而安卓工控板好像不怎么认USB设备(也有可能是我学艺不精),在开机状态下,如果出现摄像头IR/RGB数据混淆的情况,来回拔插切换摄像头的USB口,有时候能修正这个问题,有时候不能,==修复后保持USB插口不变的情况下,断电重启有时又会出现同样的问题。==个人感觉这已经脱离代码的范围了,属于玄学问题。
仔细看百度官方的Demo可以发现,在FaceRGBIROpenDebugSearchActivity
中预留了两个方法:rgbOrIr(int index, byte[] data)
、choiceRgbOrIrType(int index, byte[] data)
没有用到,实际上所有用到双目的Activity中都预留了这两个方法,我们可以直接拿来使用。
先看rgbOrIr(int index, byte[] data)
方法
private synchronized void rgbOrIr(int index, byte[] data) {
byte[] tmp = new byte[PREFER_WIDTH * PERFER_HEIGH];
try {
System.arraycopy(data, 0, tmp, 0, PREFER_WIDTH * PERFER_HEIGH);
} catch (NullPointerException e) {
Log.e(TAG, String.valueOf(e.getStackTrace()));
}
int count = 0;
int total = 0;
for (int i = 0; i < PREFER_WIDTH * PERFER_HEIGH; i = i + 10) {
total += byteToInt(tmp[i]);
count++;
}
if (index == 0) {
camemra1DataMean = total / count;
} else {
camemra2DataMean = total / count;
}
if (camemra1DataMean != 0 && camemra2DataMean != 0) {
if (camemra1DataMean > camemra2DataMean) {
camemra1IsRgb = true; //惊了,居然是把两个摄像头一帧数据的所有byte值加起来比大小
} else {
camemra1IsRgb = false;
}
rgbOrIrConfirm = true;
}
}
这个方法能标记出摄像头1(cameraId:0)是不是RGB摄像头。而第二个方法就简单了,就是判断一下用不同的方法处理不同的摄像头而已。
choiceRgbOrIrType(int index, byte[] data)
private void choiceRgbOrIrType(int index, byte[] data) {
// camera1如果为rgb数据,调用dealRgb,否则为Ir数据,调用Ir
if (index == 0) {
if (camemra1IsRgb) {
dealRgb(data);
} else {
dealIr(data);
}
} else {
if (camemra1IsRgb) {
dealIr(data);
} else {
dealRgb(data);
}
}
}
事已至此,已经可以解决这个BUG了。在官方demo的FaceRGBIROpenDebugSearchActivity
的两个摄像头回调处做如下修改。
mCamera[0].setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
rgbOrIr(0,data);
if (rgbOrIrConfirm){
//要等两个摄像头都返回一帧数据,rgbOrIrConfirm才会被赋值,此时才能判断到底哪个是RGB摄像头
choiceRgbOrIrType(0,data);
}
// dealRgb(data);
}
});
mCamera[1].setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
rgbOrIr(1,data);
if (rgbOrIrConfirm){
//要等两个摄像头都返回一帧数据,rgbOrIrConfirm才会被赋值,此时才能判断到底哪个是RGB摄像头
choiceRgbOrIrType(1,data);
}
// dealIr(data);
}
});
这样修改即使摄像头预览的RGB/IR数据是反的,对识别人脸也没有问题。如果需要让预览正常,建议在APP初始化阶段写一个Activity,调用一下两个摄像头,使用这个方法判断一下,写个APP全局变量,如果摄像头反了,在人脸识别界面反着打开摄像头就行了。
//demo开启摄像头的代码,在这用初始化阶段赋值的全局变量做个判断,来决定open(0)和open(1)是否反过来
mCamera[0] = Camera.open(0);
mCamera[1] = Camera.open(1);
mPreview[0].setCamera(mCamera[0], PREFER_WIDTH, PERFER_HEIGH);
mPreview[1].setCamera(mCamera[1], PREFER_WIDTH, PERFER_HEIGH);
==注意:百度SDK4.0版本已经修改此处代码,开启RGB摄像头的方法已变更为startTestCloseDebugRegisterFunction();
==
实测过程中,发现在完全遮挡摄像头的情况下,该方法可能会判断失误。在摄像头面前有物体晃动的情况下,也会出现几帧误判,因此建议多取几帧数据进行判断。
这个问题虽然暂时解决了,但是感觉判断方法也太神奇了叭,凭什么RGB图像的byte值和比IR图像要大?
两个相机回调函数如下:
CameraPreviewManager.getInstance().startPreview(this, mAutoCameraPreviewView,
PREFER_WIDTH, PERFER_HEIGH, new CameraDataCallback() {
@Override
public void onGetCameraData(byte[] data, Camera camera, int width, int height) {
// 摄像头预览数据进行人脸检测
......
}
});
两个相机参数data[]
的长度是一致的,图像长宽也是一致的,说明RGB返回的date[]并不是真彩图,而是256色图,将8位即一个字节的数据映射到色表中,一个字节最多可以表示256种颜色。
256色表
IR摄像头返回的是8位灰度数据,灰度图的R、G、B值相同。
至于为什么RGB图像的byte值和比IR图像要大,我至今未搞清楚,可能和图像处理这方面知识有关,好想把写这个方法的大神抓出来问一下