Android(安卓)高效聚焦方案:计算图像模糊度触发聚焦
之前一直被摄像头聚焦的问题困扰,因为我遇到的需求是要快速的连续扫描条码、二维码、手机号,所以摄像头是一只开着的,经常会遇到焦点模糊了,但聚焦不够及时的情况
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); //根据切片的清晰度判断是否需要聚焦 /* 这里清晰度标准写死为5.0,但由于手机的性能不一,对图像清晰度的要求也不能完全一样,所以我更推荐的方案是: * 每一次成功得到数据时(比如扫码成功),表示当前的清晰度正处于可解析的程度,记录一下当前切片的清晰度 * 然后多采集几次,每次都把历史采集的切片清晰度重新算一次平均值,这样几十次以后,就可以得到当前手机最适用的清晰度 */ 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) { } } } };
更多相关文章
- Android(安卓)bugs——RecyclerView scrollToPosition不会触发sc
- Android(安卓)camera: Metadata\Image从HAL到framework
- android学习之EditText需要点击两次触发onclick问题解决
- init.rc 中on propert: 触发无效
- Android触碰事件
- android入门 SeekBar
- LeakCanary原理分析
- Android长按连续触发的具体实现
- Android(安卓)版本兼容问题(一)