OpenCv学习笔记(二)—cv::Mat学习

来源:互联网 发布:win10电脑如何优化 编辑:程序博客网 时间:2024/05/16 08:16
由于在写上一篇图像的数据结构时,发现自己只知道CvMat,竟然还有Mat数据结构,真是无知了,看了这么多程序,貌似没有看到这个结构。有可能那些程序都是些老版本的例子,这是在2.0以后加上的,所以我也得紧跟呀!以下是自己的学习心得。。。。

一、Mat简介
    在2001年刚刚出现的时候,OpenCV基于 C 语言接口而建。为了在内存(memory)中存放图像,当时采用名为 IplImage 的C语言结构体,时至今日这仍出现在大多数的旧版教程和教学材料。但这种方法必须接受C语言所有的不足,这其中最大的不足要数手动内存管理,其依据是用户要为开辟和销毁内存负责。虽然对于小型的程序来说手动管理内

一、Mat简介
    在2001年刚刚出现的时候,OpenCV基于 C 语言接口而建。为了在内存(memory)中存放图像,当时采用名为 IplImage 的C语言结构体,时至今日这仍出现在大多数的旧版教程和教学材料。但这种方法必须接受C语言所有的不足,这其中最大的不足要数手动内存管理,其依据是用户要为开辟和销毁内存负责。虽然对于小型的程序来说手动管理内存不是问题,但一旦代码开始变得越来越庞大,你需要越来越多地纠缠于这个问题,而不是着力解决你的开发目标。

   幸运的是,C++出现了,并且带来类的概念,这给用户带来另外一个选择:自动的内存管理(不严谨地说)。这是一个好消息,如果C++完全兼容C的话,这个变化不会带来兼容性问题。为此,OpenCV在2.0版本中引入了一个新的C++接口,利用自动内存管理给出了解决问题的新方法。使用这个方法,你不需要纠结在管理内存上,而且你的代码会变得简洁(少写多得)。但C++接口唯一的不足是当前许多嵌入式开发系统只支持C语言。所以,当目标不是这种开发平台时,没有必要使用旧 方法(除非你是自找麻烦的受虐狂码农)。

   关于 Mat ,首先要知道的是你不必再手动地(1)为其开辟空间(2)在不需要时立即将空间释放。但手动地做还是可以的:大多数OpenCV函数仍会手动地为输出数据开辟空间。当传递一个已经存在的Mat 对象时,开辟好的矩阵空间会被重用。也就是说,我们每次都使用大小正好的内存来完成任务。

   基本上讲 Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。因此,当在程序中传递图像并创建拷贝时,大的开销是由矩阵造成的,而不是信息头。OpenCV是一个图像处理库,囊括了大量的图像处理函数,为了解决问题通常要使用库中的多个函数,因此在函数中传递图像是家常便饭。同时不要忘了我们正在讨论的是计算量很大的图像处理算法,因此,除非万不得已,我们不应该拷贝大 的图像,因为这会降低程序速度。

二、Mat的基本操作

   这里展示一个例子解释一下Mat的基本操作
[cpp] view plain copy
  1. #include<cv.h>  
  2. #include<highgui.h>  
  3. #include<iostream>  
  4. using namespace cv;  
  5. using namespace std;  
  6. int main()  
  7. {  
  8. /*********************************Mat基本操作-矩阵*******************************************/  
  9.     //二维三通道矩阵建立  
  10.     Mat M(2,2, CV_8UC3, Scalar(0,0,255)); //使用构造函数创建矩阵  
  11. /* 
  12. CV_8UC3 表示使用8位的 unsigned char 型,每个像素由三个元素组成三通道,初始化为(0,0,255) 
  13. */  
  14.     cout << "M = " << endl << " " << M << endl << endl; //格式化输出  
  15.     //三维  
  16.     int sz[3] = {3,3,3};   
  17.     Mat L(3,sz, CV_8UC(1), Scalar::all(0));  
  18. /* 
  19. 超过两维的矩阵:指定维数,然后传递一个指向一个数组的指针,这个数组包含每个维度的尺寸;其余的相同 
  20. */  
  21.     cout << "L = " << endl << " " << M << endl << endl; //格式化输出  
  22.   
  23.   
  24. /********************************************Mat基本操作-图像*******************************/  
  25.    Mat A, C;      // 只创建信息头部分  
  26.     A=imread("D:\\openCV\\openCVProject\\openCv笔记\\openCv笔记\\test.jpg", CV_LOAD_IMAGE_COLOR); // 这里为矩阵开辟内存  
  27.     Mat B(A);                // 使用拷贝构造函数  
  28.     C = A;                  // 赋值运算符  
  29.     /* 
  30.     拷贝构造函数和赋值函数 只拷贝信息头和矩阵指针 
  31.     */  
  32.   
  33.   
  34.     Mat D (A, Rect(10, 10, 100, 100) ); //选取A中一个矩形区域,即只访问其矩形区域的信息头,只是创建信息头  
  35.     Mat E = A(cv::Range::all(), Range(1,3)); // 创建访问边界的信息头。  
  36.     /* 
  37.     要创建一个感兴趣区域( ROI ),你只需要创建包含边界信息的信息头 
  38.     */  
  39.   
  40.   
  41.     Mat F = A.clone();//复制图像,包括数据  
  42.     Mat G;  
  43.     A.copyTo(G);  
  44.     /* 
  45.     拷贝矩阵本身(不只是信息头和矩阵指针), 
  46.     */  
  47.   
  48.   
  49.     //测试  
  50.     namedWindow( "a", CV_WINDOW_AUTOSIZE );  
  51.     namedWindow( "c", CV_WINDOW_AUTOSIZE );  
  52.       
  53.     imshow( "a", D);  
  54.     imshow( "c", E );  
  55.   
  56.   
  57.       
  58.     /****************************************图像的读取、处理和保存**************************************/  
  59.       Mat image;  
  60.      image = imread( "D:\\openCV\\openCVProject\\openCv笔记\\openCv笔记\\test.jpg", CV_LOAD_IMAGE_COLOR);//导入图像  
  61.   
  62.   
  63.      if( !image.data )  
  64.      {  
  65.         cout<< " No image data \n " ;  
  66.         return -1;  
  67.      }  
  68.   
  69.   
  70.      Mat gray_image;  
  71.      cvtColor( image, gray_image, CV_BGR2GRAY );//转化为灰度图  
  72.   
  73.   
  74.      imwrite( "../../images/Gray_Image.jpg", gray_image );//写入图像  
  75.   
  76.   
  77.      namedWindow( "source", CV_WINDOW_AUTOSIZE );  
  78.      namedWindow( "Gray image", CV_WINDOW_AUTOSIZE );  
  79.   
  80.   
  81.      imshow( "source", image );  
  82.      imshow( "Gray image", gray_image );  
  83.      /*******************************************************************************************/  
  84.      waitKey(0);  
  85.      return 0;  
  86. }   

 对于Mat数据结构,在对图像进行处理时要注意:

OpenCV函数中输出图像的内存分配是自动完成的(如果不特别指定的话)。
使用OpenCV的C++接口时不需要考虑内存释放问题。
赋值运算符和拷贝构造函数( ctor )只拷贝信息头。
使用函数 clone() 或者copyTo() 来拷贝一副图像的矩阵 

三、扫描图像的方法
[cpp] view plain copy
  1. #include<cv.h>  
  2. #include<highgui.h>  
  3. #include<time.h>  
  4. #include<iostream>  
  5. using namespace cv;  
  6. using namespace std;  
  7. int main()  
  8. {  
  9.     //Mat img(10,10,CV_8UC3,Scalar(0,0,255));  
  10.     Mat img,img_gray,img_gray2;  
  11.     img=imread("D:\\openCV\\openCVProject\\openCv笔记\\openCv笔记\\test.jpg", CV_LOAD_IMAGE_COLOR);  
  12.     cvtColor( img, img_gray, CV_BGR2GRAY );//转化为灰度图  
  13.     img_gray.copyTo(img_gray2);  
  14.     //方式一  
  15.     forint i=0;i<img_gray.rows;i++)  
  16.     {  
  17.         uchar* data = img_gray.ptr<uchar>(i);  
  18.         for(int j=0;j<img_gray.cols;j++)  
  19.         {  
  20.             data[j] = 255;   
  21.         }  
  22.     }  
  23.     //img.create(10,10,CV_8UC3,Scalar(0,0,255));  
  24.     //cout << "img = " << endl << " " << img_gray << endl << endl; //格式化输出  
  25.     //方式二 W*H的一幅图像看成是一个1*(w*h)的一个一维数组  
  26.     int nc;  
  27.     if(img_gray.isContinuous())//判断是否被所有的像素填满  
  28.     {  
  29.         nc = img_gray.rows*img_gray.cols*img_gray.channels();     
  30.     }  
  31.     else  
  32.     {  
  33.         cout<<"像素未填满,不可用第二种方式"<<endl;  
  34.         return -1;  
  35.     }  
  36.     uchar* data_2 = img_gray.ptr<uchar>(0);//提取第一个像素点指针  
  37.     for(int i=0;i<nc;i++)//遍历所有的元素  
  38.     {  
  39.         data_2[i] = 255;  
  40.     }  
  41.     //方式三指针扫描  
  42.     uchar* data_3 = img.data;//单个元素  
  43.     img.at<uchar>(0,0)=0;  
  44.     for(int i=0;i<img.rows;i++)//遍历所有的元素  
  45.     {  
  46.         for(int j=0;j<img.cols;j++)  
  47.         {  
  48.              data_3 = img.data + i*img.step + j * img.elemSize();   
  49.              //对各个通道赋值  
  50.              data_3[0]=100;  
  51.              data_3[1]=100;  
  52.              data_3[2]=100;  
  53.         }  
  54.     }  
  55.     /*时间函数 
  56.     double start = getTickCount(); 
  57.     finish = clock();  
  58.     duration = (double)(finish - start) / CLOCKS_PER_SEC;  
  59.     */  
  60.     //方式四 迭代器iterator扫描图像  
  61.     Mat_<Vec3b>::iterator it = img.begin<Vec3b>();    
  62.     Mat_<Vec3b>::iterator itend = img.end<Vec3b>();    
  63.     for (; it!=itend; it++)    
  64.     {    
  65.              //对各个通道赋值  
  66.              (*it)[0] = 200;    
  67.              (*it)[1] = 200;   
  68.              (*it)[2] = 200;   
  69.     }    
  70.       
  71.     //测试,根据自己的选择查看结果  
  72.     namedWindow("sorce",WINDOW_AUTOSIZE);  
  73.     namedWindow("result",WINDOW_AUTOSIZE);  
  74.   
  75.   
  76.     cv::imshow("sorce",img);  
  77.     cv::imshow("result",img_gray);  
  78.   
  79.   
  80.     waitKey(0);  
  81.     return 0;  
  82.   
  83.   
  84. }  

以上是对http://blog.csdn.net/yang_xian521/article/details/7182185#的综合,以下是其博文,正如博主所说的, data_3 = img.data + i*img.step + j * img.elemSize();,int i=0;i<img_gray.rows;i++。。。这种在循环中出现的语句识别比较耗时的,注意避免。以下是其博文

1.存取单个像素值
最通常的方法就是
[cpp] view plain copy
  1. img.at<uchar>(i,j) = 255;  
  2. img.at<Vec3b>(i,j)[0] = 255;  

如果你觉得at操作显得太笨重了,不想用Mat这个类,也可以考虑使用轻量级的Mat_类,使用重载操作符()实现取元素的操作。
[cpp] view plain copy
  1. cv::Mat_<uchar> im2= img; // im2 refers to image  
  2.    im2(50,100)= 0; // access to row 50 and column 100  

2.用指针扫描一幅图像

对于一幅图像的扫描,用at就显得不太好了,还是是用指针的操作方法更加推荐。先介绍一种上一讲提到过的
[cpp] view plain copy
  1. for (int j=0; j<nl; j++)  
  2. {  
  3.         uchar* data= image.ptr<uchar>(j);  
  4.         for (int i=0; i<nc; i++)  
  5.        {                   
  6.                   data[i] = 255;  
  7.         }  
  8. }  

更高效的扫描连续图像的做法可能是把W*H的衣服图像看成是一个1*(w*h)的一个一维数组,这个想法是不是有点奇葩,这里要利用isContinuous这个函数判断图像内的像素是否填充满,使用方法如下:
[cpp] view plain copy
  1. if (img.isContinuous())  
  2. {  
  3.         nc = img.rows*img.cols*img.channels();  
  4. }  
  5. uchar* data = img.ptr<uchar>(0);  
  6. for (int i=0; i<nc; i++)  
  7. {  
  8.         data[i] = 255;  
  9. }  

更低级的指针操作就是使用Mat里的data指针,之前我称之为暴力青年,使用方法如下:
[cpp] view plain copy
  1. uchar* data = img.data;  
  2. // img.at(i, j)  
  3. data = img.data + i * img.step + j * img.elemSize();  

3.用迭代器iterator扫描图像

和C++STL里的迭代器类似,Mat的迭代器与之是兼容的。是MatIterator_。声明方法如下:
[cpp] view plain copy
  1. cv::MatIterator_<Vec3b> it;    

或者是:
[cpp] view plain copy
  1. cv::Mat_<Vec3b>::iterator it;  
扫描图像的方法如下:
[cpp] view plain copy
  1. Mat_<Vec3b>::iterator it = img.begin<Vec3b>();  
  2. Mat_<Vec3b>::iterator itend = img.end<Vec3b>();  
  3. for (; it!=itend; it++)  
  4. {  
  5.          (*it)[0] = 255;  
  6. }  

4.高效的scan image方案总结
还是用我们之前使用过的getTickCount、getTickFrequency函数测试速度。这里我就不一一列举我测试的结果了,直接上结论。测试发现,好的编写风格可以提高50%的速度!要想减少程序运行的时间,必要的优化包括如下几个方面:


(1)内存分配是个耗时的工作,优化之;
(2)在循环中重复计算已经得到的值,是个费时的工作,优化之;举例:
[cpp] view plain copy
  1. int nc = img.cols * img.channels();  
  2. for (int i=0; i<nc; i++)  
  3. {.......}  
  4. //**************************  
  5. for (int i=0; i<img.cols * img.channels(); i++)  
  6. {......}  

后者的速度比前者要慢上好多。
(3)使用迭代器也会是速度变慢,但迭代器的使用可以减少程序错误的发生几率,考虑这个因素,可以酌情优化
(4)at操作要比指针的操作慢很多,所以对于不连续数据或者单个点处理,可以考虑at操作,对于连续的大量数据,不要使用它
(5)扫描连续图像的做法可能是把W*H的衣服图像看成是一个1*(w*h)的一个一维数组这种办法也可以提高速度。短的循环比长循环更高效,即使他们的操作数是相同的

以上的这些优化可能对于大家的程序运行速度提高并不明显,但它们毕竟是个得到速度提升的好的编程策略,希望大家能多采纳。
还有就是利用多线程也可以高效提高运行速度。OpenMP和TBB是两种流行的APT,不过对于多线程的东西,我是有些迷糊的,呵呵

5.整行整列像素值的赋值
对于整行或者整列的数据,可以考虑这种方式处理
[cpp] view plain copy
  1. img.row(i).setTo(Scalar(255));  
  2. img.col(j).setTo(Scalar(255));  
这节就先介绍这么多攻略吧~希望大家喜欢

参考资料


          1.http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/core/mat%20-%20the%20basic%20image%20container/mat%20-%20the%20basic%20image%20container.html


          2http://blog.sina.com.cn/s/blog_73ee929c01010yor.html
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 2个月宝宝厌食怎么办 宝宝吃母乳不长体重怎么办 9个月宝宝不长牙怎么办 3个月宝宝过胖怎么办 宝宝长的很慢怎么办 想一个月瘦20斤怎么办 山药弄胳膊上痒怎么办 手碰山药很痒怎么办 手摸了山药很痒怎么办 手切了山药很痒怎么办 山药弄的身上痒怎么办 疣迪去除疣体怎么办 尖锐湿庞出血了怎么办 尿道口周围烂了怎么办 尖锐湿庞复发了怎么办 宝宝脸上长湿疹怎么办如何治疗 孕妇得尖锐湿庞怎么办 痘痘留下的小坑怎么办 花洒固定座坏了怎么办 脚上起水泡烂了怎么办 月经期吃了芒果怎么办 月经量少又黑怎么办 来月经黑色的血怎么办 月经来的是黑色怎么办 来月经有血块是怎么办 月经又少又黑怎么办 来月经发黑又少怎么办 月经血发黑量少怎么办 做人流后肚子胀怎么办 怀孕见红了肚子不痛怎么办 月经来是黑色的怎么办 怀孕了长了痔疮怎么办 怀孕了有外痔疮怎么办 孕妇长痔疮很痛怎么办 孕9个月尿路感染怎么办 旁边有人尿不出来怎么办 外阴破皮了应该怎么办 脸上长脂肪粒怎么办怎么能消除 挤黑头留下的坑怎么办 长痘留下的坑怎么办 鼻子上留下黑印怎么办