Mat及其数据指针

本文深入探讨了OpenCV中Mat数据结构的使用技巧,包括Mat::data的使用、内存拷贝方法、不同数据类型间的转换及注意事项。同时介绍了如何处理图像数据的4字节对齐问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

mat 的综合使用

 http://wenku.baidu.com/link?url=rmTE6meVj-6O0o6IGFJxcdS6CVJMCeGUh8QEKZmnfTeezuyggC-M9wu6g5AQA61b3kyG9AnQosVnRYKw8yqAoTJcEsAE7MVMVetELJhs5e7



 

Mat::data的使用

在opencv中,Mat很方便;但当用到不是以opencv为主体的代码中,就不能直接使用Mat,而要转换为其它形式的数据结构。最简单的解决方案是指针,即将Mat拷贝到自定义的指针中。


Mat::data是数据段的首地址;使用memcpy()将Mat的数据拷贝至某个指针中,当然要先new一段内存。


memcpy的说明:http://blog.youkuaiyun.com/sszgg2006/article/details/7989404
常用到vector<Mat>,要用push_back方法对其进行赋值,vector的使用说明:http://blog.youkuaiyun.com/hancunai0017/article/details/7032383

 

4字节对齐的情况

但如果图像大小不是4的整数倍,某些场合下不能直接使用Mat::data。因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元(一般是补够4的倍数或8的倍数,有些地方也称作“位对齐”。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行。此时调用Mat::ptr<>()方法就等价于Mat::data

[cpp] view plain copy

1.  int nr=image.rows;        

2. int nc=image.cols;    

3. if(image.isContinuous())    

4.     {    

5.         nr=1;    

6.         nc=nc*image.rows*image.channels();    

7.     }     

8.     for(int i=0;i<nr;i++)    

9.     {         

10.         const uchar* inData=image.ptr<uchar>(i);           

11.         uchar* outData=outImage.ptr<uchar>(i);          

12.         for(int j=0;j<nc;j++)    

13.         {    

14.             *outData++=*inData++;    

15.         }    

16.     }    

 

例如保存BMP格式的图像时,BMP要求图像数据按四字节对齐,此时就需要对Mat中的数据进行补零
对齐方法就是在每一行尾部补零,零的个数可能是1~3个

 

Mat::data的默认类型

Mat::data的默认类型为uchar*,但很多时候需要处理其它类型,如float、int,此时需要将data强制类型转换,如:

[cpp] view plain copy

1. Mat src(1000,1000,CV_32F);  

2. float* myptr = (float*)src.data;  

无论Mat的type是何种类型,Mat::data均为uchar*

 

--------------------------------------------------------------------

 

指针数据拷贝至Mat

 

这个千万注意,使用Mat接收指针指定的一段内存数据,通过指针初始化一个Mat:

[cpp] view plain copy

1. Mat(row,col,CV_8U,ptr)  

此时,Mat::data就等于ptr,例子

[cpp] view plain copy

1. uchar ptr[25]={0,1};  

2. Mat mat(5,5,CV_8U,ptr);  

3. mat = mat*255;  

修改mat就修改了ptr指向内存的值

注意:Mat的类型要与指针的类型一致,如,uchar指针对应CV_8U,double指针对应CV_64F,如果把double指针赋给一个CV_32F的Mat,那Mat的每个元素只占32位,即把double数一分为二,是错误的。

这里又涉及一个问题,类型转换。opencv提供了Mat::convertTo接口进行类型转换,当然我们可以逐个元素进行强制类型转换,但有时候两者是有区别的。例如,把double转unsigned int,这是有符号数转无符号数,如果使用convertTo方法,会把负数置零;如果使用强制类型转换(unsigned int),结果是错误的,因为负数应该变成其补数,无符号的第一个比特位是有意义的

--------------------------------------------------------------------

 

Mat数据拷贝至指针

 

有些时候,我们不希望函数的调用者看到opencv的数据结构Mat,可以通过把Mat数据拷贝至一段动态申请的内存,此时千万要注意数据类型,指针和Mat要统一。

[cpp] view plain copy

1. typedef ushort  mtype;  

2. Mat src;  

3. ...  

4.     mtype* psrc = (mtype*)src.data;  

5.     mtype *pdst = new mtype [src.total()];  

6.     for(int i=0;i<h;i++) //遍历行  

7.     {         

8.         const mtype* p0 = psrc + i*w;  

9.         mtype* p1 = pdst + i*w;  

10.         for(int j=0;j<w;j++) //遍历列  

11.         {     

12.             *p1++= p0[j];         

13.         }  

14.     }   //耗时:0.7ms  

以上是最浅显的做法,如果使用memcpy可以更高效:

[cpp] view plain copy

1. memcpy(pdst,psrc,src.total()*sizeof(mtype));  //耗时:0.5ms  

src是一个1000*1000的ushort矩阵
需要注意的是,src.data需要先进行强制类型转换

 

 

 

 

 

图片分析1:考虑二维情况(stored row by row)按行存储

上面是一个 3 X 4 的矩阵,假设其数据类型为 CV_8U,也就是单通道的 uchar 类型

· 这是一个二维矩阵,那么维度为 2 M.dims == 2);

· M.rows == 3; M.cols == 4

· sizeof(uchar) = 1,那么每一个数据元素大小为 1 M.elemSize() == 1, M.elemSize1() == 1);

· CV_8U 得到 M.depth() == 0, M.channels() == 1

· 因为是二维矩阵,那么 step 数组只有两个值, step[0] step[1] 分别代表一行的数据大小和一个元素的数据大小,则 M.step[0] == 4, M.step[1] == 1

· M.step1(0) == M.cols = 4; M.step1(1) == 1

假设上面的矩阵数据类型是 CV_8UC3,也就是三通道

· M.dims == 2M.channels() == 3M.depth() == 0

· M.elemSize() == 3 (每一个元素包含3uchar值) M.elemSize1() == 1 elemSize / channels

· M.step[0] == M.cols * M.elemSize() == 12, M.step[1] == M.channels() * M.elemSize1() == M.elemSize() == 3

· M.step(0) == M.cols * M.channels() == 12 ; M.step(1) == M.channels() == 3

 

 

 

 

 

 

 

 

 

 

 

 

 

 

图片分析2:考虑三维情况(stored plane by plane)按面存储

上面是一个 3 X 4 X 6 的矩阵,假设其数据类型为 CV_16SC4,也就是 short 类型

· M.dims == 3 ; M.channels() == 4 ; M.elemSize1() == sizeof(short) == 2 ;

· M.rows == M.cols == –1

· M.elemSize() == M.elemSize1() * M.channels() == M.step[M.dims-1] == M.step[2] == 2 * 4 == 8;

· M.step[0] == 4 * 6 * M.elemSize() == 192

· M.step[1] == 6 * M.elemSize() == 48

· M.step[2] == M.elemSize() == 8

· M.step1(0) == M.step[0] / M.elemSize() == 48 / 2 == 96 (第一维度(即面的元素个数) * 通道数);

· M.step1(1) == M.step[1] / M.elemSize() == 12 / 2 == 24(第二维度(即行的元素个数/列宽) * 通道数);

· M.step1(2) == M.step[2] / M.elemSize() == M.channels() == 4(第三维度(即元素) * 通道数);

End

 

 

 

 

 

以上为Mat的存放形式

以下为Mat的一些操作方法
具体使用方法

Fn 1 :利用step

Code 1

int main()

{

    //新建一个uchar类型的单通道矩阵(grayscale image 灰度图)

    Mat m(400, 400, CV_8U, Scalar(0));

    for (int col = 0; col < 400; col++)

    {

        for (int row = 195; row < 205; row++)

        {

            //获取第[row,col]个像素点的地址并用 * 符号解析

            *(m.data + m.step[0] * row + m.step[1] * col) = 255;

        }

    }

    imshow("canvas", m);

    cvWaitKey();

    return 0;

}

Output 1 :

Code1只是演示了单通道的情况,对于多通道的例子,请看 Code2 然后再看 Code3。

Fn 2 :

使用 Mat::at 函数

· 原型 template<typename _Tp> inline _Tp& Mat::at(…) //其中参数有多个,也就是说 at 函数有多个重载

· 返回值为 Mat 类型, Mat 有个索引的重载,也就是 [] 符号的重载,用这个重载可以定位多通道数据,具体示例可以看下面代码

下面的代码把红色通道值大于128的颜色的置为白色,左边为原图,右边为处理过后的图。

Code 2 :

int main()

{   

    Mat img = imread("lena.jpg");

    imshow("Lena Original", img);

    for (int row = 0; row < img.rows; row++)

    {

        for (int col = 0; col < img.cols; col++)

        {   

            /* 注意 Mat::at 函数是个模板函数, 需要指明参数类型, 因为这张图是具有红蓝绿三通道的图,

              所以它的参数类型可以传递一个 Vec3b, 这是一个存放 3 uchar 数据的 Vec(向量). 这里

              提供了索引重载, [2]表示的是返回第三个通道, 在这里是 Red 通道, 第一个通道(Blue)[0]返回 */

            if(img.at<Vec3b>(row, col)[2] > 128)

                img.at<Vec3b>(row, col) = Vec3b(255, 255, 255);

        }

    }

    imshow("Lena Modified", img);

    cvWaitKey();

    return 0;

}

 

 

Output 2 :

 

Code 3 :

这段代码用的是 Fn1 的方式,效果和 Code 2 等价,不过是处理三通道数据而已:

int main()

{   

    Mat img = imread("lena.jpg");

    imshow("Lena Original", img);

    for (int row = 0; row < img.rows; row++)

    {

        for (int col = 0; col < img.cols; col++)

        {

            //主要是这里的代码

            if(*(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1() * 2) > 128)

            {

                //[row, col]像素的第 1 通道地址被 * 解析(blue通道)

                *(img.data + img.step[0] * row + img.step[1] * col) = 255;

                //[row, col]像素的第 2 通道地址被 * 解析(green通道), 关于elemSize1函数的更多描述请见 Fn1 里所列的博文链接

                *(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1()) = 255;

                //[row, col]像素的第 3 通道地址被 * 解析(red通道)

                *(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1() * 2) = 255;

            }

        }

    }

    imshow("Lena Modified", img);

    cvWaitKey();

    return 0;

}

Output 3 = Output 2

Fn 3 :

使用 Mat 的一个模板子类 Mat_<typename _Tp> 的 ( ) 符号重载定位一个像素

Code 4 :

int main()

{   

    Mat m(400, 400, CV_8UC3, Scalar(255, 255, 255));

    // m2 Mat_<Vec3b> 类型的, 因为 m 中元素的类型是 CV_8UC3, 可以用 Vec3b 存储 3 个通道的值

    // 注意 Mat_<CV_8UC3> 这种写法是错误的, 因为 CV_8UC3 只是一个宏定义

    // #define CV_8UC3 CV_MAKETYPE(CV_8U, 3)

    Mat_<Vec3b> m2 = m;

   

    // for 循环画一个红色的实心圆

    for (int y = 0; y < m.rows; y++)

    {

        for (int x = 0; x < m.rows; x++)

        {

            if (pow(double(x-200), 2) + pow(double(y-200), 2) - 10000.0 < 0.00000000001)

            {

                // Mat_ 模板类实现了对()的重载, 可以定位到一个像素

                m2(x, y) = Vec3b(0, 0, 255);

            }

        }

    }

    imshow("Image", m);

    cvWaitKey();

    return 0;

}

Output 4 :[ 看上去怎么有点不爽]

Fn 4 :

使用 Mat::ptr 模板函数

Code 5 :

int main()

{   

    Mat m(400, 400, CV_8UC3, Scalar(226, 46, 166));

    imshow("Before", m);

    for (int row = 0; row < m.rows; row++)

    {

        if (row % 5 == 0)

        {

            // data uchar* 类型的, m.ptr<uchar>(row) 返回第 row 行数据的首地址

            // 需要注意的是该行数据是按顺序存放的,也就是对于一个 3 通道的 Mat, 一个像素有

            // 3 个通道值, [B,G,R][B,G,R][B,G,R]... 所以一行长度为:

            // sizeof(uchar) * m.cols * m.channels() 个字节

            uchar* data = m.ptr<uchar>(row);

            for (int col = 0; col < m.cols; col++)

            {

                data[col * 3] = 102; //row行的第col个像素点的第一个通道值 Blue

                data[col * 3 + 1] = 217; // Green

                data[col * 3 + 2] = 239; // Red

            }

        }

    }

    imshow("After", m);

    cout << (int)m.at<Vec3b>(0, 0)[0] << ','; //利用 Fn 1 介绍的方法输出一下像素值到控制台

    cout << (int)m.at<Vec3b>(0, 0)[1] << ',';

    cout << (int)m.at<Vec3b>(0, 0)[2] << endl;

    cvWaitKey();

    return 0;

}

Output 5 :

End :

Author : Ggicci

谢谢阅读,有误希望指正!

--OpenCV初学者

 



Mat 内存指针的使用

http://blog.youkuaiyun.com/kelvin_yan/article/details/48315175

Mat::data的使用

在opencv中,Mat很方便;但当用到不是以opencv为主体的代码中,就不能直接使用Mat,而要转换为其它形式的数据结构。最简单的解决方案是指针,即将Mat拷贝到自定义的指针中。


Mat::data是数据段的首地址;使用memcpy()将Mat的数据拷贝至某个指针中,当然要先new一段内存。


memcpy的说明:http://blog.youkuaiyun.com/sszgg2006/article/details/7989404
常用到vector<Mat>,要用push_back方法对其进行赋值,vector的使用说明:http://blog.youkuaiyun.com/hancunai0017/article/details/7032383


4字节对齐的情况

但如果图像大小不是4的整数倍,某些场合下不能直接使用Mat::data。因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元(一般是补够4的倍数或8的倍数,有些地方也称作“位对齐”。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行。此时调用Mat::ptr<>()方法就等价于Mat::data

[cpp]  view plain  copy
  1.  int nr=image.rows;        
  2. int nc=image.cols;    
  3. if(image.isContinuous())    
  4.     {    
  5.         nr=1;    
  6.         nc=nc*image.rows*image.channels();    
  7.     }     
  8.     for(int i=0;i<nr;i++)    
  9.     {         
  10.         const uchar* inData=image.ptr<uchar>(i);           
  11.         uchar* outData=outImage.ptr<uchar>(i);          
  12.         for(int j=0;j<nc;j++)    
  13.         {    
  14.             *outData++=*inData++;    
  15.         }    
  16.     }    

例如保存BMP格式的图像时,BMP要求图像数据按四字节对齐,此时就需要对Mat中的数据进行补零
对齐方法就是在每一行尾部补零,零的个数可能是1~3个


Mat::data的默认类型

Mat::data的默认类型为uchar*,但很多时候需要处理其它类型,如float、int,此时需要将data强制类型转换,如:

[cpp]  view plain  copy
  1. Mat src(1000,1000,CV_32F);  
  2. float* myptr = (float*)src.data;  

无论Mat的type是何种类型,Mat::data均为uchar*


--------------------------------------------------------------------


指针数据拷贝至Mat


这个千万注意,使用Mat接收指针指定的一段内存数据,通过指针初始化一个Mat:

[cpp]  view plain  copy
  1. Mat(row,col,CV_8U,ptr)  
此时,Mat::data就等于ptr,例子

[cpp]  view plain  copy
  1. uchar ptr[25]={0,1};  
  2. Mat mat(5,5,CV_8U,ptr);  
  3. mat = mat*255;  

修改mat就修改了ptr指向内存的值

注意:Mat的类型要与指针的类型一致,如,uchar指针对应CV_8U,double指针对应CV_64F,如果把double指针赋给一个CV_32F的Mat,那Mat的每个元素只占32位,即把double数一分为二,是错误的。

这里又涉及一个问题,类型转换。opencv提供了Mat::convertTo接口进行类型转换,当然我们可以逐个元素进行强制类型转换,但有时候两者是有区别的。例如,把double转unsigned int,这是有符号数转无符号数,如果使用convertTo方法,会把负数置零;如果使用强制类型转换(unsigned int),结果是错误的,因为负数应该变成其补数,无符号的第一个比特位是有意义的

--------------------------------------------------------------------


Mat数据拷贝至指针


有些时候,我们不希望函数的调用者看到opencv的数据结构Mat,可以通过把Mat数据拷贝至一段动态申请的内存,此时千万要注意数据类型,指针和Mat要统一。

[cpp]  view plain  copy
  1. typedef ushort  mtype;  
  2. Mat src;  
  3. ...  
  4.     mtype* psrc = (mtype*)src.data;  
  5.     mtype *pdst = new mtype [src.total()];  
  6.     for(int i=0;i<h;i++) //遍历行  
  7.     {         
  8.         const mtype* p0 = psrc + i*w;  
  9.         mtype* p1 = pdst + i*w;  
  10.         for(int j=0;j<w;j++) //遍历列  
  11.         {     
  12.             *p1++= p0[j];         
  13.         }  
  14.     }   //耗时:0.7ms  
以上是最浅显的做法,如果使用memcpy可以更高效:

[cpp]  view plain  copy
  1. memcpy(pdst,psrc,src.total()*sizeof(mtype));  //耗时:0.5ms  
src是一个1000*1000的ushort矩阵
需要注意的是,src.data需要先进行强制类型转换


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值