OpenCV C++记录(九):二值化与图像模糊(滤波)算法
二值化
全局阈值
将灰度图二值化是图像处理基本操作,比较简单: 1
2
3
4
5
6
7double 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
4Mat 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);
大津法是最经典的阈值分割算法,思想和实现都很简单,本质是考虑找到一个阈值将图像像素划分成前景像素、背景像素,这个分割使得前景和背景差别越大,代表二值化效果越好,在数学上量化为类间方差,表示为:
cv::threshold
提供的是一种全局阈值算法,即整个图像都会根据一个阈值二值化,虽然满足了大部分需求,但是对于复杂的需求这样的处理过于笼统,因此OpenCV也提供了局部阈值算法;
局部阈值
亦称自适应阈值: 1
2
3
4
5
6
7
8
9void 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
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
3cv::Mat blurPic;
cv::blur(rawPic,blurPic,Size(3,3));
cv::imshow("blurPic",blurPic);lowPass_kernel
算子是完全一致的。
另一种类似的滤波器是盒式滤波器:其中ddepth
代表图像位深(-1
保持与原图一致),ksize
代表卷积核大小,anchor
默认中心点,normalize
代表是否归一化(若true,效果同blur),borderType
是边值填充类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14void boxFilter(
cv::InputArray src,
cv::OutputArray dst,
int ddepth,
cv::Size ksize,
cv::Point anchor = cv::Point(-1, -1),
bool normalize = true,
int borderType = 4
)
example:
cv::Mat boxPic;
cv::boxFilter(rawPic, boxPic,-1,Size(3,3));
cv::imshow("boxPic", boxPic);
高斯模糊
高斯模糊的卷积核领域像素权重与距离中心点距离相关,服从二维正态分布:
这里的距离指的是欧式距离,一个3乘3的坐标关系:
当高斯核大小确定,假定sigma系数确定(例如
二者卷积就是中心像素的结果,以下若干种算法原理与此类似,只是卷积核计算方法不同。
高斯模糊函数:ksize
代表卷积核大小,必须为奇数,sigmaX
和sigmaY
分别定义了x和y方向的标准差,因为两个方向的分布是独立的,如果自主实现高斯核,完全可以先做X方向的高斯,再做Y方向的高斯以降低复杂度;
1
2
3
4
5
6
7
8
9
10
11
12
13void GaussianBlur(
cv::InputArray src,
cv::OutputArray dst,
cv::Size ksize,
double sigmaX,
double sigmaY = (0.0),
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方法,其默认计算方法为:
sigma
为0
或负数
时,会采取该方法计算;
另一种获得高斯模糊的方法是自动计算高斯核,通过cv::Mat cv::getGaussianKernel
:其中ksize
代表核大小(必须为奇数),sigma
代表标准差,为0时按上述经验公式自动计算,ktype=6
代表默认64F
精度:
1
2
3
4
5
6
7
8
9
10
11
12
13cv::Mat cv::getGaussianKernel(
int ksize,
double sigma,
int ktype = 6
)
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
void medianBlur(cv::InputArray src, cv::OutputArray dst, int ksize)
双边滤波
双边滤波是基于高斯滤波的改进,除了对欧氏距离进行加权平均,还引入了灰度差别的高斯分布,只有距离与中心像素接近,同时灰度与中心像素高度接近情况下,像素才具备高的权重,因此对细节尤其是边缘细节保留较好,而不会导致全局模糊。但也增加了运算时间:
1
2
3
4
5
6
7
8void bilateralFilter(
cv::InputArray src,
cv::OutputArray dst,
int d, //(should be odd)
double sigmaColor,
double sigmaSpace,
int borderType = 4
)sigmaSpace
(经验值1-20),还需要指定像素差异的标准差sigmaColor
(经验值10-100+),但都不支持自动计算,而卷积核大小d
为-1
时,会根据sigma
自行计算;
参考链接: