一种快速自适应的图像二值化方法介绍 (Wellner 1993)

在手机模式识别的时候, 我们首先viewfinder里面拿到的frame通常是RGB的或者YUV的, 如果我们需要用来做模式识别的话, 通常需要首先把彩色图首先转化成灰度图. 对于RGB图像而言, 网上有充足的公式, 比如Y = 0.299R + 0.587G + 0.114B 等等. 如果是YUV的话, 直接用Y就是灰度图了. 顺带说一句, 这种灰度图通常我们用.raw文件来表示, 用photoshop或者irfanview是可以直接打开看效果的. 比如说这里就有一个灰度图的例子

这个图就是现在很流行的所谓Data Matrix的sample, 我们用手机的照相机拿到的灰度图. 现在我们要把它变化成为黑白图(二值图). 在网上广为流传着很多办法. 什么双峰法, P参数法等等. 今天的办法和这些都不相同. 这个方法就是被称之为Quick Adaptive Thresholding algorithm, 提出这个观点的人名字叫做Pierre D. Wellner.  这里的网页上就有这个算法的说明:

http://www.xrce.xerox.com/Publications/Attachments/1993-110/EPC-1993-110.pdf

这个算法的基本思想要确定一个像素的黑或者白, 用他周围, 或者扫描顺序上的其他点的一些平均值来评估阀值就可以了.用阀值和像素值比较即可. 我们现在定义出这样的模型, 比方说我们用P(n)来表示第n个点的灰度值. T(n)来表示二值化后的值

s (n) 来表示第n个点之前s个点的灰度值的和, 就是

公式1

 

用这个s和另一个变量t就可以简单的说明P(n)应该是0还是1了, 这个公式就是

一种快速自适应的图像二值化方法介绍 (Wellner 1993)_第1张图片

而且根据经验值来看, 这里的s和t最佳的取值范围是s= image.width/8, 而t=15的时候效果最好.

好的, 到这里为止, 我们的理解就是一个点t(n), 他是0还是1取决于什么呢? 就是前面s个点的和除以s (就是前s个点的平均值)*0.85 如果这个点的灰度值<前面的值, 那么就是1黑色, 如果大于就是0白色. 是不是非常简单? 至少到现在为止确实是的.但是这个算法有个问题, 我们忽略了一个问题, 就是我们现在定义T(n)的时候, 用的是平均值, 也就是说之前扫描过的若干点对于当前点的影响或者说权重是一样的, 也就是说当前点1个像素距离的像素和s-1个像素点的距离的像素的灰度值对当前点的影响是一样的. 显然根据我们直观的理解来看, 应该是离当前点越近的像素对当前点的影响越大, 越远则越小. 所以算法的作者发明了这个个更合适更高效的替代值gs (n). 这个值的意义就是:

一种快速自适应的图像二值化方法介绍 (Wellner 1993)_第2张图片

可以看到, 这里的gs (n)s (n) 的区别在于s (n) 直接是不做任何修正的s个灰度值的和, 而gs (n)则是一定比例的灰度值的和, 可以看到, 离这个n越近的像素的比重越高, 越远越低. 显然这样描述对把握像素的颜色更为准确. 而且这里的 gs (n)和 gs (n-1)通过加法和乘法就可以递归得到, 计算效率是比较高的.

 

即使到了这一步了, 还有一个问题存在, 就是我现在的颜色计算其实依赖于我的扫描顺序, 也就是说P(n)的这个序列的定义就是我的扫描顺序(一般都是水平扫描的). 这样的话, 我的像素值实际上取决于我水平位置上的邻接点的灰度值, 可是竖直方向的像素如何关联起来呢? 这里也有一个说明, 我们可以维护前面依次水平扫描产生的g_prev(n)序列, 在某个g(n)被使用之前, 我们可以让他和前一个g_prev(n)取一个平均值, 这样的话, 这个最终的值就更有说服力了.

公式四

好了, 到现在为止, 我们描述了整个算法的全过程, 在加上我们定义的初始g(n)值127*s(127表示0-255之间的中间值)就可以开始实现算法了

void quickAdaptiveThreshold(unsigned char* grayscale, unsigned char*& thres, int width, int height ) { /** / * | FOREGROUND, if pn < ((gs(n) + gs(n-w)) / (2*s)) * * color(n) = | ((100-t)/100) * | BACKGROUND_QR, otherwise * / * where pn = gray value of current pixel, * s = width of moving average, and * t = threshold percentage of brightness range * gs(n) = gs(n-1) * (1-1/s) + pn * gs(n-w) = gs-value of pixel above current pixel * */ int t = 15; int s = width >> 3; // s: number of pixels in the moving average (w = image width) const int S = 9; // integer shift, needed to avoid floating point operations const int power2S = 1 << S; // for speedup: multiply all values by 2^s, and use integers instead of floats int factor = power2S * (100-t) / (100*s); // multiplicand for threshold int gn = 127 * s; // initial value of the moving average (127 = average gray value) int q = power2S - power2S / s; // constant needed for average computation int pn, hn; unsigned char *scanline = NULL; int *prev_gn = NULL; prev_gn = new int[width]; for (int i = 0; i < width; i++) { prev_gn[i] = gn; } thres = new unsigned char[width*height]; for (int y = 0; y < height; y ++ ) { int yh = y * width; scanline = grayscale + y * width; for ( int x = 0; x <width; x ++ ) { pn = scanline[x] ; gn = ((gn * q) >> S) + pn; hn = (gn + prev_gn[x]) >> 1; prev_gn[x] = gn; pn < (hn*factor) >> S ? thres[yh+x] = 0 : thres[yh+x] = 0xff; } y ++ ; if ( y == height) break; yh = y * width; scanline = grayscale + y * width; for ( int x = width-1; x >= 0; x --) { pn = scanline[x] ; gn = ((gn * q) >> S) + pn; hn = (gn + prev_gn[x]) >> 1; prev_gn[x] = gn; pn < (hn*factor) >> S ? thres[yh+x] = 0 : thres[yh+x] = 0xff; } } delete prev_gn; }

这个算法也不是我发明创造的, 这个算法从http://mikie.iki.fi/lxr/source/VisualCodeSystem/src/RecognitionAlgorithm.cpp?v=v3 这个网址上看过来. 不过是去除了一些Symbian的痕迹, 还有有的细节上做了一些改进, 让代码更加合理了些. 经过这个算法, 我们可以来看看效果了


原图1:                                                                       二值图1:

一种快速自适应的图像二值化方法介绍 (Wellner 1993)_第3张图片

 

原图2:                                                                           二值图2:

一种快速自适应的图像二值化方法介绍 (Wellner 1993)_第4张图片

我对这个效果还是比较满意的. 哈哈. 一直要写这个文章, 今天终于写完了, 心里真是痛快了.

 

 

 

 

你可能感兴趣的:(Algorithm,算法,Integer,delete,Symbian,Matrix)