像素是图像的基本组成单位,熟悉了如何操作像素,就能更好的理解对图像的各种处理变换的实现方式了。

1.at方法

第一种操作像素的方法是使用“at”,如一幅3通道的彩色图像image的第i行j列的B、G、R分量分别表示为:

image.at<Vec3b>(i,j)[0];

image.at<Vec3b>(i,j)[1];

image.at<Vec3b>(i,j)[2];

而对于单通道的灰度图像就简单很多了:

image.at<uchar>(i,j);

这里要注意at中(i,j)的顺序表示的是第i行第j列,跟Point(i,j)和Rect(i,j)中表示第j行第i列是相反的,如果把这个搞混了,很容易导致内存异常,还不容易发现错误。

补充说明一下:opencv中坐标体系中的零点坐标定义为图片的左上角,X轴为图像矩形的上面那条水平线,从左往右;Y轴为图像矩形左边的那条垂直线,从上往下。在Point(x,y)和Rect(x,y)中,第一个参数x代表的是元素所在图像的列数,第二个参数y代表的是元素所在图像的行数,而在at(x,y)中是相反的。

演示程序如下:

#include<iostream>
#include<core/core.hpp>
#include<highgui/highgui.hpp> using namespace cv;
using namespace std; int main()
{
Mat image(Size(500,500),CV_8UC3);
image.at<Vec3b>(100,250)[0]=0;
image.at<Vec3b>(100,250)[1]=0;
image.at<Vec3b>(100,250)[2]=255;
putText(image,"at(100,250) is Here!",Point(250,100),0,0.7,Scalar(255,0,0)); image.at<Vec3b>(Point(100,250))[0]=0;
image.at<Vec3b>(Point(100,250))[1]=0;
image.at<Vec3b>(Point(100,250))[2]=255;
putText(image,"at(Point(100,250)) is Here!",Point(100,250),0,0.7,Scalar(255,0,0)); imshow("Test Function at",image);
waitKey();
return 0;
}

2.行指针方法

行指针方法的思路是获取图像每一行的首地址的指针,把每一行当成一个循环对象,逐行遍历所有的像素。例如对于一个X行、Y列的图像image,每行需要循环 的数量是Y*image.channels(),channels表示的是图像的通道,对于灰度图像channels为1,对于彩色RGB图像,channels为3。

3.指针方法

对于硬件处理芯片来说,如果图像每行的长度是4或8的倍数的话,运算起来会更加快速,如果不是4或8的倍数的话,在运算前图像行长度会被补充至固定值。
“指针方法”是一种可以高效遍历图像的方式,但是只能针对没有经过填充的连续图像,所以在使用指针方法之前需要先判断图像有没有经过填充,是否连续性。判断的方法很简单,使用Mat的成员函数isContinuous来判断,若返回值为真的话,说明图像是连续的,可以应用行指针的方法遍历像素。

4.迭代方法

使用迭代器遍历图像集合中的各个元素,相比前3中方法,这个方法简直可以永简单快捷的“无脑操作”来形容,你只需获取集合中元素的首地址以及元素的终止位置,剩下的工作交给迭代器来完成就可以了,并且我们不用关心集合的数据类型,指针总是逐次访问下一地址,直到指针到达终止元素位置。不过其缺点也是显而易见的:不容易指定访问集合中某一个位置的元素。

话不多少,下边给出一个完整的四种访问图像像素方法的示例,并对每种方法的耗时作对比,包括opencv本身的Copy方法;当然五种方法实现的是同一个简单的功能——通过逐个遍历一幅图像的每一个像素,复制该图像到另一Mat对象。
#include<iostream>
#include<core/core.hpp>
#include<highgui/highgui.hpp> using namespace cv;
using namespace std; //At方法
double CopyImageByAt(Mat originalImage, Mat &targetImage);
//行指针方法
double CopyImageByRowPtr(Mat originalImage, Mat &targetImage);
//指针方法
double CopyImageByPtr(Mat originalImage, Mat &targetImage);
//迭代方法
double CopyImageByIterator(Mat originalImage, Mat &targetImage);
//Opencv方法
double CopyFun(Mat originalImage, Mat &targetImage); int main()
{
//读入图片,注意图片路径
Mat image=imread("D:\\Picture\\lena.jpg",1); //图片读入成功与否判定
if(!image.data)
{
cout<<"you idiot!where did you hide lena!"<<endl;
//等待按键
system("pause");
return -1;
}
imshow("原始图像",image);
//输出图像
Mat targetImage(image.size(),image.type());
cout<<endl<<"At方法耗时:"<<CopyImageByAt(image,targetImage)<<endl;
imshow("At方法",targetImage);
cout<<endl<<"行指针方法耗时:"<<CopyImageByRowPtr(image,targetImage)<<endl;
imshow("行指针方法",targetImage);
cout<<endl<<"指针方法耗时:"<<CopyImageByPtr(image,targetImage)<<endl;
imshow("指针方法",targetImage);
cout<<endl<<"迭代方法耗时:"<<CopyImageByIterator(image,targetImage)<<endl;
imshow("迭代方法",targetImage);
cout<<endl<<"OpenCV Copy方法耗时:"<<CopyFun(image,targetImage)<<endl;
imshow("Copy方法",targetImage);
waitKey();
return 0;
} //使用at方法实现逐个像素复制
double CopyImageByAt(Mat originalImage, Mat &targetImage)
{
double now=getTickCount();
//行
int rows=originalImage.rows;
//列
int cols=originalImage.cols;
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
//若是灰度图像应使用如下表示:
//targetImage.at<uchar>(i,j)=originalImage.at<Vec3b>(i,j);
targetImage.at<Vec3b>(i,j)[0]=originalImage.at<Vec3b>(i,j)[0];
targetImage.at<Vec3b>(i,j)[1]=originalImage.at<Vec3b>(i,j)[1];
targetImage.at<Vec3b>(i,j)[2]=originalImage.at<Vec3b>(i,j)[2];
}
}
double end=getTickCount();
//返回方法耗时
return (end-now)/getTickFrequency();
} //使用访问每行首指针方法实现像素复制
double CopyImageByRowPtr(Mat originalImage, Mat &targetImage)
{
double now=getTickCount();
//行
int rows=targetImage.rows;
//每行总元素数量,此处图像为3通道
int totalNum=targetImage.cols*targetImage.channels();
for(int i=0;i<rows;i++)
{
//data1指向目标图像第i行的首元素
uchar *data1=targetImage.ptr<uchar>(i);
////data2指向原始图像第i行的首元素
uchar *data2=originalImage.ptr<uchar>(i);
for(int j=0;j<totalNum;j++)
{
//遍历每行所有元素
data1[j]=data2[j];
}
}
double end=getTickCount();
//返回方法耗时
return (end-now)/getTickFrequency();
} //无扩充的图像,采用指针方法逐个像素复制
double CopyImageByPtr(Mat originalImage, Mat &targetImage)
{
double now=getTickCount();
//行
int rows=targetImage.rows;
//每行总元素数量,此处图像为3通道
int totalNum=targetImage.cols*targetImage.channels();
//判断图像数据是否连续
if(originalImage.isContinuous())
{
totalNum*=rows;
rows=1;
}
//外层循环只执行一次
for(int i=0;i<rows;i++)
{
uchar *data1=targetImage.ptr<uchar>(i);
uchar *data2=originalImage.ptr<uchar>(i);
for(int j=0;j<totalNum;j++)
{
data1[j]=data2[j];
}
}
double end=getTickCount();
//返回方法耗时
return (end-now)/getTickFrequency();
} //使用迭代器遍历逐个像素复制
double CopyImageByIterator(Mat originalImage, Mat &targetImage)
{
double now=getTickCount();
//获取起始位置迭代器
Mat_<Vec3b>::iterator itBegin1=targetImage.begin<Vec3b>();
Mat_<Vec3b>::iterator itBegin2=originalImage.begin<Vec3b>();
//获取终止位置迭代器
Mat_<Vec3b>::iterator itEnd1=targetImage.end<Vec3b>();
Mat_<Vec3b>::iterator itEnd2=originalImage.end<Vec3b>();
for(;itBegin1!=itEnd1;++itBegin1)
{
(*itBegin1)[0]=(*itBegin2)[0];
(*itBegin1)[1]=(*itBegin2)[1];
(*itBegin1)[2]=(*itBegin2)[2];
++itBegin2;
}
double end=getTickCount();
//返回方法耗时
return (end-now)/getTickFrequency();
} //OpenCV Copy方法实现图像复制
double CopyFun(Mat originalImage, Mat &targetImage)
{
double now=getTickCount();
originalImage.copyTo(targetImage);
double end=getTickCount();
//返回方法耗时
return (end-now)/getTickFrequency();
}

可见,指针方法是高效快捷访问像素的首选方法,然而跟opencv的Copy方法相比,还是弱爆了……

OpenCV坐标系与操作像素的四种方法的更多相关文章

  1. golang操作文件的四种方法

    golang追加内容到文件末尾 字数349 阅读54 评论0 喜欢2 golang读写文件,网上很多教程了但是今天有个需求,想要把内容追加写到文件末尾google了好久,没有查到研究了一会儿file库 ...

  2. 【从零学习openCV】opecv操作像素

    1. 存取像素值 在opencv中能够直接对cv::Mat类型的图像调用at函数读取或赋值某个像素,我们用个简单的案例来说明: //在一张图像上增加椒盐噪声,image为输入图像.n为噪点个数 voi ...

  3. Angular--页面间切换及传值的四种方法

    1. 基于ui-router的页面跳转传参(1) 在AngularJS的app.js中用ui-router定义路由,比如现在有两个页面,一个页面(producers.html)放置了多个produce ...

  4. MYSQL获取自增ID的四种方法

    MYSQL获取自增ID的四种方法 1. select max(id) from tablename 2.SELECT LAST_INSERT_ID() 函数 LAST_INSERT_ID 是与tabl ...

  5. PHP读写XML文件的四种方法

    PHP对XML文件进行读写操作的方法一共有四种,分别是:字符串方式直接读写.DOMDocument读写. XMLWrite写和XMLReader读.SimpleXML读写,本文将依次对这四种方法进行介 ...

  6. linux安装IPython四种方法

    IPython是Python的交互式Shell,提供了代码自动补完,自动缩进,高亮显示,执行Shell命令等非常有用的特性.特别是它的代码补完功能,例如:在输入zlib.之后按下Tab键,IPytho ...

  7. 【Java】详解Java解析XML的四种方法

    XML现在已经成为一种通用的数据交换格式,平台的无关性使得很多场合都需要用到XML.本文将详细介绍用Java解析XML的四种方法. AD: XML现在已经成为一种通用的数据交换格式,它的平台无关性,语 ...

  8. 解析Xml四种方法

    关键字:Java解析xml.解析xml四种方法.DOM.SAX.JDOM.DOM4j.XPath [引言] 目前在Java中用于解析XML的技术很多,主流的有DOM.SAX.JDOM.DOM4j,下文 ...

  9. 在 Mac OS X Lion 下修改 Hosts 的四种方法

    一名刚刚使用 Mac OS X Lion 系统的朋友问我怎么该系统下修改 Hosts 文件,说网上搜了很多办法都不管用,只要编辑 Hosts 文件就出现”你不是文件 hosts 的所有者,因此没有权限 ...

随机推荐

  1. Day 1 计算机基础

    计算机基础 一.为什么学习计算机基础? 编程语言的作用:人类使机器明白并动作的指令.类似:人文社会的英语.   关系:计算机硬件 —— 操作系统(OS) —— 软件(编程语言成品,学习成果). 自语: ...

  2. java对接网银支付案例

    =================提交表单jsp,http://www.cnblogs.com/qgc88================================ <%@page con ...

  3. Jetson TK1 二:usb无线网卡的使用

    一.总体是按照群里的文档“TK1连接无线网络”的步骤操作的,但也遇到了一些问题,如下: 1.自动配置设备并下载内核源代码到指定的目录下时(估计是解压时),出现时间超前之类的问题,原因是当前本地时间是几 ...

  4. 9.Java web—JSP内置对象

    容器内置了9大对象,这些对象在jsp页无需实例化,可以直接使用. 分别为request. response .session. application .out. pageContext .confi ...

  5. Go -- 一致性哈希算法

    一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用 ...

  6. 用 centrifugo 搭建 消息推送服务器 docker + rancher 搭建

    关于消息推送服务器 目前有很多第三方的开放成熟的推送服务.鉴于项目需要 我们项目需要自己搭建 自己的推送服务. 我们的推送应用场景 聊天消息 项目内部消息提醒 移动设备接受消息 应用到的相关软件工具知 ...

  7. binary-tree-preorder-traversal——前序遍历

    Given a binary tree, return the preorder traversal of its nodes' values. For example:Given binary tr ...

  8. C++学习总结 复习篇2

      延续上一小节内容:下面继续讲解虚函数和多态 虚函数和多态 基类指针可以指向任何派生类的对象,但是不能调用派生类对象的成员. 但是,基类可以调用覆盖了虚函数的函数.(现在调用将来,这有问题,说明现在 ...

  9. Java核心技术之基本数据类型

    这篇文章.我们讨论一些java的最主要的东西.这些东西我们一般刚刚学java的时候就学过,可是不一定真正明确. 正好,我在做一个读取内存的值,涉及到bit位的值的读取和写.那就能够讨论一个java的基 ...

  10. POJ 1703 Find them, Catch them(种类并查集)

    题目链接 这种类型的题目以前见过,今天第一次写,具体过程,还要慢慢理解. #include <cstring> #include <cstdio> #include <s ...