Zbar算法流程介紹


博客轉載自:https://blog.csdn.net/sunflower_boy/article/details/50783179

zbar算法是現在網上開源的條形碼,二維碼檢測算法,算法可識別大部分種類的一維碼(條形碼),比如I25,CODE39,CODE128,不過大家更關心的應該是現在很火的QR碼的解碼效率,隨着現在生活中QR碼的普及,掃碼支付等行為越來越多的被人們接受,關於QR碼是什么,QR碼的解碼流程是什么樣的。本篇文章就互聯網上的一個開源解碼算法zbar進行簡單剖析。

源碼可以在網上搜到,或者去github上clone到本地:Zbar/Zbar

流程圖

算法流程介紹

首先是算法的初始化,構造一個掃描器ImageScanner對象,並使用其set_config()方法對掃描器進行初始化:

ImageScanner scanner;  
// configure the reader  
scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1);  

接下來是載入圖像,可以使用 ImageMagick 和 OpenCV 讀取圖片文件,並將其轉換為灰度圖像,以下以 OpenCV 為例

IplImage *img = cvLoadImage("E:\\ 文檔 \\ 測試素材 _ 一維碼二維碼 \\QRCODE\\2-1.jpg");  
IplImage *imgGray = cvCreateImage(cvGetSize(img), 8, 1);  
cvCvtColor(img, imgGray, CV_RGB2GRAY);  

構造一個圖像Image對象,並調用其構造函數對其進行初始化

int width = imgGray->widthStep;  
int height = imgGray->height;  
Image image(width, height, "Y800", imgGray->imageData, width * height);  

圖像解析,通過調用圖像掃描器對象的scan()方法,對圖像對象進行處理

int n = scanner.scan(image);  

圖像掃描,掃描器對象公有方法scan()主要為zbar_scan_image()函數,函數首先對傳入的圖像進行配置校驗,然后對傳入圖像先進行逐行掃描,掃描路徑為 Z 字型:

while(y < h) {  
    iscn->dx = iscn->du = 1;  
    iscn->umin = 0;  
    while(x < w) {  
        uint8_t d = *p;  
        movedelta(1, 0);  
        zbar_scan_y(scn, d);  
    }  
    quiet_border(iscn);  
    movedelta(-1, density);  
    iscn->v = y;  
    if(y >= h)  
        break;  
    iscn->dx = iscn->du = -1;  
    iscn->umin = w;  
    while(x >= 0) {  
        uint8_t d = *p;  
        movedelta(-1, 0);  
        zbar_scan_y(scn, d);  
    }  
    ASSERT_POS;  
    quiet_border(iscn);  
    movedelta(1, density);  
    iscn->v = y;  
}  

掃描的主要函數為zbar_scan_y(),在函數內部,以一個像素點為增量在一行內一點一點掃描過去,並且完成濾波,求取邊緣梯度,梯度閾值自適應,確定邊緣,轉化成明暗寬度流;其中確定邊緣之后調用process_edge()函數:

if(y1_rev)  
    edge = process_edge(scn, y1_1);  

在process_edge()函數內部,使用當前邊緣跟上一次保存下來的邊緣相減得到一個寬度,並將其保存到掃描器結構變量scn中並將本次邊緣信息保存下來:

scn->width = scn->cur_edge - scn->last_edge;  
scn->last_edge = scn->cur_edge;  

之后對掃描器結構變量scn中保存下來的明暗寬度流進行處理,處理函數為zbar_decode_width(scn->decoder, scn->width),該函數內部處理對象為當前行目前保存下來的寬度流,通過計算各寬度之間的寬度信息提取掃碼特征,依次通過幾種一維碼二維碼的檢測標准,尋找到符合標准的掃碼種類時更新掃描器結構變量scn中的type成員,並且更新lock成員以增加當前種類判斷的置信度(可以通過設置關掉其他種類的條碼識別):

#ifdef ENABLE_EAN  
    if((dcode->ean.enable) &&  
    (sym = _zbar_decode_ean(dcode)))  
        dcode->type = sym;  
#endif  
#ifdef ENABLE_CODE39  
    if(TEST_CFG(dcode->code39.config, ZBAR_CFG_ENABLE) &&  
    (sym = _zbar_decode_code39(dcode)) > ZBAR_PARTIAL)  
    {  
        dcode->type = sym;  
    }  
#endif  
#ifdef ENABLE_CODE128  
    if(TEST_CFG(dcode->code128.config, ZBAR_CFG_ENABLE) &&  
    (sym = _zbar_decode_code128(dcode)) > ZBAR_PARTIAL)  
        dcode->type = sym;  
#endif  
#ifdef ENABLE_I25  
    if(TEST_CFG(dcode->i25.config, ZBAR_CFG_ENABLE) &&  
    (sym = _zbar_decode_i25(dcode)) > ZBAR_PARTIAL)  
        dcode->type = sym;  
#endif  
#ifdef ENABLE_PDF417  
    if(TEST_CFG(dcode->pdf417.config, ZBAR_CFG_ENABLE) &&  
    (sym = _zbar_decode_pdf417(dcode)) > ZBAR_PARTIAL)  
        dcode->type = sym;  
#endif  
#ifdef ENABLE_QRCODE  
    if(TEST_CFG(dcode->qrf.config, ZBAR_CFG_ENABLE) &&  
    (sym = _zbar_find_qr(dcode)) > ZBAR_PARTIAL)  
        dcode->type = sym;  
#endif  

以 QR 碼為例子,函數_zbar_find_qr(dcode)內部對當前行的寬度流進行計算,判斷是否符合下列特征:

qr_finder_t *qrf = &dcode->qrf;  
qrf->s5 -= get_width(dcode, 6);  
qrf->s5 += get_width(dcode, 1);  
unsigned s = qrf->s5;  
if(get_color(dcode) != ZBAR_SPACE || s < 7)  
return ZBAR_NONE;  
int ei = decode_e(pair_width(dcode, 1), s, 7);  
if(ei)  
goto invalid;  
ei = decode_e(pair_width(dcode, 2), s, 7);  
if(ei != 2)  
goto invalid;  
ei = decode_e(pair_width(dcode, 3), s, 7);  
if(ei != 2)  
goto invalid;  
ei = decode_e(pair_width(dcode, 4), s, 7);  
if(ei)  
goto invalid;  
invalid:  
return ZBAR_NONE; 

符合當前特征的即判斷其不為 QR 碼,如果不符合,將當前寬度流描述為一個自定義的線段結構,包含兩端端點及長度等信息,並將滿足條件的橫向線段結構變量存入一個容器lines的橫向線段集合中。 對整幅圖像的逐列掃描同逐行掃描一樣,掃描路徑為 N 字型,同樣通過函數zbar_scan_y()和process_edge()進行處理找邊緣最后求取出縱向的明暗高度流,通過zbar_decode_width(scn->decoder, scn->width)函數進行處理,將符合 QR 碼的縱向線段存入lines的縱向線段集合中。

QR碼解析,QR 碼解析模塊的入口為函數_zbar_qr_decode(iscn->qr, iscn, img),函數內部結構如下:

int nqrdata = 0;  
qr_finder_edge_pt *edge_pts = NULL;  
qr_finder_center *centers = NULL;  
if(reader->finder_lines[0].nlines < 9 ||  
reader->finder_lines[1].nlines < 9)  
return(0);  
int ncenters = qr_finder_centers_locate(¢ers, &edge_pts, reader, 0, 0);  
if(ncenters >= 3) {  
void *bin = qr_binarize((unsigned char*)img->data, img->width, img->height);  
qr_code_data_list qrlist;  
qr_code_data_list_init(&qrlist);  
qr_reader_match_centers(reader, &qrlist, centers, ncenters,  
(unsigned char*)bin, img->width, img->height);  
if(qrlist.nqrdata > 0)  
nqrdata = qr_code_data_list_extract_text(&qrlist, iscn, img);  
qr_code_data_list_clear(&qrlist);  
free(bin);  
}  
if(centers)  
free(centers);  
if(edge_pts)  
free(edge_pts);  
return(nqrdata);  

首先第一步需要求出 QR 碼的三個定位圖案的中心,需要對之前求出的橫向,縱向線段集合進行篩選,聚類和求取交叉點

int ncenters = qr_finder_centers_locate(¢ers, &edge_pts, reader, 0, 0);  

函數返回的是共找到多少個交叉點,如果小於三個則此圖像無法進行 QR 碼解析。 之后對圖像進行自適應二值化處理

void *bin = qr_binarize((unsigned char*)img->data, img->width, img->height);  

之后就是解碼的主要組成部分,對 QR 碼進行碼字讀取:

qr_reader_match_centers(reader, &qrlist, centers, ncenters,(unsigned char*)bin, img->width, img->height);  

函數首先對找到的交叉點按時針順序進行排序,三個點進行仿射變化求出 QR 碼模塊寬度(所占像素個數):

version=qr_reader_try_configuration(_reader,&qrdata,_img,_width,_height,c);  

函數返回值為 QR 碼的版本數,並且求出了 QR 碼的版本碼字和模塊寬度(根據三個交叉點處於同邊的兩個點來計算,仿射變化有單應性仿射 affine homography 和全矩陣仿射 full homography ),將所求得的所有結果進行計算和比對,最終的出 QR 碼的版本結果,還需要判斷求出結果數是否大於等於 7 。如果是,求得的版本信息是經過編碼后的信息,版本號還需要解碼;如果小於 7 ,求出來的結果即是 QR 碼的版本號:

if(ur.eversion[1]==dl.eversion[0]&&ur.eversion[1]<7){  
ur_version=ur.eversion[1];  
}  
else{  
if(abs(ur.eversion[1]-dl.eversion[0])>QR_LARGE_VERSION_SLACK)  
continue;  
}  
if(ur.eversion[1]>=7-QR_LARGE_VERSION_SLACK){  
ur_version=qr_finder_version_decode(&ur,&hom,_img,_width,_height,0);  
if(abs(ur_version-ur.eversion[1])>QR_LARGE_VERSION_SLACK)  
ur_version=-1;  
}  
else  
ur_version=-1;  
if(dl.eversion[0]>=7-QR_LARGE_VERSION_SLACK){  
dl_version=qr_finder_version_decode(&dl,&hom,_img,_width,_height,1);  
if(abs(dl_version-dl.eversion[0])>QR_LARGE_VERSION_SLACK)  
dl_version=-1;  
}  
else  
dl_version=-1;  
if(ur_version>=0){  
if(dl_version>=0&&dl_version!=ur_version)  
continue;  
}  
else if(dl_version<0)  
continue;  
else  
ur_version=dl_version;  
}  

之后求 QR 碼的格式信息:

fmt_info=qr_finder_fmt_info_decode(&ul,&ur,&dl,&hom,_img,_width,_height);  

格式信息求出來之后就是 QR 碼的功能區到目前為止已全部識別並解碼出結果,之后對 QR 碼的數據區進行解析,函數為:

qr_code_decode(_qrdata,&_reader->gf,ul.c->pos,ur.c->pos,dl.c->pos,ur_version,fmt_info,_img,_width,_height)  

函數注釋為:

/*Attempts to fully decode a QR code. 
_qrdata: Returns the parsed code data. 
_gf: Used for Reed-Solomon error correction. 
_ul_pos: The location of the UL finder pattern. 
_ur_pos: The location of the UR finder pattern. 
_dl_pos: The location of the DL finder pattern. 
_version: The (decoded) version number. 
_fmt_info: The decoded format info. 
_img: The binary input image. 
_width: The width of the input image. 
_height: The height of the input image. 
Return: 0 on success, or a negative value on error.*/  
static int qr_code_decode(qr_code_data *_qrdata,const rs_gf256 *_gf,  
const qr_point _ul_pos,const qr_point _ur_pos,const qr_point _dl_pos,  
int _version,int _fmt_info,  
const unsigned char *_img,int _width,int _height)  

首先對對圖像進行消除掩模處理,並且識別出圖像中的定位圖案:

qr_sampling_grid_init(&grid,_version,_ul_pos,_ur_pos,_dl_pos,_qrdata->bbox,_img,_width,_height);  

然后將 QR 碼除去功能區之外的區域轉換為 0 和 1 的比特流:

qr_sampling_grid_sample(&grid,data_bits,dim,_fmt_info,_img,_width,_height);  

使用 Reed-Solomon 糾錯算法對提取出來的比特流進行校驗和糾錯,最后輸出最終的識別比特流。 函數nqrdata = qr_code_data_list_extract_text(&qrlist, iscn, img);對求出的比特流進行分析判斷,判斷當前 QR 碼屬於什么編碼模式,找到相應的編碼模式后對比特流進行解碼輸出,最終求得 QR 碼的解碼結果。




 


免責聲明!

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



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