zoukankan      html  css  js  c++  java
  • 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. 
    _ 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 码的解码结果。




  • 相关阅读:
    APP的LOGO设计需求
    App 中使用 Iconfont 的整套方案
    UI流程总结
    sketch制作LOGO(一)---环形光晕
    Sketch插件--Rename It
    Sketch Measure使用教程
    04 流程控制
    03 python语法注释、用户交互、格式化输出、基本数据类型、运算符
    02编程语言及python初识
    第一课
  • 原文地址:https://www.cnblogs.com/flyinggod/p/8709827.html
Copyright © 2011-2022 走看看