一幅图像包括目标、背景及噪声,想要直接提取出目标物体,通常采用灰度变换阈值化操作。图像的阈值化操作就是利用图像像素点分布规律,设定阈值进行像素点分割,进而得到二值化图像。图像阈值化操作方法有很多,常用经典的有OTUS、固定阈值、自适应阈值、双阈值及半阈值化操作。
<1>OTUS
其算法步骤如下:
(1)统计灰度级中每一个像素在整幅图像中的个数
(2)计算每个像素在整幅图像的概率分布
(3)对灰度级进行遍历搜索,计算当前灰度值下前景背景类间概率
(4)通过目标函数计算出类内与类间方差下对应的阈值
e.g:
#include <stdio.h>
#include <string>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;
int OTUS(Mat srcImage)//Image应该是一个8U的图像
{
int nCols = srcImage.cols;
int nRows = srcImage.rows;
int threshold = 0;
//初始化统计参数
int nSumPix[256];//像素个数
float nProDis[256];//概率分布
for (int i = 0; i < 256; i++)
{
nSumPix[i] = 0;
nProDis[i] = 0;
}
//统计灰度级中每个像素在整幅图像中的个数
for (int i = 0; i < nCols; i++)
{
for (int j = 0; j < nRows; j++)
{
nSumPix[(int)srcImage.at<uchar>(i, j)]++;
}
}
//统计每个灰度级占图像中的概率分布
for (int i = 0; i < 256; i++)
{
nProDis[i] = (float)nSumPix[i] / (nCols * nRows);
}
//遍历灰度级[0,255],计算出最大类间方差下的阈值
float w0, w1, u0_temp, u1_temp, u0, u1, delta_temp;
double delta_max = 0.0;
for (int i = 0; i < 256; i++)
{
w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;
for (int j = 0; j < 256; j++)
{
//背景部分
if ( j <= i)
{
//当前i为分割阈值,第一类总概率
w0 += nProDis[j];
u0_temp += j * nProDis[j];
}
//前景部分
else
{
//当前i为分割阈值,第一类总概率
w1 += nProDis[j];
u1_temp += j * nProDis[j];
}
}
//分别计算各类的平均灰度
u0 = u0_temp / w0;
u1 = u1_temp / w1;
delta_temp = (float)( w0 * w1 * pow((u0 - u1), 2));
//依次找到最大类间方差下的阈值
if (delta_temp > delta_max)
{
delta_max = delta_temp;
threshold = i;
}
}
return threshold;
}
int main()
{
Mat srcImage = imread("u=497076178,1248191674&fm=26&gp=0.jpg");
if (!srcImage.data)
{
return -1;
}
Mat srcGray;
cvtColor(srcImage, srcGray,CV_RGB2GRAY);
imshow("srcGray", srcGray);
int outThreshold = OTUS(srcGray);
std::cout << outThreshold << std::endl;
Mat otusResultImage = cv::Mat::zeros(srcGray.rows, srcGray.cols,CV_8UC1);
//调用OTUS函数
for (int i = 0; i < srcGray.rows; i++)
{
for (int j = 0; j < srcGray.cols; j++)
{
if (srcGray.at<uchar>(i,j) > outThreshold)
{
otusResultImage.at<uchar>(i, j) = 255;
}
else
{
otusResultImage.at<uchar>(i, j) = 0;
}
}
}
imshow("otusResultImage", otusResultImage);
waitKey(0);
return 0;
}
PS:这个程序有个bug没找到,有哪个大神愿意试一下说一下问题的
<2>其他阈值化
opencv中提供的阈值化为 double threshold(InputArray src,Output dst,double thresh,double maxval,int type);
其中参数src表示源图像,dst表示输出图像,thresh表示阈值设置,maxval表示预设最大值,使用THRESH_BINARY或THRESH_BINARY_INY类型;type表示阈值化处理的类型设置。
PS:该函数应用在单通道图像中固定阈值化处理,通常是为了得到二值化图像或除去噪声。
对于THRESH_BINARY二进制阈值化为
对于THRESH_BINARY_INV反二进制阈值化为
对于THRESH_TRUNC截断阈值化为
对于THRESH_TOZERO阈值化为0为
对于THRESH_TOZERO_INV反阈值化为0为
对于双阈值化为
对于半阈值化为
<3>自适应阈值化
在opencv中,其函数名为adaptiveThreshold.
void adaptiveThreshold(InputArray src,OutputArray dst, double maxval,int adaptiveMethod,int thresholdTpye,int blocksize,double C);
PS:大部分人对这个常数C很懵,我们下面来具体讲解
其中src表示源图像;dst表示输出图像;maxval表示预设满足条件的最大值;adaptiveMethod表示自适应阈值算法的选择,一共有两种;ThresholdType表示阈值类型;参数blockSize表示邻域块大小;C表示常数。
首先blockSize必须为奇数(3,5,7……),这个拿来做掩膜(PS:后面再讲),这个参数小则具有边缘检测的效果,大则达到二值化效果。
现在我们来看看常数C到底干嘛的。PS:有的帖子说是控制边缘的类型和粗细。
上面提到了自适应阈值化的两种方法,其分别为ADAPTIVE_THRESH_MEAN_C和ADAPTIVE_THRESH_GAUSSIAN_C
对于ADAPTIVE_THRESH_MEAN_C,其数学运算为:领域块内所有像素-blocksize*常数C,其阈值是区域内的均值。
对于ADAPTIVE_THRESH_GAUSSIAN_C,其数学运算为:blocksize*((领域块内所有像素-C)交叉相关高斯窗的加权总和),其阈值是加权平均值,权重是区域内的高斯值,权重随着距离减小。