(一)我们将探索以下问题的答案:
1--如何遍历图像中的每一个像素点?
2--OpenCv的矩阵值是如何存储的?
3--如何测试我们所实现算法的性能?
4--查找表是什么?为什么要用它?
(二)图像矩阵是如何存储在内存之中的?
1--图像矩阵的大小取决于我们使用的颜色模型,确切的说,取决于所用通道数。如果是灰度图像,矩阵就会像这样:
2--而对于多通道图像来说,矩阵中的列会包含多个子列,其子列个数与通道数相等,例如,RGB颜色模型的矩阵:
3--在这里,我们需要注意的是:子列的通道顺序是反过来的,是BGR,而不是我们数字图像书中常
说的RGB.很多情况下,因为内存够大,可以实现连续存储。连续存储可以提高图像的扫描速度。
(三)颜色空间的缩减
如果矩阵元素存储的是单通道像素,使用C或C++的无符号字符类型,那么像素可能有256个不同的值.
但若是三通道图像,这种存储格式的颜色数就太多了(确切的说,有一千六百多万种).用如此之多的颜色
可能会对我们算法的性能造成严重的影响。其实,有时候,我们仅适用这些颜色的一小部分,就足以达到
同样的效果。
这种情况下,常用的一种方法就是【颜色空间缩减】.其做法是:将现有颜色值除以某个输入值,以获
得较少的颜色数。例如:0到9可取新值0;10到19取值1,以此类推。
uchar(无符号字符,即0到255之间取值的数)类型除以int值,结果仍为char。因为结果是char类型的,
所以,求出来小数也要向下取整。利用这一点,刚才提到在uchar定义域中进行颜色缩减运算就可以表达
为下列形式:
Inew=Iold/10*10;
这样的话,简单的颜色缩减算法就可以由以下两步组成:
1--遍历图像矩阵中的每一个像素
2--对像素应用上述公式
值得注意的是,我们这里用到了除法和乘法运算,而这种运算又特别费时,所以,我们应尽可能用代
价较低的加减,赋值等运算替换他们。
(四)计时函数
1--OpenCv为我们提供了两个简单的计时函数---getTickCount()和getTickFrequency()
2--getTickCount()函数---返回CPU自某个事件以来走过的----时钟周期数
3--getTickFrequency()函数返回CUP一秒钟所走的时钟周期数。这样,我们就可以轻松的以秒为单位
对某运算计时
这两个函数组合起来的使用如下所示:
//【5】记录其实时间
double timeStartPtr=static_cast<double>(getTickCount());
//【6】调用颜色空间缩减函数
colorReducePtr(srcImg,dstImgPtr,32);
//【7】计算运行时间,并输出
double timeExpensePtr=((double)getTickCount()-timeStartPtr)/getTickFrequency();
cout<<"【1】ptr指针操作像素,此方法运行时间为---------->"<<timeExpensePtr<<endl;
(五)访问图像中像素的三类方法
任何图像处理算法。都是从操作每个像素开始的。即使我们不会使用OpenCv提供的各种图像
处理函数,只要我们了解图像处理算法的基本原理,也可以写出具有相同功能的程序.在OpenCv中,
提供了三种访问每个像素的方法:
1--指针访问----------ptr<>()函数
2--动态地址计算------at<>()函数
3--迭代器iterator
这三种方法,在访问速度上略有差异,指针比较快。
/*********************************************************************************************
程序功能:
图像像素点操作的三种常用方法的介绍:
1--ptr<>()------Mat类中的模板函数--可以得到图像中任意一行的--------首地址
2--at<>()-------Mat类中的模板函数--可以得到图像中任意指定元素的----地址
3--Iterator-----迭代器操作像素
编写环境:
OpenCv2.4.8+VS2010
地点时间:
陕西师范大学 2016.4.25
作者信息:
九月
**********************************************************************************************/
/********************************【头文件.命名空间包含部分】**********************************/
#include<opencv2/core/core.hpp>
#include<highgui/highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;
/***************************************【全局函数声明部分】**********************************/
void colorReducePtr(Mat& inputImg,Mat& outputImg,int div);
void colorReduceAt(Mat& inputImg,Mat& outputImg,int div);
void colorReduceIterator(Mat& inputImg,Mat& outputImg,int div);
void matHelp(Mat& inputImg);
/********************************************【main函数】*************************************/
int main(int argc,char** argv)
{
//【1】创建Mat类对象的信息头
Mat srcImg;
//【2】创建矩阵体,载入图片并显示
srcImg=imread("D:\\scenery.png",CV_LOAD_IMAGE_COLOR);
imshow("原始图像",srcImg);
//【3】显示图像的基本信息
matHelp(srcImg);
//【4】按照原图的参数规格,来创建效果图
Mat dstImgPtr;
dstImgPtr.create(srcImg.rows,srcImg.cols,srcImg.type());
Mat dstImgAt;
dstImgAt.create(srcImg.rows,srcImg.cols,srcImg.type());
Mat dstImgIterator;
dstImgIterator.create(srcImg.rows,srcImg.cols,srcImg.type());
//【5】记录其实时间
double timeStartPtr=static_cast<double>(getTickCount());
//【6】调用颜色空间缩减函数
colorReducePtr(srcImg,dstImgPtr,32);
//【7】计算运行时间,并输出
double timeExpensePtr=((double)getTickCount()-timeStartPtr)/getTickFrequency();
cout<<"【1】ptr指针操作像素,此方法运行时间为---------->"<<timeExpensePtr<<endl;
double timeStartAt=static_cast<double>(getTickCount());
colorReduceAt(srcImg,dstImgAt,32);
double timeExpenseAt=((double)getTickCount()-timeStartAt)/getTickFrequency();
cout<<"【2】at()动态地址计算,此方法运行时间为--------->"<<timeExpenseAt<<endl;
double timeStartIterator=static_cast<double>(getTickCount());
colorReduceIterator(srcImg,dstImgIterator,32);
double timeExpenseIterator=((double)getTickCount()-timeStartIterator)/getTickFrequency();
cout<<"【3】Iterator迭代器操作像素,此方法运行时间为--->"<<timeExpenseIterator<<endl;
//【8】显示效果图
imshow("效果图",dstImgIterator);
waitKey(0);
}
/***************************************************************************************************
函数功能:
利用Mat类中的成员函数和数据成员提供一些编程的参考信息
函数参数:
Mat& inputImg---输入图像的引用类型
函数返回值:
无
***************************************************************************************************/
void matHelp(Mat& inputImg)
{
//【1】图像的行数
int nRows=inputImg.rows;
//【2】图像的列数
int nCols=inputImg.cols;
//【3】图像的高度
int nHeight=inputImg.size().height;
//【4】图像的高度
int nWidth =inputImg.size().width;
//【5】图像的通道个数,彩色图像3通道,灰度图像单通道
int nChannels=inputImg.channels();
//【6】图像中的一个像素点位置所含的字节数
int nElemByte=inputImg.elemSize();
//【7】图像中的一行,所含有的字节数
int nColByte=inputImg.step;
//【8】图像中像素的总个数
int elemTotal=inputImg.total();
//【9】返回图像的列数*行数---返回图像的尺寸大小
cout<<"【1】图像的行数-------------------------------------------------->"<<nRows<<endl;
cout<<"【2】图像的列数-------------------------------------------------->"<<nCols<<endl;
cout<<"【3】图像的高度-------------------------------------------------->"<<nHeight<<endl;
cout<<"【4】图像的高度-------------------------------------------------->"<<nWidth<<endl;
cout<<"【5】图像的通道个数,彩色图像3通道,灰度图像单通道----------------->"<<nChannels<<endl;
cout<<"【6】图像中的一个像素点位置所含的字节数-------------------------->"<<nElemByte<<endl;
cout<<"【7】图像中的一行,所含有的字节数--------------------------------->"<<nColByte<<endl;
cout<<"【8】图像中像素的总个数------------------------------------------>"<<elemTotal<<endl;
cout<<"【9】返回图像的列数*行数---返回图像的尺寸大小-------------------->"<<inputImg.size()<<endl;
cout<<"【10】此外,Mat类中还有比如:at<>(),ptr<>(),data等重要的成员"<<endl;
}
/***************************************************************************************************
函数功能:
1--缩减颜色空间
2--显示使用【指针】访问像素
3--显示Mat类的模板函数ptr<>()---ptr函数可以得到图像任意一行的---首地址
函数参数:
Mat& inputImg-----输入图像
Mat& outputImg----输出图像
int div----------颜色空间缩减倍数
函数返回值:
无
***************************************************************************************************/
void colorReducePtr(Mat& inputImg,Mat& outputImg,int div)
{
outputImg=inputImg.clone(); //复制图像的信息头和矩阵体到临时变量----->深复制
int nRows=outputImg.rows; //行数
int nCols=outputImg.cols*outputImg.channels(); //列数*通过数=每一行的元素个数
//双循环,遍历所有的像素值
for(int i=0;i<nRows;i++)
{
uchar* data=outputImg.ptr<uchar>(i); //获取第i行的首地址
for(int j=0;j<nCols;j++)
{
/***************【开始处理每个像素】****************/
data[j]=data[j]/div*div+div/2;
}//for j
}//for i
}
/***************************************************************************************************
函数功能:
1--缩减颜色空间
2--显示使用【指针】访问像素
3--显示Mat类的模板函数at<>()---at函数可以返回指定数组元素的地址
函数参数:
Mat& inputImg-----输入图像
Mat& outputImg----输出图像
int div----------颜色空间缩减倍数
函数返回值:
无
***************************************************************************************************/
void colorReduceAt(Mat& inputImg,Mat& outputImg,int div)
{
outputImg=inputImg.clone(); //复制图像的信息头和矩阵体到临时变量----->深复制
int nRows=outputImg.rows; //行数
int nCols=outputImg.cols; //列数
//双循环,遍历所有的像素值
for(int i=0;i<nRows;i++)
{
for(int j=0;j<nCols;j++)
{
/***************【开始处理每个像素】****************/
outputImg.at<Vec3b>(i,j)[0]=outputImg.at<Vec3b>(i,j)[0]/div*div+div/2;//蓝色通道
outputImg.at<Vec3b>(i,j)[1]=outputImg.at<Vec3b>(i,j)[1]/div*div+div/2;//绿色通道
outputImg.at<Vec3b>(i,j)[2]=outputImg.at<Vec3b>(i,j)[2]/div*div+div/2;//红色通道
}//for j
}//for i
}
/***************************************************************************************************
函数功能:
1--缩减颜色空间
2--显示使用【指针】访问像素
3--用迭代器操作像素
函数参数:
Mat& inputImg-----输入图像
Mat& outputImg----输出图像
int div----------颜色空间缩减倍数
函数返回值:
无
***************************************************************************************************/
void colorReduceIterator(Mat& inputImg,Mat& outputImg,int div)
{
outputImg=inputImg.clone(); //复制图像的信息头和矩阵到outputImg这个临时变量中
//获取迭代器
Mat_<Vec3b>::iterator itBegin=outputImg.begin<Vec3b>(); //初始位置
Mat_<Vec3b>::iterator inEnd =outputImg.end<Vec3b>(); //终止位置
//存取彩色图像像素
for(;itBegin!=inEnd;++itBegin)
{
(*itBegin)[0]=(*itBegin)[0]/div*div+div/2;
(*itBegin)[1]=(*itBegin)[1]/div*div+div/2;
(*itBegin)[2]=(*itBegin)[2]/div*div+div/2;
}
}