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 }
写到这里。