3.5.1均值滤波
1.数学原理
概念:“把每个像素都用周围的8个像素来做均值操作 ”
作用:平滑图像的用处, 有的图像的锐度很高,用这样的均值算法,可以把锐度降低。使得图像看上去更加自然
两种3x3的平滑滤波器(线性滤波):3x3平均滤波器R=1/9 ∑Zi------滤波器模板系数都为1; 3x3加权滤波器R=1/16∑Zi,滤波器模板系数不同,中心点影响最大。
均值滤波就是将滤波器模板和图像像素进行卷积。
需要注意的是滤波模板(卷积核)必须为奇数
2.实现代码
第一;opencv自带的均值滤波函数为
void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
参数解释:
. InputArray src: 输入图像,可以是Mat类型,图像深度是CV_8U、CV_16U、CV_16S、CV_32F以及CV_64F其中的某一个。
. OutputArray dst: 输出图像,深度和类型与输入图像一致
. Size ksize: 滤波模板(卷积核)kernel的尺寸,一般使用Size(w, h)来指定,如Size(3,3)
. Point anchor=Point(-1, -1): 字面意思是锚点(卷积核的中心点),也就是处理的像素位于kernel的什么位置,默认值为(-1, -1)即位于kernel中心点,如果没有特殊需要则不需要更改
. int borderType=BORDER_DEFAULT: 用于推断图像外部像素的某种边界模式,有默认值BORDER_DEFAULT
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
#include <vector>
#include <opencv2/imgproc/imgproc.hpp>
#include<cmath>
using namespace std;
using namespace cv;
int main()
{
Mat src, dst;
src = imread("c:/users/征途/desktop/vs-cpp/project3/04.jpg");
blur(src,dst, Size(3, 3));
imshow("src", src);
imshow("dst", dst);
waitKey(0);
return 0;
}
第二,自己用c++实现均值滤波函数
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
#include <vector>
#include <opencv2/imgproc/imgproc.hpp>
#include<cmath>
using namespace std;
using namespace cv;
void myblur(const Mat& src, Mat& dst, Size ksize)
{
//若模板不是奇数模板,则报错退出
if (ksize.width % 2 == 0 || ksize.height % 2 == 0)
{
cout << "please input odd ksize" << endl;
exit(-1);
}
//根据ksize大小扩充模板边界
int awidth = (ksize.width - 1) / 2; //公式中的a
int aheight = (ksize.height - 1) / 2;// 公式中的b
Mat asrc;
//图像边界扩充函数copyMakeBorder,void copyMakeBorder( const Mat& src, Mat& dst,int top, int bottom, int left, int right,int borderType, const Scalar& value = Scalar() );
copyMakeBorder(src, asrc, aheight, aheight, awidth, awidth, BORDER_DEFAULT);
//根据图像通道数遍历图像求均值
//通道数为1
if (src.channels() == 1)
{
for (int i = 0+aheight; i < src.rows + aheight; i++) // i < 扩充后图片高
{
for (int j = 0+awidth; j < src.cols + awidth; j++)
{
int sum = 0; //sum是成绩和
int mean = 0; //mean是均值
for (int k = i - aheight; k <= i + aheight; k++)
{
for (int l = j - awidth; l <= j + awidth; l++)
{
sum += asrc.at<uchar>(k, l); //模板系数和像素乘积的和,采用的是3x3都为1的平均滤波模板
}
}
mean = sum / (ksize.width*ksize.height); // 乘积和/模板系数和
dst.at<uchar>(i - aheight, j - awidth) = mean;
}
}
}
//通道数为3
if (src.channels() == 3)
{
for (int i = aheight; i < src.rows + aheight; i++)
{
for (int j = awidth; j < src.cols + awidth; j++)
{
int sum[3] = { 0 };
int mean[3] = { 0 };
for (int k = i - aheight; k <= i + aheight; k++)
{
for (int l = j - awidth; l <= j + awidth; l++) //依次根据通道读取每个通道上的,模板系数和像素乘积的和
{
sum[0] += asrc.at<Vec3b>(k, l)[0];
sum[1] += asrc.at<Vec3b>(k, l)[1];
sum[2] += asrc.at<Vec3b>(k, l)[2];
}
}
for (int m = 0; m < 3; m++) //依次循环按照通道数将卷积后的结果赋予dst
{
mean[m] = sum[m] / (ksize.width*ksize.height);
dst.at<Vec3b>(i - aheight, j - awidth)[m] = mean[m];
}
}
}
}
}
int main()
{
Mat src, dst1,dst2;
src = imread("c:/users/征途/desktop/vs-cpp/project3/04.jpg");
//opencv自带函数
blur(src,dst1, Size(3, 3));
imshow("src", src);
imshow("dst1", dst1);
//自己编写的函数
dst2 = Mat::zeros(src.size(), src.type());
myblur(src, dst2, Size(3, 3));
imshow("dst2", dst2);
waitKey(0);
return 0;
}
结果:1为原图,2为系统自带函数结果,3为自己编写结果
3.5.2中值滤波
1.数学原理
先将掩膜中欲求的像素及其领域内的像素值排序,确定出中值,并将中值赋予给该像素点。
针对随机噪声,椒盐噪声效果好,可以保护图像尖锐边缘;对高斯噪声效果差。
应用:中值滤波对脉冲噪声(如椒盐噪声)的抑制十分有用。
缺点:易造成图像的不连续性
2.实现代码
实现的思路:用一个数组存储模板中所有的像素值,进行排序,取数组中的中间值赋值给当前像素点
1.opencv自带函数为medianBlur()
2.c++自己编写函数mymedianBlur()
代码实现:
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
#include <vector>
#include <opencv2/imgproc/imgproc.hpp>
#include<cmath>
using namespace std;
using namespace cv;
//首先进行编写冒泡排序算法
void bublle_sort(std::vector<int> &arr)
{
bool flag = true;//默认数组排序已经排好
for (int i = 0; i < arr.size() - 1; ++i)
{
while (flag = false) //如果排序错误,则执行循环体中的语句
{
for (int j = 0; j < arr.size() - 1 - i; ++j)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = true;
}
}
}
}
}
//用c++实现中值滤波
void mymedianBlur(cv::Mat& src, cv::Mat& dst, cv::Size ksize)//Mat &a的意思是创建一个矩阵a的引用
{
//图像边界扩充
if (ksize.width % 2 == 0 || ksize.height % 2 == 0) //卷积核要取奇数
{
fprintf(stderr, "Please enter odd size!");
exit(-1);
}
int hh = (ksize.height - 1) / 2;
int hw = (ksize.width - 1) / 2;
cv::Mat Newsrc;
cv::copyMakeBorder(src, Newsrc, hh, hh, hw, hw, cv::BORDER_REFLECT_101);//图像扩充,以边缘为轴,对称
dst = cv::Mat::zeros(src.rows, src.cols, src.type());
//编写中值滤波,先进行滑窗遍历图像来获取像素点像素
//首先遍历图像源
for (int i = hh; i < src.rows + hh; i++)
{
uchar* ptrdst = dst.ptr(i - hh); //滑窗内的ptrdst
for (int j = hw; i < src.cols + hw; j++)
{
std::vector<int> pix; //定义一个std内存中的容器,容器名叫pix,容器内的值为int类型,与数组类似,但是容器的大小可变
//使用滑窗算法遍历
for (int r = i - hh; r <= i + hh; r++) //定义滑窗的高
{
const uchar* ptrsrc = Newsrc.ptr(r); //指针取原图位于滑窗内的位置r处的元素
for (int c = j - hw; c <= j + hw; c++) // 滑窗的宽
{
pix.push_back(ptrsrc[c]); //在r的前提下,利用指针获取处的元素
}
}
//对读取出来的元素冒泡排序
bublle_sort(pix);
ptrdst[j - hw] = pix[(ksize.area() - 1) / 2];//将排序后的中值映射给输出图像
}
}
}
// 主函数应用
int main()
{
cv::Mat src = cv::imread("c:/users/征途/desktop/vs-cpp/project3/09中值.jpg");
Mat dst1;
Mat dst2;
cv::Size ksize(5, 5);
mymedianBlur(src, dst1, ksize);
medianBlur(src, dst2, (5,5));
imshow("my", dst1);
imshow("cv", dst2);
waitKey(0);
return 0;
}