OpenCV笔记(二)——查看Mat对象的数据的三种方法

我们有了Mat的对象之后,就可以开始对图像进行处理。

在图像的处理过程中,对数据的查看并且对其进行修改,这应当是比较频繁的操作了。

这里讲讲官方手册当中给出的三种方法。

第一种方法:使用指向Mat数据部分的指针。

代码如下:

 1 Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
 2 {
 3     // accept only char type matrices
 4     CV_Assert(I.depth() != sizeof(uchar));
 5
 6     int channels = I.channels();
 7
 8     int nRows = I.rows;
 9     int nCols = I.cols * channels;
10
11     if (I.isContinuous())
12     {
13         nCols *= nRows;
14         nRows = 1;
15     }
16
17     int i,j;
18     uchar* p;
19     for( i = 0; i < nRows; ++i)
20     {
21         p = I.ptr<uchar>(i);
22         for ( j = 0; j < nCols; ++j)
23         {
24             p[j] = table[p[j]];
25         }
26     }
27     return I;
28 }

第11行使用isContinous函数,是为了保证图像的每一行之间是连续的,不存在某一行的行尾和下一行的开头的数据之间的内存单元存放其他数据。如果该函数返回true,则我们可以把图像当成1行、row*col列的数据格式进行遍历。

第21行使用ptr函数,它接受一个参数代表从0起始的行号。ptr的返回值默认为uchar*或者const uchar*(const版本的重载)。另外ptr有模板的实现,可以通过ptr<type>实现type*和const type*的返回值。这些返回值就是返回指向指定行号的指针。使用ptr方法返回的指针进行遍历,遍历的是图像的每一个字节(或者我们指定的type长度),而非像素(注意第9行ncols的定义)。

第二种方法:使用迭代器。

代码如下:

 1 Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
 2 {
 3     // accept only char type matrices
 4     CV_Assert(I.depth() != sizeof(uchar));
 5
 6     const int channels = I.channels();
 7     switch(channels)
 8     {
 9     case 1:
10         {
11             MatIterator_<uchar> it, end;
12             for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
13                 *it = table[*it];
14             break;
15         }
16     case 3:
17         {
18             MatIterator_<Vec3b> it, end;
19             for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
20             {
21                 (*it)[0] = table[(*it)[0]];
22                 (*it)[1] = table[(*it)[1]];
23                 (*it)[2] = table[(*it)[2]];
24             }
25         }
26     }
27
28     return I;
29 }

MatIterator_是Mat的迭代器,同样支持模板。在第12行和第19行的循环中,我们使用了Mat的begin和end函数,使迭代器分别指向Mat数据部分的开头和结尾。begin和end的实现如下:

 1 template<typename _Tp> inline MatIterator_<_Tp> Mat::begin()
 2 {
 3     CV_DbgAssert( elemSize() == sizeof(_Tp) );
 4     return MatIterator_<_Tp>((Mat_<_Tp>*)this);
 5 }
 6
 7 template<typename _Tp> inline MatIterator_<_Tp> Mat::end()
 8 {
 9     CV_DbgAssert( elemSize() == sizeof(_Tp) );
10     MatIterator_<_Tp> it((Mat_<_Tp>*)this);
11     it += total();
12     return it;
13 }

注意到第4行和第10行使用了Mat_<_Tp>类型,这里可以把它看成是Mat针对特定类型的模板,不予深究。Mat_<_Tp>类型便于我们对图像的数据进行操作。举个文档里边简单的例子:

1 Mat M(100, 100, CV_8U);
2
3 Mat_<float>& M1 = (Mat_<float>&)M;
4
5 M1(99, 99) = 1.f

Mat_类型可以方便地对数据进行操作,因为OpenCV的开发者对它的括号操作符进行了重载。我们看看Mat_类型对3通道图像的处理:

1 Mat_<Vec3b> img(240, 320, Vec3b(0, 255, 0));
2
3 for (int i = 0; i < 100; i++)
4     img(i, i) = Vec3b(255, 255, 255);
5 // 对第三个通道(蓝色)单独操作
6 for (int i = 0; i < img.rows; i++)
7     for (int j = 0; j < img.cols; j++)
8         img(i, j)[2] ^= (uchar)(i ^ j);

在迭代器的操作中我们最常使用的就是++和*操作符,我们来看看它们是怎么被实现的:

 1 template<typename _Tp> inline _Tp& MatIterator_<_Tp>::operator *() const { return *(_Tp*)(this->ptr); }
 2
 3 template<typename _Tp> inline MatIterator_<_Tp> MatIterator_<_Tp>::operator ++(int)
 4 {
 5     MatIterator_ b = *this;
 6     MatConstIterator::operator ++();
 7     return b;
 8 }
 9 // 由于MatIterator_<_Tp>的自增实现调用MatConstIterator的自增运算符,我们来看看后者的实现
10 inline MatConstIterator MatConstIterator::operator ++(int)
11 {
12     MatConstIterator b = *this;
13     *this += 1;
14     return b;
15 }
16 // 上述实现又依赖+=运算法,sliceStart和sliceEnd实时跟住我们遍历的当前行的行首和行尾,避免受到Mat数据的行与行之间的不连续造成的影响
17 inline MatConstIterator& MatConstIterator::operator += (ptrdiff_t ofs)
18 {
19     if( !m || ofs == 0 )
20         return *this;
21     ptrdiff_t ofsb = ofs*elemSize;
22     ptr += ofsb;
23     if( ptr < sliceStart || sliceEnd <= ptr )
24     {
25         ptr -= ofsb;
26         seek(ofs, true);
27     }
28     return *this;
29 }

第三种方法:使用at方法或者Mat_类型。

使用at方法的好处是可以随机访问你指定的数据。代码如下:

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

在第13行,我们使用了at<uchar>(i, j),该方法返回第i行第j列的数据的引用。at方法还支持我们传入cv::Point类型的参数,例如at<uchar>(cv::Point2f(16, 18))。

我们注意到,第18行使用了Mat_<Vec3b>,我们上面也说了,它可以看做是支持模板和随机访问的Mat类的变形。Mat_重载了括号运算符以支持随机访问,其代码实现如下:

1 template<typename _Tp> inline const _Tp& Mat_<_Tp>::operator ()(int i0, int i1) const
2 {
3     CV_DbgAssert( dims <= 2 && data &&
4                   (unsigned)i0 < (unsigned)size.p[0] &&
5                   (unsigned)i1 < (unsigned)size.p[1] &&
6                   type() == DataType<_Tp>::type );
7     return ((const _Tp*)(data + step.p[0]*i0))[i1];
8 }

写到这里。

时间: 2025-01-11 07:29:41

OpenCV笔记(二)——查看Mat对象的数据的三种方法的相关文章

应用程序与设备对象交换数据的三种方法

1.缓冲区设备读写伪代码:创建设备及指定设备标记 NTSTATUS status; PDEVICE_OBJECT pDevObj; PDEVICE_EXTENSION pDevExt; //创建设备名称 UNICODE_STRING devName; RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice"); //创建设备 status = IoCreateDevice( pDriverObject, sizeof(DEVI

Node.JS的表单提交及OnceIO中接受GET/POST数据的三种方法

OnceIO 是 OnceDoc 企业私有内容(文档)管理系统的底层Web框架,它可以实现模板文件.静态文件的全缓存,运行起来完全不需要I/O操作,并且支持客户端缓存优化,GZIP压缩等(只压缩一次),拥有非常好的性能,为您节约服务器成本.它的模块化功能,可以让你的Web进行分布式存储,在一个扩展包里即可包含前端.后端和数据库定义,只需通过添加/删除目录的方式就可实现功能删减,实现真正的模块化扩展.目前 OnceIO 已经开源,本文主要介绍node.js语言中的表单提交及OnceIO中接受GET

用旭日图展示数据的三种方法

什么是旭日图? 旭日图(Sunburst Chart)是一种现代饼图,它超越传统的饼图和环图,能表达清晰的层级和归属关系,以父子层次结构来显示数据构成情况.旭日图中,离远点越近表示级别越高,相邻两层中,是内层包含外层的关系. 在实际项目中使用旭日图,可以更细分溯源分析数据,真正了解数据的具体构成.而且,旭日图不仅数据直观,而且图表用起来特别炫酷,分分钟拉高数据汇报的颜值!很多数据场景都适合用旭日图,比如,在销售汇总报告中,方便看到每个店铺的销售业绩分布(如下图): 做旭日图的三种方法 1. 用E

去除DataTable重复数据的三种方法

其中要避免目标库插入重复数据.这重复数据可能是源数据库本身就有重复数据,还有就是已经插入避免重复插入. 过滤自身重复数据解决方案 第一种:采用DataView.ToTable()方法 DataView.ToTable 方法 .NET Framework 2.0 其根据现有 DataView 中的行,创建并返回一个新的 DataTable. 重载列表 名称 说明 DataView.ToTable () 根据现有 DataView 中的行,创建并返回一个新的 DataTable. 由 .NET Co

php获取POST数据的三种方法

方法一,$_POST $_POST或$_REQUEST存放的是PHP以key=>value的形式格式化以后的数据. $_POST方式是通过 HTTP POST 方法传递的变量组成的数组,是自动全局变量.如使用$_POST['name']就可以接收到网页表单以及网页异步方式post过来的数据,即$_POST只能接收文档类型为Content-Type: application/x-www-form-urlencoded提交的数据. 方法二,使用file_get_contents("php://

什么是ViewData的, ViewBag和TempData? - MVC为当前和后续请求之间传递数据的三种方法

原文出处:http://www.codeproject.com/Articles/476967/WhatplusisplusViewData-cplusViewBagplusandplusTem ASP.NET MVC提供3种选择ViewData,ViewBag,TempData来从controller到View及后续请求传输数据. ViewData和ViewBag很相似,而TempData有所不同. 让我们来讨论或总结三者的要点: ViewBag 和 ViewData的区别: ViewData

js判断数组是否有重复数据的三种方法

var arr=new Array("aa","bb","cc","dd","aaa","aa"); // 方法1:取出数组中的一个值,与其之后的数据挨个比对>>>最容易想到,但运算最麻烦 var method1=function(array){ for(var i=0;i<array.length-1;i++) { for(var j=i+1;j<arra

python抓取网页数据的三种方法

一.正则表达式提取网页内容 解析效率:正则表达式>lxml>beautifulsoup 代码: import  re import  urllib2 urllist  = 'http://example.webscraping.com/places/default/view/United-Kingdom-239' html =  urllib2.urlopen(urllist).read() num =  re.findall('<td class="w2p_fw"&

java基础课程_数据交换三种方法

//方法一  int c = n;  n=m;  m=c;  //测试   System.out.println("n="+n+"\nm="+m);      //方法 二用 +-做交换  int x=10,y=5;  x=x+y;  //5+20  y=x-y;  //25-20  x=x-y;  //25-5  System.out.println("x="+x+"\ny="+y);    //方式三 异或效率最高  in