自適應二值化介紹:
二值化算法是用輸入像素的值I與一個值C來比較,根據比較結果確定輸出值。
自適應二值化的每一個像素的比較值C都不同,比較值C由這個像素為中心的一個塊范圍計算在減去差值delta得到。
C的常用計算方法有兩種:
1.平均值減去差值delta(使用盒過濾boxfilter,性能會非常不錯)
2.高斯分布加權和減去差值delta (使用高斯濾波GaussionBlur)
只要高興用什么其他方法都行。
最后,總算法就是用每一個像素的灰度值I,與所對應的比較值C,確定結果輸出到對應的像素。
舉個例子:如果使用平均值方法,平均值mean為190,差值delta為30。那么灰度小於160的像素為0,大於等於160的像素為255。如下圖:
如果是反向二值化,如下圖:
delta選擇負值也是可以的。
opencv中adaptiveThreshold函數分析:
參數:
_src 要二值化的灰度圖
_dst 二值化后的圖
maxValue 二值化后要設置的那個值
method 塊計算的方法(ADAPTIVE_THRESH_MEAN_C 平均值,ADAPTIVE_THRESH_GAUSSIAN_C 高斯分布加權和)
type 二值化類型(CV_THRESH_BINARY 大於為最大值,CV_THRESH_BINARY_INV 小於為最大值)
blockSize 塊大小(奇數,大於1)
delta 差值(負值也可以)
源碼和注釋如下:
/** @brief 自適應二值化 *@param _src 要二值化的灰度圖 *@param _dst 二值化后的圖 *@param maxValue 二值化后要設置的那個值 *@param method 塊計算的方法(ADAPTIVE_THRESH_MEAN_C 平均值,ADAPTIVE_THRESH_GAUSSIAN_C 高斯分布加權和) *@param type 二值化類型(CV_THRESH_BINARY 大於為最大值,CV_THRESH_BINARY_INV 小於為最大值) *@param blockSize 塊大小(奇數,大於1) *@param delta 差值(負值也可以) */ void cv::adaptiveThreshold(InputArray _src, OutputArray _dst, double maxValue, int method, int type, int blockSize, double delta) { Mat src = _src.getMat(); // 原圖必須是單通道無符號8位 CV_Assert(src.type() == CV_8UC1); // 塊大小必須大於1,並且是奇數 CV_Assert(blockSize % 2 == 1 && blockSize > 1); Size size = src.size(); // 構建與原圖像相同的圖像 _dst.create(size, src.type()); Mat dst = _dst.getMat(); if (maxValue < 0) { // 二值化后值小於0,圖像都為0 dst = Scalar(0); return; } // 用於比較的值 Mat mean; if (src.data != dst.data) mean = dst; if (method == ADAPTIVE_THRESH_MEAN_C) // 計算平均值作為比較值 boxFilter(src, mean, src.type(), Size(blockSize, blockSize), Point(-1, -1), true, BORDER_REPLICATE); else if (method == ADAPTIVE_THRESH_GAUSSIAN_C) // 計算高斯分布和作為比較值 GaussianBlur(src, mean, Size(blockSize, blockSize), 0, 0, BORDER_REPLICATE); else CV_Error(CV_StsBadFlag, "Unknown/unsupported adaptive threshold method"); int i, j; // 將maxValue夾到[0,255]的uchar范圍區間,用作二值化后的值 uchar imaxval = saturate_cast<uchar>(maxValue); // 根據二值化類型計算delta值 int idelta = type == THRESH_BINARY ? cvCeil(delta) : cvFloor(delta); // 計算生成每個像素差對應的值表格,以后查表就可以。但像素差范圍為什么是768,我確實認為512已經夠了 uchar tab[768]; if (type == CV_THRESH_BINARY) for (i = 0; i < 768; i++) // i = src[j] - mean[j] + 255 // i - 255 > -idelta ? imaxval : 0 // = src[j] - mean[j] + 255 -255 > -idelta ? imaxval : 0 // = src[j] > mean[j] - idelta ? imaxval : 0 tab[i] = (uchar)(i - 255 > -idelta ? imaxval : 0); else if (type == CV_THRESH_BINARY_INV) for (i = 0; i < 768; i++) // i = src[j] - mean[j] + 255 // i - 255 <= -idelta ? imaxval : 0 // = src[j] - mean[j] + 255 - 255 <= -idelta ? imaxval : 0 // = src[j] <= mean[j] - idelta ? imaxval : 0 tab[i] = (uchar)(i - 255 <= -idelta ? imaxval : 0); else CV_Error(CV_StsBadFlag, "Unknown/unsupported threshold type"); // 如果連續,加速運算 if (src.isContinuous() && mean.isContinuous() && dst.isContinuous()) { size.width *= size.height; size.height = 1; } // 逐像素計算src[j] - mean[j] + 255,並查表得到結果 for (i = 0; i < size.height; i++) { const uchar* sdata = src.data + src.step*i; const uchar* mdata = mean.data + mean.step*i; uchar* ddata = dst.data + dst.step*i; for (j = 0; j < size.width; j++) // 將[-255, 255] 映射到[0, 510]然后查表 ddata[j] = tab[sdata[j] - mdata[j] + 255]; } }
源碼很短小精悍,但查找表為什么是768的大小?512應該足夠了:(
使用場景:
對灰度車牌圖像做處理或陰陽車牌處理,效果比較行。blockSize根據算法確定。
Mat src = imread("d:\\src.jpg", 0); Mat bw; adaptiveThreshold(src, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 17, 0);
原圖的處理結果
OTUS二值化結果
自適應二值化結果,去掉一些錯誤區域還算完美。
轉載請注明出處,謝謝~