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方法得到的二值图像
补充:根据最佳阈值对图像进行二值化