一、常规二值化
再图像处理中,常规的二值化方法是设置一个阈值,图像中大于阈值的变为255(或0),小于阈值的变为0(或255)。此方法是最简单的一种二值化方法。但适应性不强,有着局限性,比如每个人在使用时需要根据所用环境的不同而设置不同的阈值,为使用者带来不变,鉴于此,学者们发明了自适应二值化方法。
二、自适应二值化
顾名思义,自适应二值化能够根据图像的局部特征自动调整阈值,从而更好地适应图像中的局部变化。这种局部适应性使得自适应二值化在处理具有不同光照条件、对比度和纹理的图像时表现出色。通过自适应地选择阈值,自适应二值化能够增强图像的对比度,使图像中的细节更加清晰可见。这对于后续的图像分析和处理非常重要,因为它可以提高特征提取的准确性和可靠性。
2.1大律法
在自适应二值化中,一般都采用大律法的方向进行求解阈值。
原理:大律法中,记t为前景与背景的分割阈值,前景点数占图像比例为w0,平均灰度为u0;背景点数占图像比例为w1,平均灰度为u1。图像的总平均灰度为:u=w0u0+w1u1。从最小灰度值到最大灰度值遍历t,当t使得值g=w0(u0-u)²+w1(u1-u)²最大时t即为分割的最佳阈值。
步骤:大律法的实现主要包括以下步骤。首先,计算图像的直方图,即将图像所有的像素点按照0~255共256个bin,统计落在每个bin的像素点数量。然后,归一化直方图,即将每个bin中像素点数量除以总的像素点。接着,从0到255遍历每一个可能的阈值,计算前景和背景的类间方差。最后,选择使类间方差最大的阈值作为最佳阈值
2.2 python实例
void cv2.adaptiveThreshold(src, dst, maxValue, adaptiveMethod, thresholdType, blockSize, C)
参数解读:
- src:输入图像,通常为灰度图像。
- dst:输出图像,与源图像尺寸和类型相同。
- maxValue:用于设置二值化的最大值。
- adaptiveMethod:计算阈值的方法,可以是ADAPTIVE_THRESH_MEAN_C(邻域平均值减去常数C)或ADAPTIVE_THRESH_GAUSSIAN_C(邻域高斯加权和减去常数C)。
- thresholdType:二值化类型,可以是THRESH_BINARY(超过阈值设置为maxValue,否则为0)或THRESH_BINARY_INV(超过阈值设置为0,否则为maxValue)。
- blockSize:用于计算阈值的邻域大小,必须是奇数且大于1。
- C:从均值或加权和中减去的常数,影响阈值的大小。
2.3 C++示例
void cv::adaptiveThreshold(
cv::InputArray src, // 输入灰度图像
cv::OutputArray dst, // 输出二值图像
double maxValue, // 高于阈值的像素值
int adaptiveMethod, // 自适应阈值算法
int thresholdType, // 阈值类型
int blockSize, // 邻域大小
double C // 从均值中减去的常数
);
src: 输入灰度图像。
dst: 输出二值图像。
maxValue: 高于阈值的像素值,通常为255。
adaptiveMethod: 自适应阈值算法,可以是cv::ADAPTIVE_THRESH_MEAN_C(根据邻域均值确定阈值)或cv::ADAPTIVE_THRESH_GAUSSIAN_C(根据邻域加权平均确定阈值)。
thresholdType: 阈值类型,通常为cv::THRESH_BINARY或cv::THRESH_BINARY_INV,分别表示二值化和反二值化。
blockSize: 邻域大小,用于计算自适应阈值。
C: 从均值中减去的常数,可以调整阈值的灵敏度。
三、自适应二值化进阶版
上述二值化针对的图像对象均为前景+背景两类图片,但假如图像含有前景、中景与背景三类且将前景与背景二值化同一种,将中景二值化为另一种,此时采用内置二值化函数则无效,基于此,博主根据大律法原理写出了适用于此种情况的自适应二值化方法,代码如下:
//自适应找间断点
int therr(Mat Rois1)
{
int ones,twos;
double var, stds, q1, q2, q3;//q为自适应时三个区域比重
double minVal, maxVal;//定义G通道最小值 最大值
Point minLoc, maxLoc;
minMaxLoc(Rois1, &minVal, &maxVal, &minLoc, &maxLoc);
//初始256位的全像素最小值数组 用来存放存在的像素 间隔点在这里选择
int ing_min = static_cast<int>(minVal); //G通道最小值
int ing_max = static_cast<int>(maxVal);//G通道最大值
int initial[256] = { ing_min };
for (int i = 0; i < 256; i++)
{
initial[i] = minVal;
}
//更新保存存在的数组且将二维变一维
//G通道顺序排列 定义变长的数组
//cout <<"minVal:"<<minVal<<" maxVal:"<<maxVal<<endl;
int len = Rois1.cols * Rois1.rows;//二维展开后一维数组长度
//qDebug() << "len长度:" << len << endl;
vector<int> array(len);//声明变长数组
for (int i = 0; i < (Rois1.rows); i++)
{
for ( int j = 0; j < (Rois1.cols); j++)
{
//更新保存存在元素的数组
unsigned int temp = (i * Rois1.cols + j);
initial[(Rois1.data[temp + 1])] = Rois1.data[temp + 1];
//二维图像变一维数组
array[i * Rois1.cols + j] = Rois1.data[temp + 1];
}
}
//qDebug()<< "排序开始:" << endl;
//元素进行从小到大排列 公式法
sort(array.begin(), array.end());
//qDebug() << "排序完毕:" << endl;
vector <int> ::iterator it;
vector <int> ::iterator jt;
//qDebug()<< "计算间断点:" << endl;
//利用分割点计算方差
//(int ing_min,int ing_max,int *p_array,int initial[256]) 像素最小 像素最大 像素数组 存在像素的数组(256位)
for (int i = ing_min + 1; i < ing_max; i = i + 3)
{
for (int j = i + 4; j < ing_max + 1; j = j + 5)
{
int ii = initial[i], jj = initial[j];
if ((jj < ii) || (ii == 0))
continue;
//array = array;
//int one = find_index(array, 0, len - 1, ii);
//int two = find_index(array, 0, len - 1, jj);
it = std::find(array.begin(), array.end(), ii); //find找到的值 索引位置:it - array.begin()
jt = std::find(array.begin(), array.end(), jj); //find找到的值 索引位置:jt - array.begin()
//对数组三分类
double one = it - array.begin(), two = jt - array.begin();
vector<int> data_1(one);//声明变长数组1
vector<int> data_2(two - one);//声明变长数组2
vector<int> data_3(len - two);//声明变长数组3
//复制1
for (int i = 0; i < one; i++)
{
data_1[i] = array[i];
}
//cout << "前复制完毕:" << endl;
//复制2
for (int i = one; i < two; i++)
{
data_2[i - one] = array[i];
}
//cout << "中复制完毕:" << endl;
//复制3
for (int i = two; i < len; i++)
{
data_3[i - two] = array[i];
}
//cout << "后复制完毕:" << endl;
//一
double sum = 0;
double std_1;
for (int i = 0; i < one; i++)
{
if (i == 0)
{
sum = data_1[i];
}
else
{
sum = sum + data_1[i];
}
}
double ave_1 = sum / one;
//cout << "ave_1:" << ave<< endl;
for (int i = 0; i < one; i++)
{
if (i == 0)
{
sum = (data_1[i] - ave_1) * (data_1[i] - ave_1);
}
else
{
sum = sum + (data_1[i] - ave_1) * (data_1[i] - ave_1);
}
}
q1 = one / len;
std_1 = sum * q1 / one;
//cout << "std_1:" << std_1 << endl;
//二
double std_2;
for (int i = 0; i < (two - one); i++)
{
if (i == 0)
{
sum = data_2[i];
}
else
{
sum = sum + data_2[i];
}
}
double ave_2 = (sum / (two - one));
//cout << "ave_2:" << ave<< endl;
for (int i = 0; i < (two - one); i++)
{
if (i == 0)
{
sum = (data_2[i] - ave_2) * (data_2[i] - ave_2);
}
else
{
sum = sum + (data_2[i] - ave_2) * (data_2[i] - ave_2);
}
}
q2 = (two - one) / len;
std_2 = sum * q2 / (two - one);
//cout << "std_2:" << std_2 << endl;
//三
double std_3;
for (int i = 0; i < (len - two); i++)
{
if (i == 0)
{
sum = data_3[i];
}
else
{
sum = sum + data_3[i];
}
}
double ave_3 = (sum / (len - two));
//cout << "ave_2:" << ave<< endl;
for (int i = 0; i < (len - two); i++)
{
if (i == 0)
{
sum = (data_3[i] - ave_3) * (data_3[i] - ave_3);
}
else
{
sum = sum + (data_3[i] - ave_3) * (data_3[i] - ave_3);
}
}
q3 = (len - two) / len;
std_3 = sum * q3 / (len - two);
//cout << "std_3:" << std_3 << endl;
stds = std_3 + std_2 + std_1;
//cout << "std:" << std << endl;
//迭代间隔与最小类内方差 其中间隔可以用i j也可以用one two置换 但one是位置索引 需要array[ones]得到分解值
if (i == ing_min + 1)
{
ones = i;
twos = j;
var = stds;
}
else
{
if (stds < var)
{
ones = i;
twos = j;
var = stds;
}
}
//间隔点 第一个:array[ones] 第二个:array[twos]
//cout << i << " " << j << " " << ones << " " << twos << " " << var << endl;
}
}
return ones, twos;
}
//利用间断点去二值化
Mat Binarization(Mat img)
{
Mat Rois[3], Rois0, Rois1;
split(img, Rois);//彩图通道分离 int channels = image.channels();
Rois1 = Rois[1];//分离第二个通道
//三分类 划分出间断点
int ones,twos =therr( Rois1);
//根据间断点二值化
Mat mask1, mask2, Mask;
threshold(Rois1, mask1, ones, 255, THRESH_BINARY_INV);
threshold(Rois1, mask2, twos, 255, THRESH_BINARY);
Mask = mask1 + mask2;
return Mask;
}