Otsu(最大类间差)算法思想:将图像分别用每个像素p点分割为前景区域背景区,计算在被像素点p分割的前景区域背景区的像素个数、平均像素,及像素比例,最后计算方差值,则取最大方差时的像素点p则为最佳阈值。
void skin_ThreshOtsu(Mat * src, Mat * mask)
{
Mat srcImg;
src->copyTo (srcImg);
Mat YCrCb_Img ;
cvtColor (srcImg, YCrCb_Img,CV_RGB2YCrCb );//将颜色空间从RGB转换为YCrCb
Mat Cr_Img;
vector <Mat> channels;
split(YCrCb_Img,channels);
Cr_Img = channels [2];
//threshold (Cr_Img ,Cr_Img, 0, 255, CV_THRESH_OTSU );//已实现的Otsu阈值分割
Mat otsu_Img;
Cr_Img.copyTo(otsu_Img);
//计算直方图
float histogram[256] = {0};//因计算灰度直方图,灰度范围在0~255
float pixel_sum = 0;//像素和
for (int i = 0; i< otsu_Img.rows; i++)
{
uchar * p = otsu_Img.ptr<uchar>(i);//获取第i行首行
for (int j = 0; j < otsu_Img.cols; j++)
{
histogram[(int)*p++] ++; // 在第i行的第p列时histogram的第p列加1;
}
}
long int sum = otsu_Img.size().height * otsu_Img.size().width;//总像素数
//迭代计算
int thresh = 0;//最佳阈值;
double maxvariance = 0;//最大类间方差
for (int i = 0; i < 256; i++)//i级灰度,遍历没一级像素i
{
long sum0 = 0, sum1 = 0;//存储前景灰度总和及背景灰度总和
double cnt0 = 0, cnt1 = 0;//存储前景灰度总个数及背景灰度总个数
double w0 = 0, w1 = 0;//存储前景灰度个数及背景灰度个数占总灰度个数的比值
double u0 = 0, u1 = 0;//存储前景及背景的平均灰度,灰度除以灰度个数
double variance = 0; // 最大类间方差
for (int j = 0; j < i; j++)//计算前景灰度, 0<j<i表示前景灰度,j> i 表示背景灰度
{
sum0 += j * histogram [j];
cnt0 += histogram [j];
}
w0 = cnt0 / sum;
u0 = sum0 / cnt0;
for (int j = i; j < 256; j++)//计算背景灰度
{
sum1 += j * histogram [j];
cnt1 += histogram [j];
}
w1 = 1-w0;
u1 = sum1 / cnt1;
variance = w0 * w1 * (u0 - u1) * (u0 - u1);
if (variance > maxvariance )
{
maxvariance = variance;
thresh = i;
}
}
threshold (otsu_Img, *mask, thresh, 255, CV_THRESH_BINARY );
imshow("cr_img", *mask );
//*mask = ~ Cr_Img ;
}
参考文献
https://blog.csdn.net/ap1005834/article/details/51452516
https://blog.csdn.net/onezeros/article/details/6136770
https://blog.csdn.net/qq_20823641/article/details/51480651