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

目前android中實(shí)現(xiàn)掃二維碼大多數(shù)使用的是zxing這個(gè)開源框架虏杰,要使用android的核心源碼讥蟆,因?yàn)槲覀冃枰谠创a中做修改,將框架添加到項(xiàng)目中纺阔,這里就不多說了瘸彤,網(wǎng)上都有,這里只說一下放大攝像頭部分笛钝。涉及到的文件主要有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ī)來調(diào)整
2暴构、攝像頭焦距的放大

一跪呈、獲取二維碼在掃描框中的寬度
要獲取二維碼在掃描框中的寬度,首先要對(duì)QR碼相關(guān)部分了解丹壕,具體可以參考這篇博客http://blog.csdn.net/mihenyinghua/article/details/17224019庆械,我也是讀了這篇博客后知道從哪一塊進(jìn)行著手薇溃,而不是盲目去看源碼菌赖,畢竟zxing這個(gè)庫的源碼還是不少的,而且我們只關(guān)心二維碼這一塊沐序,所以沒必要所有源碼都去閱讀琉用。
從這篇博客中了解了從掃描到得到二維碼中的信息分三步堕绩,具體的大家去閱讀博客就行了:
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來獲取掃描框?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è)通過Result的getResultPoints()來獲取凿叠,得到的是一個(gè)ResultPoint數(shù)組,通過調(diào)試嚼吞,結(jié)合安卓中的坐標(biāo)系得知的結(jié)果是resultPoints[0],resultPoints[1]分別對(duì)應(yīng)的是下圖中的左下角--左上角的點(diǎn)盒件,有這兩個(gè)點(diǎn)就足夠了,然后通過兩點(diǎn)間的距離公式來獲得大致尺寸舱禽。

這里寫圖片描述

現(xiàn)在來獲取掃描框中的二維碼大谐吹蟆:
下面是我提取出來相關(guān)的主要文件,一步一步涉及到的文件過程如下

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;
    }

接下來看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;
  }

通過DetectorResult 凸椿,我們可以獲取二維碼的定位符,所以在執(zhí)行解碼前利用定位符來獲取在掃描框中的二維碼大小翅溺,和掃描框進(jìn)行大小對(duì)比來判斷是否需要獲取放大攝像頭脑漫。
處理結(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物舒,就放大鏡頭色洞,通過handler告知此次掃描失敗重新掃描,否則不用

二冠胯、放大攝像頭火诸,調(diào)整焦距
Camera的當(dāng)前設(shè)置信息在Parameters可以獲取,通過getParameters()獲取Parameters荠察。
放大攝像頭有個(gè)前提條件就是你的手機(jī)要支持?jǐn)z像頭焦距的放大和縮小置蜀,不過目前大多數(shù)手機(jī)都支持了,我們還是判斷一下為好悉盆。parameters.isZoomSupported()盯荤,判斷是否支持焦距縮放。支持焕盟,然后設(shè)置需要放大多少秋秤,通過parameters.setZoom(int value)來設(shè)置,這個(gè)值有個(gè)限制, The valid range is 0 to {@link #getMaxZoom}.
也就是最大能設(shè)置到maxzoom航缀,這個(gè)最大值通過getMaxZoom()來獲取。設(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)系作者
  • 序言:七十年代末盾似,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子雪标,更是在濱河造成了極大的恐慌零院,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件村刨,死亡現(xiàn)場(chǎng)離奇詭異告抄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嵌牺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門打洼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逆粹,你說我怎么就攤上這事募疮。” “怎么了僻弹?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵酝锅,是天一觀的道長。 經(jīng)常有香客問我奢方,道長搔扁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任蟋字,我火速辦了婚禮稿蹲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鹊奖。我一直安慰自己苛聘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著设哗,像睡著了一般唱捣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上网梢,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天震缭,我揣著相機(jī)與錄音,去河邊找鬼战虏。 笑死拣宰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烦感。 我是一名探鬼主播巡社,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼手趣!你這毒婦竟也來了晌该?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤绿渣,失蹤者是張志新(化名)和其女友劉穎气笙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怯晕,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡潜圃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了舟茶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谭期。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吧凉,靈堂內(nèi)的尸體忽然破棺而出隧出,到底是詐尸還是另有隱情,我是刑警寧澤阀捅,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布胀瞪,位于F島的核電站,受9級(jí)特大地震影響饲鄙,放射性物質(zhì)發(fā)生泄漏凄诞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一忍级、第九天 我趴在偏房一處隱蔽的房頂上張望帆谍。 院中可真熱鬧,春花似錦轴咱、人聲如沸汛蝙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窖剑。三九已至坚洽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間西土,已是汗流浹背讶舰。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翠储,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓橡疼,卻偏偏與公主長得像援所,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子欣除,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容