结合ZXing实现类似微信扫二维码放大摄像头

目前android中实现扫二维码大多数使用的是zxing这个开源框架,要使用android的核心源码,因为我们需要在源码中做修改,将框架添加到项目中,这里就不多说了,网上都有,这里只说一下放大摄像头部分。涉及到的文件主要有DecodeHandler,MultiFormatReader,QRCodeReader。

实际应用中,我们都知道镜头离二维码太远或者太近都影响识别,二维码恰好处于扫描框中最好。
思路:
1、当要扫的二维码处于扫描框中时,获取该二维码在扫描框中的宽度,与扫描框的宽度进行对比,小于扫描框宽度的1/4,则认为二维码在扫描框中较小(镜头较远),则需要放大摄像头焦距,而不需要移动手机来调整
2、摄像头焦距的放大

一、获取二维码在扫描框中的宽度
要获取二维码在扫描框中的宽度,首先要对QR码相关部分了解,具体可以参考这篇博客http://blog.csdn.net/mihenyinghua/article/details/17224019,我也是读了这篇博客后知道从哪一块进行着手,而不是盲目去看源码,毕竟zxing这个库的源码还是不少的,而且我们只关心二维码这一块,所以没必要所有源码都去阅读。
从这篇博客中了解了从扫描到得到二维码中的信息分三步,具体的大家去阅读博客就行了:
1)、将图像进行二值化处理,1、0代表黑、白。
2)、寻找定位符、校正符,然后将原图像中符号码部分取出。(detector代码实现的功能)
3)、对符号码矩阵按照编码规范进行解码,得到实际信息(decoder代码实现的功能)

首先是扫描框的尺寸,在CamerManager中getFramingRect()方法是获取扫描框的矩形尺寸,frameRect.right-frameRect.left来获取扫描框宽度。

Rect frameRect = activity.cameraManager.getFramingRect();
if(frameRect!=null){
  int frameWidth = frameRect.right-frameRect.left;
}

最终的结果在DecodeHandler文件中,decode(byte[] data, int width, int height),是对扫描结果的解析处理。先看一下源码:

/**
 * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
 * reuse the same reader objects from one decode to the next.
 *
 * @param data   The YUV preview frame.
 * @param width  The width of the preview frame.
 * @param height The height of the preview frame.
 */
private void decode(byte[] data, int width, int height) {
  long start = System.currentTimeMillis();
  Result rawResult = null;
  PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
  if (source != null) {
    BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
    try {
      rawResult = multiFormatReader.decodeWithState(bitmap);
    } catch (ReaderException re) {
      // continue
    } finally {
      multiFormatReader.reset();
    }
  }

  Handler handler = activity.getHandler();
  if (rawResult != null) {
    // Don't log the barcode contents for security.
    long end = System.currentTimeMillis();
    Log.d(TAG, "Found barcode in " + (end - start) + " ms");
    if (handler != null) {
      Message message = Message.obtain(handler, Ids.decode_succeeded, rawResult);
              Bundle bundle = new Bundle();
              bundleThumbnail(source, bundle);
              message.setData(bundle);
              message.sendToTarget();
    }
  } else {
    if (handler != null) {
      Message message = Message.obtain(handler, Ids.decode_failed);
      message.sendToTarget();
    }
  }
}

Result 就是结果,我们需要从中获取一些信息。要获取二维码的尺寸,只需要获取两个定位的点就行了,这个通过Result的getResultPoints()来获取,得到的是一个ResultPoint数组,通过调试,结合安卓中的坐标系得知的结果是resultPoints[0],resultPoints[1]分别对应的是下图中的左下角--左上角的点,有这两个点就足够了,然后通过两点间的距离公式来获得大致尺寸。

这里写图片描述

现在来获取扫描框中的二维码大小:
下面是我提取出来相关的主要文件,一步一步涉及到的文件过程如下

DecodeHandler-->
            -->MultiFormatReader multiFormatReader = new MultiFormatReader();
MultiFormatReader-->
        setHints(){
        //二维码
                if (formats.contains(BarcodeFormat.QR_CODE)) {
                    readers.add(new QRCodeReader());
                }
        }
QRCodeReader-->Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints){

}

我们先添加一个QRCodeReader的构造方法,因为摄像头的相关配置以及扫描框相关信息在CaptureActivity中,所以把CaptureActivity放到QRCodeReader的构造方法中去

private CaptureActivity activity;

  public QRCodeReader(CaptureActivity activity) {
    this.activity = activity;
  }

接着是MultiFormatReader,

private CaptureActivity activity;
public void setActivity(CaptureActivity activity) {
    this.activity = activity;
  }
  //这个方法中有关QRCodeReader的调用改成调用有参的构造方法
 setHints(Map<DecodeHintType,?> hints){
     if (formats.contains(BarcodeFormat.QR_CODE)) {
            readers.add(new QRCodeReader(activity));
     }
     //..... 接着是
     if (readers.isEmpty()){
         readers.add(new QRCodeReader(activity));
     }
}

然后在DecodeHandler的构造方法中对MultiFormatReader的调用setHints之前先调用setActivity()保证CaptureActivity不为空

DecodeHandler(CaptureActivity activity, Map<DecodeHintType, Object> hints) {
        multiFormatReader = new MultiFormatReader();
        multiFormatReader.setActivity(activity);
        multiFormatReader.setHints(hints);
        this.activity = activity;
    }

接下来看QRCodeReader中的这个方法,我又加了三个关键注释

@Override
  public final Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
      throws NotFoundException, ChecksumException, FormatException {
    DecoderResult decoderResult;
    ResultPoint[] points;
    if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
      BitMatrix bits = extractPureBits(image.getBlackMatrix());
      decoderResult = decoder.decode(bits, hints);
      points = NO_POINTS;
    } else {
    //1、将图像进行二值化处理,1、0代表黑、白。( 二维码的使用getBlackMatrix方法 )
      //2、寻找定位符、校正符,然后将原图像中符号码部分取出。(detector代码实现的功能)
      DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
      //3、对符号码矩阵按照编码规范进行解码,得到实际信息(decoder代码实现的功能)
      decoderResult = decoder.decode(detectorResult.getBits(), hints);
      points = detectorResult.getPoints();
    }

    // If the code was mirrored: swap the bottom-left and the top-right points.
    if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
      ((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
    }

    Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
    List<byte[]> byteSegments = decoderResult.getByteSegments();
    if (byteSegments != null) {
      result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
    }
    String ecLevel = decoderResult.getECLevel();
    if (ecLevel != null) {
      result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
    }
    if (decoderResult.hasStructuredAppend()) {
      result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
                         decoderResult.getStructuredAppendSequenceNumber());
      result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
                         decoderResult.getStructuredAppendParity());
    }
    return result;
  }

通过DetectorResult ,我们可以获取二维码的定位符,所以在执行解码前利用定位符来获取在扫描框中的二维码大小,和扫描框进行大小对比来判断是否需要获取放大摄像头。
处理结果如下

@Override
  public final Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
      throws NotFoundException, ChecksumException, FormatException {
    DecoderResult decoderResult;
    ResultPoint[] points;
    if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
      BitMatrix bits = extractPureBits(image.getBlackMatrix());
      decoderResult = decoder.decode(bits, hints);
      points = NO_POINTS;
    } else {
      //1、将图像进行二值化处理,1、0代表黑、白。( 二维码的使用getBlackMatrix方法 )
      //2、寻找定位符、校正符,然后将原图像中符号码部分取出。(detector代码实现的功能)
      DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
      if(activity!=null){
        CameraManager cameraManager = activity.cameraManager;
        ResultPoint[] p = detectorResult.getPoints();
        //计算扫描框中的二维码的宽度,两点间距离公式
        float point1X = p[0].getX();
        float point1Y = p[0].getY();
        float point2X = p[1].getX();
        float point2Y = p[1].getY();
        int len =(int) Math.sqrt(Math.abs(point1X-point2X)*Math.abs(point1X-point2X)+Math.abs(point1Y-point2Y)*Math.abs(point1Y-point2Y));
        Rect frameRect = cameraManager.getFramingRect();
        if(frameRect!=null){
          int frameWidth = frameRect.right-frameRect.left;
          Camera camera = cameraManager.getOpenCamera().getCamera();
          Camera.Parameters parameters = camera.getParameters();
          int maxZoom = parameters.getMaxZoom();
          int zoom = parameters.getZoom();
          if(parameters.isZoomSupported()){
            if(len <= frameWidth/4) {//二维码在扫描框中的宽度小于扫描框的1/4,放大镜头
              if (zoom == 0) {
                zoom = maxZoom / 2;
              } else if (zoom <= maxZoom - 10) {
                zoom = zoom + 10;
              } else {
                zoom = maxZoom;
              }
              parameters.setZoom(zoom);
              camera.setParameters(parameters);
              return null;
            }
          }
        }
      }
      //3、对符号码矩阵按照编码规范进行解码,得到实际信息(decoder代码实现的功能)
      decoderResult = decoder.decode(detectorResult.getBits(), hints);
      points = detectorResult.getPoints();
    }

    // If the code was mirrored: swap the bottom-left and the top-right points.
    if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
      ((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
    }

    Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
    List<byte[]> byteSegments = decoderResult.getByteSegments();
    if (byteSegments != null) {
      result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
    }
    String ecLevel = decoderResult.getECLevel();
    if (ecLevel != null) {
      result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
    }
    if (decoderResult.hasStructuredAppend()) {
      result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
                         decoderResult.getStructuredAppendSequenceNumber());
      result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
                         decoderResult.getStructuredAppendParity());
    }
    return result;
  }
//计算扫描框中的二维码的宽度,两点间距离公式
float point1X = rawResult.getResultPoints()[0].getX();
float point1Y = rawResult.getResultPoints()[0].getY();
float point2X = rawResult.getResultPoints()[1].getX();
float point2Y = rawResult.getResultPoints()[1].getY();
int len =(int) Math.sqrt(Math.abs(point1X-point2X)*Math.abs(point1X-point2X)+Math.abs(point1Y-point2Y)*Math.abs(point1Y-point2Y));

有了扫描框的宽度和二维码的尺寸,就可以判断二维码在扫描框中是不是太小。
小于扫描框宽度的1/4,就放大镜头,通过handler告知此次扫描失败重新扫描,否则不用

二、放大摄像头,调整焦距
Camera的当前设置信息在Parameters可以获取,通过getParameters()获取Parameters。
放大摄像头有个前提条件就是你的手机要支持摄像头焦距的放大和缩小,不过目前大多数手机都支持了,我们还是判断一下为好。parameters.isZoomSupported(),判断是否支持焦距缩放。支持,然后设置需要放大多少,通过parameters.setZoom(int value)来设置,这个值有个限制, The valid range is 0 to {@link #getMaxZoom}.
也就是最大能设置到maxzoom,这个最大值通过getMaxZoom()来获取。设置完成后,然后再调用camera.setParameters(parameters);让设置生效。另外,我加了一个判断,当扫描的是二维码才进行焦距的缩放rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE
不需要的可以不用加。

最后运行的时候会有个QRCodeMultiReader报的错误,因为它继承了QRCodeReader,而我们添加了一个有参的构造函数,所以按照提示把它的构造也添加上就行了

public QRCodeMultiReader(CaptureActivity activity) {
    super(activity);
  }

运行后就可以实现类似微信的那种效果了。

需要demo的,可以到https://github.com/Alvin9234/CommonLibrary中下载

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,820评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,648评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,324评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,714评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,724评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,328评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,897评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,804评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,345评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,431评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,561评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,238评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,928评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,417评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,528评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,983评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,573评论 2 359

推荐阅读更多精彩内容