結合ZXing實現類似微信掃二維碼放大攝像頭

目前android中實現掃二維碼大多數使用的是zxing這個開源框架,要使用android的核心源碼,因為我們需要在源碼中做修改,將框架添加到項目中,這里就不多說了,網上都有,這里只說一下放大攝像頭部分。涉及到的文件主要有DecodeHandler,MultiFormatReader,QRCodeReader。

實際應用中,我們都知道鏡頭離二維碼太遠或者太近都影響識別,二維碼恰好處于掃描框中最好。
思路:
1、當要掃的二維碼處于掃描框中時,獲取該二維碼在掃描框中的寬度,與掃描框的寬度進行對比,小于掃描框寬度的1/4,則認為二維碼在掃描框中較?。ㄧR頭較遠),則需要放大攝像頭焦距,而不需要移動手機來調整
2、攝像頭焦距的放大

一、獲取二維碼在掃描框中的寬度
要獲取二維碼在掃描框中的寬度,首先要對QR碼相關部分了解,具體可以參考這篇博客http://blog.csdn.net/mihenyinghua/article/details/17224019,我也是讀了這篇博客后知道從哪一塊進行著手,而不是盲目去看源碼,畢竟zxing這個庫的源碼還是不少的,而且我們只關心二維碼這一塊,所以沒必要所有源碼都去閱讀。
從這篇博客中了解了從掃描到得到二維碼中的信息分三步,具體的大家去閱讀博客就行了:
1)、將圖像進行二值化處理,1、0代表黑、白。
2)、尋找定位符、校正符,然后將原圖像中符號碼部分取出。(detector代碼實現的功能)
3)、對符號碼矩陣按照編碼規(guī)范進行解碼,得到實際信息(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]分別對應的是下圖中的左下角--左上角的點,有這兩個點就足夠了,然后通過兩點間的距離公式來獲得大致尺寸。

這里寫圖片描述

現在來獲取掃描框中的二維碼大?。?br> 下面是我提取出來相關的主要文件,一步一步涉及到的文件過程如下

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、對符號碼矩陣按照編碼規(guī)范進行解碼,得到實際信息(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 ,我們可以獲取二維碼的定位符,所以在執(zhí)行解碼前利用定位符來獲取在掃描框中的二維碼大小,和掃描框進行大小對比來判斷是否需要獲取放大攝像頭。
處理結果如下

@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、對符號碼矩陣按照編碼規(guī)范進行解碼,得到實際信息(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中下載

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容