文章目录

    • `Zxing`使用
    • 官方Demo使用
      • 使用步骤
      • 工作流程分析
        • 相机画面预览
      • 帧数据捕获
      • 帧数据的处理
    • 官方Demo分析
      • CaptureActivity
      • CameraManager
      • CaptureActivityHandler
      • DecodeHandler
      • CaptureActivityHandler
      • 提高识别
      • 识别率优化
    • 参考

Zxing使用

zxingGoogle推出的用于识别QRCode、ISBN等图形码的解决方案。本文主要介绍Android移动端对Zxing的使用,以及官方demo的集成。

添加依赖:

compile 'com.google.Zxing:core:3.2.1'

利用core包中提供的API对相机的预览帧数据data进行解析:

PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source))MultiFormatReader multiFormatReader = new MultiFormatReader();Result rawResult = multiFormatReader.decodeWithState(bitmap);//获取结果:rawResult.getText() || rawResult.getRawBytes()

为了获取相机的预览帧数据,通常需要借助android.hardware.Camera等中的API自定义一个相机,这个工作需要对Camera的相关知识具有较好理解(这里推荐一个连载篇Android相机开发系列)。所以,大部分人会选择将官方的Demo--zxing直接拷贝至项目内,然后进行适当需改即可。

官方Demo使用

官方Demo的使用比较简单,添加依赖、拷贝java逻辑代码与资源文件至项目中即可。

使用步骤

  1. 添加依赖:
compile 'com.google.zxing:core:3.2.1'
  1. 代码与资源拷贝
  • android中的逻辑包com.google.zxing拷贝至本地项目中。
  • android-core中的CameraConfigurationUtils.java添加至项目中。
  • android中的res目录中的资源文件添加至项目当中(为了避免文件覆盖,可对文件进行内容拷贝而非直接的文件拷贝,同时有些文件按需添加即可,例如:valuse及其valuse-系类)。
  1. Activit调用与扫描结果的获取
  • 启动CauptureActivity进行扫描
   public static final int REQUEST_SCAN_QRCODE = 0X11;   private void scanQRCode() {       Intent scanIntent = new Intent(this, CaptureActivity.class);       scanIntent.setAction(Intents.Scan.ACTION);//        scanIntent.putExtra(Intents.Scan.WIDTH, 99999);//        scanIntent.putExtra(Intents.Scan.HEIGHT, 99999);       startActivityForResult(scanIntent, REQUEST_SCAN_QRCODE);   }
  • onActivityResult获取扫描结果
case REQUEST_SCAN_QRCODE:               if (resultCode == RESULT_OK){                   Bundle bundle = data.getExtras();                   String resultStr = bundle.getString(Intents.Scan.RESULT);                   if (resultStr != null){                       Toast.makeText(this, resultStr, Toast.LENGTH_SHORT).show();                   }               }               break;

最后需要在Manifest.xml中添加权限等。
现在二维码的扫描功能就基本完成了,但是依然会存在部分问题。例如:识别率敏感度不够,扫描界面不满足需求等。若要进行修改,还是要对官方Demo进行分析。下面分析官方Demo的工作流程。

工作流程分析

如果不关心工作流程可以直接飞至提高识别 机票~~~~
首先不考虑相机具体属性的参数设置。整个扫描过程分为三步:相机画面预览→捕获相机预览帧数据→处理并返回帧数据

相机画面预览

自定义相机通常使用SurfaceView对相机Camera捕获的数据进行展示。

回想一下,在自定义相机时,如何将SurfaceViewCamera关联呢?

  1. 获取SurfaceView实例的SurfaceHolder对象
mSurfaceHolder = getHolder();
  1. SurfaceHolder中添加回调SurfaceHolder.Callback
mSurfaceHolder.addCallback(this);
  1. 在回调的SurfaceHolder.Callback#surfaceCreated方法中通过Camera#setPreviewDisplay指定相机Camera的数据处理对象。
mCamera.setPreviewDisplay(holder);
  1. 最后调用相机进行预览
mCamera.startPreview();

为了便于理解,如下是对SurfaceViewSurfaceSurfaceHolder知识的补充:
SurfaceView嵌入到Window的View结构树中就好像在Window的Surface上强行打了个洞让自己显示到屏幕上,而且SurfaceView另起一个线程对自己的Surface进行刷新。特别需要注意的是SurfaceHolder.Callback的所有回调方法都是在主线程中回调的

  • SurfaceView是拥有独立绘图层的特殊View
  • Surface就是指SurfaceView所拥有的那个绘图层,其实它就是内存中的一段绘图缓冲区。
  • SurfaceView中具有两个Surface,也就是我们所说的双缓冲机制
  • SurfaceHolder顾名思义就是Surface的持有者,SurfaceView就是通过过SurfaceHolder来对Surface进行管理控制的。并且SurfaceView.getHolder方法可以获取SurfaceView相应的SurfaceHolder。
  • Surface是在SurfaceView所在的Window可见的时候创建的。我们可以使用SurfaceHolder.addCallback方法来监听Surface的创建与销毁的事件

作者:Holmofy
原文:https://blog.csdn.net/Holmofy/article/details/66578852

帧数据捕获

相机预览与用户界面完成后,当然就是帧数据捕获。

那么如何捕获相机的帧数据呢?
Camera提供了setPreviewCallback等方法,当传入一个Camera.PreviewCallback实例后,当Camera产生帧时,Camera.PreviewCallback#onPreviewFrame(byte[] data, Camera camera)的方法将被调用。这里的data就是我们需要的数据

帧数据的处理

通过zxingcore包提供的API便可以对上一步中的data进行解析。

PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source))MultiFormatReader multiFormatReader = new MultiFormatReader();Result rawResult = multiFormatReader.decodeWithState(bitmap);//获取结果:rawResult.getText() || rawResult.getRawBytes()

处理帧数据可能是一个比较耗时的过程,onPreviewFrame在执行Camera.open()时所在的线程运行。所以,可以选择在新线程中开启Camera.opne或将data放入新线程中解析,避免阻塞UI线程。

官方Demo分析

知道了大致的扫描流程,现在开始分析官方Demo,以便可以根据需求进行改动。

CaptureActivity

CaptureActivity中有两个比较重要的ViewSurfaceViewViewfinderView

SurfaceView:负责显示Camera捕获到的内容;
ViewfinderView:显示在界面上添加的动画或其他UI元素,例如:扫描线条、中间内容框等。可根据需要对其中的视图进行修改;

CaptureActivity启动后在onResume中执行

surfaceHolder.addCallback(this);

然后,SurfaceHolder.Callback#surfaceCreated中执行initCamera方法:

private void initCamera(SurfaceHolder surfaceHolder){......//从CameraConfigurationManager中读取配置数据,启动相机扫描           cameraManager.openDriver(surfaceHolder);           // Creating the handler starts the preview, which can also throw a RuntimeException.           if (handler == null) {               handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);           }           ...... }

CameraManager

进入CameraManager#openDriver 方法,以下是CameraManager#openDriver的部分代码:

//启动相机 theCamera = OpenCameraInterface.open(requestedCameraId);//初始化相机参数configManager.initFromCameraParameters(theCamera);//设置相机配置参数configManager.setDesiredCameraParameters(theCamera, false);//Camera#setPreviewDisplay将SurfaceView与Camera关联//SurfaceHolder surfaceHolder = surfaceView.getHolder();cameraObject.setPreviewDisplay(holder);

CaptureActivityHandler

再分析CaptureActivityHandler

 CaptureActivityHandler(CaptureActivity activity,                        Collection<BarcodeFormat> decodeFormats,                        Map<DecodeHintType,?> baseHints,                        String characterSet,                        CameraManager cameraManager) {   this.activity = activity;   decodeThread = new DecodeThread(activity, decodeFormats, baseHints, characterSet,       new ViewfinderResultPointCallback(activity.getViewfinderView()));   decodeThread.start();   state = State.SUCCESS;   // Start ourselves capturing previews and decoding.   this.cameraManager = cameraManager;   cameraManager.startPreview();   restartPreviewAndDecode(); }

在建立CaptureActivityHandler对象时,创建并启动了新线程decodeThread(前面我们讲过解析数据的过程是比较耗时的,上述逻辑表明相机的启动其实时在主线程中完成的,所以这里使用新线程用来处理帧数据,后面会进一步解读到这点),然后restartPreviewAndDecode(),至此已经完成了扫描的第一步-----相机画面预览
继续分析restartPreviewAndDecode():

//:CaptureActivityHandler.java private void restartPreviewAndDecode() {   if (state == State.SUCCESS) {     state = State.PREVIEW;     cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);     activity.drawViewfinder();   } }
//:CameraManager.java   private final PreviewCallback previewCallback;   public synchronized void requestPreviewFrame(Handler handler, int >message) {       OpenCamera theCamera = camera;       if (theCamera != null && previewing) {           previewCallback.setHandler(handler, message);           //设置Camera.PreviewCallback回调           theCamera.getCamera().setOneShotPreviewCallback(previewCallback);       }   }

setOneShotPreviewCallback被调用,所以相机的帧数据将在previewCallback实例中被处理,继续看previewCallback

//:PreviewCallback.java void setHandler(Handler previewHandler, int previewMessage) {   this.previewHandler = previewHandler;   this.previewMessage = previewMessage; } @Override public void onPreviewFrame(byte[] data, Camera camera) {   Point cameraResolution = configManager.getCameraResolution();   Handler thePreviewHandler = previewHandler;   if (cameraResolution != null && thePreviewHandler != null) {     Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,         cameraResolution.y, data);     message.sendToTarget();     previewHandler = null;   } else {     Log.d(TAG, "Got preview callback, but no handler or resolution available");   } }

这里的thePreviewHandler就是DecodeHandler,而previewMessage即为R.id.decode

DecodeHandler

DecodeHandler接受到消息后,会怎么处理呢?

    @Override    public void handleMessage(Message message) {        if (message == null || !running) {            return;        }        switch (message.what) {            case R.id.decode:                Log.d(TAG, "handleMessage: ");                decode((byte[]) message.obj, message.arg1, message.arg2);                break;            case R.id.quit:                running = false;                Looper.myLooper().quit();                break;        }    }

再看DecodeHandler#decode可以看出,实际就是使用core包中提供的API解析数据:

PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source))MultiFormatReader multiFormatReader = new MultiFormatReader();Result rawResult = multiFormatReader.decodeWithState(bitmap);

并将解析结果交给主线程:

//获取主线程的CaptureActivity.java中的handler        Handler handler = activity.getHandler();            if (handler != null) {                Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);                Bundle bundle = new Bundle();                bundleThumbnail(source, bundle);                message.setData(bundle);                message.sendToTarget();            }        

CaptureActivityHandler

接着分析CaptureActivityHandler得到解析数据后又将进行如何处理:
首先,handleDecode(Result rawResult, Bitmap barcode, float scaleFactor)被调用,在handleDecode中,根据source()不同执行handleDecodeExternallyhandleDecodeInternally,这只分析handleDecodeExternally
handlerDecodeExternally中又将调用sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS);

//:CaptureActivity.java    private void sendReplyMessage(int id, Object arg, long delayMS) {        Log.d(TAG, "sendReplyMessage: ");        if (handler != null) {            Message message = Message.obtain(handler, id, arg);            if (delayMS > 0L) {                handler.sendMessageDelayed(message, delayMS);            } else {                handler.sendMessage(message);            }        }    }//:CaptureActivityHandler.java      case R.id.return_scan_result:        activity.setResult(Activity.RESULT_OK, (Intent) message.obj);        activity.finish();        break;    

至此,CaptureActivityHandler.java关闭,并将结果返回。

提高识别

Zxing默认的是横屏扫码,多数情况下需要改为竖屏扫描。

  1. CaptureActivity的配置,将Activity竖屏显示:
android:screenOrientation="portrait"
  1. CameraManager类中的getFramingRectInPreview()方法,将leftrighttopbottom改变,供第4步的buildLuminanceSource内部计算使用。
//竖屏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;
  1. CameraConfigurationManager类中的setDesiredCameraParameters(OpenCamera camera, boolean safeMode)方法,在setParameters之前添加,设置PreviewDisplay的方向,使SurfaceView画面方向为竖直方向。
theCamera.setDisplayOrientation(90);
  1. DecodeHandler类中的decode(byte[] data, int width, int height)方法,在PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height)之前添加,将相机数据矩阵旋转90度。
byte[] rotatedData = new byte[data.length];for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++)   rotatedData[x * height + height - y - 1] = data[x + y * width];}PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(rotatedData, height, width);//PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);

此时,竖屏扫描已经可以实现了,但是扫描复杂的图码时,分辨率低的已经分不清纹理了,很难识别出来,所以需要优化识别率。

识别率优化

CameraConfigurationUtils类中的findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution)方法,将double screenAspectRatio = screenResolution.x / (double) screenResolution.y改为:

double screenAspectRatio;if (screenResolution.x > screenResolution.y) {   screenAspectRatio = (double) screenResolution.x / (double) screenResolution.y;} else {   screenAspectRatio = (double) screenResolution.y / (double) screenResolution.x;}

至于相机的参数设置、扫码的音效及震动提示、用户偏好等,读者可自己分析,本文就不再进行详细分析了。
另外,需要注意的是相机有相机的自己的分辨率,通常指的是它的「像素规模」,即它能拍出含有多少个像素的照片,屏幕也有屏幕本身的分辨率。

参考

Android zxing
zxing/zxing
zxing私人定制之一
Android Zxing 转换竖屏扫描且提高识别率
Android相机开发系列

更多相关文章

  1. android 10.0版本合入GMS包
  2. 指尖上的Android之实战篇(七)
  3. android 父类中添加Button问题
  4. Android(安卓)2.3状态栏中添加menu home back快捷键
  5. [转]创建不可见的Activity
  6. Android(安卓)studio安装与配置Butter knife过程
  7. java.io.IOException: Cleartext HTTP traffic to xxx.xxx.xxx.x
  8. Android(安卓)MediaPlayer源码分析总结
  9. Android(安卓)报错:Conversion to Dalvik format failed: ...

随机推荐

  1. AndroidStudio安装SDKComponentSetup无法
  2. Android 中的网络操作(HttpURLConnection)
  3. Android用Websocket实现聊天室
  4. cm-14.1 Android系统启动过程分析(四)-Laun
  5. Android中Gravity中的一些值都是些什么意
  6. Android之ButterKnife用法详解
  7. 【Android】 Painless Thread
  8. 安卓手机恶意代码——Samsapo
  9. 《第一行代码--Android》读书笔记之内容
  10. Android MediaPlayer音频播放器详解