OpenCV中值滤波与均值滤波原理及C++实现

栗子
栗子
发布时间:2026-10-15 | 4人浏览过
原创

本文详细解析OpenCV中两种基础图像滤波技术:均值滤波与中值滤波。通过C++代码实例对比两者在处理椒盐噪声时的效果,揭示均值滤波易导致图像模糊的缺陷,以及中值滤波在保留边缘细节方面的优势,为图像预处理提供实用参考。

在深入探讨具体算法之前,首先明确滤波的基本定义。

图像滤波是图像预处理的核心环节,旨在尽可能保留图像细节特征的同时抑制噪声。其处理质量直接决定后续图像处理与分析的准确性与可靠性。

左侧展示原始图像,右侧为添加噪声后的图像。

通过平滑化或滤波操作消除图像噪声是常见需求。由于信号或图像能量主要集中于幅度谱的低频和中频段,高频部分常被噪声淹没,因此降低高频成分幅度的滤波器可有效减弱噪声影响。图像滤波主要目的包括:提取对象特征以用于图像识别,以及消除数字化过程中混入的噪声。对滤波处理的要求主要有两点:一是不能损坏图像轮廓及边缘等重要信息;二是使图像清晰,视觉效果良好。

平滑滤波属于低频增强的空间域滤波技术,其目的分为两类:一是实现图像模糊效果;二是消除噪声。

空间域平滑滤波通常采用简单平均法,即计算邻近像元点的平均亮度值。邻域大小直接影响平滑效果:邻域越大,平滑效果越显著,但过大的邻域会导致边缘信息损失加剧,使输出图像模糊,因此需合理选择邻域大小。

可以将滤波器形象地比喻为一个包含加权系数的窗口。使用滤波器处理图像时,相当于将该窗口置于图像之上,透过该窗口观察所得的图像。

滤波在生活中的一个典型应用是美颜软件中的磨皮功能。若将面部瑕疵比作噪声,滤波算法的作用便是去除这些噪声,使自拍皮肤看起来更加光滑。

本文将重点介绍中值滤波与均值滤波这两种算法。

一、均值滤波

在图像的一个方块区域(通常为3x3)内,中心点的像素值被替换为该区域内所有点像素值的平均值。均值滤波即对整张图片执行上述操作。

可通过下图的矩阵进行直观理解。

均值滤波存在固有缺陷:无法很好地保护图像细节。在去噪的同时,它也会破坏图像细节,导致图像模糊,且难以有效去除噪声点,尤其是椒盐噪声。

具体实现代码如下:

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include<ctime>
using namespace cv;
using namespace std;

//均值滤波
void AverFiltering(const Mat &src,Mat &dst) {
	if (!src.data) return;
	//at访问像素点
	for (int i = 1; i<src.rows; ++i)
		for (int j = 1; j < src.cols; ++j) {
			if ((i - 1 >= 0) && (j - 1) >= 0 && (i + 1)<src.rows && (j + 1)<src.cols) {//边缘不进行处理
				dst.at<Vec3b>(i, j)[0] = (src.at<Vec3b>(i, j)[0] + src.at<Vec3b>(i - 1, j - 1)[0] + src.at<Vec3b>(i - 1, j)[0] + src.at<Vec3b>(i, j - 1)[0] +
					src.at<Vec3b>(i - 1, j + 1)[0] + src.at<Vec3b>(i + 1, j - 1)[0] + src.at<Vec3b>(i + 1, j + 1)[0] + src.at<Vec3b>(i, j + 1)[0] +
					src.at<Vec3b>(i + 1, j)[0]) / 9;
				dst.at<Vec3b>(i, j)[1] = (src.at<Vec3b>(i, j)[1] + src.at<Vec3b>(i - 1, j - 1)[1] + src.at<Vec3b>(i - 1, j)[1] + src.at<Vec3b>(i, j - 1)[1] +
					src.at<Vec3b>(i - 1, j + 1)[1] + src.at<Vec3b>(i + 1, j - 1)[1] + src.at<Vec3b>(i + 1, j + 1)[1] + src.at<Vec3b>(i, j + 1)[1] +
					src.at<Vec3b>(i + 1, j)[1]) / 9;
				dst.at<Vec3b>(i, j)[2] = (src.at<Vec3b>(i, j)[2] + src.at<Vec3b>(i - 1, j - 1)[2] + src.at<Vec3b>(i - 1, j)[2] + src.at<Vec3b>(i, j - 1)[2] +
					src.at<Vec3b>(i - 1, j + 1)[2] + src.at<Vec3b>(i + 1, j - 1)[2] + src.at<Vec3b>(i + 1, j + 1)[2] + src.at<Vec3b>(i, j + 1)[2] +
					src.at<Vec3b>(i + 1, j)[2]) / 9;
			}
			else {//边缘赋值
				dst.at<Vec3b>(i, j)[0] = src.at<Vec3b>(i, j)[0];
				dst.at<Vec3b>(i, j)[1] = src.at<Vec3b>(i, j)[1];
				dst.at<Vec3b>(i, j)[2] = src.at<Vec3b>(i, j)[2];
			}
		}
}
//图像椒盐化
void salt(Mat &image, int num) {
	if (!image.data) return;//防止传入空图
	int i, j;
	srand(time(NULL));
	for (int x = 0; x < num; ++x) {
		i = rand() % image.rows;
		j = rand() % image.cols;
		image.at<Vec3b>(i, j)[0] = 255;
		image.at<Vec3b>(i, j)[1] = 255;
		image.at<Vec3b>(i, j)[2] = 255;
	}
}
void main() {
	Mat image = imread("路飞.jpg");

	Mat Salt_Image;
	image.copyTo(Salt_Image);
	salt(Salt_Image, 3000);

	Mat image1(image.size(), image.type());
	Mat image2;
	AverFiltering(Salt_Image, image1);
	blur(Salt_Image, image2, Size(3, 3));//openCV库自带的均值滤波函数
	imshow("原图", image);
	imshow("自定义均值滤波", image1);
	imshow("openCV自带的均值滤波", image2);
	waitKey();
}

处理效果如下图所示:

从结果可见,图片变得模糊,且噪声并未被有效去除,该算法主要起到了模糊图片的作用。

二、中值滤波

首先回顾中值的概念。在一组数字如{1, 4, 6, 8, 9}中,数字6即为中值。将此概念应用于图像处理:选取3x3矩阵,包含9个像素点,将这9个像素值进行排序,并将矩阵中心点的值赋为这九个像素的中值。

相关实现代码:

//求九个数的中值
uchar Median(uchar n1, uchar n2, uchar n3, uchar n4, uchar n5,
	uchar n6, uchar n7, uchar n8, uchar n9) {
	uchar arr[9];
	arr[0] = n1;
	arr[1] = n2;
	arr[2] = n3;
	arr[3] = n4;
	arr[4] = n5;
	arr[5] = n6;
	arr[6] = n7;
	arr[7] = n8;
	arr[8] = n9;
	for (int gap = 9 / 2; gap > 0; gap /= 2)//希尔排序
		for (int i = gap; i < 9; ++i)
			for (int j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j -= gap)
				swap(arr[j], arr[j + gap]);
	return arr[4];//返回中值
}

//图像椒盐化
void salt(Mat &image, int num) {
	if (!image.data) return;//防止传入空图
	int i, j;
	srand(time(NULL));
	for (int x = 0; x < num; ++x) {
		i = rand() % image.rows;
		j = rand() % image.cols;
		image.at<Vec3b>(i, j)[0] = 255;
		image.at<Vec3b>(i, j)[1] = 255;
		image.at<Vec3b>(i, j)[2] = 255;
	}
}

//中值滤波函数
void MedianFlitering(const Mat &src, Mat &dst) {
	if (!src.data)return;
	Mat _dst(src.size(), src.type());
	for(int i=0;i<src.rows;++i)
		for (int j=0; j < src.cols; ++j) {
			if ((i - 1) > 0 && (i + 1) < src.rows && (j - 1) > 0 && (j + 1) < src.cols) {
				_dst.at<Vec3b>(i, j)[0] = Median(src.at<Vec3b>(i, j)[0], src.at<Vec3b>(i + 1, j + 1)[0],
					src.at<Vec3b>(i + 1, j)[0], src.at<Vec3b>(i, j + 1)[0], src.at<Vec3b>(i + 1, j - 1)[0],
					src.at<Vec3b>(i - 1, j + 1)[0], src.at<Vec3b>(i - 1, j)[0], src.at<Vec3b>(i, j - 1)[0],
					src.at<Vec3b>(i - 1, j - 1)[0]);
				_dst.at<Vec3b>(i, j)[1] = Median(src.at<Vec3b>(i, j)[1], src.at<Vec3b>(i + 1, j + 1)[1],
					src.at<Vec3b>(i + 1, j)[1], src.at<Vec3b>(i, j + 1)[1], src.at<Vec3b>(i + 1, j - 1)[1],
					src.at<Vec3b>(i - 1, j + 1)[1], src.at<Vec3b>(i - 1, j)[1], src.at<Vec3b>(i, j - 1)[1],
					src.at<Vec3b>(i - 1, j - 1)[1]);
				_dst.at<Vec3b>(i, j)[2] = Median(src.at<Vec3b>(i, j)[2], src.at<Vec3b>(i + 1, j + 1)[2],
					src.at<Vec3b>(i + 1, j)[2], src.at<Vec3b>(i, j + 1)[2], src.at<Vec3b>(i + 1, j - 1)[2],
					src.at<Vec3b>(i - 1, j + 1)[2], src.at<Vec3b>(i - 1, j)[2], src.at<Vec3b>(i, j - 1)[2],
					src.at<Vec3b>(i - 1, j - 1)[2]);
			}
			else
				_dst.at<Vec3b>(i, j) = src.at<Vec3b>(i, j);
		}
	_dst.copyTo(dst);//拷贝
}


void main() {
	Mat image = imread("路飞.jpg");

	Mat Salt_Image;
	image.copyTo(Salt_Image);
	salt(Salt_Image, 3000);

	Mat image3, image4;
	MedianFlitering(Salt_Image, image3);
	medianBlur(Salt_Image, image4, 3);
	imshow("自定义中值滤波处理后", image3);
	imshow("openCV自带的中值滤波", image4);
	waitKey();
}

处理效果如下图所示:

结果显示,椒盐噪声得到了很好的平滑处理,且不像均值滤波那样导致严重的模糊效果。

至此,关于OpenCV中值滤波与均值滤波的代码实现介绍完毕。如需了解更多相关内容,请搜索本站过往文章或继续阅读后续文章。感谢支持!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表