C++版OpenCV---图像的遍历

原文链接:点击打开链接

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

Mat类提供了一个at方法用于提取图像上的点,它是一个模板函数,可以取得到任意类型的图像上的点。

at方法取图像中的点的用法:

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

image.at<Vec3b>(i,j)[k]:取出彩色图像中i行j列第k通道的颜色点。其中uchar,Vec3b都是图像像素值的类型,其中Vec3b是通过typedef Vec<T,N>来定义,N代表元素的个数,T代表类型。

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

例如:Mat_<uchar>im=image;      im(i,j)=im(i,j)/div*div+div/2;

举个例子:我们要将每个通道256种颜色用64种代替,即将原来256种颜色划分为64个颜色段,每个颜色段取中间的颜色值作为代表色。

void colorReduce(Mat& image,int div)
  {
      for(int i=0;i<image.rows;i++)
      {
          for(int j=0;j<image.cols;j++)
          {
              image.at<Vec3b>(i,j)[0]=image.at<Vec3b>(i,j)[0]/div*div+div/2;
              image.at<Vec3b>(i,j)[1]=image.at<Vec3b>(i,j)[1]/div*div+div/2;
              image.at<Vec3b>(i,j)[2]=image.at<Vec3b>(i,j)[2]/div*div+div/2;
         }
     }
 }
二、高效点儿的:用指针来遍历图像

取出图像中第i行数据的指针:image.ptr<uchar>(i)。

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

三、更高效的方法

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

void colorReduce(const Mat& image,Mat& outImage,int div)
  {
      int nr=image.rows;
      int nc=image.cols;
      outImage.create(image.size(),image.type());
      if(image.isContinuous()&&outImage.isContinuous())
      {
          nr=1;
          nc=nc*image.rows*image.channels();
     }
     for(int i=0;i<nr;i++)
     {
         const uchar* inData=image.ptr<uchar>(i);
         uchar* outData=outImage.ptr<uchar>(i);
         for(int j=0;j<nc;j++)
         {
             *outData++=*inData++/div*div+div/2;
         }
     }
 }

四、用迭代器来遍历

为图像中的像素声明一个迭代器:

Matlterator_<Vec3b>it;

Mat_<Vec3b>::iterator it;

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

MatConstlterator<Vec3b>it;

或者Mat_<Vec3b>::const_iterator it;

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

 void colorReduce(const Mat& image,Mat& outImage,int div)
 {
     outImage.create(image.size(),image.type());
     MatConstIterator_<Vec3b> it_in=image.begin<Vec3b>();
     MatConstIterator_<Vec3b> itend_in=image.end<Vec3b>();
     MatIterator_<Vec3b> it_out=outImage.begin<Vec3b>();
     MatIterator_<Vec3b> itend_out=outImage.end<Vec3b>();
     while(it_in!=itend_in)
     {
         (*it_out)[0]=(*it_in)[0]/div*div+div/2;
         (*it_out)[1]=(*it_in)[1]/div*div+div/2;
         (*it_out)[2]=(*it_in)[2]/div*div+div/2;
         it_in++;
         it_out++;
     }
 }


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


五、图像的邻域操作

图像滤波操作常常要考虑它的邻域。下面进行一个简单的滤波操作,滤波算子核心公式为:

sharp(i,j)=5*image(i,j)-image(i-1,j)-image(i+1,j)-image(i,j-1)-image(i,j+1)

void ImgFilter2d(const Mat &image,Mat& result)
  {
      result.create(image.size(),image.type());
      int nr=image.rows;
      int nc=image.cols*image.channels();
      for(int i=1;i<nr-1;i++)
      {
          const uchar* up_line=image.ptr<uchar>(i-1);//指向上一行
          const uchar* mid_line=image.ptr<uchar>(i);//当前行
         const uchar* down_line=image.ptr<uchar>(i+1);//下一行
         uchar* cur_line=result.ptr<uchar>(i);
         for(int j=1;j<nc-1;j++)
         {
             cur_line[j]=saturate_cast<uchar>(5*mid_line[j]-mid_line[j-1]-mid_line[j+1]-
                 up_line[j]-down_line[j]);
         }
     }
     // 把图像边缘像素设置为0
     result.row(0).setTo(Scalar(0));
     result.row(result.rows-1).setTo(Scalar(0));
     result.col(0).setTo(Scalar(0));
     result.col(result.cols-1).setTo(Scalar(0));
 }

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

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个通道分开考虑。

vector<Mat> planes;
 // 将image分为三个通道图像存储在planes中
 split(image,planes);
 planes[0]+=image2;
 // 将planes中三幅图像合为一个三通道图像
 merge(planes,result);



  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值