Opencv学习笔记(1)cv::Mat

:cv::Mat数据机构,它是程序库中的关键部件,用来操作图像和矩阵(从计算机和数学的角度看,图像其实就是矩阵),它采用了很巧妙的内存管理机制。在开发程序时,会经常用到这个数据结构,因此有必要熟悉它。
  图片:puppy.png
Opencv学习笔记(1)cv::Mat_第1张图片
  可以用下面的程序来测试:cv::Mat数据机构的不同属性

#include 
#include 
#include 

//测试图像,它创建一幅图像
cv::Mat function() {
	//创建图像
	cv::Mat ima(500, 500, CV_8U, 50);
	//返回图像
	return ima;
}
int main(int argc, char* argv[]) {
	//创建一个240行x320列的新图像
	cv::Mat image1(240, 320, CV_8U, 100);
	cv::imshow("Image", image1);  //显示图像
	cv::waitKey(0);  //等待按键

	//重新分配一个新图像
	image1.create(200, 200, CV_8U);
	image1 = 200;

	cv::imshow("Imgae", image1); //显示图像
	cv::waitKey(0);  //等待按键

	//创建一个红色的图像
	//通道次序为BGR
	cv::Mat image2(240, 320, CV_8UC3, cv::Scalar(0, 0, 255));
	
	//或者
	//cv::Mat image2(cv::Size(320, 240), CV_8UC3);
	//image2 = cv::Scalar(0, 0, 255);

	cv::imshow("Imgae", image2); //显示图像
	cv::waitKey(0);  //等待按键

	//读入一幅图像
	cv::Mat image3 = cv::imread("puppy.png");

	//所有这些图像都指向同一个数据块
	cv::Mat image4(image3);
	image1 = image3;  //浅复制

	//这些图像是源图像的副本图像
	image3.copyTo(image2);            //copyto做深复制,目标图像会调用create方法
	cv::Mat image5 = image1.clone();  //clone创建一个完全相同的新图像

	//转换图像进行测试
	cv::flip(image3, image3, -1);

	//检查哪些图像在处理过程中受到了影响
	cv::imshow("Imgae3", image3); //显示图像
	cv::imshow("Imgae1", image1); //显示图像
	cv::imshow("Imgae2", image2); //显示图像
	cv::imshow("Imgae4", image4); //显示图像
	cv::imshow("Imgae5", image5); //显示图像
	cv::waitKey(0);  //等待按键

	//从函数中读取一个灰度图像
	cv::Mat gray = function();
	//当局部变量ima超出作用范围后,ima会被释放。但从相关引用计数器可以看到,另一个实例(即变量gray)引用了ima内部的图像数据,因此ima的内存块不会被释放

	cv::imshow("Imgae", gray); //显示图像
	cv::waitKey(0);  //等待按键

	//作为灰度图像输入
	image1 = cv::imread("HappyFish.jpg", CV_LOAD_IMAGE_GRAYSCALE);
	//如果你需要把一幅图像复制到另一幅图像中,且两者的数据类型不一定相同,那就要使用convertTo方法了(这两幅图像的通道数量必须相同)
	image1.convertTo(image2, CV_32F, 1/255.0, 0.0);

	cv::imshow("Imgae", image2); //显示图像
	cv::waitKey(0);  //等待按键
	return 0;
}

运行这个程序,你将得到下面这些图像
Opencv学习笔记(1)cv::Mat_第2张图片

实现原理

:cv::Mat有两个必不可少的组成部分:一个:头部和一个:数据块。头部包含了矩阵的所有相关信息(大小、通道数量、数据类型等),cv::Mat 头部文件的某些属性(例如通过使用 cols 、 rows 或 channels 访问)。数据块包含了图像中所有像素的值。头部有一个指向数据块的指针,即:data属性。 cv::Mat 有一个很重要的属性,即只有在明确要求时,内存块才会被复制。实际上,:大多数操作仅仅复制了 cv::Mat 的头部,因此多个对象会指向同一个数据块。这种内存管理模式可以提高应用程序的运行效率,避免内存泄漏,但是我们必须了解它带来的后果。
  新创建的 cv::Mat 对象默认大小为 0,但也可以指定一个初始大小,例如:
  // 创建一个 240 行×320 列的新图像

 cv::Mat image1(240,320,CV_8U,100);

我们需要指定每个矩阵元素的类型,这里用:CV_8U 表示每个像素对应 1字节(灰度图像),用字母 U 表示无符号;你也可用字母 S 表示有符号。对于彩色图像,你应该用三通道类型(:CV_8UC3),也可以定义 16 位和 32 位的整数(有符号或无符号),例如 CV_16SC3 。我们甚至可以使用 32位和 64位的浮点数(例如 CV_32F )。
  图像(或矩阵)的每个元素都可以包含多个值(例如彩色图像中的三个通道),因此 OpenCV引入了一个简单的数据结构:cv::Scalar ,用于在调用函数时传递像素值。该结构通常包含一个或三个值。如果要创建一个彩色图像并用红色像素初始化,可用如下代码:
  // 创建一个红色图像
  // 通道次序是 BGR

cv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));

与之类似,初始化灰度图像可这样使用这个数据结构: cv::Scalar(100) 。
  图像的尺寸信息通常也需要传递给调用函数。我们可以用属性 :cols和 :rows来获得 cv::Mat 实例的大小。 :cv::Size结构包含了矩阵高度和宽度,同样可以提供图像的尺寸信息。另外,可以用:size()方法得到当前矩阵的大小。当需要指明矩阵的大小时,很多方法都使用这种格式。
  例如,可以这样创建一幅图像:
  // 创建一个未初始化的彩色图像

cv::Mat image2(cv::Size(320,240),CV_8UC3);

可以随时用:create方法分配或重新分配图像的数据块。:如果图像已被分配,其原来的内容会先被释放。出于对性能的考虑,如果新的尺寸和类型与原来的相同,就不会重新分配内存
  // 重新分配一个新图像
  //(仅在大小或类型不同时)

image1.create(200,200,CV_8U);

一旦没有了指向 cv::Mat 对象的引用,分配的内存就会被自动释放。这一点非常方便,因为它避免了 C++动态内存分配中经常发生的内存泄漏问题。这是 OpenCV(从第 2版开始引入)中的一个关键机制,它的实现方法是:通过 cv::Mat 实现计数引用和浅复制。因此,当在两幅图像之间赋值时,图像数据(即像素)并不会被复制,此时两幅图像都指向同一个内存块。这同样适用于图像间的值传递或值返回。由于维护了一个引用计数器,因此:只有当图像的所有引用都将释放或赋值给另一幅图像时,内存才会被释放
  // 所有图像都指向同一个数据块

  cv::Mat image4(image3);
  image1= image3;

对上面图像中的任何一个进行转换都会影响到其他图像。如果要对图像内容做一个深复制,你可以使用:copyTo 方法,目标图像将会调用 create 方法。另一个生成图像副本的方法是:clone,即创建一个完全相同的新图像:
  // 这些图像是原始图像的新副本

  image3.copyTo(image2);
  cv::Mat image5= image3.clone();

在例子中,我们对 image3 做了修改。其他图像也包含了这幅图像,有的图像共用了同一个图像数据,有的图像则有图像数据的独立副本。查看显示的图像,找出哪些图像因修改image3 而产生了变化。
  如果你需要把一幅图像复制到另一幅图像中,且两者的数据类型不一定相同,那就要使用:convertTo方法了:
  // 转换成浮点型图像[0,1]

image1.convertTo(image2,CV_32F,1/255.0,0.0);

本例中的原始图像被复制进了一幅浮点型图像。这一方法包含两个可选参数:缩放比例和偏移量。需要注意的是,这两幅图像的通道数量必须相同。
  cv::Mat 对象的分配模型还能让程序员安全地编写返回一幅图像的函数(或类方法):

  cv::Mat function() {
  // 创建图像
  cv::Mat ima(240,320,CV_8U,cv::Scalar(100));
  // 返回图像
  return ima;
  }
  我们还可以从 main 函数中调用这个函数:
  // 得到一个灰度图像
  cv::Mat gray= function();

运行这条语句后,就可以用变量 gray 操作这个由 function 函数创建的图像,而不需要额外分配内存了。正如前面解释的,从 cv::Mat 实例到灰度图像实际上只是进行了一次浅复制。:当局部变量 ima 超出作用范围后, ima 会被释放。但是从相关引用计数器可以看出,另一个实例(即变量 gray )引用了 ima 内部的图像数据,因此 ima 的内存块不会被释放。
  请注意,在使用类的时候要特别小心,不要返回图像的类属性。下面的实现方法很容易引发错误:

class Test {
  // 图像属性
  cv::Mat ima;
  public:
  // 在构造函数中创建一幅灰度图像
  Test() : ima(240,320,CV_8U,cv::Scalar(100)) {}
  // 用这种方法回送一个类属性,这是一种不好的做法
  cv::Mat method() { return ima; }
  };

如果某个函数调用了这个类的 method ,就会对图像属性进行一次浅复制。副本一旦被修改,class 属性也会被“偷偷地”修改,这会影响这个类的后续行为(反之亦然)。这违反了面向对象编程中重要的封装性原理。为了避免这种类型的错误,你需要将其改成返回属性的一个副本。

你可能感兴趣的:(Opencv,opencv_contrib)