OpenCV(c++) 矩阵 Mat 类的用法与注意事项

Mat类

基本介绍

Mat类是OpenCV中使用最频繁的类之一,用于储存矩阵数据及相关操作,也是程序中图像的主要形式。

Mat类主要由两部分组成:一个描述头(matrix header)及一个指向矩阵数据的指针。其中,描述头包含了矩阵的一些基本信息,如矩阵的尺寸,所占空间大小等。

Mat类有以下几个特点:

  1. Mat类会自动分配内存,使用者无需考虑内存的管理。(老版的OpenCV中使用IPIImage类,需要用户自己管理内存)
  2. Mat类使用了引用计数系统(Reference Counting System),不同的Mat对象可共享同一个矩阵的数据。
  3. 赋值号和拷贝构造只会拷贝描述头,不会开辟新的空间保存矩阵数据。在下面的例子中,如果B,C的值发生变化,那么A的值也会发生变化。
Mat A,C;
Mat B(A);
C = A;
  1. 如果想要矩阵数据一同被拷贝,则可用cv::Mat::clone()命令。
Mat D = A.clone();

声明与初始化

1. cv::Mat A(2, 2, cv::CV_8UC3, cv::Scalar(0,0,255));
2. cv::Mat B = cv::Mat::eye(4, 4, cv::CV_64F); // 单位矩阵
3. cv::Mat C = cv::Mat::ones(2, 2, cv::CV_32F); // 元素均为1的矩阵
4. cv::Mat D = cv::Mat::zeros(3, 3, cv::CV_8UC1); // 元素均为0的矩阵

其中,CV_8UC3中的’8’表示8个比特,'U’表示无符号(unsigned),'C3’表示通道有三个通道。即,A中像素值都为无符号类型,有8个比特长,并且有3个通道。

存储形式

Mat 类本质上是一个通用的矩阵类型,可存储不同类型的图像。灰度图和RGB图的存储形式有所不同,具体看下图。
灰度图存储形式
RGB图存储形式

除此之外,一般而言,每一行的数据在内存中的地址都是连续的,但不同的行之间不一定。可以用isContinuous()成员函数判断行与行之间地址是否连续。

访问Mat中的任意元素

可使用at()函数来访问任意元素。at()是一个函数模板,需要指定元素类型,一般而言,灰度图的元素类型为uchar,RGB图的元素类型为Vec3b,是一个三维的向量。具体使用方式如下 :

uchar gray = img_gray.at<uchar>(i,j); // 灰度图
auto r = img_rgb.at<cv::Vec3b>(i,j)[0]; // RGB图
auto g = img_rgb.at<cv::Vec3b>(i,j)[1];
auto b = img_rgb.at<cv::Vec3b>(i,j)[2];
img_rgb.at<cv::Vec3b>(i,j) = cv::Vec3b(0,0,0); // 将某一点赋值为0

获得 Mat 的维度

cv::Mat m; 
// do something to m
int channels = m.channels(); // 通道数
int rows = m.rows; // 行数
int cols = m.cols; // 列数

扫描整副图像

扫描图像上的每个元素,本质上就是要访问Mat矩阵中的每个元素。主要有三种方式扫描图像:

  1. 用指针扫描:
    用成员函数ptr< type >()获取矩阵某一行首元素的指针,达到访问元素的目的。由于每一行的内存是连续的,因此能够按顺序扫描矩阵,效率较高。
// 用C语言的[]运算符访问
Mat &ScanImageAndReduceC(Mat& I, const uchar* const table) {
    CV_Assert(I.depth() == CV_8U);

    int channels = I.channels();
    int nRows = I.rows;
    int nCols = I.cols*channels;

    // 一般而言,每一行的数据在内存中都是连续的;但不同行之间可能会有所不同
    if (I.isContinuous()) {
        nCols *= nRows;
        nRows = 1;
    }

    uchar *p;
    for (int i = 0; i < nRows; ++i) {
        p = I.ptr<uchar>(i);//获得第i行的首指针
        for (int j = 0; j < nCols; ++j) {
            p[j] = table[p[j]];
        }
    }

    return I;
}
  1. 用迭代器扫描
    成员函数begin< type >()和end< type >()可获取Mat类的迭代器,进行数据元素的遍历。这种方法相比第一种方法更安全,不会有下标溢出的问题,但效率更低。
// 用迭代器迭代
Mat& ScanImageAndReduceIterator (Mat &I, const uchar* const table) {
    CV_Assert(I.depth() == CV_8U);

    const int channels = I.channels();
    switch (channels)
    {
    case 1:
        {
            MatIterator_<uchar> it, end;
            for (it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it) {
                *it = table[*it];
            }
            break;
        }
    
    case 3:
        {
            MatIterator_<Vec3b> it, end;
            for (it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it) {
                (*it)[0] = table[(*it)[0]];
                (*it)[1] = table[(*it)[1]];
                (*it)[2] = table[(*it)[2]];
            }
            break;
        }
    }
    return I;
}
  1. 通过at()函数扫描
    该方法本质上是依次任意访问矩阵中的每一个元素,在遍历时,每一个循环都要重新计算指针的地址,因此效率更低。但这种方式最直观,可读性高。
// 利用at()函数随机访问元素,达到扫描的效果
Mat& ScanImageAndReduceRandomAccess (Mat &I, const uchar* const table) {
    CV_Assert(I.depth() == CV_8U);
    
    const int channels = I.channels();
    switch (channels)
    {
    case 1:
        {
            for (int i = 0; i < I.rows; ++i) {
                for (int j = 0; j < I.cols; ++j) {
                    I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
                }
            }
            break;
        }
    
    case 3:
        {
            for (int i = 0; i < I.rows; ++i) {
                for (int j = 0; j < I.cols; ++j) {
                    I.at<Vec3b>(i,j)[0] = table[I.at<Vec3b>(i,j)[0]];
                    I.at<Vec3b>(i,j)[1] = table[I.at<Vec3b>(i,j)[1]];
                    I.at<Vec3b>(i,j)[2] = table[I.at<Vec3b>(i,j)[2]];
                }
            }
            break;
        }
    }
    return I;
}

图像操作

Input/Output/Visualizing

Mat img = imread(filename, IMREAD_GRAYSCALE);
// Mat img = imread(filename, IMREAD_COLOR);
if (img.empty()) {
cout << "something wrong\n";
}
imwrite(filename, img);
namedWiodow("demo", WINDOW_AUTOSIZE);
imshow("image", img);
waitKey(0);

访问某一像素值

  1. 灰度图:
Scalar intensity = img.at<uchar>(y,x);
  1. RGB图:
Vec3b intensity = img.at<Vec3b>(y,x);
uchar b = intensity.val[0];
uchar g = intensity.val[1];
uchar b = intensity.val[2];

选择某一子区域

Rect r(top_x, top_y, width, height);
Mat subMatrix = img(r);

颜色空间转换

Mat img = imread(filename, IMREAD_COLOR);
Mat grey;
cvColor(img, grey, COLOR_BGR2GRAY);

线性加权

可使用addWeighted函数进行线性加权:

addWeighted(src1, alpha, src2, beta, 0.0, dst);

其中,参数含义如下:
参数含义

模板运算

可使用filter2D()函数对图片进行模板运算。

Mat kernel = (Mat_<char>(3,3) <<  0, -1,  0,
                                 -1,  5, -1,
                                  0, -1,  0);
filter2D(src, dst, src.depth(), kernel);

其他常用函数

  • saturat_cast< T >(): 防止数值溢出
  • CV_Assert(): 相当于Python中的assert
  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值