1、最大類間方差法的由來
最大類間方差法是由日本學者大津(Nobuyuki Otsu)於1979年提出的,是一種自適應的閾值確定的方法,又叫大津法,簡稱OTSU。
2、最大類間方差法的原理
它是按圖像的灰度特性,將圖像分成背景和目標兩部分。背景和目標之間的類間方差越大,說明構成圖像的兩部分的差別越大,當部分目標錯分爲背景或部分背景錯分爲目標都會導致兩部分差別變小。因此,使類間方差最大的分割意味着錯分概率最小。對於圖像I(x,y),前景(即目標)和背景的分割閾值記作Th,屬於前景的像素點數佔整幅圖像的比例記爲w1,其平均灰度G1;背景像素點數佔整幅圖像的比例爲w2,其平均灰度爲G2。圖像的總平均灰度記爲G_Ave,類間方差記爲 g。
假設圖像的背景較暗,並且圖像的大小爲M*N,圖像中像素的灰度值小於閾值的像素個數記作N1,像素灰度大於閾值的像素個數記作N2,則有:
3、代碼實現
#include <opencv2\opencv.hpp>
#include <iostream>
#include <time.h>
using namespace std;
using namespace cv;
int myOtsu(Mat & src)
{
int th;
const int GrayScale = 256; //單通道圖像總灰度256級
int pixCount[GrayScale] = { 0 };//每個灰度值所佔像素個數
int pixSum = src.cols * src.rows;//圖像總像素點
float pixPro[GrayScale] = { 0 };//每個灰度值所佔總像素比例
float w0, w1, u0tmp, u1tmp, u0, u1, deltaTmp, deltaMax = 0;
for (int i = 0; i < src.cols; i++)
{
for (int j = 0; j < src.rows; j++)
{
pixCount[src.at<uchar>(j, i)]++;//統計每個灰度級中像素的個數
}
}
for (int i = 0; i < GrayScale; i++)
{
pixPro[i] = pixCount[i] * 1.0 / pixSum;//計算每個灰度級的像素數目佔整幅圖像的比例
}
// 遍歷所有從0到255灰度級的閾值分割條件,測試哪一個的類間方差最大
// 即遍歷0-255灰度級,嘗試以其中任意一個作爲閾值,計算最大類間方差
for (int i = 0; i < GrayScale; i++)
{
w0 = 0; //
w1 = 0;
u0tmp = 0;
u1tmp = 0;
u0 = 0;
u1 = 0;
deltaTmp = 0;
for (int j = 0; j < GrayScale; j++)
{
//當前閾值爲i的時,遍歷小於閾值i的灰度級(背景),
if (j <= i)
{
w0 += pixPro[j]; //計算大於閾值的像素點個數w0
u0tmp += j * pixPro[j]; //計算灰度級*當前灰度級下像素點個數 的總和:u0tmp
}
//當前閾值爲i的時,遍歷大於閾值i的灰度級(前景)
else
{
w1 += pixPro[j]; //計算大於閾值的像素點個數w1
u1tmp += j * pixPro[j]; //計算灰度級*當前灰度級下像素點個數 的總和:u0tmp
}
}
u0 = u0tmp / w0;
u1 = u1tmp / w1;
deltaTmp = (float)(w0 *w1* pow((u0 - u1), 2)); //類間方差公式 g = w1 * w2 * (u1 - u2) ^ 2
if (deltaTmp > deltaMax) //如果當前的最大類間方差值最大,則將當前類間方差值作爲最大類間方差值
{
deltaMax = deltaTmp;
th = i; //此時的閾值作爲最佳閾值
}
}
return th; //返回最佳閾值
}
int main()
{
Mat src = imread("2.png", IMREAD_GRAYSCALE);//單通道讀取圖像
/*my_dst: 自己實現的大津法 得到的處理圖像
otsu_dst:opencv自帶的大津法 得到的處理圖像
sub:兩個處理圖像相差圖
*/
Mat my_dst, otsu_dst, sub;
/*my_th: 自己實現的大津法 得到的最大類件方差 即閾值
th:opencv自帶的大津法 得到的最大類件方差 即閾值
*/
int my_th, th;
/*計算開銷時間,對比兩個算法效率*/
long my_start = clock(); //開始時間
{
my_th = myOtsu(src);
threshold(src, my_dst, my_th, 255, CV_THRESH_BINARY);
}
long my_finish = clock(); //結束時間
long my_t = my_finish - my_start;
printf("The run time is:%9.3lf\n", my_t, "ms!\n"); //輸出時間
cout << "myOtsu threshold >> " << my_th << endl;
long otsu_start = clock(); //開始時間
{
th = threshold(src, otsu_dst, 0, 255, CV_THRESH_OTSU);
}
long otsu_finish = clock(); //結束時間
long t = my_finish - my_start;
printf("The run time is:%9.3lf\n", (double)t / CLOCKS_PER_SEC, "ms!\n"); //輸出時間
cout << "Otsu threshold >> " << th << endl;
subtract(otsu_dst, my_dst, sub);//兩圖像相減
imshow("src", src);
imshow("myOtsu", my_dst);
imshow("Otsu", otsu_dst);
imshow("Sub", sub);
waitKey();
system("pause");
return 0;
}
原始圖像:
自己最大類間方差法得到的二值圖像
opencv自帶的ostu方法得到的二值圖像
補充:根據最佳閾值對圖像進行二值化