C++学习笔记——Mat类详解及元素的遍历方法

全文部分引自:http://blog.csdn.net/xiaowei_cqu/article/details/7771760
Mat的常见属性

data uchar型的指针。Mat类分为了两个部分:矩阵头和 指向矩阵部分的指针,data就是指向矩阵数据的指针
dims 矩阵的维度,例如5*6矩阵是二维矩阵,则dims=2,三维矩阵dims=3
rows 矩阵的行数
cols 矩阵的列数
size 矩阵的大小,size(cols,rows),如果矩阵的维数大于2,则是size(-1,-1)
channels 矩阵元素拥有的通道数,例如常见的彩色图像,每一个像素由RGB三部分组成,则channels=3

和Mat相关的几个数据类型

type
表示了矩阵中元素的类型以及矩阵的通道个数,它是一系列的预定义的变量,其命名规则为CV_(位数)+(数据类型)+(通道数)。具体的有以下值:
C++学习笔记——Mat类详解及元素的遍历方法_第1张图片
这里U(unsigned integer)表示的是无符号整数,S(signed integer),F(float)是浮点数。
例如:CV_16UC2,表示的是元素类型是一个16位的无符号整数,通道为2. C1,C2,C3,C4则表示通道是1,2,3,4
type一般是在创建Mat对象时设定,如果要取得Mat的元素类型,则无需使用type,使用下面的depth

depth
矩阵中元素的一个通道的数据类型,这个值和type是相关的。例如 type为 CV_16SC2,一个2通道的16位的有符号整数。那么,depth则是CV_16S。depth也是一系列的预定义值,
将type的预定义值去掉通道信息就是depth值:
CV_8U CV_8S CV_16U CV_16S CV_32S CV_32F CV_64F

elemSize
矩阵一个元素占用的字节数,例如:type是CV_16SC3,那么elemSize = 3 * 16 / 8 = 6 bytes

elemSize1
矩阵元素一个通道占用的字节数,例如:type是CV_16CS3,那么elemSize1 = 16 / 8 = 2 bytes = elemSize / channels

访问图像中的像素

高效的方法:C操作符【】
最快的是直接用C风格的内存访问操作符【】来访问:

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)  
{  
    // accept only char type matrices  
    CV_Assert(I.depth() != sizeof(uchar));  
    int channels = I.channels();  
    int nRows = I.rows ;  
    int nCols = I.cols* channels;  
    if (I.isContinuous())  
    {  
        nCols *= nRows;  
        nRows = 1;  
    }  
    int i,j;  
    uchar* p;  
    for( i = 0; i < nRows; ++i)  
    {  
        p = I.ptr<uchar>(i);  
        for ( j = 0; j < nCols; ++j)  
        {  
            p[j] = table[p[j]];  
        }  
    }  
    return I;  
}  

一般情况 isContinous为true,运行不会出错,但你可以注释掉那个if,会有访问越界的问题。
这种访问形式就是在每行定义一个指针,然后在内存上直接连续访问。如果整个数组在内存上都是连续存放的,那么只需要定义一个指针就可以访问所有的数据!如单通道的灰度图访问方式如下:

uchar* p = I.data;  
for( unsigned int i =0; i < ncol*nrows; ++i)  
    *p++ = table[*p];  

安全的方法:迭代器iterator
相比用指针直接访问可能出现越界问题,迭代器绝对是非常安全的方法:

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)  
{  
    // accept only char type matrices  
    CV_Assert(I.depth() != sizeof(uchar));  
    const int channels = I.channels();  
    switch(channels)  
    {  
    case 1:  
        {  
            MatIterator_ it, end;  
            for( it = I.begin(), end = I.end<uchar>(); it != end; ++it)  
                *it = table[*it];  
            break;  
        }  
    case 3:  
        {  
            MatIterator_ it, end;  
            for( it = I.begin(), end = I.end<Vec3b>(); it != end; ++it)  
            {  
                (*it)[0] = table[(*it)[0]];  
                (*it)[1] = table[(*it)[1]];  
                (*it)[2] = table[(*it)[2]];  
            }  
        }  
    }  
    return I;  
}  

这里我们只定义了一个迭代器,用了一个for循环,这是因为在OpenCV里迭代器会访问每一列然后自动跳到下一行,不用管在内存上是否isContinous。另外要注意的是在三通道图像中我们定义的是 格式的迭代器,如果定义成uchar,则只能访问到B即蓝色通道的值。
这种方式虽然安全,但是挺慢的,一会儿就知道了。

更慢的方法:动态地址计算
这种方法在需要连续扫描所有点的应用时并不推荐,因为它更实用与随机访问。这种方法最基本的用途是访问任意的某一行某一列:

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)  
{  
    // accept only char type matrices  
    CV_Assert(I.depth() != sizeof(uchar));  
    const int channels = I.channels();  
    switch(channels)  
    {  
    case 1:  
        {  
            for( int i = 0; i < I.rows; ++i)  
                for( int j = 0; j < I.cols; ++j )  
                    I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];  
            break;  
        }  
    case 3:  
        {  
            Mat_ _I = I;  

            for( int i = 0; i < I.rows; ++i)  
                for( int j = 0; j < I.cols; ++j )  
                {  
                    _I(i,j)[0] = table[_I(i,j)[0]];  
                    _I(i,j)[1] = table[_I(i,j)[1]];  
                    _I(i,j)[2] = table[_I(i,j)[2]];  
                }  
                I = _I;  
                break;  
        }  
    }  
    return I;  
}  

因为这种方法是为随机访问设计的,所以真的是奇慢无比。。。

你可能感兴趣的:(C++编程学习,C++学习笔记)