本篇的代码会用到关于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++;
}
}