NDK 開發(fā)實(shí)戰(zhàn) - 微信公眾號二維碼檢測

關(guān)于二維碼識別伤为,我們一般都是用的 Zxing 或者 Zbar 咒循,但它們的識別率其實(shí)不是很高,有些情況下是失靈的绞愚,比如下面這兩張圖:

騰訊 Buggly

同學(xué)給的

使用開源庫 Zxing 掃描以上兩張二維碼叙甸,有一張死活不識別。使用微信是可以的位衩,大家可以用支付寶試試(不行)裆蒸,那碰到這種情況到底該怎么辦呢?哈哈糖驴,這次終于有用武之地了僚祷,我們琢磨著來優(yōu)化一把哪痰。

我們在微信公眾號都用過這么一個(gè)功能,長按一張圖片久妆,如果該圖片包含有二維碼晌杰,會(huì)彈出識別圖中二維碼,如果該圖片不含有二維碼筷弦,則不會(huì)彈出識別二維碼這個(gè)選項(xiàng)肋演。說到這里我們大概應(yīng)該知曉了,識別二維碼其實(shí)分為兩步烂琴,第一步是發(fā)現(xiàn)截取二維碼區(qū)域爹殊,第二步是識別截取到的二維碼區(qū)域。那么 zxing 和支付寶到底是哪一步出了問題呢奸绷?首先我們來看一下第一步發(fā)現(xiàn)截取二維碼區(qū)域梗夸。

二維碼事例

上圖是一張常用的二維碼事例圖,有三個(gè)比較重要的區(qū)域号醉,分別是左上反症,右上和左下,我們只要能找到這三個(gè)特定的區(qū)域畔派,就能判定圖片中包含有二維碼铅碍。接下來我們來分析一下思路:

1. 對其進(jìn)行輪廓查找
2. 對查找的到的輪廓進(jìn)行初步過濾
3. 判斷是否符合二維碼的特征規(guī)則
4. 截取二維碼區(qū)域
5. 識別二維碼

//  判斷 X 方向上是否符合規(guī)則
bool isXVerify(const Mat& qrROI){
    ... 代碼省略
    // 判斷 x 方向從左到右的像素比例
    // 黑:白:黑:白:黑 = 1:1:3:1:1
}

//  判斷 Y 方向上是否符合規(guī)則
bool isYVerify(const Mat& qrROI){
    ... 代碼省略
    // y 方向上也可以按照 isXVerify 方法判斷
    // 但我們也可以適當(dāng)?shù)膶懞唵我恍?    // 白色像素 * 2 < 黑色像素 && 黑色像 < 4 * 白色像素 
}

int main(){
    Mat src = imread("C:/Users/hcDarren/Desktop/android/code1.png");

    if (!src.data){
        printf("imread error!");
        return -1;
    }
    imshow("src", src);

    // 對圖像進(jìn)行灰度轉(zhuǎn)換
    Mat gary;
    cvtColor(src, gary, COLOR_BGR2GRAY);
    // 二值化
    threshold(gary, gary, 0, 255, THRESH_BINARY | THRESH_OTSU);
    imshow("threshold", gary);
    // 1. 對其進(jìn)行輪廓查找
    vector<vector<Point> > contours;
    findContours(gary, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);

    for (int i = 0; i < contours.size(); i++)
    {
        // 2. 對查找的到的輪廓進(jìn)行初步過濾
        double area = contourArea(contours[i]);
        // 2.1 初步過濾面積 7*7 = 49
        if (area < 49){
            continue;
        }
        
        RotatedRect rRect = minAreaRect(contours[i]);
        float w = rRect.size.width;
        float h = rRect.size.height;
        float ratio = min(w, h) / max(w, h);
        // 2.2 初步過濾寬高比大小
        if (ratio > 0.9 && w< gary.cols/2 && h< gary.rows/2){
            Mat qrROI = warpTransfrom(gary, rRect);
            // 3. 判斷是否符合二維碼的特征規(guī)則
            if (isYVerify(qrROI) && isXVerify(qrROI)) {
                drawContours(src, contours, i, Scalar(0, 0, 255), 4);
            }
        }
    }

    imshow("src", src);
    imwrite("C:/Users/hcDarren/Desktop/android/code_result.jpg", src);

    waitKey(0);
    return 0;
}
處理結(jié)果

代碼是非常簡單的,關(guān)鍵是我們要善于學(xué)會(huì)去分析线椰,多多培養(yǎng)解決問題的能力胞谈,只要知道實(shí)現(xiàn)思路,其他一切都不是問題了憨愉。那么有意思的就來了烦绳,當(dāng)掃描第二張圖的時(shí)候,我們發(fā)現(xiàn)死活都識別不了配紫。那么細(xì)心的同學(xué)可能明白了径密,我們上面的代碼是按照正方形的特征來識別的,而第二張圖是圓形的特征笨蚁,因此 Zxing 無法識別也是正常的睹晒,因?yàn)樵蹅冊趯懘a的時(shí)候根本沒考慮這么個(gè)情況。那么我們怎么才能做到識別圓形的特征呢括细?考驗(yàn)我們的時(shí)候到了伪很,我們能想到三種解決方案:

1. 再寫一套識別圓形特征的代碼
2. 借鑒人臉識別的方案,采用訓(xùn)練樣本的方式識別
3. 換一種檢查方案奋单,只寫一套代碼

人臉識別在下期文章中會(huì)寫到锉试,訓(xùn)練樣本的方式比較麻煩,如果之前沒接觸過览濒,那么需要一定的時(shí)間成本呆盖,但這種方案應(yīng)該是最好的拖云。再寫一套圓形識別的代碼,感覺維護(hù)困難应又,作為一個(gè)有靈魂的工程師總覺得別扭宙项。那這里我們就采用第三種方案了,其實(shí)知識點(diǎn)也就那么多株扛,還是那句話多培養(yǎng)我們分析解決問題的能力尤筐。

我們仔細(xì)觀察,他們其實(shí)還是有很多共同點(diǎn)洞就,我們對其進(jìn)行輪廓篩選的時(shí)候會(huì)發(fā)現(xiàn)盆繁,都是一個(gè)大輪廓里面套兩個(gè)小輪廓。具體流程如下:

1. 對其進(jìn)行輪廓查找
2. 對查找的到的輪廓進(jìn)行初步過濾
3. 判斷是否是一個(gè)大輪廓套兩個(gè)小輪廓且符合特征規(guī)則(面積比例判斷)
4. 截取二維碼區(qū)域
5. 識別二維碼

extern "C"
JNIEXPORT jobject JNICALL
Java_com_darren_ndk_day76_MainActivity_clipQrBitmap(JNIEnv *env, jobject instance, jobject bitmap) {
    Mat src;
    cv_helper::bitmap2mat(env, bitmap, src);

    // 對圖像進(jìn)行灰度轉(zhuǎn)換
    Mat gary;
    cvtColor(src, gary, COLOR_BGR2GRAY);

    // 二值化
    threshold(gary, gary, 0, 255, THRESH_BINARY | THRESH_OTSU);

    // 1. 對其進(jìn)行輪廓查找
    vector<Vec4i> hierarchy;
    vector<vector<Point> > contours;
    vector<vector<Point> > contoursRes;
    /*
     參數(shù)說明:https://blog.csdn.net/guduruyu/article/details/69220296
        輸入圖像image必須為一個(gè)2值單通道圖像
        contours參數(shù)為檢測的輪廓數(shù)組旬蟋,每一個(gè)輪廓用一個(gè)point類型的vector表示
        hiararchy參數(shù)和輪廓個(gè)數(shù)相同油昂,每個(gè)輪廓contours[ i ]對應(yīng)4個(gè)hierarchy元素hierarchy[ i ][ 0 ] ~hierarchy[ i ][ 3 ],
            分別表示后一個(gè)輪廓倾贰、前一個(gè)輪廓冕碟、父輪廓、內(nèi)嵌輪廓的索引編號躁染,如果沒有對應(yīng)項(xiàng)鸣哀,該值設(shè)置為負(fù)數(shù)架忌。
        mode表示輪廓的檢索模式
            CV_RETR_EXTERNAL 表示只檢測外輪廓
            CV_RETR_LIST 檢測的輪廓不建立等級關(guān)系
            CV_RETR_CCOMP 建立兩個(gè)等級的輪廓吞彤,上面的一層為外邊界,里面的一層為內(nèi)孔的邊界信息叹放。如果內(nèi)孔內(nèi)還有一個(gè)連通物體饰恕,這個(gè)物體的邊界也在頂層。
            CV_RETR_TREE 建立一個(gè)等級樹結(jié)構(gòu)的輪廓井仰。具體參考contours.c這個(gè)demo
        method為輪廓的近似辦法
            CV_CHAIN_APPROX_NONE 存儲(chǔ)所有的輪廓點(diǎn)埋嵌,相鄰的兩個(gè)點(diǎn)的像素位置差不超過1,即max(abs(x1-x2)俱恶,abs(y2-y1))==1
            CV_CHAIN_APPROX_SIMPLE 壓縮水平方向雹嗦,垂直方向,對角線方向的元素合是,只保留該方向的終點(diǎn)坐標(biāo)了罪,例如一個(gè)矩形輪廓只需4個(gè)點(diǎn)來保存輪廓信息
            CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS 使用teh-Chinl chain 近似算法
        offset表示代表輪廓點(diǎn)的偏移量聪全,可以設(shè)置為任意值泊藕。對ROI圖像中找出的輪廓,并要在整個(gè)圖像中進(jìn)行分析時(shí)难礼,這個(gè)參數(shù)還是很有用的娃圆。
     */
    findContours(gary, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0));
    int tCC = 0; // 臨時(shí)用來累加的子輪廓計(jì)數(shù)器
    int pId = -1;// 父輪廓的 index
    for (int i = 0; i < contours.size(); i++) {
        if (hierarchy[i][2] != -1 && tCC == 0) {
            pId = i;
            tCC++;
        } else if (hierarchy[i][2] != -1) {// 有父輪廓
            tCC++;
        } else if (hierarchy[i][2] == -1) {// 沒有父輪廓
            tCC = 0;
            pId = -1;
        }
        // 找到了兩個(gè)子輪廓
        if (tCC >= 2) {
            contoursRes.push_back(contours[pId]);
            tCC = 0;
            pId = -1;
        }
    }
    // 找到過多的符合特征輪廓玫锋,對其進(jìn)行篩選
    if (contoursRes.size() > FEATURE_NUMBER) {
        contoursRes = filterContours(gary, contoursRes);
    }

    // 沒有找到符合的條件
    if (contoursRes.size() < FEATURE_NUMBER) {
        return NULL;
    }
    
    for (int i = 0; i < contoursRes.size(); ++i) {
        drawContours(src, contoursRes, i, Scalar(255, 0, 0), 2);
    }

    // 裁剪二維碼,交給 zxing 或者 zbar 處理即可
    
    cv_helper::mat2bitmap(env, src, bitmap);

    return bitmap;
}
處理結(jié)果

開發(fā)中我們最喜歡做的就是拿過來直接用讼呢,但最好還是明白其中的原理撩鹿,因?yàn)槲覀儫o法斷定開發(fā)中會(huì)出什么幺蛾子。像微信這樣的大廠自然得自己這一套悦屏,其實(shí)好的框架能夠拿過來優(yōu)化優(yōu)化三痰,個(gè)人認(rèn)為就已經(jīng)差不多了。當(dāng)然以上寫法在某些特定場景下窜管,可能還是會(huì)存在些許漏洞散劫,這就靠我們不斷的去琢磨優(yōu)化了。

視頻地址:https://pan.baidu.com/s/1m7Epc4TVNs8fSXi2ifXkhQ
視頻密碼:5g3z

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末幕帆,一起剝皮案震驚了整個(gè)濱河市获搏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌失乾,老刑警劉巖常熙,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碱茁,居然都是意外死亡裸卫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門纽竣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來墓贿,“玉大人,你說我怎么就攤上這事蜓氨×” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵穴吹,是天一觀的道長幽勒。 經(jīng)常有香客問我,道長港令,這世上最難降的妖魔是什么啥容? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮顷霹,結(jié)果婚禮上咪惠,老公的妹妹穿的比我還像新娘。我一直安慰自己泼返,他們只是感情好硝逢,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般渠鸽。 火紅的嫁衣襯著肌膚如雪叫乌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天徽缚,我揣著相機(jī)與錄音憨奸,去河邊找鬼。 笑死凿试,一個(gè)胖子當(dāng)著我的面吹牛排宰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播那婉,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼板甘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了详炬?” 一聲冷哼從身側(cè)響起盐类,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呛谜,沒想到半個(gè)月后在跳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隐岛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年猫妙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聚凹。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡都毒,死狀恐怖冕广,靈堂內(nèi)的尸體忽然破棺而出志笼,到底是詐尸還是另有隱情掸鹅,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布单旁,位于F島的核電站,受9級特大地震影響饥伊,放射性物質(zhì)發(fā)生泄漏象浑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一琅豆、第九天 我趴在偏房一處隱蔽的房頂上張望愉豺。 院中可真熱鬧,春花似錦茫因、人聲如沸蚪拦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽驰贷。三九已至盛嘿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間括袒,已是汗流浹背次兆。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锹锰,地道東北人芥炭。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像恃慧,于是被迫代替她去往敵國和親园蝠。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361