我們有了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 }
寫到這里。