OpenCV Mat遍历的方法

Posted pan_jinquan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV Mat遍历的方法相关的知识,希望对你有一定的参考价值。

OpenCV像素遍历常用的是三种方法:ptr指针,迭代器(iterator)以及动态地址at。

  • 动态地址at不适合用于像素遍历,速度太慢了,比较适合随机访问的方式;
  • 使用Mat的ptr指针进行图像遍历更加高效,

特别的:一般图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行进行处理。

因此最高效的遍历方法如下

void image_copy(cv::Mat &src, cv::Mat &dst) 
    int h = src.rows;
    int w = src.cols;
    dst.create(src.size(), src.type());
    if (src.isContinuous() && dst.isContinuous()) 
        h = 1;
        w = w * src.rows * src.channels();
    
    for (int i = 0; i < h; i++) 
        uchar *sptr = src.ptr<uchar>(i);
        uchar *dptr = dst.ptr<uchar>(i);
        for (int j = 0; j < w; j++) 
            *dptr++ = *sptr++;
            //dptr[j] = sptr[j];
        
    

 PS:一般经过裁剪的Mat图像,都不再连续了,如cv::Mat crop_img = src(rect);crop_img 是不连续的Mat图像,如果想转为连续的,最简单的方法,就是将不连续的crop_img 重新clone()一份给新的Mat就是连续的了。关于Mat连续存储的问题,可见:http://blog.csdn.net/guyuealian/article/details/78614662
 

其他遍历方式 ,可参考:

实现方式: https://blog.csdn.net/keith_bb/article/details/53071133


void image_copy1(cv::Mat &src, cv::Mat &dst) 
    //使用ptr指针
    int n = src.channels();
    int h = src.rows;          //获取图像矩阵行数
    int w = src.cols;          //获取图像矩阵列数
    dst.create(src.size(), src.type()); //初始化返回结果
    for (int i = 0; i < h; i++) 
        //获取矩阵每行首地址指针
        uchar *sptr = src.ptr<uchar>(i);
        uchar *dptr = dst.ptr<uchar>(i);
        for (int j = 0; j < w; j++) 
            uchar b = sptr[n * j];
            uchar g = sptr[n * j + 1];
            uchar r = sptr[n * j + 2];
            dptr[n * j] = b;
            dptr[n * j + 1] = g;
            dptr[n * j + 2] = r;
        
    


void image_copy2(cv::Mat &src, cv::Mat &dst) 
    //使用ptr指针
    int n = src.channels();
    int h = src.rows;              //获取图像矩阵行数
    int w = src.cols * n;          //三通道图像每行元素个数为列数x通道数
    dst.create(src.size(), src.type());
    for (int i = 0; i < h; i++) 
        //获取矩阵每行首地址指针
        uchar *sptr = src.ptr<uchar>(i);
        uchar *dptr = dst.ptr<uchar>(i);
        for (int j = 0; j < w; j++) 
            dptr[j] = sptr[j];
        
    


void image_copy3(cv::Mat &src, cv::Mat &dst) 
    //使用ptr指针
    int n = src.channels();
    int h = src.rows;              //获取图像矩阵行数
    int w = src.cols * n;          //三通道图像每行元素个数为列数x通道数
    dst.create(src.size(), src.type());
    for (int i = 0; i < h; i++) 
        //获取矩阵每行首地址指针
        uchar *sptr = src.ptr<uchar>(i);
        uchar *dptr = dst.ptr<uchar>(i);
        for (int j = 0; j < w; j += n) 
            //uchar b = sptr[j];
            //uchar g = sptr[j + 1];
            //uchar r = sptr[j + 2];
            uchar b = *sptr++;
            uchar g = *sptr++;
            uchar r = *sptr++;
            dptr[j] = b;
            dptr[j + 1] = g;
            dptr[j + 2] = r;
        
    


void image_copy(cv::Mat &src, cv::Mat &dst) 
    int h = src.rows;
    int w = src.cols;
    dst.create(src.size(), src.type());
    if (src.isContinuous() && dst.isContinuous()) 
        h = 1;
        w = w * src.rows * src.channels();
    
    for (int i = 0; i < h; i++) 
        uchar *sptr = src.ptr<uchar>(i);
        uchar *dptr = dst.ptr<uchar>(i);
        for (int j = 0; j < w; j++) 
            *dptr++ = *sptr++;
            //dptr[j] = sptr[j];
        
    


以下是图像融合的算法:

(1)这是完全使用OpenCV的接口实现的图像融合,循环5次耗时30.29811 ms


void image_fusion(cv::Mat &imgBGR, cv::Mat matte, cv::Mat &out, cv::Mat bg) 
    if (matte.channels() == 1) 
        matte.convertTo(matte, CV_32FC1, 1.0 / 255, 0);
        cv::cvtColor(matte, matte, cv::COLOR_GRAY2BGR);
     else 
        matte.convertTo(matte, CV_32FC3, 1.0 / 255, 0);
    
    //out = imgBGR.clone();
    vector<float> ratio(float) imgBGR.cols / bg.cols, (float) imgBGR.rows / bg.rows;
    float max_ratio = *max_element(ratio.begin(), ratio.end());
    if (max_ratio > 1.0) 
        cv::resize(bg, bg, cv::Size(int(bg.cols * max_ratio), int(bg.rows * max_ratio)));
    
    bg = image_center_crop(bg, imgBGR.cols, imgBGR.rows);
    bg.convertTo(bg, CV_32FC3, 1, 0);
    imgBGR.convertTo(out, CV_32FC3, 1, 0);
    // Fix a Bug: 1 - alpha实质上仅有B通道参与计算,多通道时(B,G,R),需改Scalar(1.0, 1.0, 1.0)-alpha
    // out = out.mul(alpha) + bgi.mul(1 - alpha);
    out = out.mul(matte) + bg.mul(cv::Scalar(1.0, 1.0, 1.0) - matte);
    out.convertTo(out, CV_8UC3, 1, 0);

(2)这是通过遍历的方式实现的图像融合,循环5次耗时24.44169 ms

注意到matte需要除以255,把它放在循环体外进行乘法和除法运算,可以明显加快

void image_fusion_fast(cv::Mat &imgBGR, cv::Mat matte, cv::Mat &out, cv::Mat bg) 
    assert(matte.channels() == 1);
    out.create(imgBGR.size(), CV_8UC3);
    vector<float> ratio(float) imgBGR.cols / bg.cols, (float) imgBGR.rows / bg.rows;
    float max_ratio = *max_element(ratio.begin(), ratio.end());
    if (max_ratio > 1.0) 
        cv::resize(bg, bg, cv::Size(int(bg.cols * max_ratio), int(bg.rows * max_ratio)));
    
    bg = image_center_crop(bg, imgBGR.cols, imgBGR.rows);
    int h = imgBGR.rows;
    int w = imgBGR.cols;
    int n = imgBGR.channels();
    // 循环体外进行乘法和除法运算
    matte.convertTo(matte, CV_32FC1, 1.0 / 255, 0);
    for (int i = 0; i < h; ++i) 
        uchar *sptr = imgBGR.ptr<uchar>(i);
        uchar *dptr = out.ptr<uchar>(i);
        float *mptr = matte.ptr<float>(i);
        uchar *bptr = bg.ptr<uchar>(i);
        for (int j = 0; j < w; ++j) 
            //float alpha = mptr[j] / 255; //循环体尽量减少乘法和除法运算
            float alpha = mptr[j];
            float _alpha = 1.f - alpha;
            dptr[n * j] = uchar(sptr[n * j] * alpha + bptr[n * j] * _alpha);
            dptr[n * j + 1] = uchar(sptr[n * j + 1] * alpha + bptr[n * j + 1] * _alpha);
            dptr[n * j + 2] = uchar(sptr[n * j + 2] * alpha + bptr[n * j + 2] * _alpha);
        
    

(3)如果输入的Mat都是连续存储的,则可以转换为向量的形式进行遍历,循环5次耗时23.10372ms


void image_fusion_fast(cv::Mat &imgBGR, cv::Mat matte, cv::Mat &out, cv::Mat bg) 
    assert(matte.channels() == 1);
    out.create(imgBGR.size(), CV_8UC3);
    vector<float> ratio(float) imgBGR.cols / bg.cols, (float) imgBGR.rows / bg.rows;
    float max_ratio = *max_element(ratio.begin(), ratio.end());
    if (max_ratio > 1.0) 
        cv::resize(bg, bg, cv::Size(int(bg.cols * max_ratio), int(bg.rows * max_ratio)));
    
    bg = image_center_crop(bg, imgBGR.cols, imgBGR.rows);
    int n = imgBGR.channels();
    int h = imgBGR.rows;
    int w = imgBGR.cols * n;
    // 循环体外进行乘法和除法运算
    matte.convertTo(matte, CV_32FC1, 1.0 / 255, 0);
    for (int i = 0; i < h; ++i) 
        uchar *sptr = imgBGR.ptr<uchar>(i);
        uchar *dptr = out.ptr<uchar>(i);
        float *mptr = matte.ptr<float>(i);
        uchar *bptr = bg.ptr<uchar>(i);
        for (int j = 0; j < w; j += n) 
            //float alpha = mptr[j] / 255; //循环体尽量减少乘法和除法运算
            float alpha = mptr[j / 3];
            float _alpha = 1.f - alpha;
            dptr[j] = uchar(sptr[j] * alpha + bptr[j] * _alpha);
            dptr[j + 1] = uchar(sptr[j + 1] * alpha + bptr[j + 1] * _alpha);
            dptr[j + 2] = uchar(sptr[j + 2] * alpha + bptr[j + 2] * _alpha);
        
    





(3)这是通过遍历的方式,去除了循环体内大部分乘法,实现的图像融合,循环5次耗时23.10372ms


void image_fusion_fast(cv::Mat &imgBGR, cv::Mat matte, cv::Mat &out, cv::Mat bg) 
    assert(matte.channels() == 1);
    out.create(imgBGR.size(), CV_8UC3);
    vector<float> ratio(float) imgBGR.cols / bg.cols, (float) imgBGR.rows / bg.rows;
    float max_ratio = *max_element(ratio.begin(), ratio.end());
    if (max_ratio > 1.0) 
        cv::resize(bg, bg, cv::Size(int(bg.cols * max_ratio), int(bg.rows * max_ratio)));
    
    bg = image_center_crop(bg, imgBGR.cols, imgBGR.rows);
    int n = imgBGR.channels();
    int w = imgBGR.cols * imgBGR.rows * n;
    // 循环体外进行乘法和除法运算
    matte.convertTo(matte, CV_32FC3, 1.0 / 255, 0);
    uchar *sptr = imgBGR.ptr<uchar>(0);
    uchar *dptr = out.ptr<uchar>(0);
    float *mptr = matte.ptr<float>(0);
    uchar *bptr = bg.ptr<uchar>(0);
    for (int j = 0; j < w; j += n) 
        //float alpha = mptr[j] / 255; //循环体尽量减少乘法和除法运算
        float alpha = mptr[j / 3];
        float _alpha = 1.f - alpha;
        dptr[j] = uchar(sptr[j] * alpha + bptr[j] * _alpha);
        dptr[j + 1] = uchar(sptr[j + 1] * alpha + bptr[j + 1] * _alpha);
        dptr[j + 2] = uchar(sptr[j + 2] * alpha + bptr[j + 2] * _alpha);
    

以上是关于OpenCV Mat遍历的方法的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 绑定中循环遍历 OpenCV Mat

OpenCV Mat遍历的方法

OpenCV Mat遍历的方法

在 OpenCV 中循环遍历 16 位 Mat 像素的有效方法

OpenCV基于字节指针进行高效像素遍历

opencv中Mat类型数据操作与遍历