Accessing an OpenCV Mat Data using C++

Refer from http://blog.csdn.net/xiaowei_cqu/article/details/7771760

图像容器Mat

还是先看Mat的存储形式。Mat和Matlab里的数组格式有点像,但一般是二维向量,如果是灰度图,一般存放 <uchar>类型;如果是RGB彩色图,存放 <Vec3b>类型。
单通道灰度图数据存放格式:
Accessing an OpenCV Mat Data using C++_第1张图片
多通道的图像中,每列并列存放通道数量的子列,如RGB三通道彩色图:
Accessing an OpenCV Mat Data using C++_第2张图片
注意通道的顺序反转了:BGR。通常情况内存足够大的话图像的每一行是连续存放的,也就是在内存上图像的所有数据存放成一行,这中情况在访问时可以提供很大方便。可以用  isContinuous()函数来判断图像数组是否为连续的。

// 7x2: rows * cols or height * width
Mat m(7, 2, CV_8UC3);
cout << "height: " << m.rows << endl;// 7
cout << "width: " << m.cols << endl; // 2
cout << "m.elemSize():" << m.elemSize() << endl;// 3, channels * each element size in bytes( elemSize1( ))
cout << "m.elemSize1():" << m.elemSize1() << endl; // 1,  each element size in bytes
cout << "m.step1():" << m.step1() << endl; // 6,  (width of a single matrix row in bytes) / (element size in bytes)

Mat::depth

Returns the depth of a matrix element.

C++: int Mat:: depth ( ) const

The method returns the identifier of the matrix element depth (the type of each individual channel). For example, for a 16-bit signed element array, the method returns CV_16S . A complete list of matrix types contains the following values:

  • CV_8U - 8-bit unsigned integers ( 0..255 )
  • CV_8S - 8-bit signed integers ( -128..127 )
  • CV_16U - 16-bit unsigned integers ( 0..65535 )
  • CV_16S - 16-bit signed integers ( -32768..32767 )
  • CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
  • CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
  • CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )

访问图像中的像素


高效的方法:C操作符[ ]

最快的是直接用C风格的内存访问操作符[]来访问:
[cpp]  view plain copy
  1. Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)  
  2. {  
  3.     // accept only char type matrices  
  4.     CV_Assert(I.depth() != sizeof(uchar));  
  5.     int channels = I.channels();  
  6.     int nRows = I.rows ;  
  7.     int nCols = I.cols* channels;  
  8.     if (I.isContinuous())  
  9.     {  
  10.         nCols *= nRows;  
  11.         nRows = 1;  
  12.     }  
  13.     int i,j;  
  14.     uchar* p;  
  15.     for( i = 0; i < nRows; ++i)  
  16.     {  
  17.         p = I.ptr<uchar>(i);  
  18.         for ( j = 0; j < nCols; ++j)  
  19.         {  
  20.             p[j] = table[p[j]];  
  21.         }  
  22.     }  
  23.     return I;  
  24. }  
注意: 书中这段代码是有问题的,前面写成了 
[cpp]  view plain copy
  1. int nRows = I.rows * channels;  
  2. int nCols = I.cols;  
一般情况 isContinous为true,运行不会出错,但你可以注释掉那个if,会有访问越界的问题。
这种访问形式就是在每行定义一个指针,然后在内存上直接连续访问。如果整个数组在内存上都是连续存放的,那么只需要定义一个指针就可以访问所有的数据!如单通道的灰度图访问方式如下:
[cpp]  view plain copy
  1. uchar* p = I.data;  
  2. for( unsigned int i =0; i < ncol*nrows; ++i)  
  3.     *p++ = table[*p];  

安全的方法:迭代器iterator

相比用指针直接访问可能出现越界问题,迭代器绝对是非常安全的方法:
[cpp]  view plain copy
  1. Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)  
  2. {  
  3.     // accept only char type matrices  
  4.     CV_Assert(I.depth() != sizeof(uchar));  
  5.     const int channels = I.channels();  
  6.     switch(channels)  
  7.     {  
  8.     case 1:  
  9.         {  
  10.             MatIterator_<uchar> it, end;  
  11.             for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)  
  12.                 *it = table[*it];  
  13.             break;  
  14.         }  
  15.     case 3:  
  16.         {  
  17.             MatIterator_<Vec3b> it, end;  
  18.             for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)  
  19.             {  
  20.                 (*it)[0] = table[(*it)[0]];  
  21.                 (*it)[1] = table[(*it)[1]];  
  22.                 (*it)[2] = table[(*it)[2]];  
  23.             }  
  24.         }  
  25.     }  
  26.     return I;  
  27. }  
这里我们只定义了一个迭代器,用了一个for循环,这是因为在OpenCV里迭代器会访问每一列然后自动跳到下一行,不用管在内存上是否isContinous。另外要注意的是在三通道图像中我们定义的是 <Vec3b>格式的迭代器,如果定义成uchar,则只能访问到B即蓝色通道的值。
这种方式虽然安全,但是挺慢的,一会儿就知道了。

更慢的方法:动态地址计算

这种方法在需要连续扫描所有点的应用时并不推荐,因为它更实用与随机访问。这种方法最基本的用途是访问任意的某一行某一列:
[cpp]  view plain copy
  1. Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)  
  2. {  
  3.     // accept only char type matrices  
  4.     CV_Assert(I.depth() != sizeof(uchar));  
  5.     const int channels = I.channels();  
  6.     switch(channels)  
  7.     {  
  8.     case 1:  
  9.         {  
  10.             forint i = 0; i < I.rows; ++i)  
  11.                 forint j = 0; j < I.cols; ++j )  
  12.                     I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];  
  13.             break;  
  14.         }  
  15.     case 3:  
  16.         {  
  17.             Mat_<Vec3b> _I = I;  
  18.   
  19.             forint i = 0; i < I.rows; ++i)  
  20.                 forint j = 0; j < I.cols; ++j )  
  21.                 {  
  22.                     _I(i,j)[0] = table[_I(i,j)[0]];  
  23.                     _I(i,j)[1] = table[_I(i,j)[1]];  
  24.                     _I(i,j)[2] = table[_I(i,j)[2]];  
  25.                 }  
  26.                 I = _I;  
  27.                 break;  
  28.         }  
  29.     }  
  30.     return I;  
  31. }  
因为这种方法是为随机访问设计的,所以真的是奇慢无比。。。

减小颜色空间 color space reduction

现在来介绍下上述函数对每个元素的操作,也就是用table更改像素值。这里其实是做了个减小颜色空间的操作,这在一些识别之类的应用中会大大降低运算复杂度。类如uchar类型的三通道图像,每个通道取值可以是0~255,于是就有 256*256个不同的值。我们可以通过定义:
0~9 范围的像素值为 0
10~19 范围的像素值 为 10
20~29 范围的像素值为 20
。。。。。。
着这样的操作将颜色取值降低为 26*26*26 种情况。这个操作可以用一个简单的公式:

来实现,因为C++中int类型除法操作会自动截余。 类如 Iold=14; Inew=(Iold/10)*10=(14/10)*10=1*10=10;
在处理图像像素时,每个像素需要进行一遍上述计算也需要一定的时间花销。但我们注意到其实只有 0~255 种像素,即只有256种情况。进一步可以把256种计算好的结果提前存在表中 table 中,这样每种情况不需计算直接从 table 中取结果即可。
[cpp]  view plain copy
  1. int divideWith=10;   
  2. uchar table[256];  
  3. for (int i = 0; i < 256; ++i)  
  4.     table[i] = divideWith* (i/divideWith);  
于是table[i]存放的是值为i的像素减小颜色空间的结果,这样也就可以理解上述方法中的操作:
[cpp]  view plain copy
  1. p[j] = table[p[j]];  

LUT : Look up table

OpenCV 很聪明的有个 LUT 函数就是针对这种 Look up talbe 的操作:
[cpp]  view plain copy
  1. Mat lookUpTable(1, 256, CV_8U);  
  2. uchar* p = lookUpTable.data;  
  3. forint i = 0; i < 256; ++i)  
  4.     p[i] = table[i];  
  5. for (int i = 0; i < times; ++i)  
  6.     LUT(I, lookUpTable, J);  

算法计时

为了验证几种方法的效率,可以用一个简单的计时和输出:
[cpp]  view plain copy
  1. double t;  
  2. t = (double)getTickCount();  
  3. t = 1000*((double)getTickCount() - t)/getTickFrequency();  
  4. t /= times;  

实验结果


原图:
Accessing an OpenCV Mat Data using C++_第3张图片

降低颜色空间结果:
Accessing an OpenCV Mat Data using C++_第4张图片

算法时间:
Accessing an OpenCV Mat Data using C++_第5张图片

更清楚的时间对比表:
Accessing an OpenCV Mat Data using C++_第6张图片

转载请注明出处:http://blog.csdn.net/xiaowei_cqu/article/details/7771760
实验代码下载:http://download.csdn.net/detail/xiaowei_cqu/4443761


Refer from https://tolleybot.wordpress.com/2011/05/14/accessing-an-opencv-mat-data-using-c/

The new C++ interface to OpenCV is a great addition to the very popular computer vision library. For the most part the learning curve is not step, but I tend to find myself doing things in to many steps only to realize that there is already a function for that particular task.

One of the things I had some frustration with was accessing the new Mat classes data elements. The OpenCV documentation site (http://opencv.willowgarage.com/documentation/cpp/index.html) hosted at willowsgarage.com provides complete documentation of the Mat class. Its still a little unclear though exactly how to access elements in an efficient way, especially for multi-channel matrices.

I thought I would share a code bit that shows exactly how this is done.

1. int n = m_CurrentImage.channels();
2. // Get A pointer to the data
3. uchar* ptr = m_CurrentImage.data +m_CurrentImage.step*y;
4. // sample point in image
5. uchar Blue = ptr[n*x];
6. uchar Green = ptr[n*x+1];
7. uchar Red = ptr[n*x+2];

Variable Definitions:

  • n – The number of channels in the matrix
  • m_CurrentImage – The actual Mat class
  • ptr – The pointer to the start location of the data of the Mat
  • Blue – The blue value
  • Green – The green value
  • Red – The red value
  • x – The col where we want to retrieve the data
  • y – The row where we want to retrieve the data

The matrix in the code above is called m_CurrentImage, its a three channel matrix of unsigned char. Itsimportant to note that the code above would need to be changed to reflect the data type your Mat is using.

For example, to use a double type Mat, the code would be:

1. int n = m_CurrentImage.channels();
2. // Get A pointer to the data
3. double* ptr = (double*)m_CurrentImage.data +m_CurrentImage.step*y;
4. // sample point in image
5. double Blue = ptr[n*x];
6. double Green = ptr[n*x+1];
7. double Red = ptr[n*x+2];

From above the first thing we do is retrieve the number of channels in line 1. Line 3 contains the important step. Using n and y we find the start point for the location in memory where we wish to retrieve data. Once we have defined ptr we can then access the B, G, & R values of the matrix (or whatever you want to call them). We simply offset the ptr by x (the row) and the number of channels n.

This is a very easy way to access the data given any number of channels.

Hope this helps anyone getting a little frustrated at accessing data using the Mat class.


你可能感兴趣的:(Accessing an OpenCV Mat Data using C++)