服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - OpenCV 通过Mat遍历图像的方法汇总

OpenCV 通过Mat遍历图像的方法汇总

2022-10-07 16:20一杯清酒邀明月 C/C++

对图像中的所有点或特殊点进行运算,所以遍历图像就显得很重要,如何高效的遍历图像是一个很值得探讨的问题,本文给大家带来了多种方法操作OpenCV 通过Mat遍历图像,感兴趣的朋友一起看看吧

我们在实际应用中对图像进行的操作,往往并不是将图像作为一个整体进行操作,而是对图像中的所有点或特殊点进行运算,所以遍历图像就显得很重要,如何高效的遍历图像是一个很值得探讨的问题。

Color Reduce
还是使用经典的Reduce Color的例子,即对图像中的像素表达进行量化。如常见的RGB24图像有256256256中颜色,通过Reduce Color将每个通道的像素减少8倍至256/8=32种,则图像只有323232种颜色。假设量化减少的倍数是N,则代码实现时就是简单的value/N*N,通常我们会再加上N/2以得到相邻的N的倍数的中间值,最后图像被量化为(256/N)(256/N)(256/N)种颜色。

并对图像降色彩后的彩色直方图进行统计。

 

方法一、直接对图像像素修改.at<typename>(i,j)

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

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;
      }
  }
}

OpenCV 通过Mat遍历图像的方法汇总

通过上面的例子我们可以看出,at方法取图像中的点的用法:

image.at<uchar>(i,j):取出灰度图像中i行j列的点。
image.at<Vec3b>(i,j)[k]:取出彩色图像中i行j列第k通道的颜色点,k=[0,1,2],分别代表B,G,R。

其中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;

 

二、用指针.ptr<uchar>(k)来遍历输入图像,数组[]生成输出图像

上面的例程中可以看到,我们实际喜欢把原图传进函数内,但是在函数内我们对原图像进行了修改,而将原图作为一个结果输出,很多时候我们需要保留原图,这样我们需要一个原图的副本。

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;
      }
  }
}

OpenCV 通过Mat遍历图像的方法汇总

从上面的例子中可以看出,取出图像中第i行数据的指针:image.ptr<uchar>(i)。

值得说明的是:程序中将3通道的数据转换为1通道,在建立在每一行数据元素之间在内存里是连续存储的,每个像素三通道像素按顺序存储。也就是一幅图像数据最开始的三个值,是最左上角的那像素的三个通道的值。

但是这种用法不能用在行与行之间,因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。但是我们可以申明一个连续的空间来存储图像,这个话题引入下面最为高效的遍历图像的机制。

 

三、用指针.ptr<uchar>(k)来遍历输入图像,指针方式生成输出图像

与上述方法二遍历图像的方法相同,而生成输出图像的方式从数组换成了指针的方式。因此只需改动一句话。

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++ = *inData++ / div*div + div / 2;
      }
  }
}

OpenCV 通过Mat遍历图像的方法汇总

 

四、用指针.ptr<uchar>(k)来遍历输入图像,指针方式结合位运算生成输出图像

与上述方法遍历图像的方法相同,而生成输出图像的方式从加减乘除基本四则运算的方式换成了位运算的方式。

这里特别需要注意的是,位运算的优先级是低于乘除加减的,所以一定要在位运算加括号。

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();
  //对数换底公式log a(b) = log b/log a
  int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
  // mask used to round the pixel value  e.g. for div=16, mask= 0xF0
  uchar mask = 0xFF << n;
  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++ = (*inData++ & mask)  + div / 2;
      }
  }
}

OpenCV 通过Mat遍历图像的方法汇总

 

五、用指针.ptr<uchar>(k)来遍历输入图像,指针方式结合取模运算生成输出图像

与上述方法遍历图像的方法相同,而生成输出图像的方式从位运算的方式换成了取模运算的方式。

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();

  int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
  // mask used to round the pixel value  e.g. for div=16, mask= 0xF0
  uchar mask = 0xFF << n;
  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++)
      {
          int Data = *inData++;
          *outData++ = Data - Data%div + div / 2;
      }
  }
}

OpenCV 通过Mat遍历图像的方法汇总

 

六、连续图像isContinuous()函数方法。

上面已经提到过了,一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,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;
      }
  }
}

OpenCV 通过Mat遍历图像的方法汇总

用指针除了用上面的方法外,还可以用指针来索引固定位置的像素:

image.step返回图像一行像素元素的个数(包括空白元素),image.elemSize()返回一个图像像素的大小。

image.at<uchar>(i,j)=image.data+i*image.step+j*image.elemSize();

 

七、迭代器Mat_iterator方法。

下面的方法可以让我们来为图像中的像素声明一个迭代器:

MatIterator_<Vec3b> it;
Mat_<Vec3b>::iterator it;

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

MatConstIterator<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++;
  }
}

OpenCV 通过Mat遍历图像的方法汇总

如果你想从第二行开始,则可以从

image.begin<Vec3b>()+image.rows

开始。

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

到此这篇关于OpenCV 通过Mat遍历图像的几种方法的文章就介绍到这了,更多相关OpenCV遍历图像内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.cnblogs.com/ybqjymy/p/15935894.html

延伸 · 阅读

精彩推荐
  • C/C++详解C++构造函数

    详解C++构造函数

    这篇文章主要为大家介绍了C++构造函数,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助...

    weixin_4636942511452022-02-25
  • C/C++C语言实现航班管理系统

    C语言实现航班管理系统

    这篇文章主要为大家详细介绍了C语言实现航班管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    dandelionLYY8222021-08-11
  • C/C++c语言实现的hashtable分享

    c语言实现的hashtable分享

    哈希表效率高,众所周知。应用广泛,php中大部分存储使用的都是hashtable,包括变量,数组…如何使用c语言实现hashtable呢,现提供自己的思路,如有不妥之...

    C语言教程网8612021-01-14
  • C/C++linux下c语言的多线程编程

    linux下c语言的多线程编程

    这篇文章主要介绍了linux下c语言的多线程编程,需要的朋友可以参考下...

    nfcm7662021-06-06
  • C/C++C/C++回调函数介绍

    C/C++回调函数介绍

    回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就...

    C语言教程网11402021-01-05
  • C/C++C++编程中指针的声明与基本使用讲解

    C++编程中指针的声明与基本使用讲解

    这篇文章主要介绍了详解C++编程中C++编程中指针的声明与基本使用讲解,文中举了简单的例子来讲如何在基本的数据结构中使用指针,以及固定和可变指针的...

    C++教程网8002021-03-23
  • C/C++C++存储方案和动态分配

    C++存储方案和动态分配

    这篇文章主要介绍了C++存储方案和动态分配,下面来看文章对此的详细介绍吧,需要的小伙伴可以参考一下...

    Coder梁(ID:Coder_LT)10592022-08-04
  • C/C++C语言链表实现学生管理系统

    C语言链表实现学生管理系统

    这篇文章主要为大家详细介绍了C语言链表实现学生管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    搬砖的小孩有肉吃5632021-07-30