【图像处理OpenCV(C++版)】——5.4 图像平滑之中值平滑(滤波)

前言

😊😊😊欢迎来到本博客😊😊😊

🌟🌟🌟 本专栏主要结合OpenCV和C++来实现一些基本的图像处理算法并详细解释各参数含义,适用于平时学习、工作快速查询等,随时更新。

😊😊😊 具体食用方式:可以点击本专栏【OpenCV快速查找(更新中)】–>搜索你要查询的算子名称或相关知识点,或者通过这篇博客👉通俗易懂OpenCV(C++版)详细教程——OpenCV函数快速查找(不断更新中)]查阅你想知道的知识,即可食用。

🎁🎁🎁支持:如果觉得博主的文章还不错或者您用得到的话,可以悄悄关注一下博主哈,如果三连收藏支持就更好啦!这就是给予我最大的支持!😙😙😙


学习目标

  • 了解中值平滑含义
  • 熟悉中值原理
  • C++实现中值平滑案例

  每一张图像都可能包含某种程度的噪声,噪声可以理解为由一种或者多种原因造成的灰度值的随机变化。
  在大多数情况下,通过平滑技术(也常称为滤波技术)进行抑制或者去除,其中具备保持边缘(Edge Preserving)作用的平滑技术得到了更多的关注。
  常用的平滑处理算法包括基于二维离散卷积高斯平滑、均值平滑,基于统计学方法的中值平滑,具备保持边缘作用的平滑算法的双边滤波、导向滤波等。

  下面将详细介绍中值平滑技术原理、常见应用及实现。


一、中值平滑原理

1.1 相关概念

  中值平滑,类似于卷积,也是一种邻域运算,但计算的不是加权求和,而是对邻域中的像素点按灰度值进行排序,然后选择该组的中值作为输出的灰度值

1.2 原理

  假设输入图像为I,高为R、宽为C,对于图像中的任意位置(r,c),0≤r≤R,0≤c≤C,取以(r,c)为中心、宽为W、高为H的邻域,其中WH均为奇数,对邻域中的像素点灰度值进行排序,然后取中值,作为输出图像O(r,c)位置处的灰度值。

  例如如下图像矩阵:

  取以位置(1,1)为中心的3×3邻域,对邻域中的像素点灰度值按从小到大进行排序:

  可以看出141是该组灰度值的中值,那么输出图像O(1,1)=141,依此类推,会得到输出图像的所有像素点的灰度值。当然,对边缘处的处理和前几章学的卷积运算一样,可采用多种策略,而对边界进行镜像补充是较为理想的一种选择

1.3 作用

  在图像处理中,中值滤波最重要的能力是去除椒盐噪声,常用来保护边缘信息,是经典的平滑噪声的方法,该方法法对消除椒盐噪音非常有效,椒盐噪声是指在图像传输系统中由于解码误差等原因,导致图像中出现孤立的白点或者黑点

二、C++实现

  接下来介绍中值平滑的实现及其效果。

2.1 函数介绍

  OpenCV并没有提供直接计算中数的函数,可以利用另外两个函数sort()reshape()间接计算中数。我们先了解下这两个函数:

  1、sort()

void cv::sort(InputArray src,
			OutputArray dst,
			int flags 
)	
参数解释
src输入单通道矩阵
dst输出矩阵,大小和数据类型与src一致
flags排序规则,CV_SORT_EVERY_ROW:对每行排序;CV_SORT_EVERY_COLUMN :对每列排序;CV_SORT_ASCENDING:升序排序;CV_SORT_DESCENDING :降序排序

  2、reshape()

  reshape()函数比较有意思,它既可以改变矩阵的通道数,又可以对矩阵元素进行序列化,非常有用的一个函数。

reshape	(int cn,
		int rows = 0 
)	

  这个函数参数比较少,但设置的时候却要千万小心。

参数解释
cn表示通道数(channels), 如果设为0,则表示保持通道数不变,否则则变为设置的通道数
rows表示矩阵行数。如果设为0,则表示保持原有的行数不变,否则则变为设置的行数

  
  对于取邻域的中值的方法,需要利用Mat的成员函数reshape()将矩阵变为一行或者一列,然后使用sort()函数进行排序,最后取中间位置的数即为中数,例如:

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include <cmath>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;
int main(int argc, char** argv){
	Mat ImgMatrix = (Mat_<float>(3, 3) << 1, 2, 3, 4, 5, 6,7,8,9);
	
	//将ImgMatrix转为1行,通道数不变
	Mat ImgMatrix_R = ImgMatrix.reshape(1,1);

	//进行排序
	Mat dst;
	cv::sort(ImgMatrix_R,dst,CV_SORT_EVERY_ROW);

	//获取中值
	float medianvalue=dst.at<float>(0, (dst.cols-1) / 2);

	cout << "median value is:" << medianvalue << endl;

	waitKey(0);

	return 0;
	
}

2.2 具体实现

  对于图像的中值平滑,为了省去判断边界的问题,需要对输入的图像矩阵进行扩充边界的操作。具体代码如下:

Mat medianSmooth(const Mat &Image, Size size, int borderType = BORDER_DEFAULT){

	CV_Assert(Image.type() == CV_8UC1);
	int H = size.height;
	int W= size.width;
	
	//窗口的高、宽均为奇数,一般设置两者是相同的
	CV_Assert(H>0 && W>0);
	CV_Assert(H%2==1 && W%2== 1);

	//对原图像矩阵进行边界扩充
	int h=(H - 1)/2;
	int w=(W-1)/2;
	Mat ImagePadding;
	copyMakeBorder(Image, ImagePadding, h, h, w, w, borderType);
	//输入图像的高、宽
	int rows = Image.rows;
	int cols = Image.cols;
	//中值平滑后的输出图像
	Mat medianI(Image.size(),CV_8UC1);
	int i=0,j=0;
	//中数的位置
	int index=(H*W - 1)/ 2;
	for (int r=h;r< h+rows; r++)
	{
		for (int c=w;c<w+cols; c++)
		{
			//取以当前位置为中心、大小为 size 的邻域
			Mat region = ImagePadding(Rect(c-w,r-h, W,H)).clone();
	
			//将该邻域转换成行矩阵
			region = region.reshape(1,1);
	
			//排序
			cv::sort(region,region,CV_SORT_EVERY_ROW);
	
			//取中数
			uchar mValue = region.at<uchar>(0,index);
			medianI.at<uchar>(i,j) = mValue;
			j++;
		}
		i++;
		j=0;
	}
	return medianI;
}

  上述函数只能处理8位图,其他数据类型与之类似。利用该函数对图像进行中值平滑的主函数代码如下:

int main(int argc, char** argv) {
	cv::Mat src = cv::imread("D:/VSCodeFile/OpenCV_CSDN/image/logo_gray.jpeg");
	CV_Assert(!src.empty());
	src.convertTo(src, CV_8UC1);

	//中值滤波
	Mat medianImage=medianSmooth(src,Size(5,5));

	imshow("src", src);
	imshow("medianImage", medianImage);

	waitKey(0);

	return 0;
}

  一般来说,如果图像中出现较亮或者较暗的物体,若该物体大小小于中值平滑的窗口半径,那么它们基本上会被滤掉,而较大的目标则几乎会原封不动地保存下来。因此,中值平滑的窗口尺寸需要根据所遇到的不同问题而进行相应的调整

  中值平滑需要对邻域中的所有像素点按灰度值排序,一般比卷积运算要慢,有一些算法能够加速中值平滑。在OpenCV中提供了这样的函数:

void cv::medianBlur(InputArray src,
		OutputArray dst,
		int ksize 
)	
参数解释
src输入矩阵
dst输出矩阵,其大小与数据类型和src一致
ksize大于1且为奇数的核大小

  对于上述代码,这里仅需几行即可解决:

```cpp
int main(int argc, char** argv) {
	cv::Mat src = cv::imread("D:/VSCodeFile/OpenCV_CSDN/image/logo_gray.jpeg");
	CV_Assert(!src.empty());
	src.convertTo(src, CV_8UC1);

	//中值滤波
	Mat dst;
	cv::medianBlur(src, dst, 5);
	

	imshow("src", src);
	imshow("dst", dst);

	waitKey(0);

	return 0;
}

  中值平滑只是排序统计平滑中的一种,如果将取邻域的中值变为取邻域中的最小值或者最大值 ,显然会使图像变暗或者变亮。这类方法就是后面要介绍的形态学处理的基础。

  高斯平滑、均值平滑在去除图像噪声时,会使图像的边缘信息变得模糊,后面将介绍在图像平滑处理过程中可以保持边缘的平滑算法:双边滤波和导向滤波。


三、 总结

  最后,长话短说,大家看完就好好动手实践一下,切记不能三分钟热度、三天打鱼,两天晒网。OpenCV是学习图像处理理论知识比较好的一个途径,大家也可以自己尝试写写博客,来记录大家平时学习的进度,可以和网上众多学者一起交流、探讨,有什么问题希望大家可以积极评论交流,我也会及时更新,来督促自己学习进度。希望大家觉得不错的可以点赞、关注、收藏。


🚶🚶🚶 今天的文章就到这里啦~
喜欢的话,点赞👍、收藏⭐️、关注💟哦 ~
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cqy阳

预祝上岸,感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值