6.1徊哑、方法概述
閾值分割的核心就是如何選取閾值随闽, 選取正確的閾值是分割成功的關(guān)鍵过牙。
1社牲、全局閾值分割
全局閾值分割指的是將灰度值大于thresh
(閾值)的像素設(shè)為白色粪薛,小于或者等于 thresh
的像素設(shè)為黑色; 或者反過來搏恤, 將大于thresh
的像素設(shè)為黑色违寿, 小于或者等于thresh
的像素設(shè)為白色湃交, 兩者的區(qū)別只是呈現(xiàn)形式不同。
double threshold( InputArray src, OutputArray dst,double thresh, double maxval, int type );
需要注意的是藤巢,當(dāng)類型為THRESH_OTSU
或THRESH_TRIANGLE
時搞莺,輸入?yún)?shù)src
只支持uchar
類型, 這時thresh
也是作為輸出參數(shù)的掂咒, 即通過Otsu
和TRIANGLE
算法自動計算出來才沧。
2、局部閾值分割
局部閾值分割的核心也是計算閾值矩陣绍刮,比較常用的是后面提到的自適應(yīng)閾值算法(又稱移動平均值算法) 温圆, 是一種簡單但是高效的局部閾值算法,其核心思想就是把每一個像素的鄰域的“平均值”作為該位置的閾值孩革。
6.2岁歉、直方圖技術(shù)法
一幅含有一個與背景呈現(xiàn)明顯對比的物體的圖像具有包含雙峰的直方圖,兩個峰值對應(yīng)于物體內(nèi)部和外部較多數(shù)目的點膝蜈,兩個峰值之間的波谷對應(yīng)于物體邊緣附近相對較少數(shù)目的點锅移。
直方圖技術(shù)法就是首先找到這兩個峰值,然后取兩個峰值之 間的波谷位置對應(yīng)的灰度值彬檀,就是所要的閾值帆啃。
一 種常用的方式是先對直方圖進行高斯平滑處理,逐漸增大高斯濾波器的標(biāo)準差窍帝,直到能從平滑后的直方圖中得到兩個唯一的波峰和它們之間唯一的最小值努潘。但這種方式需要手動調(diào)節(jié),下面介紹一種規(guī)則自動選取波峰和波谷的方式坤学。
假設(shè)輸入圖像為I疯坤, 高為H、 寬為W深浮,histogramI
代表其對應(yīng)的灰度直方圖压怠, histogramI (k)
代表灰度值等于k的像素點個數(shù), 其中0≤k≤255飞苇。
第一步: 找到灰度直方圖的第一個峰值菌瘫,并找到其對應(yīng)的灰度值。顯然布卡,灰度直方圖的最大值就是第一個峰值且對應(yīng)的灰度值用
firstPeak
表示雨让。第二步: 找到直方圖的第二個峰值,并找到其對應(yīng)的灰度值忿等。第二個峰值不一定是直方圖的第二大值栖忠,因為它很有可能出現(xiàn)在第一個峰值的附近。
- 第三步: 找到這兩個峰值之間的波谷,如果出現(xiàn)兩個或者多個波谷庵寞,則取左側(cè)的波 谷即可狸相,其對應(yīng)的灰度值即為閾值。
int threshTwoPeaks(const Mat &image, Mat &thresh_out)
{
//計算灰度直方圖
Mat histogram = calGrayHist(image);
//找到灰度直方圖最大峰值對應(yīng)的灰度值
Point firstPeakLoc;
minMaxLoc(histogram, NULL, NULL, NULL, &firstPeakLoc);
int firstPeak = firstPeakLoc.x;
//尋找灰度直方圖第二個峰值對應(yīng)的灰度值
Mat measureDists = Mat::zeros(Size(256, 1), CV_32FC1);
for (int k = 0; k < 256; k++)
{
int hist_k = histogram.at<int>(0, k);
measureDists.at<float>(0, k) = pow(float(k - firstPeak), 2)*hist_k;
}
Point secondPeakLoc;
minMaxLoc(measureDists, NULL, NULL, NULL, &secondPeakLoc);
int secondPeak = secondPeakLoc.x;
//找到兩個之間的最小值對應(yīng)的灰度值捐川,作為閾值
Point threshLoc;
int thresh = 0;
if (firstPeak < secondPeak)//第一個峰值在第二個峰值的左側(cè)
{
minMaxLoc(histogram.colRange(firstPeak, secondPeak), NULL, NULL, NULL, &threshLoc);
thresh = firstPeak + threshLoc.x + 1;
}
else//第一個峰值在第二個峰值的右側(cè)
{
minMaxLoc(histogram.colRange(secondPeak, firstPeak), NULL, NULL, NULL, &threshLoc);
thresh = secondPeak + threshLoc.x + 1;
}
//閾值分割
threshold(image, thresh_out, thresh, 255, THRESH_BINARY);
return thresh;
}
6.3脓鹃、熵方法
利用熵計算閾值的步驟如下:
- 第一步: 計算I 的累加概率直方圖,又稱零階累積矩属拾,記為
- 第二步: 計算各個灰度級的熵将谊,記為
- 第三步: 計算使
f (t) =f1(t) +f2(t)
最大化的t值, 該值即為得到的閾值渐白, 即thresh=argtmax(f (t) )
尊浓, 其中
6.4、Otsu閾值處理
在對圖像進行閾值分割時纯衍,所選取的分割閾值應(yīng)使前景區(qū)域的平均灰度栋齿、背景區(qū)域 的平均灰度與整幅圖像的平均灰度之間的差異最大, 這種差異用區(qū)域的方差來表示襟诸。 Otsu[2]提出了最大方差法瓦堵, 該算法是在判別分析最小二乘法原理的基礎(chǔ)上推導(dǎo)得出的, 計算過程簡單歌亲, 是一種常用的閾值分割的穩(wěn)定算法菇用。
- 第一步: 計算灰度直方圖的零階累積矩(或稱累加直方圖) 。
- 第二步: 計算灰度直方圖的一階累積矩陷揪。
- 第三步: 計算圖像I 總體的灰度平均值mean惋鸥, 其實就是k=255時的一階累積距, 即
- 第四步: 計算每一個灰度級作為閾值時悍缠, 前景區(qū)域的平均灰度卦绣、 背景區(qū)域的平均灰 度與整幅圖像的平均灰度的方差。 對方差的衡量采用以下度量:
- 第五步: 找到上述最大的σ2(k)飞蚓, 然后對應(yīng)的k即為Otsu自動選取的閾值滤港, 即
int otsu(const Mat &image, Mat &OtsuThreshImage)
{
//計算灰度直方圖
Mat histogram = calGrayHist(image);
//歸一化灰度直方圖
Mat normHist;
histogram.convertTo(normHist, CV_32FC1, 1.0 / (image.rows*image.cols), 0.0);
//計算累加直方圖(零階累積距)和一階累積距
Mat zeroCumuMoment = Mat::zeros(Size(256, 1), CV_32FC1);
Mat oneCumuMoment = Mat::zeros(Size(256, 1), CV_32FC1);
for (int i = 0; i < 256; i++)
{
if (i == 0)
{
zeroCumuMoment.at<float>(0, i) = normHist.at<float>(0, i);
oneCumuMoment.at<float>(0, i) = normHist.at<float>(0, i);
}
else
{
zeroCumuMoment.at<float>(0, i) = normHist.at<float>(0, i) + zeroCumuMoment.at<float>(0, i - 1);
oneCumuMoment.at<float>(0,i)= normHist.at<float>(0, i) + oneCumuMoment.at<float>(0, i - 1);
}
}
//計算類間方差
Mat variance = Mat::zeros(Size(256, 1), CV_32FC1);
//總平均值
float mean = oneCumuMoment.at<float>(0, 255);
for (int j = 0; j < 256; j++)
{
if (zeroCumuMoment.at<float>(0, j) == 0 || zeroCumuMoment.at<float>(0, j) == 1)
{//分母不能為零
variance.at<float>(0, j) = 0;
}
else
{
variance.at < float>(0, j) = pow(mean*zeroCumuMoment.at<float>(0, j) - oneCumuMoment.at<float>(0, j), 2) / zeroCumuMoment.at<float>(0, j)*(1.0 - zeroCumuMoment.at<float>(0, j));
}
}
//找到最值作為閾值
Point maxLoc;
minMaxLoc(variance, NULL, NULL, NULL, &maxLoc);
int thresh = maxLoc.x;
//閾值處理
threshold(image, OtsuThreshImage, thresh, 255, THRESH_BINARY);
return thresh;
}
6.5、自適應(yīng)閾值
在不均勻照明或者灰度值分布不均的情況下趴拧,如果使用全局閾值分割溅漾, 那么得到的分割效果往往會很不理想。那么想到的策略是針對每一個位置的灰度值 設(shè)置一個對應(yīng)的閾值著榴, 而該位置閾值的設(shè)置也和其鄰域有必然的關(guān)系樟凄。
在對圖像進行平滑處理時,均值平滑兄渺、高斯平滑、中值平滑用不同規(guī)則計算出以當(dāng)前像素為中心的鄰域內(nèi)的灰度“平均值”, 所以可以使用平滑處理后的輸出結(jié)果作為每個 像素設(shè)置閾值的參考值挂谍,如用均值濾波后的結(jié)果乘以某個比例系數(shù)作為最后的閾值矩陣叔壤。
平滑算子的寬度必須大于被識別物體的寬度,平滑算子的尺寸越大口叙,平滑后的結(jié)果越能更好地作為每個像素的閾值的參考炼绘,當(dāng)然也不能無限大。
- 第一步: 對圖像進行平滑處理妄田, 平滑結(jié)果記為
fsmooth(I)
俺亮,其中fsmooth
可以代表 均值平滑、高斯平滑疟呐、中值平滑脚曾。 - 第二步: 自適應(yīng)閾值矩陣
Thresh=(1-ratio) *fsmooth(I)
,一般令ratio=0.15
启具。 - 第三步: 利用局部閾值分割的規(guī)則進行閾值分割本讥。
Mat adaptiveThresh(Mat I, int radius, float radio, METHOD method)
{
//step1:對圖像矩陣進行平滑處理
Mat smooth;
switch (method)
{
case MEAN:
boxFilter(I, smooth, CV_32FC1, Size(2 * radius + 1, 2 * radius + 1));
break;
case GAUSS:
GaussianBlur(I, smooth, Size(2 * radius + 1, 2 * radius + 1), 0, 0);
break;
case MEDIAN:
medianBlur(I, smooth, 2 * radius + 1);
break;
default:
break;
}
//step2:平滑結(jié)果乘于比例系數(shù),然后圖像矩陣與其做差
I.convertTo(I, CV_32FC1);
smooth.convertTo(smooth, CV_32FC1);
Mat diff = I - (1.0 - radio)*smooth;
//step3:閾值處理鲁冯,當(dāng)≥0拷沸,輸出值為255,反之薯演,輸出值為0
Mat out = Mat::zeros(diff.size(), CV_8UC1);
for (int r = 0; r < I.rows; r++)
{
for (int c = 0; c < I.cols; c++)
{
if (diff.at<float>(r, c) > 0)
{
out.at<uchar>(r, c) = 255;
}
}
}
return out;
}
就可以理解OpenCV提供的自適應(yīng)閾值函數(shù):
void adaptiveThreshold( InputArray src, OutputArray dst,
double maxValue, int adaptiveMethod,
int thresholdType, int blockSize, double C );
6.6撞芍、二值圖的邏輯運算
OpenCV提供的兩個函數(shù)bitwise_and
和bitwise_or
分別實現(xiàn)了兩 個矩陣之間的與運算和或運算,它們本質(zhì)上完成的是兩個矩陣對應(yīng)位置數(shù)值的邏輯運算跨扮。