Opencv的Mat内容小记

转载自知乎博主:逍遥王可爱   https://zhuanlan.zhihu.com/p/258369721

1.Mat是什么

       Mat是一种图像容器,是二维向量,灰度图的Mat一般存放<uchar>类型,RGB彩色图像一般存放<Vec3b>类型。

       单通道灰度图数据存放样式:

preview

 

RGB三通道彩色图存放形式不同,每列并列存放通道数量的子列(注意通道数量反转为了BGR):

       通常情况下Mat的每一行是连续存放的,也就是在内存上图像的所有数据存放成一行,在用指针访问时可以提供很大方便。可以用 isContinuous()函数来判断图像数组是否为连续的,语句为

if (img.isContinuous()){}

2.如何创建Mat对象

       Mat到底有哪些参数呢,OpenCV官方文档对Mat的描述如下:

class CV_EXPORTS Mat
{
public:
// ... a lot of methods ...
...
/*! includes several bit-fields:
- the magic signature
- continuity flag
- depth
- number of channels
*/
int flags;
//! the array dimensionality, >= 2
int dims;
//! the number of rows and columns or (-1, -1) when the array has more than 2 dimensions
int rows, cols;
//! pointer to the data
uchar* data;
//! pointer to the reference counter;
// when array points to user-allocated data, the pointer is NULL
int* refcount;
// other members
...
};

       当我们用构造函数创建一个Mat对象的时候,可以指定图像的数据类型有CV_8UC1、CV_8UC3、CV_32FC1、CV_32FC3等多种形式,如定义

Mat img(480, 640, CV_16UC1);

       就是创建一个640*480的16位深的单通道Mat对象。CV_8UC3自然就是最常见的8位3通道图(即255的RGB图像)。

       当然最常用的是用opencv读入一张本地图像,此时自动就是一个Mat对象,但有时我们需要像python里的numpy那样方便地转换Mat对象的数据类型,numpy里可以这样操作

img=img.astype(np.uint16)#此时转为了16位深的无符号整数类型

       在C++的opencv里,就是这样转为16位的无符号整数三通道的:

using namespace cv;
Mat Data = imread("xxxx.jpg");
Mat output;
Data.convertTo(output,CV_16UC3);//想覆盖原图的话直接Data,convertTo(Data,CV_16UC3);即可

       Mat也可以通过深拷贝创建一个新地址的Mat,避免影响原始的Mat:

Mat copy;
image.copyTo(copy);
//也可用下面的语句
Mat copy = image.clone();

       除此之外,python的numpy里经常采用如下的方式构建全1的矩阵:

import numpy as np
FULL_KERNEL_5 = np.ones((5, 5), np.uint8)

       那么c++里如何构建这样的Mat呢?

Mat kernel = Mat::ones(3,3,CV_8UC1);

       这样就创建了一个全为1的矩阵了(类似的可以zeros等等是一样的创建方法)。

       有时我们对图像进行一些像素值的缩放,也就是对某个图像进行全部元素的加减乘除,python里自然非常方便,如所有元素除以100,那么image / 100.0即可。c++里使用convertTo非常容易实现这点(不必for循环了)。

       如果我们要对矩阵A进行元素的加减乘除操作得到B,即B=a*A+b,则

A.convertTo(B,CV_32F,a,b);

       举个例子,刚刚提到的全部元素除以100那就是

A.convertTo(B,CV_32F,0.01,0.00);

       Mat也支持自己给值初始化:

Mat T_cam_in_O_g0 = (Mat_ <float>(1, 3) << 8, -2.60446658, -8.59534703);

       如果要把已存在的vector转为Mat,那怎么转呢?

       一维vector转Mat:

cv::Mat convertVector2Mat(vector<float> v, int rows) 
{     cv::Mat mat = cv::Mat(v);
      //将vector变成单列的mat     
      cv::Mat dest = mat.reshape(1, rows).clone();//PS:必须clone()一份,否则返回出错     
       return dest; 
}

       二维vector转Mat:

void two_d_vector2Mat(vector<vector<float>> src,Mat &dst)
{
    Mat temp(src.size(),src.at(0).size(),CV_32FC1);
    for(int i=0; i<temp.rows; ++i)
        for(int j=0; j<temp.cols; ++j)
            temp.at<float>(i, j) = src.at(i).at(j);
    temp.copyTo(dst);
}

3.Mat对象如何访问元素

       从Mat的类定义看,用data可以方便访问图像元素值。用Mat存储一幅图像时,若图像在内存中是连续存储的(即Mat对象的isContinuous == true),则可将图像的数据看成是一个一维数组,而其data(uchar*)成员就是指向图像数据的第一个字节的指针,因此可以用data指针访问图像数据,OpenCV中将data定义为uchar*类型。那么我们如何通过data指针去访问和修改图像的某一个像素值呢,对于数据为uchar类型的Mat对象,可以直接用data访问和修改,对于其他类型的Mat对象,将data强制转换成指向Mat对象对应数据类型的指针即可访问。

       可以把Mat对象方便地转为一维指针数组,便于指针操作(指针访问数组进行操作速度更快):

unsigned short* Array = (unsigned short*)Data.data;
delete[] Array;

       当然借用opencv里Mat类的智能指针最佳,这样即可auto data = Data.ptr();

       对这个数组进行了一系列图像处理操作后,如果要保存回Mat便于后续imshow和imwrite,保存方式可以这样:

Mat newData(480, 640, CV_16UC3, Array);

       当然这样是灵活的提供了自己创的指针,用了后还需要delete,为了方便可以用Mat自带的智能指针进行操作。

       这里举一个实例:如果我们只对像素值小于0.1的像素才执行膨胀操作(也就是为了填充0),python里搞一个mask就可以轻易实现

empty_pixels = (image < 0.1)
dilated = cv2.dilate(image)
depth_map[empty_pixels] = dilated[empty_pixels]

       在c++里则不然,用智能指针可以这样操作:(构建行智能指针数组)

 Mat depth_tmp;//初始化一个矩阵备用
 dilate(image, tmp);
 for (int k = 0; k < 480; k++)
    {
      auto *data1 = image.ptr<float>(k);//创建行智能指针
      auto *data2 = tmp.ptr<float>(k);
      for (int q = 0; q < 640; q++)
        {
           if (data1[q] < 0.1)//小于0.1的视作空像素(空洞)
            {
             data1[q] =  data2[q];//对于image里的的空洞,把tmp赋给image
            }
        }
    }

       这样使用智能指针也可以(这样可以操作每个元素了,但速度慢于上面的方法)

 for (int k = 0; k < 480; k++)
    {
      for (int q = 0; q < 640; q++)
        {
           auto *ptr = depthArray.ptr<uint16_t>(k, q);
           if (ptr[0] ==0)
            {
             ptr[0] = 10;
            }
        }
    }

       当然也可以不用指针,而是用at方法去读取任意的[i,j]位置(i为多少行,即y坐标,j为多少列,即x坐标)的像素值,但这个方法速度慢于指针读取的方法,depthArray.at<uint16_t>(i, j)。

4.Mat的计算

       转置:mat.t()

       求逆:cv::invert(matrix, mat_inv, DECOMP_LU);

       但这里invert得到的是double类型,转为float
       mat_inv.convertTo(mat_inv, CV_32FC1);

       矩阵乘法:A*B(当然A可以是矩阵也可是某个标量)

       矩阵取某行/列或者某些行/列:out_mat = mat.colRange(0,2).clone();//复制第0列和第1列(注意左闭右开)

       如果需要把某个Mat结果(这里是result)存入到float *out这样的指针当中,这时可以用memcpy

memcpy(out, result, sizeof(float) * result.rows * result.cols);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值