OpenCV 4.x API 详解与C++实例-Mat数据类型详解

第二节 Mat数据类型详解

1、Mat数据类型描述

我们有多种从现实世界中获取数字图像的方法:数码相机,扫描仪,计算机断层扫描和磁共振成像等等。 在每种情况下,我们(人类)看到的都是图像。 但是,当将其转换为数字设备时,我们记录的是图像每个点的数值。如下图所示:

OpenCV 4.x API 详解与C++实例-Mat数据类型详解_第1张图片

例如,在上图中,您可以看到汽车的镜子不过是一个包含所有像素点强度值的矩阵。OpenCV中的Mat是一个N维稠密数组,或多通道数组。它可用于存储实值或复值矢量和矩阵,灰度或彩色图像,体素体积,矢量场,点云,张量,直方图(尽管非常高维的直方图可能更好地存储在SparseMat中)。数组M的数据布局由数组M.step []定义,以便元素$(i_0,\cdots,i_{M.dims-1}) $的地址,其中 0 ≤ i k < M . s i z e [ k ] 0\leq i_k0ik<M.size[k], 计算为:

a d d r ( M i 0 , ⋯   , i M . d i m s − 1 ) = M . d a t a + M . s t e p [ 0 ] ∗ i 0 + M . s t e p [ 1 ] ∗ i 1 + ⋯ + M . s t e p [ M . d i m s − 1 ] ∗ i M . d i m s − 1 addr(M_{i_0,\cdots,i_{M.dims-1}}) = M.data + M.step[0]*i_0 + M.step[1]*i_1 + \cdots + M.step[M.dims-1]*i_{M.dims-1} addr(Mi0,,iM.dims1)=M.data+M.step[0]i0+M.step[1]i1++M.step[M.dims1]iM.dims1

在二维数组的情况下,上述公式可简化为:

a d d r ( M i , j ) = M . d a t a + M . s t e p [ 0 ] ∗ i + M . s t e p [ 1 ] ∗ j addr(M_{i,j}) = M.data + M.step[0]*i + M.step[1]*j addr(Mi,j)=M.data+M.step[0]i+M.step[1]j

注意, M . s t e p [ i ] ≥ M . s t e p [ i + 1 ] M.step [i]\ge M.step [i + 1] M.step[i]M.step[i+1](实际上, M . s t e p [ i ] ≥ M . s t e p [ i + 1 ] ∗ M . s i z e [ i + 1 ] M.step [i]\ge M.step [i + 1] * M.size [i + 1] M.step[i]M.step[i+1]M.size[i+1])。 这意味着二维矩阵逐行存储,三维矩阵逐平面存储,依此类推。 M.step [M.dims-1]是最小的,并且始终等于元素大小M.elemSize()。

因此,Mat中的数据布局与标准工具包和SDK中的大多数密集数组类型兼容,例如Numpy(ndarray),Win32(独立设备位图)以及其他,也就是说,与使用步骤( 或跨步)以计算像素的位置。 由于这种兼容性,可以为用户分配的数据制作Mat头,并使用OpenCV函数就地对其进行处理。

2、Mat的基本操作

2.1 、Mat创建


OpenCV提供很多创建Mat的方法,常用的几种方法如下:

	// 1、创建一个3x3矩阵,float类型,单通道矩阵
    cv::Mat m1(3,3,CV_32FC1);
    for(int y = 0;y < 3;y++){
        for (int x = 0;x < 3;x++){
            m1.at(y,x) = (y+1) * (x+1);
        }
    }
    std::cout <<"m1 = \n"<< m1 << std::endl;

    // 2、重新创建矩阵,将m1的旧值删除,如果新矩阵比旧矩阵空间大,则重新分配空间
    m1.create(5,5,CV_32FC1);
    std::cout << "new m1 = \n" << m1 << std::endl;

    // 3、重新赋值
    m1.setTo(cv::Scalar(255.0));
    std::cout << "new m1 value = " << m1 << std::endl;

    // 4、创建一个3x3矩阵,float类型,3通道
    cv::Mat m2(3,3,CV_32FC3,cv::Scalar(255));
    std::cout << "m2 = \n" << format(m2, cv::Formatter::FMT_PYTHON) << std::endl;

    // 5、创建一个3x3矩阵,float类型,1通道
    cv::Mat m3(cv::Size(3,3),CV_32FC1);
    m3.setTo(cv::Scalar(255.0));
    std::cout << "m3 = \n" << m3 << std::endl;

    // 6、创建一个3x3矩阵,float类型,1通道,数组赋值
    float data[] = {1,2,3,4,5,6,7,8,9};
    cv::Mat m4(cv::Size(3,3),CV_32FC1,data);
    std::cout << "m4 = \n" << m4 << std::endl;

    // 7、创建一个3x3全零矩阵,1通道
    cv::Mat m5 = cv::Mat::zeros(cv::Size(3,3),CV_32FC1);
    std::cout << "m5 = \n" << m5 << std::endl;

    // 8、创建一个3x3的全1矩阵,1通道
    cv::Mat m6 = cv::Mat::ones(3,3,CV_32FC1);
    std::cout << "m6 = \n" << m6 << std::endl;

    // 9、创建一个3x3的单位矩阵,1通道
    cv::Mat m7 = cv::Mat::eye(3,3,CV_32FC1);
    std::cout << "m7 = \n" << m7 << std::endl;

	// 10、使用逗号分隔的初始化程序创建矩阵
    cv::Mat m8 = (cv::Mat_(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);
    std::cout << "m8 = \n" << m8 << std::endl;

2.2、Mat元素访问


1)通过at<>访问,单通道矩阵

// 1、元素访问,单通道
std::cout << "m4.at(1,1) = " << m4.at(1,1) << std::endl;

2)通过at<>访问,多通道矩阵

// 2、元素访问,多通道
int counts = 1;
cv::Mat m9(3,3,CV_32FC3);
for(int i = 0;i < 3;i++){
    for(int j = 0;j < 3;j++){
        for(int k = 0;k < 3;k++){
           m9.at(i,j)[k] = counts;
           counts++;
        }
    }
}
std::cout << "m9 = \n" << format(m9, cv::Formatter::FMT_PYTHON) << std::endl;
std::cout << "m9.at(1,1) = " << m9.at(1,1) << std::endl;

3)通过指针方式访问

// 3、通过指针方式访问
for(int y = 0;y < 3;y++){
  float * dataptr = m9.ptr(y);
  for(int x = 0;x < 3;x++){
          float * curdata = dataptr + x * 3;
          curdata[0] = curdata[0] * curdata[0];
          curdata[1] = curdata[1] * curdata[1];
          curdata[2] = curdata[2] * curdata[2];
        }
    }
std::cout << "m9 = " << format(m9, cv::Formatter::FMT_PYTHON) << std::endl;

4)通过迭代器访问

// 4、通过迭代器访问
// 常量迭代器
cv::MatConstIterator_ it = m9.begin();
cv::MatConstIterator_ itend = m9.end();
float sum = 0.0;
for(;it != itend;++it){
    sum += *it;
}
std::cout << "sum of m9 = " << sum << std::endl;

// 改变矩阵元素的值
cv::Mat_::iterator it1 = m9.begin();
cv::Mat_::iterator it1end = m9.end();
for(;it1 != it1end;++it1){
    (*it1)[0] = (float) sqrt((*it1)[0]);
    (*it1)[1] = (float) sqrt((*it1)[1]);
    (*it1)[2] = (float) sqrt((*it1)[2]);
}
std::cout << "\nm9 = " << format(m9, cv::Formatter::FMT_PYTHON) << std::endl;

5)块方式访问

// 5、通过块访问
// 提取整行数据
cv::Mat rowdata = m9.rowRange(0,1);
std::cout << "row = \n" << format(rowdata, cv::Formatter::FMT_PYTHON) << std::endl;
rowdata = m9.row(0);
std::cout << "row = \n" << format(rowdata, cv::Formatter::FMT_PYTHON) << std::endl;

//提取整列数据
cv::Mat coldata = m9.colRange(0,1);
std::cout << "col = \n" << format(coldata, cv::Formatter::FMT_PYTHON) << std::endl;
coldata = m9.col(2);
std::cout << "col = \n" << format(coldata, cv::Formatter::FMT_PYTHON) << std::endl;

2.3、Mat运算

1)加法运算

// 1、加法运算
cv::Mat m10 = m3 + m4;
std::cout << "m3 + m4 = \n" << m10 << std::endl;
m10 = m3 + 10;
std::cout << "m3 + 10 = \n" << m10 << std::endl;

2)减法运算

// 2、减法运算
cv::Mat m11 = m3 - m4;
std::cout << "m3 - m4 = \n" << m11 << std::endl;
m11 = m3 - 100;
std::cout << "m3 - 100  = \n" << m11 << std::endl;

3)乘法运算

// 3、乘法运算
// 与常量相乘
cv::Mat m12 = m3 * 0.5;
std::cout << "m3 * 0.5 = " << m12 << std::endl;
// 按矩阵乘法运算
cv::Mat m13 = m3 * m4;
std::cout << "m3 * m4 = " << m13 << std::endl;
// 按逐个元素相乘
cv::Mat m14 = m3.mul(m4);
std::cout << "m3.mul(m4) = " << m14 << std::endl;

4)除法运算

// 4、除法运算
// 与常量相除
cv::Mat m15 = m3 / 0.5;
std::cout << "m15 = " << m15 << std::endl;
// 按元素相除
cv::Mat m16 = m3 / m4;
std::cout << "m16 = " << m16 << std::endl;

6)矩阵求逆

// 矩阵求逆
cv::Mat m17 = m4.inv();
std::cout << "m17 = " << m17 << std::endl;

7)矩阵转置

// 矩阵转置
cv::Mat m22 = m3.t();
std::cout << "m22 = " << m22 << std::endl;

8)矩阵逻辑运算

// 矩阵逻辑运算
// 按元素与运算
cv::Mat m18 = m3 & m4;
std::cout << "m18 = " << m18 << std::endl;
// 与常量进行与运算
m18 = m3 & 0xF0;
std::cout << "m18 = " << m18 << std::endl;

// 按元素或运算
cv::Mat m19 = m3 | m4;
std::cout << "m19 = " << m19 << std::endl;
// 与常量进行或运算
m19 = m3 | 0xFA;
std::cout << "m19 = " << m19 << std::endl;

// 按元素异或运算
cv::Mat m20 = m3 ^ m4;
std::cout << "m20 = " << m20 << std::endl;
// 与常量进行异或运算
m20 = m3 ^ 0xFF;
std::cout << "m20 = " << m20 << std::endl;

// 非运算
cv::Mat m21 = ~m3;
std::cout << "m21 = " << m21 << std::endl;

9)矩阵比较

// 矩阵比较,按元素比较
cv::Mat m23 = m3 >= m4;
std::cout << "m3 >= m4?\n" << m23 << std::endl;

10)矩阵最大、最小值提取

// 矩阵最大值、最小值提取
// 矩阵间最大、最小值提取
cv::Mat m24 = cv::max(m3,m4);
std::cout << "m24 = " << m24 << std::endl;
cv::Mat m25 = cv::min(m3,m4);
std::cout << "m25 = " << m25 << std::endl;
// 矩阵与常量最大、最小值提取
cv::Mat m26 = cv::min(m3,0.5);
std::cout << "m26 = " << m26 << std::endl;
cv::Mat m27 = cv::max(m3,1.2);
std::cout << "m27 = " << m27 << std::endl;

2.4 其他函数


Opencv的Mat类型还提供了其他很多函数,方便使用。

m1 = m0.clone():完全复制所有数据元素
m0.copyTo(m1):将m0复制给m1;如果需要给m1分配空间,则与clone方法效果一样
m0.converTo(m1,type,scale,offset):转换矩阵数据类型并进行尺度变换和增加偏置之后赋值给m1
m0.assignTo(m1,type):只在内部使用,被convertTo函数调用
m0.setTo(s,mask):将矩阵所有元素设置为s,如果mask存在,则只对mask区域进行操作
m0.reshape(chan,rows):改变矩阵形状
m0.push_back(s):在末尾增加一个mx1大小数组
m0.push_back(m1):向mxn大小矩阵增加k行并复制到m1中,m1大小必须为kxn
m0.pop_back(n):向mxm大小矩阵移除n行(默认是1)
m0.locateROI(size,offset):将m0矩阵全尺寸写入cv::Size变量size,如果m0矩阵只是一个大矩阵的一小块区域,还会写入一个Point类型的offset
m0.adjustROI(t,b,l,r),通过上、下、左、右调整ROI范围
m0.total():计算数组序列的元素的数目(不包括通道)
m0.isContinous():如果m0没有空隙,则返回true
m0.elemSize():返回矩阵元素的位长度(比如三通道浮点矩阵将返回12)
m0.elemSize1():返回矩阵基本元素的位长度(比如三通道浮点矩阵将返回4)
m0.type():返回矩阵元素的类型(如CV_32FC3)
m0.depth():返回矩阵中的元素类型(比如CV_32F)
m0.size():返回矩阵m0的大小
m0.empty():如果矩阵没有元素,则返回true,否则,返回false。

3、SparseMat数据类型描述及操作


在OpenCV中,SparseMat类表示多维稀疏数值数组。稀疏数组可以存储Mat可以存储的任何类型的元素。 稀疏意味着仅存储非零元素(尽管由于对稀疏矩阵进行操作,其存储的元素中的某些元素实际上可以变为0。您可以检测这些元素并使用SparseMat :: erase删除它们 )。 非零元素存储在哈希表中,该表在填充时会增长,因此搜索时间平均为O(1)(无论元素是否存在)。

1)创建SparseMat

// 创建SparseMat
const int dims = 5;
int size[5] = {10, 10, 10, 10, 10};
cv::SparseMat sparse_mat(dims, size, CV_32F);

2)SparseMat数据访问

SparseMat的数据访问有四种方式:cv::SparseMat::ptr()、cv::SparseMat::ref()、cv::SparseMat::value()、cv::SparseMat::find()

ptr方式访问示例如下:

// 创建SparseMat
const int dims = 5;
int size[5] = {10, 10, 10, 10, 10};
cv::SparseMat sparse_mat(dims, size, CV_32F);
for(int i = 0; i < 1000; i++)
{
    int idx[dims];
    for(int k = 0; k < dims; k++)
        idx[k] = rand() % size[k];
        // 赋值给SparseMat
    sparse_mat.ref(idx) += 1.f;
}
cout << "nnz = " << sparse_mat.nzcount() << endl;

SparseMat还有很多函数,在这里就不做一一说明,感兴趣的同学可以参考文档。

你可能感兴趣的:(OpenCV,4.x,API,详解与C++实例,opencv)