微信二維碼引擎OpenCV開源研究


《微信二維碼引擎OpenCV開源研究》
一、編譯和Test測試
        opencv_wechat_qrcode的編譯需要同時下載opencv(https://github.com/opencv/opencv)和opencv_contrib(https://github.com/opencv/opencv_contrib),如果需要正常運行Test,還要下載opencv_extra(https://github.com/opencv/opencv_extra)。
       Windows環境下,使用Cmake進行編譯,總的來說是“兩次Configue一次Generate",這個過程中,由於網絡和基礎環境原因,可能出現各種問題,需要根據實際情況解決,其中一個 必須解決的一個問題是需要自己下載模型文件,改名后拷貝到指定目錄下來。
在cmake的過程中,可以關閉不需要生產的模塊。
       
打開VisualStudio,選擇”批生成Install" ,確保編譯過程中不出現錯誤。
       如果上面都順利,那么我們能夠在Cmake中“where to build the binaries"目錄下得到新建的 Install目錄。
       進一步,將 opencv_extra解壓出來的testdata目錄防止install下,則可以開啟Test測試。
這些圖片還是非常有代表性的,具體位置:testdata\cv\qrcode
進入VisualStudio,找到opencv_test_wechat_qrcode,右擊設置為啟動,如果看到全綠回顯,證明前面配置全部正確。
這樣,我們就可以在opencv_wechat_qrcode中設置斷點,逐句解析其實現。
二、代碼理解
在Opencv_wechat_qrcode中,wechat_qrcode.cpp是主要文件,其他的是配合文件。
vector < float > WeChatQRCode : :Impl : :getScaleList( const  int width,  const  int height) {
     if (width  <  320  || height  <  320return { 1. 02. 00. 5};
     if (width  <  640  && height  <  640return { 1. 00. 5};
     return { 0. 51. 0};
}
根據分辨率獲得縮放的可能選項。
Mat SuperScale : :processImageScale( const Mat  &src,  float scale,  const  bool  &use_sr,
                                   int sr_max_size) {
    Mat dst  = src;
     if (scale  ==  1. 0) {   // src
         return dst;
    }
     int width  = src.cols;
     int height  = src.rows;
     if (scale  ==  2. 0) {   // upsample
         int SR_TH  = sr_max_size;
         if (use_sr  && ( int)sqrt(width  * height  *  1. 0< SR_TH  && net_loaded_) {
             int ret  = superResoutionScale(src, dst);
             if (ret  ==  0return dst;
        }
        { resize(src, dst, Size(), scale, scale, INTER_CUBIC); }
    }  else  if (scale  <  1. 0) {   // downsample
        resize(src, dst, Size(), scale, scale, INTER_AREA);
    }
     return dst;
}
具體調用方法是使用dnn的方法
int SuperScale : :superResoutionScale( const Mat  &src, Mat  &dst) {
    Mat blob;
    dnn : :blobFromImage(src, blob,  1. 0  /  255, Size(src.cols, src.rows), { 0.0f},  falsefalse);
    srnet_.setInput(blob);
     auto prob  = srnet_.forward();
    dst  = Mat(prob.size[ 2], prob.size[ 3], CV_8UC1);
     for ( int row  =  0; row  < prob.size[ 2]; row ++) {
         const  float  *prob_score  = prob.ptr < float >( 00, row);
         for ( int col  =  0; col  < prob.size[ 3]; col ++) {
             float pixel  = prob_score[col]  *  255. 0;
            dst.at <uint8_t >(row, col)  =  static_cast <uint8_t >(CLIP(pixel,  0.0f,  255.0f));
        }
    }
     return  0;
}
這里的 srnet_就是特定的網絡。
int DecoderMgr : :decodeImage(cv : :Mat src,  bool use_nn_detector, string & result) {
     int width  = src.cols;
     int height  = src.rows;
     if (width  < =  20  || height  < =  20)
         return  - 1;   // image data is not enough for providing reliable results
    std : :vector <uint8_t > scaled_img_data(src.data, src.data  + width  * height);
    zxing : :ArrayRef <uint8_t > scaled_img_zx  =
        zxing : :ArrayRef <uint8_t >( new zxing : :Array <uint8_t >(scaled_img_data));
    zxing : :Ref <zxing : :Result > zx_result      ;
    decode_hints_.setUseNNDetector(use_nn_detector);
    Ref <ImgSource > source;
    qbarUicomBlock_  =  new UnicomBlock(width, height);
     // Four Binarizers
     int tryBinarizeTime  =  4;
     for ( int tb  =  0; tb  < tryBinarizeTime; tb ++) {
         if (source  == NULL  || height  * width  > source - >getMaxSize()) {
            source  = ImgSource : :create(scaled_img_zx.data(), width, height);
        }  else {
            source - >reset(scaled_img_zx.data(), width, height);
        }
         int ret  = TryDecode(source, zx_result);
         if ( !ret) {
            result  = zx_result - >getText() - >getText();
             return ret;
        }
         // try different binarizers
        binarizer_mgr_.SwitchBinarizer();
    }
     return  - 1;
}

相比較直接使用ZXing來解碼,這里做了很多的前置算法操作.目前能夠看懂的部分就是 tryBinarizeTime=4,這里進行了4次運算。每一次都是trydecode,這種模式是可以借鑒的。
int DecoderMgr : :TryDecode(Ref <LuminanceSource > source, Ref <Result > & result) {
     int res  =  - 1;
    string cell_result;
     // get binarizer
    zxing : :Ref <zxing : :Binarizer > binarizer  = binarizer_mgr_.Binarize(source);
    zxing : :Ref <zxing : :BinaryBitmap > binary_bitmap( new BinaryBitmap(binarizer));
    binary_bitmap - >m_poUnicomBlock  = qbarUicomBlock_;
    result  = Decode(binary_bitmap, decode_hints_);
    res  = (result  == NULL)  ?  1  :  0;
     if (res  ==  0) {
        result - >setBinaryMethod( int(binarizer_mgr_.GetCurBinarizer()));
    }
     return res;
}

TryDecode這個就是具體的解碼操作,具體就是調用

 zxing : :Ref <zxing : :qrcode : :QRCodeReader > reader_;
從Test結果來看,如果不適用DNN,是33個通過;如果使用DNN是30個通過。這里的差異的原因是什么?那么使用DNN、訓練這些模型的價值體現在哪里?
[  PASSED  ] 30 tests.
[  FAILED  ] 3 tests, listed below :
[  FAILED  ] Objdetect_QRCode.regression / 0, where GetParam() = "version_1_down.jpg"
[  FAILED  ] Objdetect_QRCode.regression / 10, where GetParam() = "version_4_left.jpg"
[  FAILED  ] Objdetect_QRCode.regression / 19, where GetParam() = "link_ocv.jpg"
三、借鑒使用
1、規范的語法和構建
我也在向OpenCV提交代碼,我需要從這個例子中學到業務方面的操作。
2、CNN方法和傳統方法無縫連接
    p  = makePtr <WeChatQRCode : :Impl >();
     if ( !detector_caffe_model_path.empty()  &&  !detector_prototxt_path.empty()) {
         // initialize detector model (caffe)
        p - >use_nn_detector_  =  true;
        CV_Assert(utils : :fs : :exists(detector_prototxt_path));
        CV_Assert(utils : :fs : :exists(detector_caffe_model_path));
        p - >detector_  = make_shared <SSDDetector >();
         auto ret  = p - >detector_ - >init(detector_prototxt_path, detector_caffe_model_path);
        CV_Assert(ret  ==  0);
    }  else {
        p - >use_nn_detector_  =  false;
        p - >detector_  = NULL;
    }
當CNN無法正確調用的時候,幾個圖片都是一致的,采用傳統方法進行處理:
3、最后我想說值得學習的還有這個思想
傳統已經無法實現很好解決的問題,我們通過訓練模型方式來進行解決。OpenCV用於實際的解碼,就部署來說是非常方便的。這是一種絕佳的配合。二維碼的掃描具有很強的專用性,只有微信、支付寶一類的平台軟件才會具備,當然我們自己也可以寫來測試一下。因此這里將其開源出來,是非常聰明的選擇。







免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM