2021SC@SDUSC
一、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()函数以及几个函数进行过分析,本次代码分析则继续对该函数对于特征的使用和处理进行分析。
二、代码分析
数据结构解析
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] | 符号配置 |
扫描器特征回收
扫描器在对二维图像进行扫描时,将识别到的特征进行记录和保存,当当前扫描部分结束时,需要对特征进行回收。
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;
}
}
}
回收步骤:
遍历扫描器存放的特征数据结构,若当前特征与当前扫描器链接且被引用,则直接在特征列表中截断,不允许在链表中继续链接,然后断开链接。
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系统中的回收站功能类似,只是将这部分数据结构从扫描器中移除,在某个地方存放起来,需要时仍可以调用。
该函数也提供了将回收桶中的数据彻底移除的方式。
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执行过程中频繁被调用。
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在回收(循环)图像时采用的方式是:遍历所有图像特征,根据特征标识符选择复活一组图像特征,然后销毁一组图像特征,实现内存空间有效利用,不占用过多内存空间。
扫描器新增特征
前面分析了扫描器对特征的回收和断开链接的过程,与之相对的,扫描器同样也需要加入新的特征的功能。
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)对于未缓存的特征,加入集合中还需要对索引和对已过滤特征进行变更。
三、总结
本次代码分析对扫描器关于特征的回收和新增,以及特征和图像间的处理方式进行分析,下次将对扫描器配置和缓存部分展开分析。