文档章节

OpenCV优化:图像的遍历4种方式

dillonxiao
 dillonxiao
发布于 2017/05/11 17:22
字数 2086
阅读 32
收藏 0
点赞 0
评论 0

OpenCV优化:图像的遍历4种方式

我们在实际应用中对图像进行的操作,往往并不是将图像作为一个整体进行操作,而是对图像中的所有点或特殊点进行运算,所以遍历图像就显得很重要,如何高效的遍历图像是一个很值得探讨的问题。

一、遍历图像的4种方式:at<typename>(i,j)

Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。下面我们通过一个图像处理中的实际来说明它的用法。

在实际应用中,我们很多时候需要对图像降色彩,因为256*256*256实在太多了,在图像颜色聚类或彩色直方图时,我们需要用一些代表性的颜色代替丰富的色彩空间,我们的思路是将每个通道的256种颜色用64种代替,即将原来256种颜色划分64个颜色段,每个颜色段取中间的颜色值作为代表色。

复制代码

 1 void colorReduce(Mat& image,int div) 2 { 3     for(int i=0;i<image.rows;i++) 4     { 5         for(int j=0;j<image.cols;j++) 6         { 7             image.at<Vec3b>(i,j)[0]=image.at<Vec3b>(i,j)[0]/div*div+div/2; 8             image.at<Vec3b>(i,j)[1]=image.at<Vec3b>(i,j)[1]/div*div+div/2; 9             image.at<Vec3b>(i,j)[2]=image.at<Vec3b>(i,j)[2]/div*div+div/2;10         }11     }12 }

复制代码

image

通过上面的例子我们可以看出,at方法取图像中的点的用法:

image.at<uchar>(i,j):取出灰度图像中i行j列的点。

image.at<Vec3b>(i,j)[k]:取出彩色图像中i行j列第k通道的颜色点。其中uchar,Vec3b都是图像像素值的类型,不要对Vec3b这种类型感觉害怕,其实在core里它是通过typedef Vec<T,N>来定义的,N代表元素的个数,T代表类型。

更简单一些的方法:OpenCV定义了一个Mat的模板子类为Mat_,它重载了operator()让我们可以更方便的取图像上的点。

Mat_<uchar> im=image;

im(i,j)=im(i,j)/div*div+div/2;

二、高效一点:用指针来遍历图像

上面的例程中可以看到,我们实际喜欢把原图传进函数内,但是在函数内我们对原图像进行了修改,而将原图作为一个结果输出,很多时候我们需要保留原图,这样我们需要一个原图的副本。

复制代码

 1 void colorReduce(const Mat& image,Mat& outImage,int div) 2 { 3     // 创建与原图像等尺寸的图像 4     outImage.create(image.size(),image.type()); 5     int nr=image.rows; 6     // 将3通道转换为1通道 7     int nl=image.cols*image.channels(); 8     for(int k=0;k<nr;k++) 9     {10         // 每一行图像的指针11         const uchar* inData=image.ptr<uchar>(k);12         uchar* outData=outImage.ptr<uchar>(k);13         for(int i=0;i<nl;i++)14         {15             outData[i]=inData[i]/div*div+div/2;16         }17     }18 }

复制代码

从上面的例子中可以看出,取出图像中第i行数据的指针:image.ptr<uchar>(i)。

值得说明的是:程序中将三通道的数据转换为1通道,在建立在每一行数据元素之间在内存里是连续存储的,每个像素三通道像素按顺序存储。也就是一幅图像数据最开始的三个值,是最左上角的那像素的三个通道的值。

但是这种用法不能用在行与行之间,因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。但是我们可以申明一个连续的空间来存储图像,这个话题引入下面最为高效的遍历图像的机制。

三、更高效的方法

上面已经提到过了,一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行。

复制代码

 1 void colorReduce(const Mat& image,Mat& outImage,int div) 2 { 3     int nr=image.rows; 4     int nc=image.cols; 5     outImage.create(image.size(),image.type()); 6     if(image.isContinuous()&&outImage.isContinuous()) 7     { 8         nr=1; 9         nc=nc*image.rows*image.channels();10     }11     for(int i=0;i<nr;i++)12     {13         const uchar* inData=image.ptr<uchar>(i);14         uchar* outData=outImage.ptr<uchar>(i);15         for(int j=0;j<nc;j++)16         {17             *outData++=*inData++/div*div+div/2;18         }19     }20 }

复制代码

用指针除了用上面的方法外,还可以用指针来索引固定位置的像素:

image.step返回图像一行像素元素的个数(包括空白元素),image.elemSize()返回一个图像像素的大小。

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

四、还有吗?用迭代器来遍历。

下面的方法可以让我们来为图像中的像素声明一个迭代器:

MatIterator_<Vec3b> it;

Mat_<Vec3b>::iterator it;

如果迭代器指向一个const图像,则可以用下面的声明:

MatConstIterator<Vec3b> it; 或者

Mat_<Vec3b>::const_iterator it;

下面我们用迭代器来简化上面的colorReduce程序:

复制代码

 1 void colorReduce(const Mat& image,Mat& outImage,int div) 2 { 3     outImage.create(image.size(),image.type()); 4     MatConstIterator_<Vec3b> it_in=image.begin<Vec3b>(); 5     MatConstIterator_<Vec3b> itend_in=image.end<Vec3b>(); 6     MatIterator_<Vec3b> it_out=outImage.begin<Vec3b>(); 7     MatIterator_<Vec3b> itend_out=outImage.end<Vec3b>(); 8     while(it_in!=itend_in) 9     {10         (*it_out)[0]=(*it_in)[0]/div*div+div/2;11         (*it_out)[1]=(*it_in)[1]/div*div+div/2;12         (*it_out)[2]=(*it_in)[2]/div*div+div/2;13         it_in++;14         it_out++;15     }16 }

复制代码

如果你想从第二行开始,则可以从image.begin<Vec3b>()+image.rows开始。

上面4种方法中,第3种方法的效率最高!

五、图像的邻域操作

很多时候,我们对图像处理时,要考虑它的邻域,比如3*3是我们常用的,这在图像滤波、去噪中最为常见,下面我们介绍如果在一次图像遍历过程中进行邻域的运算。

下面我们进行一个简单的滤波操作,滤波算子为[0 –1 0;-1 5 –1;0 –1 0]。

它可以让图像变得尖锐,而边缘更加突出。核心公式即:sharp(i.j)=5*image(i,j)-image(i-1,j)-image(i+1,j

)-image(i,j-1)-image(i,j+1)。

复制代码

 1 void ImgFilter2d(const Mat &image,Mat& result) 2 { 3     result.create(image.size(),image.type()); 4     int nr=image.rows; 5     int nc=image.cols*image.channels(); 6     for(int i=1;i<nr-1;i++) 7     { 8         const uchar* up_line=image.ptr<uchar>(i-1);//指向上一行 9         const uchar* mid_line=image.ptr<uchar>(i);//当前行10         const uchar* down_line=image.ptr<uchar>(i+1);//下一行11         uchar* cur_line=result.ptr<uchar>(i);12         for(int j=1;j<nc-1;j++)13         {14             cur_line[j]=saturate_cast<uchar>(5*mid_line[j]-mid_line[j-1]-mid_line[j+1]-15                 up_line[j]-down_line[j]);16         }17     }18     // 把图像边缘像素设置为019     result.row(0).setTo(Scalar(0));20     result.row(result.rows-1).setTo(Scalar(0));21     result.col(0).setTo(Scalar(0));22     result.col(result.cols-1).setTo(Scalar(0));23 }

复制代码

image

上面的程序有以下几点需要说明:

1,staturate_cast<typename>是一个类型转换函数,程序里是为了确保运算结果还在uchar范围内。

2,row和col方法返回图像中的某些行或列,返回值是一个Mat。

3,setTo方法将Mat对像中的点设置为一个值,Scalar(n)为一个灰度值,Scalar(a,b,c)为一个彩色值。

六、图像的算术运算

Mat类把很多算数操作符都进行了重载,让它们来符合矩阵的一些运算,如果+、-、点乘等。

下面我们来看看用位操作和基本算术运算来完成本文中的colorReduce程序,它更简单,更高效。

将256种灰度阶降到64位其实是抛弃了二进制最后面的4位,所以我们可以用位操作来做这一步处理。

首先我们计算2^8降到2^n中的n:int n=static_cast<int>(log(static_cast<double>(div))/log(2.0));

然后可以得到mask,mask=0xFF<<n;

用下面简直的语句就可以得到我们想要的结果:

result=(image&Scalar(mask,mask,mask))+Scalar(div/2,div/2,div/2);

很多时候我们需要对图像的一个通信单独进行操作,比如在HSV色彩模式下,我们就经常把3个通道分开考虑。

1 vector<Mat> planes;2 // 将image分为三个通道图像存储在p

本文转载自:http://www.cnblogs.com/ronny/

共有 人打赏支持
dillonxiao
粉丝 2
博文 49
码字总数 309
作品 0
盐城
高级程序员

暂无相关文章

JDK1.6和JDK1.7中,Collections.sort的区别,

背景 最近,项目正在集成测试阶段,项目在服务器上运行了一段时间,点击表格的列进行排序的时候,有的列排序正常,有的列在排序的时候,在后台会抛出如下异常,查询到不到数据,而且在另外一...

tsmyk0715 ⋅ 7分钟前 ⋅ 0

spring RESTful

spring RESTful官方文档:http://spring.io/guides/gs/rest-service/ 1. 可以这么去理解RESTful:其实就是web对外提供的一种基于URL、URI的资源供给服务。不是一个原理性知识点。是一个方法论...

BobwithB ⋅ 8分钟前 ⋅ 0

C++ 中命名空间的 5 个常见用法

相信小伙伴们对C++已经非常熟悉,但是对命名空间经常使用到的地方还不是很明白,这篇文章就针对命名空间这一块做了一个叙述。 命名空间在1995年被引入到 c++ 标准中,通常是这样定义的: 命名...

柳猫 ⋅ 11分钟前 ⋅ 0

@Conditional派生注解

@Conditional派生注解(Spring注解版原生的@Conditional作用) 作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效; @Conditional扩展注解 作用(判...

小致dad ⋅ 12分钟前 ⋅ 0

适配器模式

适配器模式 对象适配器 通过私有属性来实现的类适配器 通过继承来实现的接口适配器 通过继承一个默认实现的类实现的

Cobbage ⋅ 15分钟前 ⋅ 0

Java 限流策略

概要 在大数据量高并发访问时,经常会出现服务或接口面对暴涨的请求而不可用的情况,甚至引发连锁反映导致整个系统崩溃。此时你需要使用的技术手段之一就是限流,当请求达到一定的并发数或速...

轨迹_ ⋅ 19分钟前 ⋅ 0

GridView和子View之间的间隙

默认的情况下GridView和子View之间会有一个间隙,原因是GridView为了在子View被选中时在子View周围显示一个框。去掉的办法如下: android:listSelector="#0000" 或 setSelector(new ColorDra...

国仔饼 ⋅ 22分钟前 ⋅ 0

idea插件开发

1 刷新页面要使用多线程 2 调试要使用restart bug 不要去关闭调试的idea 否则再次启动会卡住

林伟琨 ⋅ 22分钟前 ⋅ 0

Java 内存模型

物理机并发处理方案 绝大多数计算任务,并不是单纯依赖 cpu 的计算完成,不可避免需要与内存交互,获取数据。内存要拿到数据,需要和硬盘发生 I/O 操作。计算机存储设备与 cpu 之间的处理速度...

长安一梦 ⋅ 29分钟前 ⋅ 0

思路分析 如何通过反射 给 bean entity 对象 的List 集合属性赋值?

其实 这块 大家 去 看 springmvc 源码 肯定可以找到实现办法。 因为 spirngmvc 的方法 是可以 为 对象 参数里面的 list 属性赋值的。 我也没有看 具体的 mvc 源码实现,我这里只是 写一个 简...

之渊 ⋅ 50分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部