Mat - 图像的容器

在对图像进行处理时,首先需要将图像载入到内存中,而Mat就是图像在内存中的容器,管理着图像在内存中的数据。Mat是C++ 的一个类,由于OpenCV2中引入了内存自动管理机制,所以不必手动的为Mat开辟内存空间以及手动的释放内存。Mat中包含的数据主要由两个部分构成:矩阵头(矩阵尺寸、存储方法、存储地址等信息)和一个指向存储图像所有像素值的矩阵(根据所选的存储方法不同的矩阵可以是不同的维数)的指针。

在图像处理中,对图像的处理不可能是在一个函数中完成的,这就需要在不同的函数间传递Mat。同时,图像处理的计算量是很大,除非万不得已就不要去传递比较大的Mat。这就要求使用某种机制来实现Mat的快速传递。Mat中主要有矩阵头和一个指向矩阵的指针,矩阵头是一个常数值,但是矩阵保存了图像所有的像素值,通常会比矩阵头大几个数量级,因此传递Mat是主要的消耗是在矩阵复制上。为了解决这个问题,OpenCV中引入了计数机制。每个Mat都有自己的信息头,但是共享同一个矩阵,也就是在传递Mat时,只复制矩阵头和指向矩阵的指针。

   1:      Mat a,c ; 
   2:      a = imread("d:\\test.jpg",1) ;
   3:      Mat b(a) ; //拷贝构造函数
   4:      a = c ; //复制运算符

上面代码中3个Mat对象a,b,c指向同一个矩阵,由于都指向了同一个矩阵,某一个对象对矩阵进行操作时也会影响到其他对象读取到的矩阵。

多个对象同时使用一个矩阵,那么当不需要该矩阵时,谁来负责清理?简单的回答是,最后一个使用它的对象。通过引用计数机制,无论什么时候Mat对象的信息头被复制了,都会增加矩阵的引用次数加1;反之,当一个Mat的信息头被释放后,引用计数就会被减1;当计数被减到0时,矩阵就会被释放。

当然,有些时候还是需要拷贝矩阵本身的,这时候可以使用clone和 copyTo。通过clone和copyTo创建的Mat,都有自己的矩阵,修改其中一个的矩阵不会对其他的造成影响。

访问像素的三种方法

对图像像素值的访问是图像处理最基本的要求,在OpenCV中提供了三种方式来访问图像的像素值。

矩阵在内存中的存储

首先来看一下图像像素值在内存中的保存方式。前面提到,像素值是以矩阵的方式保存的,矩阵的大小取决于图像采用的颜色模型,确切的说是图像的通道数。如果是灰度图像,矩阵是这样的:

矩阵的每一个元素代表一个像素 值。而对多通道图像来说,一个像素值需要多个矩阵元素来存储,矩阵中的列会包含多个子列,其子列数和通道数目相等。以常见的RGB模型来说:

而且,如果内存比较大,图像中的各行各列就可以一行一行的连接起来,形成一个长行。连续存储有助于提升图像的扫描速度,使用iscontinuous来判断矩阵是否是连续存储的。

颜色空间缩减

如果矩阵元素存储的是单通道像素,使用8位无符号来保存每个元素,那么像素可能有256个不同的值。如果是三通道的话,就会用一千六百多种颜色。如此多的颜色在有些时候不是必须的,而且会对算法的性能造成严重的影响。在这种情况下,最常用的做法就是颜色空间的缩减,也就是将现有的颜色空间进行映射,以获得较少的颜色数。例如:颜色值0到9映射为0,10到19映射为10,以此类推。

以简单颜色空间缩减为例,使用OpenCV提供的三种方式来遍历图像像素。将各个颜色值映射关系存储到表中,在对格像素的颜色值进行处理时,直接进行查表。下面是对映射表的初始化:

   1:   
   2:      uchar table[256] ;
   3:      int divideWith  = 10;
   4:      for(int i = 0 ; i < 256 ; i ++)
   5:          table[i] = (uchar) ( divideWith * (i / divideWith));

这里将各个像素的颜色值整除以10,然后再乘以10,这样会像上面所说的将0到9的颜色值映射为0,10到19的颜色值映射为10,以此类推。

指针遍历图像 Efficient Way

   1:  Mat& scanImageWithPointer(Mat &img , const uchar * const table)
   2:  {
   3:      CV_Assert(img.depth () == sizeof(uchar));
   4:   
   5:      int channels = img.channels() ;
   6:   
   7:      int rows = img.rows * channels;
   8:      int cols = img.cols ;
   9:   
  10:      if(img.isContinuous()) {
  11:          cols *= rows ;
  12:          rows = 1 ;
  13:      }
  14:   
  15:      uchar * p ;
  16:      for(int i = 0 ; i < rows ; i ++){
  17:          p = img.ptr<uchar>(i);
  18:          for(int j = 0 ; j < cols ; j ++){
  19:              p[j] = table[p[j]] ;
  20:          }
  21:      }
  22:      return img ;
  23:  }

首先使用断言,只处理使用8位无符号数保存元素值的矩阵。然后在取出举证的行数和列数,如果是多通道的话矩阵是有子列的,用通道数乘以矩阵的行数作为最终遍历时行数。另外,调用isContinuous来判断矩阵在内存中是不是连续存储的。p = img.ptr<uchar>(i); 来获取每一行开始处的指针,然后遍历至改行的末尾。如果是连续存储的,就只需要获取一次每行的开始指针,一路遍历下去即可。

Mat中的data字段会返回指向矩阵第一行第一列的指针,通过可以使用该字段来检查图像是否被载入成功了。当矩阵是连续存储时,也可以通过data来遍历整个图像。

   1:      uchar * p = img.data ;
   2:      for(unsigned int i = 0 ; i < img.rows * img.cols * img.channels() ; i ++)
   3:          *p ++ = table[*p] ;

但是这种代码可读性差,并且进一步操作困难,其在性能上的表现并不明显的优于上面的方法。

迭代遍历图像 Safe Method

   1:  Mat& scanImageWithIterator(Mat &img,const uchar * const table)
   2:  {
   3:      CV_Assert(img.depth () == sizeof(uchar));
   4:   
   5:      const int channels = img.channels() ;
   6:   
   7:      switch (channels){
   8:      case 1:
   9:          {
  10:              MatIterator_<uchar> it,end ;
  11:              end = img.end<uchar>() ;
  12:              for(it = img.begin<uchar>(); it != end ; it ++) {
  13:                  *it = table[*it] ;
  14:              }
  15:              break ;
  16:          }
  17:      case 2:
  18:          {
  19:              MatIterator_<Vec3b> it,end ;
  20:              end = img.end<Vec3b>() ; 
  21:              for(it = img.begin<Vec3b>(); it != end ; it ++) {
  22:                  (*it)[0] = table[(*it)[0]] ;
  23:                  (*it)[1] = table[(*it)[1]] ;
  24:                  (*it)[2] = table[(*it)[2]] ;
  25:              }
  26:              break ;
  27:          }
  28:      }
  29:      return img ;
  30:  }
有两种方式来获取图像矩阵的迭代器
   1:  cv::MatIterator_<cv::Vec3b> it = image.begin<cv::Vec3b>();
   2:  cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();

同样的获取图像的常量迭代器也有两种方式

   1:    cv::MatConstIterator_<cv::Vec3b> it = image.begin<cv::Vec3b>();
   2:      cv::Mat_<cv::Vec3b>::const_iterator end = image.end<cv::Vec3b>();

 
另外需要注意的就是,对于多通道图像(例如三通道),每列中有3个uchar元素,也就是一个有3个元素的vector,在OpenCV中使用Vec3b来命名。如果要访问第n个子列,只需要使用[]来操作就可以了。而且,OpenCV迭代中扫过一行的所有列后会自动的跳到下一列,所以在RGB中如果使用uchar来进行迭代不实用Vec3b的话只能获取到B通道的值。

at<>遍历图像

这种方法不推荐用来遍历图像,它主要用来获取或更改图像的中随机元素的。基本用途是用来访问特定的矩阵元素(知道行数和列数)

   1:  Mat& scanImageWithAt(Mat& img,const uchar * const table)
   2:  {
   3:      CV_Assert(img.depth () == sizeof(uchar));
   4:      const int channels = img.channels() ;
   5:   
   6:      switch (channels){
   7:      case 1:
   8:          {
   9:              for (int i = 0 ; i < img.rows ; i ++)
  10:                  for(int j = 0 ; j < img.cols ; j ++)
  11:                      img.at<uchar>(i,j) = table[img.at<uchar>(i,j)] ;
  12:              break ;
  13:          }
  14:      case 2:
  15:          {
  16:              Mat_<Vec3b> I = img ;
  17:              for(int i = 0 ; i < I.rows ; i ++){
  18:                  for(int j = 0 ; j < I.cols ; j ++){
  19:                      I(i,j)[0] = table[I(i,j)[0]] ;
  20:                      I(i,j)[1] = table[I(i,j)[1]] ;
  21:                      I(i,j)[2] = table[I(i,j)[2]] ;
  22:                  }
  23:              }
  24:              img = I ;
  25:              break ;
  26:          }
  27:      }
  28:      return img ;
  29:  }

在Mat种保存矩阵元素的数据类型是很重要的,Mat类中也提供了很多的模板方法来处理不同的矩阵元素类型,这种灵活性却会使得简单的代码复杂化,因此就有了Mat_模板类来简化操作。也就是说如果知道了Mat的矩阵元素的数据类型,就可以将其转换为Mat_模板类来简化代码。
前面提到,at<>主要是用来访问矩阵的随机元素的,下面使用该方法来给一张图像添加椒盐噪声。
   1:  //n添加椒盐噪声的个数
   2:  void salt(Mat& img,int n)
   3:  {
   4:      for(int k = 0 ; k < n  ; k ++) {
   5:          int i = rand() % img.rows ;
   6:          int j = rand() % img.cols ;
   7:          
   8:          if(img.channels() == 1){
   9:              img.at<uchar>(i,j) = 255 ;
  10:          }else if(img.channels() == 3){
  11:              img.at<Vec3b>(i,j)[0] = 255 ;
  12:              img.at<Vec3b>(i,j)[1] = 255 ;
  13:              img.at<Vec3b>(i,j)[2] = 255 ;
  14:          }
  15:      }
  16:  }

lenna图片添加椒盐噪声后

这里要提下,在使用imshow将Mat显示到命名窗口时,需要调用waitkey()这个函数下,不然的话在命名窗口显示不出来。

LUT

在图像处理中,对图像的所有像素重新映射是很常见的,在OpenCV中提供一个函数来实现该该操作,不需要去扫描整个图像,operation on array :LUT。

   1:      Mat lookupTable(1,256,CV_8S);
   2:      uchar *p = lookupTable.data ;
   3:      for(int i = 0 ; i < 256 ; i ++)
   4:          p[i] = table[i] ;
   5:   
   6:      Mat result ;
   7:      LUT(img,lookupTable,result) ;

LUT的函数原型
void LUT(InputArray src, InputArray lut, OutputArray dst)
src 输入的8位矩阵
lut 256个元素的查找表,为了应对多通道输入矩阵,查找表要么是单通道(此时,输入矩阵的多个通道使用相同的查找表),要么和输入矩阵有相同的通道数
dst 输出矩阵。和输入矩阵有相同的尺寸和通道
 

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

OpenCV2学习笔记(一)的更多相关文章

  1. OpenCV2学习笔记05:矩阵翻转

    对图像进行翻转或旋转可以使用cv::flip()函数,可以实现将一个二维矩阵沿X轴.Y轴或者同时沿XY轴翻转.函数原型如下: C++: void flip(InputArray src, Output ...

  2. OpenCV2学习笔记03:Qt中配置OpenCV环境

    在Qt中开发基于OpenCV的应用时,需要配置对应函数库到环境变量,这时候我们需要使用到qmake能够识别的变量来指定环境变量. INCLUDEPATH: 用于指定搜索头文件到文件夹路径. LIBS: ...

  3. OpenCV2学习笔记04:图像的读取与显示

    1. 图像读取:imread() Mat imread( ) 参数介绍: filename: 待加载的文件名称. flags: 此标志用来指定被加载图像的颜色类型(color type).这个标志的取 ...

  4. OpenCV2学习笔记02:MSVC2013搭建OpenCV开发环境

    我这里编译的库是通过手动编译的.只是需要注意的是,手动编译一般会产生大量的文件,差不多7个多G的样子,实在是有点浪费硬盘存储呀,其实我们可以删除掉没有用的东西.因为我们在编译的时候指定了一个目录比如我 ...

  5. OpenCV2学习笔记01:Linux下OpenCV开发环境的搭建

    个人已经厌倦了Windows下的开发方式,于是决定转到Linux平台上来,当然我也知道这个转变会很艰辛,但是我还是要坚持.所以,后面的所有开发我都会基于Linux和Qt,先从开发环境的搭建开始做起,当 ...

  6. OpenCV2学习笔记(十四):基于OpenCV卡通图片处理

    得知OpenCV有一段时间.除了研究的各种算法的内容.除了从备用,据导游书籍和资料,尝试结合链接的图像处理算法和日常生活,第一桌面上(随着摄像头)完成了一系列的视频流处理功能.开发平台Qt5.3.2+ ...

  7. OpenCV2学习笔记(十五):利用Cmake高速查找OpenCV函数源代码

    在使用OpenCV时,在对一个函数的调用不是非常了解的情况下,通常希望查到该函数的官方声明.而假设想进一步研究OpenCV的函数,则必须深入到源码. 在VS中我们能够选中想要查看的OpenCV函数,点 ...

  8. opencv2.4.13+python2.7学习笔记--使用 knn对手写数字OCR

    阅读对象:熟悉knn.了解opencv和python. 1.knn理论介绍:算法学习笔记:knn理论介绍 2. opencv中knn函数 路径:opencv\sources\modules\ml\in ...

  9. OpenCV学习笔记(一)——OpenCV3.1.0+VS2015开发环境配置

    摘要: 由于最近AR(增强现实)这个概念非常火爆,各种基于AR的应用及游戏逐渐面向大众,而在AR中最重要的两个技术就是跟踪识别和增强渲染,其中跟踪识别是通过OpenCV这个开源的计算机视觉库来实现的, ...

随机推荐

  1. 解读ASP.NET 5 & MVC6系列(4):核心技术与环境配置

    asp.net 5是下一代的asp.net,该版本进行了全部重写以适用于跨平台,新新版本中,微软引入了如下工具与命令:DNVM.DNX.DNU. DNVM(.NET Version Manager): ...

  2. 皮裤原理和运营微信公众号dotNET跨平台

    经常碰到有同学对.NET跨平台存在各种疑惑和误解,原因是什么呢?当然我是知道.NET的跨平台不是问题,而且微软2014年的努力可圈可点,而且还有很多人对.NET的前景感到困惑.春节期间突然明白了,这就 ...

  3. Web前端开发工程师养成计划【转载】

    Web前端开发工程师养成计划(入门篇) 最原始的忠告:这个世界上有想法的人很多,但是有想法又能实现它的人太少! 首先要感谢伟大的Web2.0概念.产品概念.用户体验概念.jQuery插件,是它们在中国 ...

  4. 企业IT管理员IE11升级指南【3】—— IE11 新的GPO设置

    企业IT管理员IE11升级指南 系列: [1]—— Internet Explorer 11增强保护模式 (EPM) 介绍 [2]—— Internet Explorer 11 对Adobe Flas ...

  5. 【Java并发编程实战】-----“J.U.C”:Condition

    在看Condition之前,我们先来看下面这个例子: 工厂类,用来存放.取出商品: public class Depot { private int depotSize; //仓库大小 private ...

  6. MySQL 存储过程和函数

    概述 一提到存储过程可能就会引出另一个话题就是存储过程的优缺点,这里也不做讨论,一般别人问我我就这样回答你觉得它好你就用它.因为mysql中存储过程和函数的语法非常接近所以就放在一起,主要区别就是函数 ...

  7. 自动化安装SQL Server+SP就那么简单

    随着业务.企业规模的日益壮大,DB的数量也在不断增多,配置一台新增DB,从服务器的参数配置,磁盘阵列规划,DB安装部署,DB参数调优等等一列步骤下来,手工操作的效率变得越来越低,因为我负责的数据库近些 ...

  8. 基于Spring Mvc实现的Excel文件上传下载

    最近工作遇到一个需求,需要下载excel模板,编辑后上传解析存储到数据库.因此为了更好的理解公司框架,我就自己先用spring mvc实现了一个样例. 基础框架 之前曾经介绍过一个最简单的spring ...

  9. Apache Spark源码剖析

    Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著   ISBN 978-7-121-25420- ...

  10. iOS-屏幕适配-UI布局

    iOS 屏幕适配:autoResizing autoLayout和sizeClass 一.图片解说 -------------------------------------------------- ...