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));
//若创建失败,返回NULL
if(!iscn)
return(NULL);
//初始化图片扫描器(初始化图片扫描器中的解码器和扫描器)
iscn->dcode = zbar_decoder_create();
iscn->scn = zbar_scanner_create(iscn->dcode);
//若解码器或扫描器初始化失败,返回NULL
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;
}
//判断图片扫描器是否应用于ZBar扫描器,如果是,则从ZBar扫描器上卸载
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
 //先判断有没有设定y密度
if(ydensity > 0)
{
while(y < h)
//y从0以ydensity递增到h
{
while(x < w)
//x先从0递增到w,再递减回0
{
x += 1;
zbar_scan_y();
}
x = w - 1;
y = y + ydensity
//y从0以ydensity递增到h
if(y >= h)
break;
while(x >= 0)
// x开始递减
{c
x -= 1;
zbar_scan_y();
}
x = 0 + 1;
y = y + ydensity;
}
//接着判断x方向扫描密度,同理Z字形扫描
}
1
2
w = img->width;
h = img->height;

以上代码中w表示图像宽度,h表示图像高度,函数在循环过程中反复调用zbar_scan_y()来完成滤波,阈值,确定边缘,转化成宽度流。

根据代码进行绘制,大致能得到ZBar扫描图像像素的顺序大致如下:

在这里插入图片描述

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