分类: 算法学习2014-04-13 23:43 1312人阅读 评论(0) 收藏 举报算法
opencv架构
咱们在实际应用中对图像进行的操做,每每并非将图像做为一个总体进行操做,而是对图像中的全部点或特殊点进行运算,因此遍历图像就显得很重要,如何高效的遍历图像是一个很值得探讨的问题。学习
Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,能够取到任何类型的图像上的点。下面咱们经过一个图像处理中的实际来讲明它的用法。优化
在实际应用中,咱们不少时候须要对图像降色彩,由于256*256*256实在太多了,在图像颜色聚类或彩色直方图时,咱们须要用一些表明性的颜色代替丰富的色彩空间,咱们的思路是将每一个通道的256种颜色用64种代替,即将原来256种颜色划分64个颜色段,每一个颜色段取中间的颜色值做为表明色。spa
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方法取图像中的点的用法:.net
image.at<uchar>(i,j):取出灰度图像中i行j列的点。3d
image.at<Vec3b>(i,j)[k]:取出彩色图像中i行j列第k通道的颜色点。其中uchar,Vec3b都是图像像素值的类型,不要对Vec3b这种类型感受惧怕,其实在core里它是经过typedef Vec<T,N>来定义的,N表明元素的个数,T表明类型。指针
更简单一些的方法:OpenCV定义了一个Mat的模板子类为Mat_,它重载了operator()让咱们能够更方便的取图像上的点。code
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 }
上面的程序有如下几点须要说明:
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);
做者:☆Ronny丶
出处:http://www.cnblogs.com/ronny/
本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。