前言
在上一篇博客,具体分析了Camera相关配置的一些问题,但是在实际实用真机调试的时候,发现还是有很多与摄像头相关的问题,包括只支持横屏扫描、识别精准度不高、扫描区域小等。因此尝试对相关问题进行改进
一、扫描方向问题
将 Zxing 项目使用真机调试后,所显示的页面如下:
也就是说,Zxing 项目在默认情况下是横屏扫描的,并且不支持随手机自动旋转。在前面已经学习了 Android 中 Camera 方向的一些知识,这里尝试修改一下源码,以使项目支持自动旋转。
首先修改 AndroidManifest.xml 文件,将下面设置屏幕横向的代码注释
<uses-feature android:name="android.hardware.screen.landscape"/>
MyOrientationDetector
在 Android 中,OrientationEventListener 可以监听屏幕旋转角度,因此为了实现 Zxing 项目的自动旋转,要自定义一个类来继承 OrientationEventListener
这里将其定义在 CaptureActivity 中
private class MyOrientationDetector extends OrientationEventListener {
// 继承构造方法
MyOrientationDetector(Context context) {
super(context);
}
@Override
public void onOrientationChanged(int orientation) {
// 如果手机平放,无法检测方向
if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN)
return;
if (orientation > 350 || orientation < 10)
orientation = 0;
else if (orientation > 80 && orientation < 100)
orientation = 90;
else if (orientation > 170 && orientation < 190)
orientation = 180;
else
return;
}
}
在 CaptureActivity 中声明变量
MyOrientationDetector myOrientationDetector;
在 onCreate 中创建 MyOrientationDetector 实例对象
myOrientationDetector = new MyOrientationDetector(this);
在 onResume 中判断是否允许屏幕自动旋转,如果允许则启动监听
if (prefs.getBoolean(PreferencesActivity.KEY_DISABLE_AUTO_ORIENTATION, true)) {
setRequestedOrientation(getCurrentOrientation());
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); // 旋转
myOrientationDetector.enable(); //启用监听
}
在 onPause 中关闭屏幕旋转监听
myOrientationDetector.disable();
打开真机调试,此时已经可以竖屏显示页面:
但是仅仅实现了屏幕旋转监听,此时无法扫描探测到二维码,还需要进行更多的修改
首先是相机预览框的设置,在 CameraManager 的 getFramingRectInPreview() 方法中,需要设置竖屏情况下预览框的绘制
if(screenResolution.x < screenResolution.y){
// 竖屏情况
rect.left = rect.left * cameraResolution.y / screenResolution.x;
rect.right = rect.right * cameraResolution.y / screenResolution.x;
rect.top = rect.top * cameraResolution.x / screenResolution.y;
rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
} else {
// 横屏情况
rect.left = rect.left * cameraResolution.x / screenResolution.x;
rect.right = rect.right * cameraResolution.x / screenResolution.x;
rect.top = rect.top * cameraResolution.y / screenResolution.y;
rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
}
但是在真机调试中发现,虽然页面显示了取景框,扫描二维码还是没有反应。在查找资料后得知,在 PreviewCallback 的 onPreviewFrame 方法中,获取的是默认情况下预览页面的一帧数据,也就是说虽然屏幕的方向是竖向的,扫描得到的图像仍是横向的。因此需要对获取到的图像数组进行反置,这里在 DecodeHandler 的 decode 方法中处理:
private void decode(byte[] data, int width, int height) {
if (width < height) {
// 竖屏情况,将数组内容反置
byte[] rotatedData = new byte[data.length];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++)
rotatedData[y * width + width - x - 1] = data[y + x * height];
}
data = rotatedData;
}
......
再进行真机调试,则成功扫码解码!
二、图形拉伸问题
在上一篇博客中,我们分析到获得相机分配率的函数时,已经了解到了 Zxing 项目获取相机最佳分辨率 findBestPreviewSizeValue 方法中,如果没有找到精确匹配的尺寸,那么就选择最大尺寸。但是带来的问题是最大尺寸宽高比与屏幕宽高比相差可能很大,造成图像拉伸问题。
因此这里考虑修改下寻找尺寸的策略,将算法的核心从最大的尺寸改为比例最接近的尺寸,这样便能够最原始地接近屏幕分辨率的宽高比
在 CameraConfigurationUtils 中定义一个内部类 SizeComparator,用以比较两个尺寸的接近程度
private static class SizeComparator implements Comparator<Camera.Size> {
private final int width;
private final int height;
// 宽高比
private final float ratio;
// 构造函数
SizeComparator(int width, int height) {
if (width < height) {
this.width = height;
this.height = width;
} else {
this.width = width;
this.height = height;
}
this.ratio = (float) this.height / this.width;
}
@Override
public int compare(Camera.Size size1, Camera.Size size2) {
int width1 = size1.width;
int height1 = size1.height;
int width2 = size2.width;
int height2 = size2.height;
float ratio1 = Math.abs((float) height1 / width1 - ratio);
float ratio2 = Math.abs((float) height2 / width2 - ratio);
int result = Float.compare(ratio1, ratio2);
if (result != 0) {
return result;
} else {
int minGap1 = Math.abs(width - width1) + Math.abs(height - height1);
int minGap2 = Math.abs(width - width2) + Math.abs(height - height2);
return minGap1 - minGap2;
}
}
}
再定义方法 findCloselySize,在所有相机支持的预览尺寸中进行排序,得到与宽高比最接近的尺寸
/**
* 通过对比得到与宽高比最接近的尺寸(如果有相同尺寸,优先选择)
*
* @param surfaceWidth 需要被进行对比的原宽
* @param surfaceHeight 需要被进行对比的原高
* @param preSizeList 需要对比的预览尺寸列表
* @return 得到与原宽高比例最接近的尺寸
*/
protected Camera.Size findCloselySize(int surfaceWidth, int surfaceHeight, List<Camera.Size> preSizeList) {
Collections.sort(preSizeList, new SizeComparator(surfaceWidth, surfaceHeight));
return preSizeList.get(0);
}
三、相机预览倍数设置及聚焦时间调整
如果使用zxing默认的相机配置,会发现需要离二维码很近才能够识别出来,但这样会带来一个问题——聚焦困难。解决办法就是调整相机预览倍数以及减小相机聚焦的时间。
通过测试可以发现,每个手机的最大放大倍数几乎是不一样的,这可能和摄像头的型号有关。如果设置成一个固定的值,那可能会产生在某些手机上过度放大,某些手机上放大的倍数不够。索性相机的参数设定里给我们提供了最大的放大倍数值,通过取放大倍数值的N分之一作为当前的放大倍数,就完美地解决了手机的适配问题。
// 需要判断摄像头是否支持缩放
Parameters parameters = camera.getParameters();
if (parameters.isZoomSupported()) {
// 设置成最大倍数的1/10,基本符合远近需求
parameters.setZoom(parameters.getMaxZoom() / 10);
}
zxing默认的相机聚焦时间是2s,可以根据扫描的视觉适当调整。聚焦时间的调整也很简单,在 AutoFocusCallback
这个类里,调整 AUTO_FOCUS_INTERVAL_MS
这个值就可以了
总结
在一开始分析完 Camera 部分代码,并且在真机调试中发现相机的相关问题后,就打算对这些问题进行优化。但是当时对 Zxing 的代码很不熟悉,了解到的只是片面的内容。因此在后面分析完扫码流程部分后,才真正地实现了优化,并且补写了这篇博客。对 Zxing 代码的理解更进一步加深了。