OpenCV(2).Mat类初识

669 阅读5分钟
  1. Mat类介绍
  2. Mat对象的创建
  3. create()函数
  4. Matlab风格的函数
  5. 矩阵元素的表达
  6. 像素的读写(at函数/迭代器/指针)

Mat类是什么

  • 之前提到数字图像可以使用矩阵来表示。OpenCV的Mat类是一个优秀的表示图像的类,同时也是一个通用的表示矩阵的类

Mat类的组成

class CV_EXPORTS Mat
{
public:
    //! 一系列构造函数
    ...
    
        /*! flags参数包含一些关于矩阵的信息:
         - 矩阵标识
         - 数据是否连续
         - 深度
         - 通道数目
     */
    int flags;
    //! 矩阵的维数,取值大于或等于2
    int dims;
    //! 矩阵的行数和列数,如果矩阵超过2维,这两个变量的值都为-1
    int rows, cols;
    //! 指向数据的指针
    uchar* data;
    //! helper fields used in locateROI and adjustROI
    const uchar* datastart;
    const uchar* dataend;
    const uchar* datalimit;
    //! custom allocator
    MatAllocator* allocator;
    //! 其他成员变量和成员函数
    ...
    
    //! 与UMat的交互
    UMatData* u;
    //行步长
    MatSize size;
    MatStep step;
}

Mat对象的创建

1.使用构造函数
无参数构造函数
Mat::Mat()
创建指定大小、类型为 type 的图像
Mat::Mat(int rows, int cols, int type)
Mat::Mat(Size size, int type)
Mat::Mat(int ndims, const int* sizes, int type)
Mat::Mat(const std::vector<int>& sizes, int type)
有四种方式指定图像大小:
1. 通过设定 int rows 和 int cols 指定行数和列数
2. 通过 Size(cols, rows)指定行数和列数
3. 通过 int ndims 和 int* sizes 设定维数和每个维度的形状指定图像尺寸
4. 通过容器 vector<int> & sizes 设定维数和每个维度的形状指定图像尺寸
参数type:规定了矩阵中元素的类型以及矩阵通道数,它是interface.h中定义的预定义常量
定义格式为:CV_(位数)+(数据类型)+(通道数)
具体有以下值:
CV_8UC1    CV_8UC2    CV_8UC3    CV_8UC4    CV_8SC1    CV_8SC2    CV_8SC3    CV_8SC4
CV_16UC1   CV_16UC2   CV_16UC3   CV_16UC4   CV_16SC1   CV_16SC2   CV_16SC3   CV_16SC4
CV_32SC1   CV_32SC2   CV_32SC3   CV_32SC4   
CV_32FC1   CV_32FC2   CV_32FC3   CV_32FC4   CV_64FC1   CV_64FC2   CV_64FC3   CV_64FC4
--这里U表示无符号整数(unsigned integer),S是有符号整数(signed integer),F时浮点数(float)
例如:CV_8UC3,表示的时元素类型时一个8位无符号整数,通道为3
C1,C2,C3,C4 表示通道数1234

创建指定大小、类型为 type、行步长为 step 的图像
Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
Mat::Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0)
Mat::Mat(const std::vector<int>& sizes, int type, void* data, const size_t* stpes=0)
行步长是指矩阵每行所占的字节数,包含在每行末为对齐而添加的字节。如果 step 设 定为 AUTO_STEP 或 0 则表示不计算补齐的字节数。此构造函数不创建图像数据所需的
内存,而是直接使用 data 所指的内存。
创建大小、类型与 m 相同的图像
Mat::Mat(const Mat& m)
此构造函数不会复制图像数据,新的图像与 m 共享图像数据,引用计数增加。所以,
如果修改新创建的这个矩阵,m 也被修改了。
创建大小、类型与指定 m 部分相同的图像
Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all())
Mat::Mat(const Mat& m, const Rect& roi)
Mat::Mat(const Mat& m, const Range* ranges)
Mat::Mat(const Mat& m, const std::vector<Range>& ranges) 此构造函数不会复制图像数据,新图像与指定的 m 部分共享图像数据,引用计数增加。
所以,如果修改新创建的这个矩阵,m 的内容也被修改了。
有四种方式指定 m 区域:
1. 通过 rowRange 和 colRange 指定
2. 通过 Rect 指定
3. 通过 Range 的数组指定
4. 通过 Range 的容器指定
  • 例子
//创建行数位3,列数位2,类型位8位无符号整型,所有元素初始值位(0,0,255)的三通道图像
    Mat M(3, 2, CV_8UC3,Scalar(0,0,255));
// 输出M
    cout << M << endl;
打印结果:
[  0,   0, 255,   0,   0, 255;   0,   0, 255,   0,   0, 255;   0,   0, 255,   0,   0, 255]
另外,如果需要创建更多通道数的图像,可以使用宏CV_8UC(n)定义
    //创建行数3,列数2,通道数为5的图像
    Mat M1(3, 2, CV_8UC(5));
2.使用create()函数
Mat 类的 create()函数也可以创建图像,它是 Mat 类的一个重要方法。
Mat::create(int rows, int cols, int type)
Mat::create(Size size, int type)
Mat::create(int ndims, const int* sizes, int type)
Mat::create(const std::vector<int>& sizes, int type)
OpenCV 的很多函数和方法均调用 create()创建输出矩阵。如果 create()函数指定的参数
与当前的图像参数相同,则不进行内存申请,如果参数不同,则会减少当前图像数据内存的
索引,并重新申请内存。
需要注意的是,create()函数不能设置图像像素的初始值。
  • 例子
//2.create函数
Mat m2(3, 2, CV_8UC3);
m2.create(2, 2, CV_8UC2);
cout << m2 << endl;
打印结果:
[205, 205, 205, 205; 205, 205, 205, 205]
3.使用Matlab风格的函数
OpenCV 也提供了如下一些 Matlab 风格的静态成员函数,使用很方便,也可以使代码简
洁:
Mat::zeros(int rows, int cols, int type), 
Mat::zeros(Size size, int type)
Mat::zeros(int ndims, const int* sz, int type)
Mat::ones(int rows, int cols, int type), 
Mat::ones(Size size, int type), 
Mat::ones(int ndims, const int* sz, int type)
Mat::eye(int rows, int cols, int type)
Mat::eye(Size size, int type)
  • 代码实现:
//Matlab风格函数
Mat zero = Mat::zeros(2, 3, CV_8UC1);
cout << zero << endl;
Mat one =  Mat::ones(2, 3, CV_32F);
cout << one << endl;
//对角线
Mat eye = Mat::eye(2, 3, CV_64F);
cout << eye << endl;
打印结果:
[  0,   0,   0;
   0,   0,   0]
[1, 1, 1;
 1, 1, 1]
[1, 0, 0;
 0, 1, 0]

矩阵元素的表达

  • 对于单通道元素,其元素类型一般为8U(8位无符号整数),也可以为16S,32F等,这些类型可以用uchar,short,float等C/C++语言中的基本数据类型表达
  • 对于多通道元素,如RGB彩色图像一般使用8UCV3表示,在内存中的存储表现为(Bij,Gij,Rij ...),也可以将图像看作一个二位矩阵,但此时矩阵的元素不再是基本数据类型。而是用模板类Vec来表示一个向量,
typedef Vec<uchar, 2> Vec2b;    //2维uchar向量
typedef Vec<uchar, 3> Vec3b;    //3维uchar向量
typedef Vec<uchar, 4> Vec4b;    //4维uchar向量
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<ushort, 2> Vec2w;
typedef Vec<ushort, 3> Vec3w;
typedef Vec<ushort, 4> Vec4w;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<int, 6> Vec6i;
typedef Vec<int, 8> Vec8i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;    //6维float向量
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;
Vec类型的结构为:内部使用[]数组存储
Vec3b color;   //描述RGB颜色的color变量
color[0] = 255; //B分量
color[1] = 0;   //G
color[2] = 0;   //R

像素的读写

  • 读取某个像素的值并进行设置新值,也可以对图像所有像素进行遍历
1.at()函数:可以返回指定矩阵元素的引用
    //单通道图像:对图像进行遍历并进行单个元素的赋值
    Mat grayImg(480, 640, CV_8UC1);
    //uchar value = grayImg.at<uchar>(1, 2);
    //cout <<"value:" << value << endl;
    for (int i = 0; i < grayImg.rows; i++)
    {
        for (int j = 0; j < grayImg.cols; j++)
        {
            grayImg.at<uchar>(i, j) = (i + j) % 255;
        }
    }
    imshow("grayImg", grayImg);

灰色图像赋值效果

    //彩色图像
    Mat colorImg(480, 640, CV_8UC3);
    for (int i = 0; i < colorImg.rows; i++)
    {
        for (int j = 0; j < colorImg.cols; j++)
        {
            Vec3b pixel;    //彩色图像单个像素有三个变量
            pixel[0] = i % 255; //Blue
            pixel[1] = j % 255; //Green
            pixel[2] = 0;       //Red
            colorImg.at<Vec3b>(i, j) = pixel;
        }
    }
    imshow("colorImg", colorImg);

彩色图像赋值后效果

2.使用迭代器遍历图像元素
    //迭代器方式遍历图像元素
    Mat grayImg(480, 640, CV_8UC1);
    //MatIterator_<uchar> ite= grayImg.begin<uchar>();
    //MatIterator_<uchar> end = grayImg.end<uchar>();
    MatIterator_<uchar> grayIte, grayEnd;   //地址
    for (grayIte = grayImg.begin<uchar>(), grayEnd = grayImg.end<uchar>();
        grayIte != grayEnd; grayIte++){
        *grayIte = rand() % 255;
    }
    imshow("grayImg", grayImg);
    //彩色图像
    Mat colorImg(480, 640, CV_8UC3);
    MatIterator_<Vec3b> colorIte, colorEnd;
    for (colorIte = colorImg.begin<Vec3b>(), colorEnd = colorImg.end<Vec3b>();
        colorIte != colorEnd; colorIte++) {
            (*colorIte)[0] = rand() % 255; //Blue
            (*colorIte)[1] = rand() % 255; //Green
            (*colorIte)[2] = rand() % 255; //Red
    }
    imshow("colorImg", colorImg);
3.还可以使用指针访问元素
  • 注意:C/C++中的指针操作,没有类型和越界检查,如果指针访问出错,很难进行问题定位
  • 如果程序的运行速度有要求,使用指针进行遍历效率很高
    //指针方式遍历像素
    Mat grayImg(480, 640, CV_8UC1);
    for (int i = 0; i < grayImg.rows; i++)
    {
        uchar* p = grayImg.ptr<uchar>(i);
        for (int j = 0; j < grayImg.cols; j++)
        {
            p[j] = (i + j) % 255;
        }
    }
    imshow("grayImg", grayImg);
    //彩色图像
    Mat colorImg(480, 640, CV_8UC3);
    for (int i = 0; i < colorImg.rows; i++)
    {
        Vec3b* p = colorImg.ptr<Vec3b>(i);
        for (int j = 0; j < colorImg.cols; j++)
        {
            p[j][0] = i % 255; //Blue
            p[j][1] = j % 255; //Green
            p[j][2] = 0;       //Red
        }
    }
    imshow("colorImg", colorImg);