三、Mat对象的-访问与创建

一、Mat对象的-访问

我们在对图像操作时,往往不是对图像的整体的操作,而是对图像的像素进行操作,所以高效的遍历图像就尤为重要。

我们以CV_8UC3类型图像降色彩为例,来分析四种遍历方式。


at与重载()

  • at <typename> (i,j):
    Mat类提供一个at的方法取得图像上的像素值,它是一个模板函数,可以取到任意类型的图像上的点.它的源码在mat.inl.hpp中,
bool colorReduce(Mat &_src, Mat &_dst, int div)
{
    if (_src.empty())
    {
        cout << "the input image is empty..." << endl;
        return false;
    }
    //指定_dst的属性
    _src.copyTo(_dst);
    for (int i = 0; i < _src.rows; i++)
    {
        for (int j = 0; j < _src.cols; j++)
        {
            _dst.at<Vec3b>(i, j)[0] = _src.at<Vec3b>(i, j)[0] / div*div + div / 2;
            _dst.at<Vec3b>(i, j)[1] = _src.at<Vec3b>(i, j)[1] / div*div + div / 2;
            _dst.at<Vec3b>(i, j)[2] = _src.at<Vec3b>(i, j)[2] / div*div + div / 2;
        }
    }
    return true;
}

注释:
1. _src.at(i, j)[0]是取出B通道的第i行第j列的像素点,[1]是G通道,[2]是R通道。
2. Vec3b是像素值类型,它在matx.hpp中是通过typedef Vec< T, N> 来定义的,T代表类型,但它对单通道的数据时用uchar、short、int、float、double。例如:
* 取CV_8UC1类型的点则用uchar
* 取CV_8UC3类型的点则用Vec3b
at对下面类型的数据均支持

/** @name Shorter aliases for the most popular specializations of Vec<T,n>
  @{
*/
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;

typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;

typedef Vec<ushort, 2> Vec2w;
typedef Vec<ushort, 3> Vec3w;
typedef Vec<ushort, 4> Vec4w;

typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<int, 6> Vec6i;
typedef Vec<int, 8> Vec8i;

typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;

typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;
/** @} */

当然OpenCV还定义了一个更加简单的方式,

Mat_<uchar> im = image;
im(i,j)=im(i,j)/ div*div + div / 2;

效果与上一种方法相同,花费时间相差不多,但是操作看起来更加简单

bool colorReduce1(Mat &_src, Mat &_dst, int div)
{
    if (_src.empty())
    {
        cout << "the input image is empty..." << endl;
        return false;
    }
    //指定_dst的属性
    _src.copyTo(_dst);
    Mat_<Vec3b> im_dst = _dst;
    Mat_<Vec3b> im_src = _src;
    for (int i = 0; i < _src.rows; i++)
    {
        for (int j = 0; j < _src.cols; j++)
        {
            im_dst(i, j)[0] = im_src(i, j)[0] / div*div + div / 2;
            im_dst(i, j)[1] = im_src(i, j)[1] / div*div + div / 2;
            im_dst(i, j)[2] = im_src(i, j)[2] / div*div + div / 2;
        }
    }
    return true;
}

对比这这两种方法:

  • at这里是由cv::Vec3b &cv::Mat::at < cv::Vec3b >(int i0,int i1)定义,当然它也有重载一边支持其他形式的数据的访问。
  • 另一种方式是对()进行重载,由cv::Vec3b &cv::Mat_< cv::Vec >::operator()(int idx0, int idx1).

用数组来遍历图像

**Mat.ptr< uchar >(i).

bool colorReduce2(Mat &_src, Mat &_dst, int div)
{
    if (_src.empty())
    {
        cout << "the input image is empty..." << endl;
        return false;
    }
    //指定_dst的属性
    _src.copyTo(_dst);

    for (int i = 0; i < _src.rows; i++)
    {
        const uchar * _src_data = _src.ptr<uchar>(i);
        uchar *_dst_data = _dst.ptr<uchar>(i);
        for (int j = 0; j < (_src.cols*_src.channels()); j++)
        {
            _dst_data[j] = _src_data[j] / div*div + div / 2;
        }
    }
    return true;
}

程序中把三个通道的变成了一个通道、这是建立在每一行数据元素之间在内存里是连续存储的前提下,每个像素的三通道像素按顺序存储。也就是一幅图像数据最开始的三个值,是最左上角的那像素的三个通道的值。
但是这种用法不能够用在行与行之间,因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元,这些空白单元对图像来说是没有意思的,只是为了在某些架构上更有效率,比如Inter MMX可以更有效的处理那种个数是4或者是8倍数的行。但是我们可以申请一个连续的空间来存储图像,前面提到由create开辟的空间是连续的。

  • 这种方法所需时间几乎是前两种方法的近二十分之一
  • 而且操作也较为简单

与at对比:
at不需要指定行首指针,ptr需要指定行首指针。

用连续指针来遍历图像

如果图像行与行之间有空白单元,那么指针连续访问方式就没法使用了。但是有些图像是连续的,对这些连续的图像进行来进行这种操作可以提高效率,其实这跟数组那个方式有些类似,但是数组那个支持行与行之间有空白单元的,这个不支持含有空白单元的

bool colorReduce3(Mat &_src, Mat &_dst, int div)
{
    if (_src.empty())
    {
        cout << "the input image is empty..." << endl;
        return false;
    }
    //指定_dst的属性
    _src.copyTo(_dst);
    if (_src.isContinuous())
    {
        const uchar * _src_data = _src.ptr<uchar>(0);
        uchar *_dst_data = _dst.ptr<uchar>(0);
        for (int j = 0; j < (_src.cols*_src.rows*_src.channels()); j++)
        {
            *_dst_data++ = *_src_data++ / div*div + div / 2;
        }
    }
    else
    {
        return false;
    }
    return true;
}

用指针除了用通道数的方法得到图像总的长度外,还可以用指针来索引固定位置的像素:
还可以用下面Mat的两个属性
* Mat.step 返回图像一行像素元素的个数(包括空白元素)。
* Mat.elemSize() 返回一个图像像素的大小。

Mat.at<uchar>(i,j) = Mat.data+i*Mat.step+j*Mat.elemSize();

测试代码:

bool colorReduce4(Mat &_src, Mat &_dst, int div)
{
    if (_src.empty())
    {
        cout << "the input image is empty..." << endl;
        return false;
    }

    //指定_dst的属性
    _src.copyTo(_dst);
    for (int i = 0; i < _src.rows; i++)
    {
        for (int j = 0; j < (_src.cols); j++)
        {
            *(_dst.data + i*_dst.step + j*_dst.elemSize()) = *(_src.data + i*_src.step + j*_src.elemSize()) / div*div + div / 2;
            *(_dst.data + i*_dst.step + j*_dst.elemSize()) = *(_src.data + i*_src.step + (j+1)*_src.elemSize()) / div*div + div / 2;
            *(_dst.data + i*_dst.step + j*_dst.elemSize()) = *(_src.data + i*_src.step + (j+2)*_src.elemSize()) / div*div + div / 2;
        }
    }
    return true;
}

但是这样的效率并不高,应该它的乘法运算变多了。

迭代器来遍历

可以用下面两种方式来让我们来声明一个迭代器:

MatIterator_<Vec3b> it;
Mat_<Vec3b>::iterator it;

如果迭代器是指向一幅const图像,则可以用下面来两种方式声明:

MatConstIterator_<Vec3b> it;
Mat_<Vec3b>::const_iterator it;

详见代码

//迭代器方式
bool colorReduce5(Mat &_src, Mat &_dst, int div)
{
    if (_src.empty())
    {
        cout << "the input image is empty..." << endl;
        return false;
    }

    //指定_dst的属性
    _src.copyTo(_dst);

    MatConstIterator_<Vec3b> it_in_data = _src.begin<Vec3b>();
    MatConstIterator_<Vec3b> it_in_end = _src.end<Vec3b>();

    MatIterator_<Vec3b> it_out_data = _dst.begin<Vec3b>();
    MatIterator_<Vec3b> it_out_end = _dst.end<Vec3b>();

    while (it_in_data != it_in_end)
    {
        (*it_out_data)[0] = (*it_in_data)[0] / div*div + div / 2;
        (*it_out_data)[1] = (*it_in_data)[1] / div*div + div / 2;
        (*it_out_data)[2] = (*it_in_data)[2] / div*div + div / 2;
        it_in_data++;
        it_out_data++;
    }

    return true;
}

第二种方法

//迭代器方式
bool colorReduce5(Mat &_src, Mat &_dst, int div)
{
    if (_src.empty())
    {
        cout << "the input image is empty..." << endl;
        return false;
    }

    //指定_dst的属性
    _src.copyTo(_dst);

    MatConstIterator_<Vec3b> it_in_data = _src.begin<Vec3b>();
    //MatConstIterator_<Vec3b> it_in_end = _src.end<Vec3b>();
    Mat_<Vec3b>::const_iterator it_in_end = _src.end<Vec3b>();

    MatIterator_<Vec3b> it_out_data = _dst.begin<Vec3b>();
    //MatIterator_<Vec3b> it_out_end = _dst.end<Vec3b>();
    Mat_<Vec3b>::iterator it_out_end = _dst.end<Vec3b>();

    while (it_in_data != it_in_end)
    {
        (*it_out_data)[0] = (*it_in_data)[0] / div*div + div / 2;
        (*it_out_data)[1] = (*it_in_data)[1] / div*div + div / 2;
        (*it_out_data)[2] = (*it_in_data)[2] / div*div + div / 2;
        it_in_data++;
        it_out_data++;
    }

    return true;
}

如果想从第二行开始,直接在起始地址上加上行数
_src.begin()+src.ptr(2);


对比效率

毫无疑问,指针访问连续存储的用时最少,但是这种方式的有缺点。
迭代器的方式:指示某个元素,操作后,指示下一个元素的位置。
应该也是连续访问的其他的方式大径相同,都是得到数据的行首地址与列数量来访问每个像素。应该是其中的取值赋值花费了较多的事件。下面是以上集中方法运行的时间
花费时间 顺序是从上到下
测试程序文件

二、Mat对象的-创建

使用Mat的构造函数创建矩阵

OpenCV提供了丰富的Mat的构造方法,定义在Mat.hpp文件中,下面例举了部分的函数

Mat();
Mat(int rows, int cols, int type);
Mat(Size size, int type);
Mat(int rows, int cols, int type, const Scalar& s);
Mat(Size size, int type, const Scalar& s);
Mat(int ndims, const int* sizes, int type);
Mat(int ndims, const int* sizes, int type, const Scalar& s);
Mat(const Mat& m);
Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);
Mat(Size size, int type, void* data, size_t step=AUTO_STEP);
Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0);
Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all());
Mat(const Mat& m, const Rect& roi);
Mat(const Mat& m, const Range* ranges);

Mat(int rows, int cols, int type, const Scalar& s);为例

Mat M(3, 3, CV_8UC3, Scalar(0, 0, 255));
    cout << "M=:" << endl << M << endl;

只要指定出相应的类型,均可正确创建图像,就不详细叙述。但是有些函数只是创建了一幅空图像。图像中并没有值,所以需要给图像赋值。这将在下面提到;
它还重载了()、=,这样它的操作就会变的方便了许多。
A=B; Mat B(A);这样的把A复制给B;
创建多维数组

//Mat(int ndims, const int* sizes, int type, const Scalar& s);
int sz[3]={2,2,2};
Mat L(3, sz,CV_8UC1,Scalar::all(0));

还需要注意一些其他的:
Mat B(A)只是复制了A的信息头给B,没有复制A的数据;还有一种赋值操作也是只复制信息头,只复制信息头的操作称为浅复制,Rect()也是浅复制。要把A完全复制给B的话可以用Mat的成员函数B=A.clone()A.copyTo(B),完全复制的称为深复制。

实用技巧
当测试一个算法是最好先输入一个小矩阵,看下运算结果,如果正确的话,再输入要处理的矩阵。所以要会定义小矩阵的方法
Mat C = (Mat_<double>(3, 3)<<0, -1, 0, -1, 5, -1, 0, -1, 0);Mat_<Vec3b> C1(3, 3, Vec3b(0, 255, 0));输出是
结果

为已经存在的IplImage指针创建信息头

//这种方式适合OpenCV2,在OpenCV3.1上就报错。
IplImage * src = cvLoadImage("F:/Opencv/myimage/51CTO/4.jpg", CV_LOAD_IMAGE_COLOR);
Mat mtx(src);
//OpenCV3上的转换方式看下
IplImage * src = cvLoadImage("F:/Opencv/myimage/51CTO/4.jpg", CV_LOAD_IMAGE_COLOR);
    Mat mtx=cvarrToMat(src);//opencv3上的操作

使用create()对Mat类对象初始化

注意,这种方法不能为矩阵设置初值,只是在改变尺寸是为矩阵数据重新分配内存

    Mat M;
    cout << "M=:" << endl << M << endl;
    M.create(4, 4, CV_8UC2);
    cout << "M=:" << endl << M << endl;
    M = Scalar(0, 255);//赋值
    cout << "M=:" << endl << M << endl;

Matlab类型的函数

zeros ones eye

    Mat E = Mat::eye(4, 4, CV_8UC1);
    cout << "E=:" << endl << E << endl;

    Mat Z = Mat::zeros(4, 4, CV_8UC1);
    cout << "Z=:" << endl << Z << endl;

    Mat O = Mat::ones(4, 4, CV_8UC1);
    cout << "O=:" << endl << O << endl;

后记

其是OpenCV中关于Mat对象的访问与创建不止这些,想到Mat支持高维矩阵的创建,和矩阵的多角度的访问,那么它的操作可想而知。这里只是简单的叙述一些,以便在以后的学习过程中使用,等到对C++与OpenCV有一个新的认识后还会回来再看Mat的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值