Several methods to traverse image pixels

Here is what I sort of based on the contents of several bloggers, link is given in this reprint:
[ https://blog.csdn.net/daoqinglin/article/details/23628125 ]
[ https://blog.csdn.net / xuhang0910 / Article This article was / Details / 47,058,419 ]
[ https://blog.csdn.net/github_35160620/article/details/51708659 ]

First you have to tell us about the expression of matrix elements:

For single-channel image, the element type is generally 8U (i.e., 8-bit unsigned integer), of course, may be 16S, 32F32F like; these types may be used directly basic data types expressed uchar, short, float like C / C ++ language .

If the multi-channel image, such as an RGB color image, three channels are needed to represent. In this case, if the image is still regarded as a two-dimensional matrix elements of the matrix that is no longer the basic data types.

opencv vec may represent a vector used for expression of matrix elements.


OpenCV Optimization: 4 ways to traverse the image

A four ways traversal image: at <typename> (i, j)

Mat class provides methods for obtaining at a point on the image, which is a template function, the point can take on any type of image.

In practical applications, we often need to drop the color image, since 256 * 256 * 256 too many, at the time of image color clustering or color histogram, we need to use some color instead of the typical rich color space, our idea is to 256 colors for each channel used in place of 64 kinds, i.e. 256 colors of the original color divided 64 segments, each segment takes an intermediate color values ​​of the color as the representative color.

复制代码
 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 }
复制代码

image

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

Mat_<uchar> im=image;

im(i,j)=im(i,j)/div*div+div/2;

需要注意的是,如果遍历图像并不推荐使用 需要注意的是,如果遍历图像并不推荐使用 at() 函数。使用at()函数优点是代码可读性高,但效率不高。

二、高效一点:用指针来遍历图像

OpenCV Mat数据类型指针ptr的使用

    cv::Mat image = cv::Mat(400, 600, CV_8UC1); //宽400,长600
    uchar * data00 = image.ptr<uchar>(0);
    uchar * data10 = image.ptr<uchar>(1);
    uchar * data01 = image.ptr<uchar>(0)[1];
  
  

    解释:

    • 定义了一个Mat变量image。
    • data00是指向image第一行第一个元素的指针。
    • data10是指向image第二行第一个元素的指针。
    • data01是指向image第一行第二个元素的指针。

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

    复制代码
     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     // 把图像边缘像素设置为0
19     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 }
复制代码

image

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

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/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


Guess you like

Origin blog.csdn.net/lanlanrening/article/details/85092948