《微信二維碼引擎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
<
320)
return {
1.
0,
2.
0,
0.
5};
if (width
<
640
&& height
<
640)
return {
1.
0,
0.
5};
return {
0.
5,
1.
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
==
0)
return 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},
false,
false);
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
>(
0,
0, 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用於實際的解碼,就部署來說是非常方便的。這是一種絕佳的配合。二維碼的掃描具有很強的專用性,只有微信、支付寶一類的平台軟件才會具備,當然我們自己也可以寫來測試一下。因此這里將其開源出來,是非常聰明的選擇。