Mat数据结构及访问Mat中像素

Mat数据结构

一开始OpenCV是基于C语言的,在比较早的教材例如《学习OpenCV》中,讲解的存储图像的数据结构还是IplImage,这样需要手动管理内存。现在存储图像的基本数据结构是Mat

Mat是opencv中保存图像数据的基本容器。其定义如下:

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类可以表示n维的单通道或多通道数组,它可以存储实数/复数的向量和矩阵,单色或彩色图像等。向量 M M的布局是由数组 M.step[] M.step[]决定的,元素 (i0,...,iM.dims1) (i0,...,iM.dims−1)的地址为(其中 0ik<M.size[k] 0≤ik<M.size[k]):

addr(Mi0,...,iM.dims1)=M.data+M.step[0]i0+M.step[1]i1+...+M.step[M.dims1]iM.dims1 addr(Mi0,...,iM.dims−1)=M.data+M.step[0]∗i0+M.step[1]∗i1+...+M.step[M.dims−1]∗iM.dims−1

Mat对象的数据布局和CvMat、Numpy等兼容,实际上它和以step(strides)方式计算像素地址方式的数据结构兼容。
在上面的数据结构可以看出,Mat数据结构中指针信息可以共享,即矩阵头信息独立,矩阵数据可以共享,使用引用计数器,类似智能指针。这样用户使用时,用户可以分配Mat的头信息,共享数据信息,并在原地处理信息,这样可以极大的节省内存。

创建Mat对象

1、使用构造函数Mat(nrows, ncols, type[, fillValue])或者create(nrows, ncols, type)
这样可以创建一个nrows行,ncol列的矩阵,类型为type。例如CV_8UC1表示8位单通道, CV_32FC2表示双通道32位floating-point双通道。

// make a 7x7 complex matrix filled with 1+3j.
Mat M(7,7,CV_32FC2,Scalar(1,3));
// and now turn M to a 100x60 15-channel 8-bit matrix.
// The old content will be deallocated
M.create(100,60,CV_8UC(15));

对于type,格式为CV_位数+数值类型+C通道数,例如:
CV_8UC1表示:单通道阵列,8bit无符号整数
CV_8US2表示:2通道阵列,8bit有符号整数)
2、创建多维矩阵

// create a 100x100x100 8-bit array
int sz[] = {100, 100, 100};
Mat bigCube(3, sz, CV_8U, Scalar::all(0));

3、使用拷贝构造函数或赋值操作符时,只是创建了矩阵头,共享了矩阵信息,时间复杂度为O(1)。Mat::clone()函数是深拷贝,拷贝了Mat的所有信息。

4、只创建信息头部分,时间复杂度为O(1),可以使用这个特征Mat局部信息:

// add the 5-th row, multiplied by 3 to the 3rd row
M.row(3) = M.row(3) + M.row(5)*3;

// now copy the 7-th column to the 1-st column
// M.col(1) = M.col(7); // this will not work
Mat M1 = M.col(1);
M.col(7).copyTo(M1);

// create a new 320x240 image
Mat img(Size(320,240),CV_8UC3);
// select a ROI
Mat roi(img, Rect(10,10,100,100));
// fill the ROI with (0,255,0) (which is green in RGB space);
// the original 320x240 image will be modified
roi = Scalar(0,255,0);

可以创建ROI(Region of interest)区域

Mat A = Mat::eye(10, 10, CV_32S);
// extracts A columns, 1 (inclusive) to 3 (exclusive).
Mat B = A(Range::all(), Range(1, 3));
// extracts B rows, 5 (inclusive) to 9 (exclusive).
// that is, C ~ A(Range(5, 9), Range(1, 3))
Mat C = B(Range(5, 9), Range::all());
Size size; Point ofs;
C.locateROI(size, ofs);
// size will be (width=10,height=10) and the ofs will be (x=1, y=5)

5、使用用户开辟的数据创建Mat的header部分

6、使用Mat::eye(), Mat::zeros(), Mat::ones()创建矩阵;或使用Mat_()

Mat E = Mat::ones(2, 2, CV_32F);
Mat O = (Mat_<float>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);

矩阵运算

Mat支持矩阵运算。A、B表示Mat类型对象,s表示标量,alpha表示实数,支持以下运算:
** 加:A+B, A-B, A+s, s+B,-A
** 缩放:A*alpha
对应像素相乘/除:A.mul(B),A/B, alpha/A。这里要求A、B大小相等,数据类型通道数一致。
转置:A.t(),相当于A^T
** 求逆和伪逆矩阵。A.inv([method])宝石求逆,A.inv([method])*B表示求X,其中X满足AX=B
逻辑位操作 A & B, ~A
内积:A.cross(B), A.dot(B),表示对应像素相乘,求和。

常用接口

1、C++: size_t Mat::total() const
返回像素总数
2、C++: int Mat::depth() const
返回矩阵type类型对应的数值。
3、C++: int Mat::channels() const
返回通道数
4、C++: bool Mat::empty() const
Mat::total() = 0 或 Mat::data = NULL,则方法返回 true


访问Mat中的像素

Mat中存储的图像像素,具体如何存储取决于使用的颜色模型和通道数,例如RGB图像对应的存储矩阵如下

RGB存储的子列通道是反过来的:BGR。如果内存足够大,可以连续存储,通过方法Mat::isContinuous()可以判断矩阵是否连续。
访问图像的像素,即访问某位置像素在内存中对应的地址。以提取彩色RGB图像某一通道图像为例:可以有如下方法:

1、使用指针

Mat存储的图像,每一行都是连续的,可以取得每一行开头指针来访问图像像素。例如提取一副图像中R通道的图像,G、B通道像素全部置零,可以获取每一行开头的指针,使用指针遍历每一行的所有像素。如果图像在内存中的存储是连续的,还可以一次遍历所有像素。

/*
original:原图像
new_image:新图像
channel:提取的通道 0 1 2分别表示RGB
*/
void ExtractRGB(Mat& original, Mat& new_image, int channel){
    CV_Assert(channel < 3);
    // accept only char type matrices
    CV_Assert(original.depth() != sizeof(uchar));

    int channels = original.channels();
    //只接受3通道图像
    CV_Assert(channels == 3);

    new_image = original.clone();

    int nRows = new_image.rows;
    int nCols = new_image.cols * channels;

    if (new_image.isContinuous())
    {
        nCols *= nRows;
        nRows = 1;
    }

    int i, j;
    uchar* p;
    for (i = 0; i < nRows; ++i)
    {
        p = new_image.ptr<uchar>(i);
        for (j = 0; j < nCols; ++j)
        {
            if (0 == (j + 1 + channel) % 3){
                //保留
            }
            else
                p[j] = 0;
            
        }
    }
    return ;
}

2、使用迭代器

在上面的使用裸指针的方法,不安全,不注意的话会造成内存越界访问。迭代器是封装了的指针,相对指针更加安全。

/*
original:原图像
new_image:新图像
channel:提取的通道 0 1 2分别表示BGR
*/
void ExtractRGBIterator(Mat& original, Mat& new_image, int channel){
    CV_Assert(channel < 3);
    // accept only char type matrices
    CV_Assert(original.depth() != sizeof(uchar));

    int channels = original.channels();
    //只接受3通道图像
    CV_Assert(channels == 3);

    new_image = original.clone();

    int i = (channel + 1) % 3;
    int j = (channel + 2) % 3;

    MatIterator_<Vec3b> it, end;
    //3通道的图像,迭代器对应三个像素(*it)[0]、(*it)[1]、(*it)[2]
    for (it = new_image.begin<Vec3b>(), end = new_image.end<Vec3b>(); it != end; ++it)
    {
        (*it)[i] = 0;
        (*it)[j] = 0;
    }
}

3、实时计算

如果想随机获取某一位置像素,例如(i,j)出的像素,要动态实时计算其偏移,OpenCV提供相关接口

*
original:原图像
new_image:新图像
channel:提取的通道 0 1 2分别表示BGR
*/
void ExtractRGBRandomAcccess(Mat& original, Mat& new_image, int channel){
    CV_Assert(channel < 3);
    // accept only char type matrices
    CV_Assert(original.depth() != sizeof(uchar));

    int channels = original.channels();
    //只接受3通道图像
    CV_Assert(channels == 3);

    new_image = original.clone();

    Mat_<Vec3b> _I = new_image;

    int m = (channel + 1) % 3;
    int n = (channel + 2) % 3;

    for (int i = 0; i < new_image.rows; ++i)
        for (int j = 0; j < new_image.cols; ++j)
        {
            _I(i, j)[m] = 0;
            _I(i, j)[n] = 0;
            
        }
}

以上3个方法中,第一种最快,第三种最慢;因为第三种是随机访问像素使用的,每次都会计算(i,j)像素对应的地址。



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要输出MATLAB数据每个像素的值,可以使用MATLAB编程语言。首先,需要读取MAT文件并获取图像变量的值。然后,根据图像变量的维度,可以使用循环来遍历每个像素并输出其值。 假设MAT文件的图像变量名为img,并且它是一个二维矩阵。 ``` % 读取MAT文件 load('file.mat'); % 获取图像变量的尺寸 [height, width] = size(img); % 遍历每个像素,并输出其值 for i = 1:height for j = 1:width pixelValue = img(i, j); disp(['Pixel at (' num2str(i) ',' num2str(j) ') = ' num2str(pixelValue)]); end end ``` 以上代码假定MAT文件已经加载到MATLAB工作区,并且图像变量名为img。通过使用两个嵌套的循环,代码遍历了图像的每个像素,并使用disp函数输出了每个像素的值。 请注意,以上的代码适用于二维矩阵,如果图像是多维的,需要根据图像的维度进行相应的修改。这个例子仅仅是一个简单的示例来回答问题,实际应用可能需要根据具体需求进行更多的处理。 ### 回答2: 要输出mat数据的每一个像素值,首先需要确认mat是一个什么类型的数据结构。在OpenCVmat是一个多维数组,存储像素值的类型可以是单通道或多通道的。这里以单通道的情况为例。 在输出像素值之前,首先要确定mat的尺寸大小(宽度和高度),以确定需要遍历的像素数。可以通过mat.rows和mat.cols来获取。 然后,可以使用双重循环来遍历mat的每一个像素。外层循环控制行数,内层循环控制列数。 在循环,可以使用mat.at<数据类型>(行索引, 列索引)来获取对应位置的像素值。例如,若mat是一个CV_8UC1类型的mat,表示像素值范围为0-255的灰度图像,则可以使用mat.at<uchar>(i, j)来获取(i, j)位置的像素值。 在循环输出每一个像素值,可以使用cout或者其他适合的输出方式,将像素值打印出来。 下面是一个简单的示例代码: ```cpp #include <iostream> #include <opencv2/opencv.hpp> int main() { cv::Mat mat = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE); // 读取灰度图像 int rows = mat.rows; int cols = mat.cols; for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { uchar pixel_value = mat.at<uchar>(i, j); std::cout << "Pixel value at (" << i << ", " << j << "): " << static_cast<int>(pixel_value) << std::endl; } } return 0; } ``` 上述代码使用OpenCV库读取一个灰度图像,并输出每一个像素的值。在实际应用,需要根据具体的需求对代码进行修改和优化。 ### 回答3: 要输出MATLAB的二维矩阵数据mat的每一个像素值,首先我们需要将该二维矩阵转化为MATLAB的图像矩阵。接下来,我们可以使用MATLAB的循环结构来遍历图像矩阵的每一个像素,并输出其值。 具体步骤如下: 1. 首先,将MATLAB的二维矩阵mat转化为图像矩阵,可以使用im2uint8()函数将其转化为8位整型矩阵。 img = im2uint8(mat); 2. 然后,获取图像矩阵的行数和列数,以便遍历每个像素。 [rows, cols] = size(img); 3. 使用两个嵌套的循环结构来遍历图像矩阵的每一个像素,并输出其值。 for i = 1:rows for j = 1:cols pixel_value = img(i, j); disp(['Pixel at position (', num2str(i), ',', num2str(j), ') has value: ', num2str(pixel_value)]); end end 通过以上步骤,我们可以逐个输出矩阵mat每个像素的值。代码,我们使用disp()函数将输出结果显示在命令行窗口,并使用num2str()函数将数值转化为字符串以输出。最后的输出结果将包括每个像素的位置和相应的像素值。 需要注意的是,上述代码是在MATLAB环境运行的,如果您需要在其他编程环境实现类似的功能,可以根据具体的编程语言语法进行相应的修改。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值