ZBar源码分析——img_scanner.c
一、ZBar的工作流程
通过分析ZBar项目的结构,可以看到ZBar的工作流程大致分为4个步骤:
(一)读入图像并配置参数;
(二)扫描读入的图像并根据梯度变化分析其明暗宽度流(根据明暗宽度流可以得出读入图像中的条码类型,如:二维码,code93,code128等);
(三)分析读入图像的像素点及其特征;
(四)找到条码格式信息并解码,最后输出恢复出来的码字。
二、Image Scanner
Image Scanner,顾名思义是实现对读入图像进行扫描的功能模块。本次要分析的scanner.c就是这一功能模块的核心之一。
ZBar实现Image Scanner的核心主要由img_scanner.c和scanner.c两个文件组成。
其中,img_scanner.c中的核心函数是zbar_scan_image(),而scanner.c中的核心函数是zbar_scan_y()。经过简单分析得到,zbar_scan_image主要负责ZBar对读入图像的扫描工作,函数主要根据设定的扫描密度(density)控制像素点读取(按Z字形读取,这也是ZBar名称的由来),scanner.c文件内的zbar_scan_y()来完成滤波,阈值,确定边缘,转化成宽度流。
zbar_scan_image()的实现需要借助zbar_scan_y()扫描图像形成宽度流的结果。
三、img_scanner.c
本次源码分析主要对img_scanner.c文件进行分析(主要分析zbar_scan_image()函数)。
zbar_image_scanner_create() 创建(安装)图片扫描器Image Scanner并将其应用于解码以及各类条码的识别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| zbar_image_scanner_t *zbar_image_scanner_create () { zbar_image_scanner_t *iscn = calloc(1, sizeof(zbar_image_scanner_t)); if(!iscn) return(NULL); iscn->dcode = zbar_decoder_create(); iscn->scn = zbar_scanner_create(iscn->dcode);
if(!iscn->dcode || !iscn->scn) { zbar_image_scanner_destroy(iscn); return(NULL); }
zbar_decoder_set_userdata(iscn->dcode, iscn); zbar_decoder_set_handler(iscn->dcode, symbol_handler); #ifdef ENABLE_QRCODE iscn->qr = _zbar_qr_create(); #endif
CFG(iscn, ZBAR_CFG_X_DENSITY) = 1; CFG(iscn, ZBAR_CFG_Y_DENSITY) = 1; zbar_image_scanner_set_config(iscn, 0, ZBAR_CFG_POSITION, 1); zbar_image_scanner_set_config(iscn, 0, ZBAR_CFG_UNCERTAINTY, 2); zbar_image_scanner_set_config(iscn, ZBAR_QRCODE, ZBAR_CFG_UNCERTAINTY, 0); zbar_image_scanner_set_config(iscn, ZBAR_CODE128, ZBAR_CFG_UNCERTAINTY, 0); zbar_image_scanner_set_config(iscn, ZBAR_CODE93, ZBAR_CFG_UNCERTAINTY, 0); zbar_image_scanner_set_config(iscn, ZBAR_CODE39, ZBAR_CFG_UNCERTAINTY, 0); zbar_image_scanner_set_config(iscn, ZBAR_CODABAR, ZBAR_CFG_UNCERTAINTY, 1); zbar_image_scanner_set_config(iscn, ZBAR_COMPOSITE, ZBAR_CFG_UNCERTAINTY, 0); return(iscn); }
|
当需要卸载Image Scanner时,不能直接free(img_scanner)释放内存空间,这会直接导致ZBar扫描器zbar_scanner、ZBar解码器zbar_decoder以及各类条码识别应用发生空指针异常。因此需要先判断Image Scanner在各个部件和应用上的使用情况,若正在使用,则需要先解除Image Scanner的应用,最后才能直接释放内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| void zbar_image_scanner_destroy (zbar_image_scanner_t *iscn) { int i; dump_stats(iscn); if(iscn->syms) { if(iscn->syms->refcnt) zbar_symbol_set_ref(iscn->syms, -1); else _zbar_symbol_set_free(iscn->syms); iscn->syms = NULL; }
if(iscn->scn) zbar_scanner_destroy(iscn->scn); iscn->scn = NULL; 判断图片扫描器是否应用于ZBar解码器,如果是,则从ZBar解码器上卸载 if(iscn->dcode) zbar_decoder_destroy(iscn->dcode); iscn->dcode = NULL;
for(i = 0; i < RECYCLE_BUCKETS; i++) { zbar_symbol_t *sym, *next; for(sym = iscn->recycle[i].head; sym; sym = next) { next = sym->next; _zbar_symbol_free(sym); } } #ifdef ENABLE_QRCODE if(iscn->qr) { _zbar_qr_destroy(iscn->qr); iscn->qr = NULL; } #endif free(iscn); }
|
下面是对zbar_scan_image()部分代码片段的分析:
zbar_scan_image()主要实现了Z字形扫描图像密度,核心思想是通过扫描密度(density)来控制像素点读取。
函数分为从横向和纵向扫描图像两个部分,实现逻辑相差不大,下面给出纵向扫描部分的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| density = CFG(iscn, ZBAR_CFG_Y_DENSITY); if(density > 0) { const uint8_t *p = data; int x = 0, y = 0; int border = (((img->crop_h - 1) % density) + 1) / 2; if(border > img->crop_h / 2) border = img->crop_h / 2; border += img->crop_y; assert(border <= h); svg_group_start("scanner", 0, 1, 1, 0, 0); iscn->dy = 0; movedelta(img->crop_x, border); iscn->v = y; while(y < cy1) { int cx0 = img->crop_x;; zprintf(128, "img_x+: %04d,%04d @%p\n", x, y, p); svg_path_start("vedge", 1. / 32, 0, y + 0.5); iscn->dx = iscn->du = 1; iscn->umin = cx0; while(x < cx1) { uint8_t d = *p; movedelta(1, 0); zbar_scan_y(scn, d); } ASSERT_POS; quiet_border(iscn); svg_path_end(); movedelta(-1, density); iscn->v = y; if(y >= cy1) break; zprintf(128, "img_x-: %04d,%04d @%p\n", x, y, p); svg_path_start("vedge", -1. / 32, w, y + 0.5); iscn->dx = iscn->du = -1; iscn->umin = cx1; while(x >= cx0) { uint8_t d = *p; movedelta(-1, 0); zbar_scan_y(scn, d); } ASSERT_POS;c quiet_border(iscn); svg_path_end(); movedelta(1, density); iscn->v = y; } svg_group_end(); } iscn->dx = 0;
|
这部分代码主要实现了纵向扫描图像密度,由于其中调用函数关系较为复杂,若只看坐标部分,可简化为以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| if(ydensity > 0) { while(y < h) { while(x < w) { x += 1; zbar_scan_y(); } x = w - 1; y = y + ydensity
if(y >= h) break; while(x >= 0)
{c x -= 1; zbar_scan_y(); } x = 0 + 1; y = y + ydensity; }
}
|
1 2
| w = img->width; h = img->height;
|
以上代码中w表示图像宽度,h表示图像高度,函数在循环过程中反复调用zbar_scan_y()来完成滤波,阈值,确定边缘,转化成宽度流。
根据代码进行绘制,大致能得到ZBar扫描图像像素的顺序大致如下:

以上便是本次的代码分析报告,如有错误,请指正。