OpenCV4(C++) —— Mat类

文章目录

  • 前言
  • 一、初识Mat类
  • 二、Mat类的创建(构造)与赋值
    • 1、类的常规三种构造(默认、有参、拷贝)
      • (1)默认构造
      • (2)输入矩阵尺寸和数据类型——有参构造
      • (3)利用已有矩阵构造——拷贝构造
    • 2、赋值方式
      • (1)构造时给每个通道赋值
      • (2)给通道内每个元素赋值
      • (3)使用成员函数赋值


前言

在python的opencv中,也就是导入:import cv2。使用的是numpy和内置ndarray来存储和处理图像数据。而对于C++的opencv是没有ndarray的,也没有numpy库,而是使用cv::Mat类来表示图像数据与进行处理的。

python是一种动态类型语言,无需显示的指定变量的类型。直接使用imread返回的就是ndarray对象,后续使用numpy库可以直接对其进行相关图像操作。

import  cv2
import  numpy as np
image = cv2.imread("lena.jpg")
print(type(image)) # 
print(image.shape) # h,w,c

C++是一种静态类型语言,任何变量都需要显示地指定一个数据类型。而对于图像这种多维数组来说,就定义了Mat类来管理。

#include   
#include  
using namespace std;

int main()
{
    cv::Mat image = cv::imread("lena.jpg");
    int h = image.rows;  // row为高度
    int w = image.cols;  // col为宽度
    int ch = image.channels();  // 通道
    cout << h << " " << w <<" "<< ch << endl;
}

一、初识Mat类

  Mat类分为矩阵头指向存储数据的矩阵指针。矩阵头中包含矩阵的尺寸、存储方法、地址和引用次数等,只存放这几个固定的数据类型,所以矩阵头所占空间是固定的。图像复制和传递过程中主要的开销是存放矩阵数据。
  一般的赋值操作,只是复制矩阵头和存放矩阵数据的指针,两者还是指向同一矩阵数据(浅拷贝),若需克隆一份新的矩阵数据,则要深拷贝。(在python中也是如此)

int main()
{
    cv::Mat image1   // 矩阵头
    image1 = cv::imread("lena.jpg");  // 矩阵指针将指向该矩阵像素
    cv::Mat image2 = image1;  // 浅拷贝
    cv::Mat image3 = image1.clone();  // 深拷贝
}

注:Mat类利用自动内存管理技术解决了内存自动释放的问题,当变量不再需要时立即释放内存。
  当发生A浅拷贝B时,两种指向同一矩阵数据。只删除A,B仍然指向该矩阵数据;当A、B都删除时,才会释放该内存。能够这么做的原因是上面说到的矩阵头中的引用次数,复制一次,引用次数+1,删除一次,引用次数-1,当引用次数为0时才会释放内存(发现了吗,和智能指针shared_ptr一样的原理)。

(2)图像数据类型
  C++中,内置的数据类型有int,float,char等。我们指定,不同的编译平台,位数会发生变化。所以OpenCV中根据数值变量存储位数长度重新命名了新的数据类型,如下表:

数据类型 所代表类型与取值范围
CV_8U 8位无符号整数(0-——255)
CV_8S 8位符号整数(-128——127)
CV_16U 16位无符号整数(0——65535)
CV_16S 16位符号整数(-32768——32767)
CV_32S 32位无符号整数
CV_32F 32位浮点整数

  图像的单个灰度像素值为(0-255),所以CV_8U是最常用的。但图像还有RGB通道之分,所以OpenCV还定义了通道标识符:C1、C2、C3、C4,来分别表示单通道、双通道、三通道和四通道。为此,完整的图像数据类型为:CV_8UC3,表示8位的三通道数据

二、Mat类的创建(构造)与赋值

1、类的常规三种构造(默认、有参、拷贝)

(1)默认构造

  Mat可以进行默认构造,这种方式不需要输入任何的参数,在后续给变量赋值的时候会自动判断矩阵的类型与大小,实现灵活的存储,常用于存储读取的图像数据或某个函数运算的输出结果

cv::Mat image;
image = cv::imread("lena.jpg"); 

cv::Mat zero_roi;
cv::inRange(mask2, 0, 0, zero_roi); //cv::inRange(输入图像,下界,上界,输出图像);

(2)输入矩阵尺寸和数据类型——有参构造

常规语法:

//cv::Mat image(rows, cols, type)
cv::Mat image(600, 400, CV_8UC3);  // 创建一个高600,宽400,3通道的8位无符号矩阵数据

还有一种方式是采用cv::Size()进行赋值
cv::Size 类可以方便地指定图像的尺寸,例如创建一个具有特定宽度和高度的图像,或者在图像处理过程中获取图像的尺寸信息。但要注意,Size()里的高、宽与常规语法是相反的,宽在前,高在后。

//cv::Size size(cols, rows);
cv::Size size(400, 600);
cv::Mat image(size, CV_8UC3);  // 创建一个高600,宽400,3通道的8位无符号矩阵数据

(3)利用已有矩阵构造——拷贝构造

  默认的拷贝是浅拷贝,若想创建一份不会影响到原数据的,需要用clone()来进行深拷贝,常用在函数参数上

cv::Size size(400, 600);
cv::Mat image1(size, CV_8UC3);
cv::Mat image2 = image1;  //浅拷贝
cv::Mat image3 = image1.clone(); // 深拷贝
//cv::Mat image3(image1.clone) 拷贝构造的另一种写法,隐式构造

//当要对图片传入函数中进行处理,而又不会影响到原图
cv::Mat func(cv::Mat& images)
{
	cv::Mat img_input;
	ima_input = images.clone();
	......
	return xxx;
}

利用已有矩阵构造,还可以进行类似截图的操作,可以采用cv::Range或cv::Rect。注意cv::Rect里的高和宽与默认相反,先定义宽,再定义高。

// 1、Range指定行(高)和列(宽)的范围。下例是0到299行,共300行;0到399列,共400列
cv::Mat image2 = image1(cv::Range(0, 300), cv::Range(0, 400)); 
// cv::Mat image2(image1, (cv::Range(0, 300), cv::Range(0, 400)); 隐式构造

//2、Rect指定左上角坐标和矩形的宽度和高度来定义区域
cv::Mat image2(image1, cv::Rect(0, 0, 400, 300));//左上角左边(0,0),宽400,高300

2、赋值方式

上面讲述了多种构造方式,但只是创建了对象,还没有数据值赋给它。OpenCV4给予了多种赋值方式。

(1)构造时给每个通道赋值

  在构造时,加上从cv::Scalar()直接赋值。这种方式是赋给一个通道的所有相同数据,如单通道时cv::Scalar(0),表示全部赋值0;三通道时,cv::Scalar(0,0,0),表示三个通道全都赋值为0。另外一提,彩色图片在OpenCV中默认三通道的顺序是B、G、R。但是,在用这种方式赋值时,实际应用中大多数都是为了得到一个全黑或全白的矩阵,来用作后续处理。

cv::Mat image(600, 400, CV_8UC3, cv::Scalar(0,0,0));  // 全黑的3通道

(2)给通道内每个元素赋值

   (1)中的方式是赋给一个通道(矩阵)相同的数据,也可以给矩阵内每一个元素进行赋值,如枚举、循环、数组。枚举的个数要与矩阵元素个数相同,所以此方法一般用在矩阵数据比较少的情况。但一般图像数据都较大,所以在实际应用中很少使用。

(3)使用成员函数赋值

   Mat类中,自定义了可以初始化的矩阵,如eys,ones,zeros,diag,来生成单位矩阵、对角矩阵等。很少用,这种类型的矩阵一般都是为了后续计算而创建,所以通常采用张量tensor来创建这些矩阵。如: auto zero_tensor = torch::zeros_like(image); //创建一个与image大小相同的全零张量zero_tensor

你可能感兴趣的:(opencv,opencv,人工智能,计算机视觉)