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
_zbar_image_scanner_alloc_sym (zbar_image_scanner_t *iscn,
zbar_symbol_type_t type,
int datalen)
{
...
}

扫描器特征分配的功能主要由_zbar_image_scanner_alloc_sym函数实现,这一函数的工作主要有两个方面:

(1)回收旧的特征

(2)为扫描器分配新的特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
zbar_symbol_t *sym = NULL;
int i;
for(i = 0; i < RECYCLE_BUCKETS - 1; i++)
if(datalen <= 1 << (i * 2))
break;
for(; i > 0; i--)
if((sym = iscn->recycle[i].head)) {
STAT(sym_recycle[i]);
break;
}
if(sym) {
iscn->recycle[i].head = sym->next;
sym->next = NULL;
assert(iscn->recycle[i].nsyms);
iscn->recycle[i].nsyms--;
}
else {
sym = calloc(1, sizeof(zbar_symbol_t));
STAT(sym_new);
}

扫描器首先对其之前分配过的特征进行回收。

这一步分为两种情况:

若该扫描器中之前被分配过特征,则对其进行直接回收;

若该扫描器为刚初始化过的扫描器,则不能像上面那样操作,否则会出现空指针异常,这时则需要为扫描器初始化一个新的特征向量。

在回收开始之前,首先需要进行一系列判断:

首先要对在上篇博客中提到的扫描器回收站以及扫描器循环体进行检查,若其中还有未处理的数据,则终止函数执行。

若通过了上一步的代码检查,则遍历指针,对其进行逐一回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if(datalen > 0) {
sym->datalen = datalen - 1;
if(sym->data_alloc < datalen) {
if(sym->data)
free(sym->data);
sym->data_alloc = datalen;
sym->data = malloc(datalen);
}
}
else {
if(sym->data)
free(sym->data);
sym->data = NULL;
sym->datalen = sym->data_alloc = 0;
}
return(sym);

另一方面,在进行特征初始化及分配时,需要对特征向量的长度与扫描器预留空间进行判断,若当前特征向量无法分配到该扫描器中,则释放刚初始化的特征,将该空间留给合适的特征向量。

特征处理

这一功能的实现需要借助解码器。

特征处理器数据初始化:对特征类型、特征尺寸以及解码得到的数据内容进行初始化。

1
2
3
4
5
6
zbar_image_scanner_t *iscn = zbar_decoder_get_userdata(dcode);
zbar_symbol_type_t type = zbar_decoder_get_type(dcode);
int x = 0, y = 0, dir;
const char *data;
unsigned datalen;c
zbar_symbol_t *sym;

为扫描位置选择一个临时位置并固定,并获取边界。

1
2
3
4
5
6
7
8
9
10
11
12
if(TEST_CFG(iscn, ZBAR_CFG_POSITION)) {
int w = zbar_scanner_get_width(iscn->scn);
int u = iscn->umin + iscn->du * zbar_scanner_get_edge(iscn->scn, w, 0);
if(iscn->dx) {
x = u;
y = iscn->v;
}
else {
x = iscn->v;
y = u;
}
}

保存/显示所有解码部分的数据标识

1
2
3
4
5
6
7
if(type <= ZBAR_PARTIAL) {
zprintf(256, "partial symbol @(%d,%d)\n", x, y);
return;
}
data = zbar_decoder_get_data(dcode);
datalen = zbar_decoder_get_data_length(dcode);

为扫描器匹配更加合适的特征,并将新的特征点添加到现有集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for(sym = iscn->syms->head; sym; sym = sym->next)
if(sym->type == type &&
sym->datalen == datalen &&
!memcmp(sym->data, data, datalen)) {
sym->quality++;
zprintf(224, "dup symbol @(%d,%d): dup %s: %.20s\n",
x, y, zbar_get_symbol_name(type), data);
if(TEST_CFG(iscn, ZBAR_CFG_POSITION))
/* add new point to existing set */
/* FIXME should be polygon */
sym_add_point(sym, x, y);
return;
}
sym = _zbar_image_scanner_alloc_sym(iscn, type, datalen + 1);
sym->configs = zbar_decoder_get_configs(dcode, type);
sym->modifiers = zbar_decoder_get_modifiers(dcode);

初始化第一个特征向量

1
2
3
4
5
6
7
8
9
 if(TEST_CFG(iscn, ZBAR_CFG_POSITION)) {
zprintf(192, "new symbol @(%d,%d): %s: %.20s\n",
x, y, zbar_get_symbol_name(type), data);
sym_add_point(sym, x, y);
}
dir = zbar_decoder_get_direction(dcode);
if(dir)
sym->orient = (iscn->dy != 0) + ((iscn->du ^ dir) & 2);
_zbar_image_scanner_add_sym(iscn, sym);

以上就是关于扫描器特征部分要补充的代码说明。

下面对扫描器cache部分的代码展开解析。

缓存过滤器

ZBar扫描器中的缓存过滤器有以下功能:

*主要用于扫描视频帧

*从连续图像中复制扫描结果

*一致性检查和结果滞后

*清除缓存

启动/禁用缓存过滤器

1
2
3
4
5
6
7
8
9
10
void zbar_image_scanner_enable_cache (zbar_image_scanner_t *iscn,
int enable)
{
if(iscn->cache) {
/* recycle all cached syms */
_zbar_image_scanner_recycle_syms(iscn, iscn->cache);
iscn->cache = NULL;
}
iscn->enable_cache = (enable) ? 1 : 0;
}

扫描器接口默认禁用缓存过滤器(通过enable参数控制)。

当要启用缓存过滤器时,设定enable为1,这时扫描器会将缓存中的特征向量全部回收,并准备启动缓存。

访问Cache

访问扫描器的缓存内容时,要进行以下工作:

(1)寻找访存入口

(2)回收过时缓存项:在访存时,若发现缓存区中有特征的形成时间与进入缓存区的时间的时间差过长,则判定为超时特征,并对其进行回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while(*entry) {
if((*entry)->type == sym->type &&
(*entry)->datalen == sym->datalen &&
!memcmp((*entry)->data, sym->data, sym->datalen))
break;
if((sym->time - (*entry)->time) > CACHE_TIMEOUT) {
/* recycle stale cache entry */
zbar_symbol_t *next = (*entry)->next;
(*entry)->next = NULL;
_zbar_image_scanner_recycle_syms(iscn, *entry);
*entry = next;
}
else
entry = &(*entry)->next;
}

在Cache中缓存特征

当启用缓存过滤器,则允许扫描器在缓存区中存放特征,这相当于在内存中开辟一块空间,用于临时存放特征向量,实现高速存取。

启用缓存过滤器后,要访问Cache时,首先寻找缓存入口,若没有找到,则声明一个新的扫描器,将其初始化后,得到缓存空间和缓存入口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if(iscn->enable_cache) {
uint32_t age, near_thresh, far_thresh, dup;
zbar_symbol_t *entry = cache_lookup(iscn, sym);
if(!entry) {
/* FIXME reuse sym */
entry = _zbar_image_scanner_alloc_sym(iscn, sym->type,
sym->datalen + 1);
entry->configs = sym->configs;
entry->modifiers = sym->modifiers;
memcpy(entry->data, sym->data, sym->datalen);
entry->time = sym->time - CACHE_HYSTERESIS;
entry->cache_count = 0;
/* add to cache */
entry->next = iscn->cache;
iscn->cache = entry;
}s

接下来进行一致性检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
age = sym->time - entry->time;
entry->time = sym->time;
near_thresh = (age < CACHE_PROXIMITY);
far_thresh = (age >= CACHE_HYSTERESIS);
dup = (entry->cache_count >= 0);
if((!dup && !near_thresh) || far_thresh) {
int type = sym->type;
int h = _zbar_get_symbol_hash(type);
entry->cache_count = -iscn->sym_configs[0][h];
}
else if(dup || near_thresh)
entry->cache_count++;
sym->cache_count = entry->cache_count;

通过一致性检查后,则允许使用缓存实现高速存取。


三、总结

本次代码分析对扫描器模块对于特征的分配和处理方式进行了补充,并对其关于缓存过滤器的代码进行了分析。