自適應閾值分割算法
- 閾值分割算法或者二值化算法是用輸入像素的值 與一個值 來比較,根據比較結果確定輸出值。
- 自適應閾值分割的每一個像素的比較值(閾值) 都不同,閾值 由這個像素爲中心的一個塊範圍計算在減去差值 delta 得到。
算法優勢: 自適應閾值分割是在像素點的局部相鄰區域內獨立計算閾值, 再進行二值化分割, 尤其適用於明暗程度不一致的目標。
自適應閾值的計算方法
常用的兩種方法:
- 平均值減去差值delta(使用盒過濾boxfilter,性能會非常不錯)
- 高斯分佈加權和減去差值delta (使用高斯濾波GaussionBlur)
舉個例子:如果使用平均值方法,平均值mean爲190,差值delta爲30。那麼灰度小於160的像素爲0,大於等於160的像素爲255。如下圖:
如果是反向二值化,如下圖:
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;
}
CALL_HAL(adaptiveThreshold, cv_hal_adaptiveThreshold, src.data, src.step, dst.data, dst.step, src.cols, src.rows,
maxValue, method, type, blockSize, delta); // 硬件加速算法 cv_hal_adaptiveThreshold 未作實現,直接忽略
// 用於比較的值
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];
}
}
算法示例
Mat src = imread("d:\\src.jpg", 0);
cv::adaptiveThreshold(src, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 17, 0);