二值化

全局阈值

将灰度图二值化是图像处理基本操作,比较简单:

1
2
3
4
5
6
7
double threshold(
InputArray src, //输入灰度图
OutputArray dst, //输出二值图
double thresh, //分割阈值
double maxval, //最大阈值,是否生效与类型相关
int type //二值化类型
);
二值化类型包括:

  • THRESH_BINARY普通二值化,像素大于thresh的设为maxval,其余为0;
  • THRESH_BINARY_INV反二值化,像素大于thresh的设为0,其余为maxval;
  • THRESH_TRUNC截断二值化,像素大于thresh设置为thresh,其余不变;
  • THRESH_TOZERO零二值化,像素大于thresh则不变,其余为0;
  • THRESH_TOZERO_INV反的零二值化,像素大于thresh设置为0,其余不变;
  • THRESH_OTSU大津法,适用于双峰图像;
  • THRESH_TRIANGLE:根据灰度直方图分布确定最优阈值,适用于单峰图像(前景集中且单一);

最后两种算法是二值化的特别算法,只是为了找出图像的最佳分割阈值,因此经常和其他类型配合使用,例如:

1
2
3
4
Mat rawPic = imread("D:/Documents/Desktop/note/jLena10.jpeg",0);
Mat binary;
threshold(rawPic,binary,0,255,cv::THRESH_BINARY| cv::THRESH_OTSU);
imshow("test",binary);
此时可以使用double类型接收自动计算的最佳阈值,对于其他类型,返回值是设定的thresh;

大津法是最经典的阈值分割算法,思想和实现都很简单,本质是考虑找到一个阈值图像像素划分成前景像素背景像素,这个分割使得前景和背景差别越大,代表二值化效果越好,在数学上量化为类间方差,表示为: 其中表示背景像素比例表示背景像素的平均灰度表示前景像素比例表示前景像素的平均灰度表示图像的平均灰度。可见使用一次遍历(0到255),取得一个阈值使得取值最大,即为最大类间方差,对应阈值为最佳分割阈值

cv::threshold提供的是一种全局阈值算法,即整个图像都会根据一个阈值二值化,虽然满足了大部分需求,但是对于复杂的需求这样的处理过于笼统,因此OpenCV也提供了局部阈值算法;

局部阈值

亦称自适应阈值

1
2
3
4
5
6
7
8
9
void adaptiveThreshold(
cv::InputArray src, //输入灰度图
cv::OutputArray dst, //输出二值图
double maxValue, //最大灰度,同thresholdType有关
int adaptiveMethod, //cv::ADAPTIVE_THRESH_MEAN_C(均值法)或ADAPTIVE_THRESH_GAUSSIAN_C(高斯法)
int thresholdType, //二值化方法,cv::THRESH_BINARY等,但不能为大津法或灰度直方法
int blockSize, //块大小,必须为奇数(以确定中心像素
double C //微调阈值
)
局部阈值的确定采取了两种简单的方法,但求在局部计算中仍具有较高的计算速度:

  • cv::ADAPTIVE_THRESH_MEAN_C:计算blockSize平均灰度,该灰度减去C就是分割阈值

  • cv::ADAPTIVE_THRESH_GAUSSIAN_C:计算blockSize加权平均灰度,即还要考虑每个像素与中心像素的距离,减去C作为局部分割阈值;

局部阈值导致亮度高的地方保持高阈值、亮度低的地方保持低阈值,在背景提取、人像分割等一些场合适用。

图像模糊

二维卷积

亦称图像滤波、图像平滑等。卷积核在图像处理中有重要的应用,在边缘处理卷积核的作用是抑制边缘外(高频)的像素,实现边缘的提取,在图像模糊中则相反,因为图像的噪声多数与附近像素格格不入,卷积核则根据核中每个像素重新计算中心像素,弱化了偶发噪声的高频特性,实现去噪目的。

所以显而易见的,边缘提取的卷积核相当于一个高通滤波器图像平滑的卷积核相当于一个低通滤波器卷积核越大边缘提取越敏感、图像平滑越模糊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(){
Mat rawPic = imread("D:/Documents/Desktop/note/jLena10.jpeg",0);

cv::Mat highPass_kernel = (Mat_<double>(3,3)<< //拉普拉斯核
-1,-1,-1,
-1,8, -1,
-1,-1,-1
);
cv::Mat high;
cv::filter2D(rawPic,high,-1,highPass_kernel);
cv::imshow("highPass",high);

cv::Mat lowPass_kernel = (Mat_<double>(3,3)<<
1,1,1,
1,1,1,
1,1,1
)/9;
cv::Mat low;
cv::filter2D(rawPic,low,-1,lowPass_kernel);
cv::imshow("lowPass",low);

cout<<"Done"<<endl;
waitKey(0);
cv::destroyAllWindows();
return 0;
}
图像的低通和高通滤波器

均值滤波/盒式滤波

注意上面我们对lowPass_kernel进行了归一化,从卷积的计算原理可知,中心像素会被替换成邻域像素的加权和,如果不进行归一化,那么中心像素大概率突破255,成为一张白图;

因为lowPass_kernel卷积核的权值都是一样的,以上这种处理实际上是均值滤波,OpenCV提供了cv::blur而无需我们自定义算子,而且此算子已经归一化(卷积核权重和为1

1
2
3
cv::Mat blurPic;
cv::blur(rawPic,blurPic,Size(3,3));
cv::imshow("blurPic",blurPic);
效果和上述自定义lowPass_kernel算子是完全一致的。

另一种类似的滤波器是盒式滤波器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void boxFilter(
cv::InputArray src,
cv::OutputArray dst,
int ddepth, //输出图像位深,-1同src
cv::Size ksize, //卷积核大小
cv::Point anchor = cv::Point(-1, -1), //默认中心点
bool normalize = true, //是否归一化,true效果同blur
int borderType = 4 //边值填充方式
)

example:
cv::Mat boxPic;
cv::boxFilter(rawPic, boxPic,-1,Size(3,3));
cv::imshow("boxPic", boxPic);
盒式滤波器的灵活点在于提供了是否归一化、改变锚点等选择,在一些图像计算中更加灵活。

高斯模糊

高斯模糊的卷积核领域像素权重距离中心点距离相关,服从二维正态分布

这里的距离指的是欧式距离,一个3乘3的坐标关系: 坐标关系

当高斯核大小确定,假定sigma系数确定(例如),卷积核也就确定: 卷积核系数

二者卷积就是中心像素的结果,以下若干种算法原理与此类似,只是卷积核计算方法不同。

高斯模糊函数:ksize代表卷积核大小,必须为奇数sigmaXsigmaY分别定义了x和y方向的标准差,因为两个方向的分布是独立的,在OpenCV实现中先做了X方向的高斯,再做Y方向的高斯以降低复杂度,接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
void GaussianBlur(
cv::InputArray src,
cv::OutputArray dst,
cv::Size ksize, //卷积核大小
double sigmaX, //去噪系数,一般0.5到1.5
double sigmaY = (0.0), //去噪系数,一般0.5到1.5
int borderType = 4
)

example:
cv::Mat gusBlur;
cv::GaussianBlur(rawPic, gusBlur, Size(3, 3), 1, 1, cv::BORDER_DEFAULT);
cv::imshow("gusBlur",gusBlur);
当仅指定一个sigmaX另一个sigmaY也会设置为该值;而且,为了方便任何不具备高斯数学基础的用户,OpenCV提供了从卷积核ksize自动估算sigma方法,其默认计算方法为: 当传入的sigma0负数,会采取该方法计算;

对于sigma参数的推荐值一般在0.51.5间,用于去除一般的图像噪声,当需要产生明显模糊平滑效果(例如UI设计、风格生成等),则采用3到5乃至以上的取值

另一种获得高斯模糊的方法是自动计算高斯核,通过cv::Mat cv::getGaussianKernel:其中ksize代表核大小(必须为奇数),sigma代表标准差,为0时按上述经验公式自动计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
cv::Mat cv::getGaussianKernel(
int ksize,
double sigma,
int ktype = 6 //默认CV_64F精度
)

example:
cv::Mat x = cv::getGaussianKernel(3,0);
cv::Mat y = cv::getGaussianKernel(3,0).t();
cv::Mat gus = x * y;
cv::Mat gusPic;
cv::filter2D(rawPic, gusPic, -1, gus, Point(-1, -1), 0, cv::BORDER_DEFAULT);
cv::imshow("gusPic",gusPic);
从性能上看,GaussianBlur已经做了足够的优化,其分别对两个方向进行卷积运算,结果和高斯核的结果是大同小异的,由于精度问题等做不到输出完全相等的Mat。

中值滤波

上述策略均考虑灰度的加权平均情况,却对灰度原本的相似性漠不关心,因此图像效果一般是全局的模糊,使得中心像素细节大大弱化,中值滤波不会产生新的像素值,而是从邻域分布中取像素的中值作为中心像素像素值:ksize仍然要求奇数

1
2
3
4
void medianBlur(cv::InputArray src, 
cv::OutputArray dst,
int ksize //卷积核大小
)
这种效果是典型的油画风格,因为容易出现块状聚集的像素

双边滤波

双边滤波是基于高斯滤波的改进,除了对欧氏距离进行加权平均,还引入了灰度差别的高斯分布,只有距离与中心像素接近,同时灰度与中心像素高度接近情况下,像素才具备高的权重,因此对细节尤其是边缘细节保留较好,而不会导致全局模糊。但也增加了运算时间:

1
2
3
4
5
6
7
8
void bilateralFilter(
cv::InputArray src,
cv::OutputArray dst,
int d, //奇数卷积核大小,-1时根据两个sigma计算
double sigmaColor, //空间距离标准差,1-20
double sigmaSpace, //颜色差异标准差,10-100+
int borderType = 4
)
使用双边滤波主要原因是保留边缘信息以保留后续良好的边缘检测效果,这里考虑的空间距离标准差主要是考虑周围像素的范围大小,参数越大,更多像素参与平滑计算,图像平滑效果越好,但丧失细节也更大;颜色差异标准差则控制了多少范畴内将两个像素值看成类似,所以值越大,边缘的细节也丧失越多。

参考链接:

  1. 图像滤波算法总结

  2. 高斯模糊的算法

  3. OpenCV实现二维高斯核GaussianKernel