OpenCV中图像遍历与像素操作

OpenCV中表示图像的数据结构是cv::Mat,Mat对象本质上是一个由数值组成的矩阵。矩阵的每一个元素代表一个像素,对于灰度图像,像素是由8位无符号数来表示(0代表黑,255代表白);对于彩色图像,每个像素是一个三元向量,即由三个8位无符号数来表示三个颜色通道(Opencv中顺次为蓝、绿、红)。
我们先来介绍下cv::Mat类的获取像素的成员函数at(),其函数原型如下:

template<typename _Tp> _Tp& at(int i0, int i1);
//由于Mat可以存放任意数据类型的元素,所以该函数是用模板函数来实现的
//它本身不会进行任何数据类型转换,在调用的过程中需要指明像素的数据类型
//即要与矩阵中的数据类型相匹配。如:
img.at<uchar>(i,j)=255
img.at<cv::Vec3b>(i,j)[0]=255

在OpenCV中一般有四种图像遍历的方式,第一种自然是最平凡的数组遍历啦。为方便起见,以下所有的示例都是In-place变换操作。

1、数组遍历

在这里我们通过操作像素的办法来实现图像的镜像变换,即实现flip(img,img,1)的功能。代码如下:

#include<iostream>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>

using namespace std;
using namespace cv;

void Flip(Mat &img)
{
    int rows=img.rows;
    int cols=img.cols;
    for(int i=0; i<rows; i++)
    {
        for(int j=0; j<cols/2; j++)
        {
            uchar t;
            if(img.channels()==1)
            {
                t=img.at<uchar>(i,j);
                img.at<uchar>(i,j)=img.at<uchar>(i,cols-1-i);
                img.at<uchar>(i,cols-1-i)=t;
            }
            else if(img.channels()==3)
            {
                for(int k=0; k<3; k++)
                {
                    t=img.at<Vec3b>(i,j)[k];
                    img.at<Vec3b>(i,j)[k]=img.at<Vec3b>(i,cols-1-j)[k];
                    img.at<Vec3b>(i,cols-1-j)[k]=t;
                }
            }
        }
    }
}

int main()
{
    Mat img1=imread("test.jpg");  //将任意一张名为test.jpg的图片放置于工程文件夹test中
    imshow("First",img1);
    if(!img1.data)
    {
        cout<<"error! The image is not built!"<<endl;
        return -1;
    }
    Flip(img1);
    imshow("Second",img1);
    waitKey();
    return 0;
}

效果如下:
这里写图片描述

2、指针遍历

OpenCV中cv::Mat类提供了成员函数ptr得到图像任意行的首地址。ptr函数是一个模板函数,其原型为:

template<typename _Tp> _Tp* Mat::ptr(int i=0)

在这里我们通过操作像素的办法来实现图像的水平反转,即实现flip(img,img,0)的功能。代码如下:

#include<iostream>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>

using namespace std;
using namespace cv;

void Flip(Mat &img)
{
    int rows=img.rows;
    int cols=img.cols*img.channels();
    for(int i=0; i<rows/2; i++)
    {
        uchar *p=img.ptr<uchar>(i);
        uchar *q=img.ptr<uchar>(rows-1-i);
        uchar t;
        for(int j=0; j<cols;j++)
        {
            t=*p;
            *p++=*q;
            *q++=t;
        }
    }
}

int main()
{
    Mat img1=imread("test.jpg");  //将任意一张名为test.jpg的图片放置于工程文件夹test中
    imshow("First",img1);
    if(!img1.data)
    {
        cout<<"error! The image is not built!"<<endl;
        return -1;
    }
    Flip(img1);
    imshow("Second",img1);
    waitKey();
    return 0;
}

效果如下:
这里写图片描述

指针遍历图像的过程中,我们可能会受以往遍历矩阵的影响,得到图像首行地址后,想直接通过一个循环去遍历rows*cols*img.channels()的内存,但是考虑到一些多媒体处理芯片在行的长度为是4或8的倍数时,对图像的处理会更加高效,所以OpenCV中对图像的每行会填补一些额外像素(不显示、不保存),将填补后的行的长度称为关键字,成员变量step代表以字节为单位的图像的有效宽度。因此,我们只有在图像的有效宽度等于图像的真实宽度,即没有填补时,进行一重循环遍历。我们可以通过cv::Mat的成员函数isContinuous来判断图像是否对行进行了填充,返回值为真,表示没有对行进行填充,反之填充。此外,我们可以通过cv::Mat的成员变量data得到图像的首地址,等效于上面程序中的一种写法如下:

uchar *p=img.data;    //首行首地址
*p += img.step;       //次行首地址
……

3、迭代器遍历

只要对对C++稍有了解,就知道迭代器是专门用于遍历数据集合的一种非常重要的特殊的类,用其遍历隐藏了在给定集合上元素迭代的具体实现方式。C++的STL为每个容器类型都提供了迭代器,OpenCV同样为cv::Mat提供了与STL迭代器兼容的迭代器。

cv::Mat实例的迭代器可以通过创建一个cv::MatIterator_的实例来得到,由于这是一个模板类,所以在声明时需指定图像像素的数据类型。

cv::MatIterator_<cv::Ver3b> it;

另外可使用定义在Mat_内部的迭代器类型

cv::Mat_<cv::Verc3b>::iterator it;

在这里我们通过操作像素的办法来实现图像中心对称反转。代码如下:

#include<iostream>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>

using namespace std;
using namespace cv;

void Flip(Mat &img)
{
    uchar t;
    if(img.channels()==1)
    {
        Mat_<uchar>::iterator it=img.begin<uchar>();
        Mat_<uchar>::iterator itend=img.end<uchar>();
        itend--;  //通过end成员函数得到的迭代器已超出集合,所以在这里自减
        for(;it<itend;it++,itend--)
        {
            t=*it;*it=*itend;*itend=t;
        }
    }
    else if(img.channels()==3)
    {
        Mat_<Vec3b>::iterator it=img.begin<Vec3b>();
        Mat_<Vec3b>::iterator itend=img.end<Vec3b>();
        itend--;
        for(;it<itend;it++,itend--)
            for(int k=0; k<3; k++)
            {
                t=(*it)[k];(*it)[k]=(*itend)[k];(*itend)[k]=t;
            }
    }
}

int main()
{
    Mat img1=imread("test.jpg");  //将任意一张名为test.jpg的图片放置于工程文件夹test中
    imshow("First",img1);
    if(!img1.data)
    {
        cout<<"error! The image is not built!"<<endl;
        return -1;
    }
    Flip(img1);
    imshow("Second",img1);
    waitKey();
    return 0;
}

效果如下:
这里写图片描述

4、核心函数LUT

LUT是最被推荐的用于实现批量图像元素查找和更该操作图像方法。在图像处理中,对于一个给定的值,将其替换成其他的值是一个很常见的操作,OpenCV 提供里一个函数直接实现该操作,并不需要自己扫描图像,就是:operationsOnArrays:LUT() <lut>,一个包含于core module的函数. 首先我们建立一个mat型用于查表

Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.data; 
for( int i = 0; i < 256; ++i)
   p[i] = table[i];

然后我们调用函数 (I 是输入 J 是输出):
LUT(I, lookUpTable, J);
这里,简单提一下LUT的用法,有时间专门写一篇关于该函数的博客。

5、效率探讨

一般图像规模比较大的话,图像的遍历是一项相当耗时的工作,因此为提高效率,以下几点值得我们注意:
1)对于可提前计算的变量应避免写在循环体内;如

int cols=img.cols*img.channels();
for(int i=0;i<cols;i++)   //而不是
// for(int i=p;i<img.cols*img.channels();i++)

2)在以上四种图像遍历方法中,从效率来看使用 OpenCV 内置函数LUT可以获得最快的速度,这是因为OpenCV库可以通过英特尔线程架构启用多线程。其次,指针遍历最快,迭代器遍历次之,at方法遍历最慢。一般情况下,我们只有在对任意位置的像素进行读写时才考虑at方法。
3)要对一个像素进行多项操作,在一个循环内完成要高效于几次循环完成;此外,列数*通道数 这样的长循环要比在一个内循环一次性处理三个通道要慢得多。例如以下Flip函数要比我们在上面的指针遍历程序中写的要高效。

void Flip(Mat &img)
{
    int rows=img.rows;
    int cols=img.cols;
    for(int i=0; i<rows/2; i++)
    {
        uchar *p=img.ptr<uchar>(i);
        uchar *q=img.ptr<uchar>(rows-1-i);
        uchar t;
        for(int j=0; j<cols;j++)
        {
            t=*p;*p++=*q;*q++=t;
            t=*p;*p++=*q;*q++=t;
            t=*p;*p++=*q;*q++=t;
        }
    }
}

当然,如果把上面内循环中值交换的过程用 位运算 代替会更加高效(摊手)。

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值