結(jié)合ZXing實(shí)現(xiàn)類似微信掃二維碼放大攝像頭

目前android中實(shí)現(xiàn)掃二維碼大多數(shù)使用的是zxing這個(gè)開(kāi)源框架,要使用android的核心源碼,因?yàn)槲覀冃枰谠创a中做修改,將框架添加到項(xiàng)目中,這里就不多說(shuō)了,網(wǎng)上都有,這里只說(shuō)一下放大攝像頭部分。涉及到的文件主要有DecodeHandler,MultiFormatReader,QRCodeReader。

實(shí)際應(yīng)用中,我們都知道鏡頭離二維碼太遠(yuǎn)或者太近都影響識(shí)別,二維碼恰好處于掃描框中最好。
思路:
1、當(dāng)要掃的二維碼處于掃描框中時(shí),獲取該二維碼在掃描框中的寬度,與掃描框的寬度進(jìn)行對(duì)比,小于掃描框?qū)挾鹊?/4,則認(rèn)為二維碼在掃描框中較小(鏡頭較遠(yuǎn)),則需要放大攝像頭焦距,而不需要移動(dòng)手機(jī)來(lái)調(diào)整
2、攝像頭焦距的放大

一、獲取二維碼在掃描框中的寬度
要獲取二維碼在掃描框中的寬度,首先要對(duì)QR碼相關(guān)部分了解,具體可以參考這篇博客http://blog.csdn.net/mihenyinghua/article/details/17224019,我也是讀了這篇博客后知道從哪一塊進(jìn)行著手,而不是盲目去看源碼,畢竟zxing這個(gè)庫(kù)的源碼還是不少的,而且我們只關(guān)心二維碼這一塊,所以沒(méi)必要所有源碼都去閱讀。
從這篇博客中了解了從掃描到得到二維碼中的信息分三步,具體的大家去閱讀博客就行了:
1)、將圖像進(jìn)行二值化處理,1、0代表黑、白。
2)、尋找定位符、校正符,然后將原圖像中符號(hào)碼部分取出。(detector代碼實(shí)現(xiàn)的功能)
3)、對(duì)符號(hào)碼矩陣按照編碼規(guī)范進(jìn)行解碼,得到實(shí)際信息(decoder代碼實(shí)現(xiàn)的功能)

首先是掃描框的尺寸,在CamerManager中getFramingRect()方法是獲取掃描框的矩形尺寸,frameRect.right-frameRect.left來(lái)獲取掃描框?qū)挾取?/p>

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

最終的結(jié)果在DecodeHandler文件中,decode(byte[] data, int width, int height),是對(duì)掃描結(jié)果的解析處理。先看一下源碼:

/**
 * 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 就是結(jié)果,我們需要從中獲取一些信息。要獲取二維碼的尺寸,只需要獲取兩個(gè)定位的點(diǎn)就行了,這個(gè)通過(guò)Result的getResultPoints()來(lái)獲取,得到的是一個(gè)ResultPoint數(shù)組,通過(guò)調(diào)試,結(jié)合安卓中的坐標(biāo)系得知的結(jié)果是resultPoints[0],resultPoints[1]分別對(duì)應(yīng)的是下圖中的左下角--左上角的點(diǎn),有這兩個(gè)點(diǎn)就足夠了,然后通過(guò)兩點(diǎn)間的距離公式來(lái)獲得大致尺寸。

這里寫(xiě)圖片描述

現(xiàn)在來(lái)獲取掃描框中的二維碼大?。?br> 下面是我提取出來(lái)相關(guān)的主要文件,一步一步涉及到的文件過(guò)程如下

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){

}

我們先添加一個(gè)QRCodeReader的構(gòu)造方法,因?yàn)閿z像頭的相關(guān)配置以及掃描框相關(guān)信息在CaptureActivity中,所以把CaptureActivity放到QRCodeReader的構(gòu)造方法中去

private CaptureActivity activity;

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

接著是MultiFormatReader,

private CaptureActivity activity;
public void setActivity(CaptureActivity activity) {
    this.activity = activity;
  }
  //這個(gè)方法中有關(guān)QRCodeReader的調(diào)用改成調(diào)用有參的構(gòu)造方法
 setHints(Map<DecodeHintType,?> hints){
     if (formats.contains(BarcodeFormat.QR_CODE)) {
            readers.add(new QRCodeReader(activity));
     }
     //..... 接著是
     if (readers.isEmpty()){
         readers.add(new QRCodeReader(activity));
     }
}

然后在DecodeHandler的構(gòu)造方法中對(duì)MultiFormatReader的調(diào)用setHints之前先調(diào)用setActivity()保證CaptureActivity不為空

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

接下來(lái)看QRCodeReader中的這個(gè)方法,我又加了三個(gè)關(guān)鍵注釋

@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、將圖像進(jìn)行二值化處理,1、0代表黑、白。( 二維碼的使用getBlackMatrix方法 )
      //2、尋找定位符、校正符,然后將原圖像中符號(hào)碼部分取出。(detector代碼實(shí)現(xiàn)的功能)
      DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
      //3、對(duì)符號(hào)碼矩陣按照編碼規(guī)范進(jìn)行解碼,得到實(shí)際信息(decoder代碼實(shí)現(xiàn)的功能)
      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;
  }

通過(guò)DetectorResult ,我們可以獲取二維碼的定位符,所以在執(zhí)行解碼前利用定位符來(lái)獲取在掃描框中的二維碼大小,和掃描框進(jìn)行大小對(duì)比來(lái)判斷是否需要獲取放大攝像頭。
處理結(jié)果如下

@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、將圖像進(jìn)行二值化處理,1、0代表黑、白。( 二維碼的使用getBlackMatrix方法 )
      //2、尋找定位符、校正符,然后將原圖像中符號(hào)碼部分取出。(detector代碼實(shí)現(xiàn)的功能)
      DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
      if(activity!=null){
        CameraManager cameraManager = activity.cameraManager;
        ResultPoint[] p = detectorResult.getPoints();
        //計(jì)算掃描框中的二維碼的寬度,兩點(diǎn)間距離公式
        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、對(duì)符號(hào)碼矩陣按照編碼規(guī)范進(jìn)行解碼,得到實(shí)際信息(decoder代碼實(shí)現(xiàn)的功能)
      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;
  }
//計(jì)算掃描框中的二維碼的寬度,兩點(diǎn)間距離公式
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));

有了掃描框的寬度和二維碼的尺寸,就可以判斷二維碼在掃描框中是不是太小。
小于掃描框?qū)挾鹊?/4,就放大鏡頭,通過(guò)handler告知此次掃描失敗重新掃描,否則不用

二、放大攝像頭,調(diào)整焦距
Camera的當(dāng)前設(shè)置信息在Parameters可以獲取,通過(guò)getParameters()獲取Parameters。
放大攝像頭有個(gè)前提條件就是你的手機(jī)要支持?jǐn)z像頭焦距的放大和縮小,不過(guò)目前大多數(shù)手機(jī)都支持了,我們還是判斷一下為好。parameters.isZoomSupported(),判斷是否支持焦距縮放。支持,然后設(shè)置需要放大多少,通過(guò)parameters.setZoom(int value)來(lái)設(shè)置,這個(gè)值有個(gè)限制, The valid range is 0 to {@link #getMaxZoom}.
也就是最大能設(shè)置到maxzoom,這個(gè)最大值通過(guò)getMaxZoom()來(lái)獲取。設(shè)置完成后,然后再調(diào)用camera.setParameters(parameters);讓設(shè)置生效。另外,我加了一個(gè)判斷,當(dāng)掃描的是二維碼才進(jìn)行焦距的縮放rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE
不需要的可以不用加。

最后運(yùn)行的時(shí)候會(huì)有個(gè)QRCodeMultiReader報(bào)的錯(cuò)誤,因?yàn)樗^承了QRCodeReader,而我們添加了一個(gè)有參的構(gòu)造函數(shù),所以按照提示把它的構(gòu)造也添加上就行了

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

運(yùn)行后就可以實(shí)現(xiàn)類似微信的那種效果了。

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容