一種快速自適應的圖像二值化方法介紹 (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)來表示二值化後的值
用fs (n) 來表示第n個點之前s個點的灰度值的和, 就是
用這個s和另一個變量t就可以簡單的說明P(n)應該是0還是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). 這個值的意義就是:
可以看到, 這裡的gs (n) 和fs (n) 的區別在於fs (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之間的中間值)就可以開始實現算法了
view plaincopy to clipboardprint?
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:
原圖2: 二值圖2:
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/hhygcy/archive/2009/06/12/4264857.aspx