【碎片知识(5)· 计算机视觉基础】自定义均值滤波器和OpenCV中blur平均滤波函数的对比

本文介绍了OpenCV中Mat类的基础知识,包括Mat类型的说明、初始化方式以及成员函数,如clone、convertTo和at等。此外,文章探讨了自定义均值滤波器与OpenCV提供的blur平均滤波函数的对比,强调了在图像处理中选择适当方法的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本篇的代码会用到关于Mat类的相关知识,开篇先对Mat类进行小结,对其中一些内容仔细推敲的理解可以防止debug中很多棘手问题的出现。

OpenCV中Mat类型的相关说明

OpenCV3中Mat类:

1.1         关于Mat类型的说明(信息来源于OpenCV手册):

OpenCV最早在内存中存放图像的方式为"IplImage"的C语言结构体,这将C语言中对于内存管理的缺点带入了进来,即用户需要为开辟和释放内存负责。于是引入C++中类的概念,即内存的自动管理。

“Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。因此,当在程序中传递图像并创建拷贝时,大的开销是由矩阵造成的,而不是信息头。

OpenCV是一个图像处理库,囊括了大量的图像处理函数,为了解决问题通常要使用库中的多个函数,因此在函数中传递图像是家常便饭。这意味着这些图像处理算法具有极大的计算量,因此,除非万不得已,不应该拷贝大的图像,因为这会降低程序速度。”

1.2         初始化

1)        Mat image = imread("图像路径”);用于直接读取一幅图像

2)        Mat image(9, 9, CV_8UC3, Scalar(0, 0, 255));这是标准的初始化格式,四个参数分别表示创建矩阵的高(rows)、宽(cols)、数据类型和通道、BGR值

  关于数据类型,很多OpenCV的函数支持的数据深度只有8位和32位,所以使用CV_64F需谨慎,但是vs的编译器又会把float数据自动变成double型。

  第四个参数一次设置各通道元素的值,一般rgb图像的数据类型都为8UC3,这边也需要注意:mat存储图像的通道顺序为BGR,所以上面代码第二行表示把这个9*9,3通道图像的R值都设为255,GB都为0。

CV_8U

8-bit unsigned integers ( 0 ~ 255 )

CV_8S

8-bit signed integers ( -128 ~ 127 )

CV_16U

16-bit unsigned integers ( 0 ~ 65535 )

CV_16S

16-bit signed integers ( -32768 ~ 32767 )

CV_32S

32-bit signed integers ( -2147483648 ~ 2147483647 )

CV_32F

32-bit floating-point numbers ( -FLT_MAX ~ FLT_MAX, INF, NAN )

CV_64F

CV_64F - 64-bit floating-point numbers ( -DBL_MAX ~ DBL_MAX, INF, NAN )

3)        image.create(100,60,CV_8UC(15));调用了create函数来进行初始化

1.3         Mat类成员函数

1)        clone函数image2 = image1.clone()将image1完全拷贝到image2,同时拷贝image1中的所有数据,且拷贝的矩阵是连续的

  注意:所谓完全拷贝,就是不仅拷贝头和指针,还会开辟一块新的内存,将数据也复制过来。而Mat image2 = image1;的方式仅拷贝了头和指针,所以image1和image2的头和指针都指向同一个内存数据,即,如果image1改变了,image2也会改变。

2)        convertTo函数image1.convertTo(image2,type, scale, offset);将image1中的元素转换成type类(CV_32F等),作scale尺度的缩放,offset偏移,写入image2中

3)        setTo函数image1.setTo(s,mask);将m0中所有的元素的值设为s,如果使用mask,则只设定mask中的非零元素

4)        reshape函数image1.reshape(chan,rows);改变二维矩阵的的实际形状,不进行数据拷贝,若chan或rows为0,则表示不作改变

5)        isContinue函数image1.isContinuous();如果image1所有的行在内存空间中打包时都没有间隙,则返回true

6)        type函数:image1.type();返回m0中元素的有效类型标识符(如CV_32FC3)

7)        total函数:image1.total();计算所有数组元素的个数,不考虑通道

8)        channels函数image1.channels();返回m0中元素的通道数目

9)        size、rows、cols函数

iamge1.size();以cv::Size对象的形式返回image的大小

iamge1.rows();返回image的行数。 

iamge1.cols();返回image的列数

10)     push_back函数m0.push_back(m1);对mxn矩阵作k行扩展,并将m1拷贝到这些行中;m1的大小为kxn

11)     at函数:image1.at<>(i, j)[c];用于访问矩阵内某点的值

  其中<>内应填入image1元素的数据类型,uchar对应的是CV_8U,char对应的是CV_8S,int对应的是CV_32S,float对应的是CV_32F,double对应的是CV_64F。

  若m0是多通道矩阵,则数据类型应为Vec3b,c为当前处理的通道序号。i,j,c都是从零开始的。

1.4         注意:

1)        Mat是C++的数据结构,如果在子函数内部定义了Mat,在该函数返回时会自动释放掉Mat的数据,所以不要想着通过取Mat的数据指针来传参。只能通过内部new一段内存,把Mat的数据逐个元素地扔进内存里。

2)        Mat的(i,j)是按(行,列)的规则,而图像中则是(高,宽);跟Size(x,y),Rect(x,y)的(x,y)是不同的,其对应的是长宽,即列数和行数

/* ******************************************* 【我是分割线】******************************************** */

以下是代码实现内容,首先声明鉴于作者水平有限,这个代码并不完美,如果读者发现相关问题,欢迎指正和讨论。具体的代码说明在注释中。

/* *******************************************************************************************
		任务目标:
				1. 使用OpenCV自带的均值滤波器对目标图片进行"均值滤波";
				2. 自行编写均值滤波函数对图像进行"均值滤波"处理,对比二者结果;
				3. 对图像增加"椒盐噪声",测试椒盐噪声对"均值滤波"的影响。
******************************************************************************************* */

#include <iostream>
#include <opencv2/opencv.hpp>
#include <ctime>
// 声明头文件,OpenCV包含很多类型头文件,如果不清楚具体头文件的区别,
// 直接对"opencv.hpp"进行整体声明将所有头文件囊括进来。
// "time.h"的作用是通过时间变量来改变随机生成函数的随机因子。

using namespace std;
using namespace cv;
// 命名空间"std"和"cv"的声明。

void AverageFiltering ( const Mat &src, Mat &dst, int mask_rows, int mask_cols );
// 自定义"均值滤波器",参数为(源图像,输出图像,掩膜行数,掩膜列数)。
void SaltandPepperNoise ( Mat &image );
// 自定义"椒盐噪声加入"函数模块,参数为(待处理图像)。

int main()
{
	//char *fn = "E:/Study/Computer vision/OpenCV Test Workshop/Avefiltering and SP Noise/Average-filtering and Salt-and-pepper Noise/baboon.jpg";
	Mat sourceimage = imread ("baboon.jpg");  // 源图像。
	// 用"imread"函数读取路径中的图像。
	
	Mat blurimage;
	// "OpenCV"自带均值滤波器的输出图像&自定义均值滤波器的输出图像。
	Mat averagefilteringimage(sourceimage.size(), sourceimage.type());

	int setting_rows, setting_cols;
	// 定义整型变量存放自"定义均值滤波器"掩膜的 行数(高、纵坐标"i")和 列数(宽、横坐标"j")。

	cout << "Input setting rows (height) for the mask of Average Filtering: " << endl;
	while (1)
	{
		cin >> setting_rows;  // 输入掩膜的高度。
		
		if (setting_rows % 2)
		{
			break;
		}
		else
		{
			cout << "Input an odd, please: " << endl;
		}  // 检测输入的行数是否为奇数。
	}
	cout << "Input setting columns (width) for the mask of Average Filtering: " << endl;
	while (1)
	{
		cin >> setting_cols;  // 输入掩膜的宽度。

		if (setting_cols % 2)
		{
			break;
		}
		else
		{
			cout << "Input an odd, please: " << endl;
		}  // 检测输入的列数是否为奇数。
	}

	blur(sourceimage, blurimage, Size(3, 3));  
	// 用OpenCV自带的均值滤波器"blur"函数处理源图像并存放到"blurimage"中。
	AverageFiltering(sourceimage, averagefilteringimage, setting_rows, setting_cols);
	// 用自定义均值滤波器处理源图像并存放在"aceragefilteringimage"中。

	Mat saltandpepperimage;  // 定义"saltandpepperimage"存放加入椒盐噪声的源图像。
	sourceimage.copyTo(saltandpepperimage);  
	// 复制sourceimage矩阵到saltandpepperimage中。

	SaltandPepperNoise(saltandpepperimage);
	// 用自定义的椒盐噪声添加函数处理源图像并存在"saltandpepperimage"中。

	Mat blurimage_sp, averagefilteringimage_sp;
	// 加入了椒盐噪声后分别用自带&自定义均值滤波器处理的输出图像。

	blur(saltandpepperimage, blurimage_sp, Size(3, 3));
	AverageFiltering(saltandpepperimage, averagefilteringimage_sp, setting_rows, setting_cols);
	// 自带&和自定义均值滤波器对椒盐噪声图像的处理。

	imshow("Source Image",sourceimage);  // 源图像。
	imshow("Blur Image without Salt_Pepper Noise", blurimage);
	// OpenCV自带均值滤波的处理结果。
	imshow("Average Filtering without Salt_Pepper Noise", averagefilteringimage);
	// 自定义均值滤波器的处理结果。
	imshow("Salt and Pepper Noise Image", saltandpepperimage);
	// 加入椒盐噪声的源图像。
	imshow("Blur Image with Salt_Pepper Noise", blurimage_sp);
	// OpenCV自带均值滤波处理椒盐噪声图像的结果。
	imshow("Average Filtering Image with Salt_Pepper Noise", averagefilteringimage_sp);
	
	waitKey();  // 防止闪退。
	
	return (0);
}

// 自定义均值滤波器,参数为(输入图像,输出图像,行数(高),列数(宽))。
void AverageFiltering(const Mat &src, Mat &dst, int mask_rows, int mask_cols)
{	
	if (!src.data)
	{
		cout << "Read source image failure in Average Filtering!!!" << endl;
		return;
	}  // 检验&src的地址是否读取成功。
	
	int i = 1, j, m, n;
	// 定义整型变量"i"和"j",用来指代中心像素的 第"i"行(纵坐标)& 第"j"列(横坐标)。
	// 定义"m"和"n"为掩膜内运算的 第"m"行(纵坐标)和 第"n"列(横坐标)的坐标。
	int sum_b, sum_g, sum_r;
	// 定义关于"BGR"的叠加辅助参数,这里使用数组会发生内存越界。
	int mask_n = mask_rows * mask_cols;  // "mask_n"是掩膜一共的像素数(高×宽)。
	
	// "src.rows"和"src.cols"是图像的 行数(高)和 列数(宽)。
	while ( i < src.rows )  // 当掩膜中心像素的 纵坐标(第"i"行)小于"src"的总行数(图像高度)。
	{
		j = 1;
		
		while ( j < src.cols )  // 掩膜中心像素的 横坐标(第"j"列)小于"src"的总列数(图像宽度)。
		{  
			sum_b = 0;
			sum_g = 0;
			sum_r = 0;  // 每移动一次中心像素位置,将叠加辅助参数清零一次。
			
			// 判断所取掩膜是否超出边界,如果在边界内,则进行均值滤波。
			if ( i >= (mask_rows - 1) / 2 && i < (src.rows - ((mask_rows - 1) / 2)) && j >= (mask_cols - 1) / 2 
				&& j < (src.cols - ((mask_cols - 1) / 2)) )
			{
				m = (i - ((mask_rows - 1) / 2));  // 对掩膜的辅助运算像素坐标赋初值。

				while (m <= (i + ((mask_rows - 1) / 2)))
				{
					n = (j - ((mask_cols - 1) / 2));
					
					while (n <= (j + ((mask_cols - 1) / 2)))
					{
						sum_b += src.at<Vec3b>(m, n)[0];  // 蓝色像素值累加。
						sum_g += src.at<Vec3b>(m, n)[1];  // 绿色像素值累加。
						sum_r += src.at<Vec3b>(m, n)[2];  // 红色像素值累加。
						n++;
					}
					m++;
				}

				dst.at<Vec3b>(i, j)[0] = sum_b / mask_n;  // 蓝色像素求均值。
				dst.at<Vec3b>(i, j)[1] = sum_g / mask_n;  // 绿色像素求均值。
				dst.at<Vec3b>(i, j)[2] = sum_r / mask_n;  // 红色像素求均值。
			}
			else  // 如果掩膜任一部分超出图像边界,则中心像素值保留原值。
			{  // OpenCV对RGB的存储顺序是BGR。
				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];  // 红色像素值。
			}
			j++;
		}
		i++;
	}
}

// 椒盐噪声添加函数。
void SaltandPepperNoise(Mat &image)
{
	if (!image.data)
	{
		cout << "Read image failure in Salt and Pepper Noise!!!" << endl;
		return;
	}  // 检验"src"首地址是否成功读取。

	srand((unsigned) time (NULL));  // "srand()"函数产生一个以当前时间开始的随机种子。

	int k = 0, i, j;
	int image_n = image.rows * image.cols;  // "image_n"指"image"的总像素数。
	int num = image_n / 100;  // "num"代表添加的噪声数量,此处选为源图像总像素的1/30。

	while (k < num)
	{
		i = rand() % image.rows;
		j = rand() % image.cols;  // "i"和"j"随机取从0到"image.rows"和"image.cols"的值。

		image.at<Vec3b>(i, j)[0] = 255;
		image.at<Vec3b>(i, j)[1] = 255;
		image.at<Vec3b>(i, j)[2] = 255;  // 对BGR三个分量都取最大像素值,所以全部是白噪点。
		
		k++;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值