ZBar源码分析——Image Scanner模块(三)

[TOC]


一、Image Scanner

Image Scanner,顾名思义是实现对读入图像进行扫描的功能模块。

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()来完成滤波,阈值,确定边缘,转化成宽度流。

前面的代码分析对img_scanner.c的核心函数zbar_scan_image()函数以及几个函数进行过分析,本次代码分析则继续对该函数对于特征的使用和处理进行分析。


二、代码分析

数据结构解析

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
typedef struct recycle_bucket_s {
int nsyms;
zbar_symbol_t *head;
} recycle_bucket_t;
/* image scanner state */
struct zbar_image_scanner_s {
zbar_scanner_t *scn; /* associated linear intensity scanner */
zbar_decoder_t *dcode; /* associated symbol decoder */
#ifdef ENABLE_QRCODE
qr_reader *qr; /* QR Code 2D reader */
#endif
const void *userdata; /* application data */
/* user result callback */
zbar_image_data_handler_t *handler;
unsigned long time; /* scan start time */
zbar_image_t *img; /* currently scanning image *root* */
int dx, dy, du, umin, v; /* current scan direction */
zbar_symbol_set_t *syms; /* previous decode results */
/* recycled symbols in 4^n size buckets */
recycle_bucket_t recycle[RECYCLE_BUCKETS];
int enable_cache; /* current result cache state */
zbar_symbol_t *cache; /* inter-image result cache entries */
/* configuration settings */
unsigned config; /* config flags */
unsigned ean_config;
int configs[NUM_SCN_CFGS]; /* int valued configurations */
int sym_configs[1][NUM_SYMS]; /* per-symbology configurations */
#ifndef NO_STATS
int stat_syms_new;
int stat_iscn_syms_inuse, stat_iscn_syms_recycle;
int stat_img_syms_inuse, stat_img_syms_recycle;
int stat_sym_new;
int stat_sym_recycle[RECYCLE_BUCKETS];
#endif
};

这里定义了两个数据结构用于代码调用。

recycle_bucket_s为特征回收数据结构,在结构体中定义了特征指针和索引数据。

zbar_image_scanner_s为ZBar扫描器数据结构,存放当前扫描器的状态、用户反馈数据、被回收的特征数据以及扫描器配置数据。

zbar_scanner_t *scn 与扫描器关联的线性强度扫描仪
zbar_decoder_t *dcode 与扫描器关联的关联符号解码器
qr_reader *qr 二维码阅读器
const void *userdata 应用数据
unsigned long time 扫描开始时间
zbar_image_t *img 当前扫描图像
int dx, dy, du, umin, v 当前扫描位置
zbar_symbol_set_t *syms 曾经被解码得到的结果
int enable_cache 当前结果缓存状态
unsigned config 配置标志
int configs[NUM_SCN_CFGS] 有值配置
int sym_configs[1][NUM_SYMS] 符号配置

扫描器特征回收

扫描器在对二维图像进行扫描时,将识别到的特征进行记录和保存,当当前扫描部分结束时,需要对特征进行回收。

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
void _zbar_image_scanner_recycle_syms (zbar_image_scanner_t *iscn,
zbar_symbol_t *sym)
{
zbar_symbol_t *next = NULL;
for(; sym; sym = next) {
next = sym->next;
if(sym->refcnt && _zbar_refcnt(&sym->refcnt, -1)) {
/* unlink referenced symbol */
/* FIXME handle outstanding component refs (currently unsupported)*/
assert(sym->data_alloc);
sym->next = NULL;
}
else {
int i;
recycle_bucket_t *bucket;
/* recycle unreferenced symbol */
if(!sym->data_alloc) {
sym->data = NULL;
sym->datalen = 0;
}
if(sym->syms) {
if(_zbar_refcnt(&sym->syms->refcnt, -1))
assert(0);
_zbar_image_scanner_recycle_syms(iscn, sym->syms->head);
sym->syms->head = NULL;
_zbar_symbol_set_free(sym->syms);
sym->syms = NULL;
}
for(i = 0; i < RECYCLE_BUCKETS; i++)
if(sym->data_alloc < 1 << (i * 2))
break;
if(i == RECYCLE_BUCKETS) {
assert(sym->data);
free(sym->data);
sym->data = NULL;
sym->data_alloc = 0;
i = 0;
}
bucket = &iscn->recycle[i];
/* FIXME cap bucket fill */
bucket->nsyms++;
sym->next = bucket->head;
bucket->head = sym;
}
}
}

回收步骤:

遍历扫描器存放的特征数据结构,若当前特征与当前扫描器链接且被引用,则直接在特征列表中截断,不允许在链表中继续链接,然后断开链接。

1
2
3
4
5
6
7
if(sym->refcnt && _zbar_refcnt(&sym->refcnt, -1)) {
/* unlink referenced symbol */
/* FIXME handle outstanding component refs (currently unsupported) */
assert(sym->data_alloc);
sym->next = NULL;
}

若未被引用,则不能在链表中进行操作,这部分数据在扫描器中的存储结构比较尴尬:可能在后续扫描过程中被引用,即被其他扫描器结构引用。

在回收时,需要调用回收桶数据结构,将这部分数据存放到回收桶中,这与Windows系统中的回收站功能类似,只是将这部分数据结构从扫描器中移除,在某个地方存放起来,需要时仍可以调用。

该函数也提供了将回收桶中的数据彻底移除的方式。

1
2
3
4
5
6
7
8
9
10
for(i = 0; i < RECYCLE_BUCKETS; i++)
if(sym->data_alloc < 1 << (i * 2))
break;
if(i == RECYCLE_BUCKETS) {
assert(sym->data);
free(sym->data);
sym->data = NULL;
sym->data_alloc = 0;
i = 0;
}

扫描器图像回收

当扫描器要对链接的图像进行回收时,就需要借助特征回收函数。另一方面,该函数被设定为内联函数,说明这个函数在ZBar执行过程中频繁被调用。

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
inline void zbar_image_scanner_recycle_image (zbar_image_scanner_t *iscn,
zbar_image_t *img)
{
zbar_symbol_set_t *syms = iscn->syms;
if(syms && syms->refcnt) {
if(recycle_syms(iscn, syms)) {
STAT(iscn_syms_inuse);
iscn->syms = NULL;
}
else
STAT(iscn_syms_recycle);
}
syms = img->syms;
img->syms = NULL;
if(syms && recycle_syms(iscn, syms))
STAT(img_syms_inuse);
else if(syms) {
STAT(img_syms_recycle);
/* select one set to resurrect, destroy the other */
if(iscn->syms)
_zbar_symbol_set_free(syms);
else
iscn->syms = syms;
}
}

ZBar在回收(循环)图像时采用的方式是:遍历所有图像特征,根据特征标识符选择复活一组图像特征,然后销毁一组图像特征,实现内存空间有效利用,不占用过多内存空间。

扫描器新增特征

前面分析了扫描器对特征的回收和断开链接的过程,与之相对的,扫描器同样也需要加入新的特征的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void _zbar_image_scanner_add_sym(zbar_image_scanner_t *iscn,
zbar_symbol_t *sym)
{
zbar_symbol_set_t *syms;
cache_sym(iscn, sym);
syms = iscn->syms;
if(sym->cache_count || !syms->tail) {
sym->next = syms->head;
syms->head = sym;
}
else {
sym->next = syms->tail->next;
syms->tail->next = sym;
}
if(!sym->cache_count)
syms->nsyms++;
else if(!syms->tail)
syms->tail = sym;
_zbar_symbol_refcnt(sym, 1);
}

这里对特征的数据结构zbar_symbol_s进行补充分析:

zbar_symbol_type_t type 符号类型
unsigned int configs 符号布尔配置位掩码
unsigned int modifiers 符号修饰符位掩码
unsigned int data_alloc 数据的分配大小
unsigned int datalen 二进制符号数据的长度
char *data 符号数据
unsigned pts_alloc pts的分配大小
unsigned npts 位置多边形中的点数
point_t *pts 位置多边形中的点列表
zbar_orientation_t orient 粗方向
refcnt_t refcnt 引用计数
zbar_symbol_t *next 结果(或同级)链接列表
zbar_symbol_set_t *syms 分量
unsigned long time 相对符号捕获时间
int cache_count 缓存状态
int quality 相对符号可靠性度量

以及特征集zbar_symbol_set_s的数据结构:

refcnt_t refcnt 索引标识
int nsyms 已过滤特征的数量
zbar_symbol_t *head 第一个解码特征结果
zbar_symbol_t *tail 最后一个未过滤的特征结果

在扫描器中添加新特征时,需要对特征的缓存状态和在特征集合中的位置进行判断,有以下几种情况:

(1)特征已被缓存或者当前特征不在集合的尾部,则直接在特征集合中插入新特征。

(2)特征未被缓存且者当前特征在集合的尾部,则改变集合尾部指针指向,在尾部添加新特征。

(3)对于未缓存的特征,加入集合中还需要对索引和对已过滤特征进行变更。


三、总结

本次代码分析对扫描器关于特征的回收和新增,以及特征和图像间的处理方式进行分析,下次将对扫描器配置和缓存部分展开分析。