OpenCV优化:图像的遍历4种方式
小知识:反色
反色原理很简单,在一个rgb色彩空间中,可将任何一种颜色看成笛卡尔坐标中的一个点,对于任意点,反色就是计算以(128, 128,128)为中心时该点的对称点,比如rgb(100, 150, 200)对应的反色就是rgb(155, 105, 55)。
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 }
通过上面的例子我们可以看出,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倍数的行。但是我们可以申明一个连续的空间来存储图像,这个话题引入下面最为高效的遍历图像的机制。
)
指针方式遍历2。由于图像是按照行和列顺序存储的,所以该方法遍历图像时需要先获取图像每一行的首地址,然后从该行首地址向后遍历至行末尾。用法如下:
- int row = img.rows;
- int step = img.step;
- uchar * pImg = img.data;
- for (int i = 0; i < row; i++)
- {
- for (int j = 0; j < step; j++)
- {
- pImg[j] = 255 - pImg[j];
- }
- pImg += img.step;
- }
- 或者:
- int row = img.rows;
- int step = img.step;
- uchar * pImg = NULL;
- for (int i = 0; i < row; i++)
- {
- pImg = img.ptr<uchar>(i);
- for (int j = 0; j < step; j++)
- {
- pImg[j] = 255 - pImg[j];
- }
- }
三、更高效的方法
上面已经提到过了,一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,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 // 把图像边缘像素设置为0 19 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 }
上面的程序有以下几点需要说明:
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分为三个通道图像存储在planes中 3 split(image,planes); 4 planes[0]+=image2; 5 // 将planes中三幅图像合为一个三通道图像 6 merge(planes,result);
OpenCV优化:图像的遍历4种方式相关推荐
- 优化网站速度的几种方式
优化网站速度的几种方式 在业界有篇很经典的文章,来源于yahoo的开发者社区,它讲述的是如果优化自己的网站的速度,包括从服务器端到客户端.这篇文章影响了很多人,原文可以参照这里:Best Practi ...
- python读取图像数据流_浅谈TensorFlow中读取图像数据的三种方式
本文面对三种常常遇到的情况,总结三种读取数据的方式,分别用于处理单张图片.大量图片,和TFRecorder读取方式.并且还补充了功能相近的tf函数. 1.处理单张图片 我们训练完模型之后,常常要用图片 ...
- Map遍历四种方式及其效率
目录 1.Map介绍 2.Map数据结构及扩容 2.1.数组:寻址容易,插入和删除元素困难 2.2.链表:寻址困难,插入和删除元素容易 2.3.Map数组长度默认16,扩容负载因子为0.75 3.Ma ...
- opencv学习(四)之像素遍历三种方式
在上一篇文章中介绍了图像颜色空间缩减.查找表等内容.在对图像像素进行遍历时共有三种方法: (1). C操作符[] (指针方式访问) (2). 迭代器iterator (3). 动态地址计算 这三种像素 ...
- TensorFlow中读取图像数据的三种方式(转)
附加一个链接关于DatasetAPI:https://zhuanlan.zhihu.com/p/30751039 本文面对三种常常遇到的情况,总结三种读取数据的方式,分别用于处理单张图片.大量图片,和 ...
- 基于“FFD形变+梯度下降优化”图像配准的一种加速方法
前文我们讲过FFD形变与梯度下降优化算法的原理: 梯度下降法详解 图像配准系列之基于B样条的FFD自由变换原理与C++实现 图像配准系列之基于FFD形变与梯度下降法的图像配准 1. "FFD ...
- 网站seo优化引流的三种方式
中安云城现在为大家共享一下引流的几种方式,现在需要细分一下. 1.社群引流 这个方式其实也挺常见的,选择与你推广的产品相关的论坛发布文章会对你的推广引流效果更好一点,具有特征性,同时也不要去不太活跃以 ...
- java for遍历hashmap_Java中HashMap遍历几种方式
目录 一.使用迭代器 二.for each 遍历 一.使用迭代器 第一种: Map map = new HashMap(); Iterator iter = map.entrySet().iterat ...
- Python+opencv裁剪/截取图片的几种方式
文章目录 前言 一.手动单张裁剪/截取 二.根据图片的位置坐标进行裁剪/截取 三.根据xml文件截取/裁剪目标 四.opencv获取边缘并根据bounding box截取/裁剪目标 五.用YOLO目标 ...
最新文章
- 从无到有:微信后台系统的演进之路
- 汇编语言出现的历史背景
- 【渝粤题库】陕西师范大学200411 数学建模 作业(专升本)
- 别克gl8服务器返回数据为空,老司机教学:别克GL8商务车总线控制系统故障解决...
- OpenGL研究3.0 多边形区域填充
- 如果没有,那么就去创造
- Spark 独立部署模式
- IOS-网络(数据安全:MD5加密)
- SQL Server - THROW字句对比RAISERROR子句
- 配置Apache2+PHP5+MYSQL5
- 获取客户端登录ip地址
- 16G kingston U盘 解除写保护
- lucas–kanade_异常检测常用光流法量化对比:Farneback/Horn-Schunck / Lucas–Kanade
- UNIX v6 fork()源码分析
- C++ primer plus 第六版 第二章 编程练习答案
- tenacity重试机制
- SpringCloud 基础架构搭建
- MT6755/HelioP10处理器性能,MT6755芯片规格资料
- html布局间距,CSS Grid布局:列和间距
- Python GUI设计 tkinter 笔记
热门文章
- mysql 20小时内_生产环境删除数据库,如何实现在1小时内快速恢复?
- 用计算机进行图片处理教学设计,三年级信息技术上教学设计
- mysql update join 锁表_Mysql事务中Update是否会锁表?
- 合同模板布局html,套打模板制作(合同类模板)
- em算法的java实现_EM算法 - Java教程 - 找一找教程网
- 微信内测版抢先体验,可发 4K 无损视频
- 这个“老婆生成器”说我有5个前妻...
- 微信继续加持上班摸鱼功能!网友:离被开除更进一步...
- 一项无聊的研究与论文,导致整个大学被Linux封杀!
- JAR冲突问题的解决以及运行状态下如何查看加载的类