ZBar源码分析——Image Scanner关键函数
一、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()来完成滤波,阈值,确定边缘,转化成宽度流。
二、关键函数(补充说明)
1 2 3 4 5
| static inline unsigned calc_thresh (zbar_scanner_t *scn) dx = x - lastedge; t = thresh*dx; t /= scn->width; t /=8;
|
这一段程序主要是为了使thresh逐渐回归到thresh_min。
如果dx在8width范围内,则thresh=thresh(1-dx/8w)
即如果dx=width,则thresh=7/8thresh。如果dx超出8w范围,则直接置为thresh_min。
thresh在每次确定新的边界时更新为此处的y11的0.44倍。

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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| zbar_symbol_type_t zbar_scan_y (zbar_scanner_t *scn, int y) { register int x = scn->x; register int y0_1 = scn->y0[(x - 1) & 3]; register int y0_0 = y0_1; register int y0_2, y0_3, y1_1, y2_1, y2_2; zbar_symbol_type_t edge; if(x) { y0_0 += ((int)((y - y0_1) * EWMA_WEIGHT)) >> ZBAR_FIXED; scn->y0[x & 3] = y0_0; } else y0_0 = y0_1 = scn->y0[0] = scn->y0[1] = scn->y0[2] = scn->y0[3] = y; y0_2 = scn->y0[(x - 2) & 3]; y0_3 = scn->y0[(x - 3) & 3]; y1_1 = y0_1 - y0_2; { register int y1_2 = y0_2 - y0_3; if((abs(y1_1) < abs(y1_2)) && ((y1_1 >= 0) == (y1_2 >= 0))) y1_1 = y1_2; } y2_1 = y0_0 - (y0_1 * 2) + y0_2; y2_2 = y0_1 - (y0_2 * 2) + y0_3; dbprintf(1, "scan: x=%d y=%d y0=%d y1=%d y2=%d", x, y, y0_1, y1_1, y2_1); edge = ZBAR_NONE; if((!y2_1 || ((y2_1 > 0) ? y2_2 < 0 : y2_2 > 0)) && (calc_thresh(scn) <= abs(y1_1))) { char y1_rev = (scn->y1_sign > 0) ? y1_1 < 0 : y1_1 > 0; if(y1_rev) edge = process_edge(scn, y1_1); if(y1_rev || (abs(scn->y1_sign) < abs(y1_1))) { int d; scn->y1_sign = y1_1; scn->y1_thresh = (abs(y1_1) * THRESH_INIT + ROUND) >> ZBAR_FIXED; dbprintf(1, "\tthr=%d", scn->y1_thresh); if(scn->y1_thresh < scn->y1_min_thresh) scn->y1_thresh = scn->y1_min_thresh; d = y2_1 - y2_2; scn->cur_edge = 1 << ZBAR_FIXED; if(!d) scn->cur_edge >>= 1; else if(y2_1) scn->cur_edge -= ((y2_1 << ZBAR_FIXED) + 1) / d; scn->cur_edge += x << ZBAR_FIXED; dbprintf(1, "\n"); } } else dbprintf(1, "\n");
scn->x = x + 1; return(edge); }
|
寄存器变量
1 2 3 4
| register int x = scn->x; register int y0_1 = scn->y0[(x - 1) & 3]; register int y0_0 = y0_1; register int y0_2, y0_3, y1_1, y2_1, y2_2;
|
Tips:
1.寄存器变量可以用来优化加速c语言程序
2.声名只需在类型前多加register 即可,eg register int quick; (quick 就是一个整形的寄存器变量)
3.register只是一个建议型关键字,能不能声名成功还取决于编译器(建议型的关键字还有c++中的 inline),若不幸没有请求成功,则变量变成一个普通的自动变量。
4.是无法对一个register变量取地址的(因为寄存器变量多放在寄存器而非内存中,内存有地址,而寄存器是无地址的)
5.即便没有请求成寄存器变量,没有如愿的放入寄存器中,但是,依然不能对他取地址,因为他已经被声明为register了
什么情况用寄存器变量:
当对一个变量频繁被读写时,需要反复访问内存,从而花费大量的存取时间。为此,C语言提供了一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写,从而提高效率。寄存器变量的说明符是register。对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量,而循环计数是应用寄存器变量的最好候选者。
什么变量可以声明为寄存器变量:
只有局部自动变量和形参才可以定义为寄存器变量。因为寄存器变量属于动态存储方式,凡需要采用静态存储方式的量都不能定义为寄存器变量,因为静态变量储存在静态区,包括:模块间全局变量、模块内全局变量、局部static变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| if(x) { y0_0 += ((int)((y - y0_1) * EWMA_WEIGHT)) >> ZBAR_FIXED; scn->y0[x & 3] = y0_0; } else y0_0 = y0_1 = scn->y0[0] = scn->y0[1] = scn->y0[2] = scn->y0[3] = y; y0_2 = scn->y0[(x - 2) & 3]; y0_3 = scn->y0[(x - 3) & 3]; y1_1 = y0_1 - y0_2; { register int y1_2 = y0_2 - y0_3; if((abs(y1_1) < abs(y1_2)) && ((y1_1 >= 0) == (y1_2 >= 0))) y1_1 = y1_2; }
|
函数一开头使用EWMA对原始数据滤波,抑制突变。
再用y0数组存储邻近点的数据,用来求一阶导数和二阶导数, scn->y0[(x - 1) & 3]很巧妙的限制住了数组索引不越界,循环使用。 紧接着开始求一阶导,二阶导。

1 2 3 4 5 6 7 8
| y1_1 = y0_1 - y0_2; { register int y1_2 = y0_2 - y0_3; if((abs(y1_1) < abs(y1_2)) && ((y1_1 >= 0) == (y1_2 >= 0))) y1_1 = y1_2; }
|
一阶求导数,如果同向且变化率减小,那么认为变化率为常数,此针对屋顶变化边界类型。
三、总结
本次博客对ZBar扫描器的几个重要函数的代码进行了补充说明。如有不足,敬请指正。