之前一直被摄像头聚焦的问题困扰,因为我遇到的需求是要快速的连续扫描条码、二维码、手机号,所以摄像头是一只开着的,经常会遇到焦点模糊了,但聚焦不够及时的情况
Android中常用的聚焦方案有几种,我也都用上了,还还是无法避免一些不能及时聚焦的情况,常用方式如下:
__
1、定时聚焦(一般1-2秒自动聚焦一次,但是因为频繁聚焦,会导致有相当一部分时间,相机处于聚焦中的模糊状态,很影响体验)
2、手动点击聚焦(需要手动操作,操作麻烦)
3、传感器触发聚焦,当手机位置或角度发生改变时触发聚焦(当手机位置没动,但拍摄的内容变动引起焦点模糊时,无法触发聚焦)
如上三种方式,我同时使用的情况下(传感器监听+手动聚焦,另外再计时超过2秒没有触发过聚焦,则自动聚焦一次),最影响体验就是:在手机没动,拍摄内容变容引起焦点模糊时,不能及时触发聚焦,只能等待自动聚焦的计时达到2秒,而且手机性能参差不齐,对于配置较低的手机,聚焦一次花的时间较长,这样一来大部分时间都浪费在了聚焦的模糊过程中,扫描速度变得很慢
解决思路 : 手机聚焦一次花的时间,和聚焦的成功率,这个要主要靠系统和硬件,我们没办法优化,所以主要的目的就是
减少聚焦的次数,同时提高聚焦的准确性,在只有焦点模糊需要聚焦时才聚焦,画面清晰时就算等1分钟也不会聚焦一次,才是最佳效果(经过测试在聚焦频率上已经和系统相机差不多了,只要画面模糊,马上能触发聚焦,画面清晰则始终不聚焦)
集成OpenCv
集成步骤资料很多,重复的我就不写了,我是参考这篇文章配置的OpenCv :
https://www.cnblogs.com/yunfang/p/6149831.html
不过这篇文章中用的不是最新的Opencv-android-sdk,我下载的是最新版本,同时这里需要注意:导入opencv-android-sdk 的 .so文件时,只兼容最基础的 armeabi 就好了,如果全部加进去,你最终打包的apk会非常大……
OpenCv实现
1、初始化OpenCv
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (!OpenCVLoader.initDebug()) {
Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, loaderCallback);
} else {
Log.d(TAG, "OpenCV library found inside package. Using it!");
loaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}
private BaseLoaderCallback loaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case BaseLoaderCallback.SUCCESS:
Log.d(TAG, "加载成功");
break;
default:
super.onManagerConnected(status);
Log.d(TAG, "加载失败");
break;
}
}
};
2、在帧数据回调中切片计算清晰度
private int blurringFrame = 0;
private int clearFrame = 0;
private boolean isHandling = false;
public void onPreviewFrame(final byte[] data, final Camera camera) {
//如果有一帧图像正在处理中,先等待其处理结束,避免过度耗费性能(这里更好优化方案是记录聚焦动作,如果正在聚焦过程中,那画面大多是模糊的,没有必要处理这些图像,但是由于部分手机的聚焦回调时间不够精准,有的甚至经常收不到回调,所以不太可靠)
if(isHandling)
return;
//识别中不处理其他帧数据
new Thread(new Runnable() {
@Override
public void run() {
try {
isHandling = true;
//获取Camera预览尺寸
Camera.Size size = camera.getParameters().getPreviewSize();
int left = (int) (size.width / 2 - getResources().getDimension(R.dimen.x40));
int top = (int) (size.height / 2 - getResources().getDimension(R.dimen.x40));
int right = (int) (left + getResources().getDimension(R.dimen.x20));
int bottom = (int) (top + getResources().getDimension(R.dimen.x120));
final YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
if (image != null) {
//切片,这里默认取了屏幕中央的一小部分,也是默认的焦点
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image.compressToJpeg(new Rect(left, top, right, bottom), getQuality(size.height), stream);
Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
if (bmp == null)
return;
//计算清晰度
double laplacian = getLaplacian(bmp);
Log.d(TAG, "清晰度: " + laplacian);
//清晰度不够
if (laplacian < 5.0f) {
blurringFrame++;
//如果没有再聚焦过程中,且连续5帧清晰度较低,则触发一次聚焦
if (!isFocusing && blurringFrame >= 5)
startFocus();
//有的手机摄像头太差,大部分时间都达不到5.0的清晰度,导致无论画面清晰还是模糊,始终都是 isFocusing=true,所以这里加个上限,当连续40帧清晰度都不够,直接触发聚焦(类似定时聚焦功能)
if (blurringFrame > 40)
startFocus();
} else {
//如果连续3帧清晰度足够,解除“正在聚焦”的状态(不能完全依赖聚焦成功回调,有的手机经常收不到回调)
if (++clearFrame > 3) {
clearFrame = 0;
blurringFrame = 0;
isFocusing = false;
}
}
isHandling = false;
//清晰度正常,开始处理图像(文字识别、扫码 等)
......
} catch (Exception ex) {
isHandling = false;
}
}
}).start();
}
/**
* 计算图像清晰度
*/
private double getLaplacian(Bitmap bmp) {
Mat img = new Mat();
//bitmap->mat
Utils.bitmapToMat(bmp, img);
Mat imageGrey = new Mat();
Imgproc.cvtColor(img, imageGrey, Imgproc.COLOR_RGB2GRAY);
Mat imageSobel = new Mat();
// Imgproc.Sobel(imageGrey, imageSobel, CV_16U, 1, 1) // sobel 梯度
Imgproc.Laplacian(imageGrey, imageSobel, CV_16U); //拉普拉斯梯度
//图像的平均灰度
return Core.mean(imageSobel).val[0];
}
/**
* 聚焦
* 回调
*/
Camera.AutoFocusCallback autoFocusCB = new Camera.AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
//聚如果焦失败,重新聚焦
if (!success) {
postDelayed(doAutoFocus, 500);
} else {
// isFocusing = false; //有的手机再聚焦成功后,画面还没有清晰,就执行了回调,这时候解除聚焦状态,直接开始计算模糊度,会触发无限聚焦 ,所以这里不再依赖回调来判断聚焦状态
}
}
};
/**
* 开始聚焦
*/
private Runnable doAutoFocus = new Runnable() {
public void run() {
if (mCamera != null) {
try {
mCamera.autoFocus(autoFocusCB);
} catch (Exception e) {
}
}
}
};