您的位置:首页 > 运维架构

opencv中遍历图像(Mat格式)

2014-10-28 23:12 309 查看
当Mat为多通道图像时,如果如果我们将其内容输出到终端,则可以看出其列数为Mat::cols的n倍,当然n为Mat的通道数。虽是如此,但是Mat::cols的数值并没有随之改变。遍历整幅图像时,如果是灰度图,那么一般是按<uchar>类型存放;如果是RGB彩图,那么一般是按<Vec3b>类型存放。

灰度图的存放格式:



多通道的图像中,每列并列存放通道数量的子列,如RGB三通道彩色图:



注意通道的顺序反转了:BGR。通常情况内存足够大的话图像的每一行是连续存放的,也就是在内存上图像的所有数据存放成一行,这中情况在访问时可以提供很大方便。

在实际应用中,我们很多时候需要对图像降色彩,因为256*256*256实在太多了,在图像颜色聚类或彩色直方图时,我们需要用一些代表性的颜色代替丰富的色彩空间,我们的思路是将每个通道的256种颜色用64种代替,即将原来256种颜色划分64个颜色段,每个颜色段取中间的颜色值作为代表色。

下面的例子对Mat图像进行扫描,并对像素点的像素进行压缩,压缩间隔为div=64,并比较扫描压缩及压缩的效率,效率最高的是采用.ptr及减少循环次数来遍历图像,并采用位操作来对图像像素进行压缩。

访问图像中的像素:


最高效的方法:C操作符[ ]

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

//利用.ptr和*++进行图像像素遍历
Void ScanImage(const Mat& im, Mat& outImage, int div=64)

{

CV_Assert(im.depth()!=sizeof(uchar));

int channels=im.channels();

int rows=im.rows();

int cols=im.cols() * channels;

outImage.create(im.size(),im.type());

if (im.isContinuous())

{

cols*=rows;

rows=1; //用 isContinuous()函数来判断图像数组是否为连续的,一行。

}

int i,j;

for (i=0;i<rows;i++)

{

const uchar* inData=im.ptr<uchar>(i);

uchar* outData=outImage.ptr<uchar>(j);

for (j=0;j<cols;j++)

{

*outData++=*inData++/div*div+div/2;

}

}

}

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

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

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


高效的方法:用指针来遍历图像

//利用.ptr和数组下标进行图像像素遍历
Void ScanImage(const Mat& im, Mat& outImage,int div=64)

{
//创建与原图像等尺寸的图像
outImage.create(im.size(),im.type());
int rows=im.rows;
//将3通道转为1通道
int nl=im.cols()*im.channels();
for (int i=0;i<rows;i++)
{
//每一行图像的指针
const uchar* inData=im.ptr<uchar>(i);
uchar* outIData=outImage.prt<uchar>(i);
for (int j=0;j<nl;j++)
{
outData[j]=inData[j]/div*div+div/2; //减少图像中颜色总数的关键算法: if div=64,then the total number of colors is 4*4*4;整数除法时,向下取整
}
}
}

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

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

但是这种用法不能用在行与行之间,因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。但是我们可以申明一个连续的空间来存储图像,这个话题引入下面最为高效的遍历图像的机制。
//利用.ptr和数组下标进行图像像素遍历,位操作运算用于减少图像颜色总数
void ScanImage(const
Mat& im, Mat& outImage,int div=64)
{

//创建与原图像等尺寸的图像
outImage.create(im.size(),im.type());
int rows=im.rows;
int cols=im.cols * im.channels();
int n=static_cast<int>(log(static_cast<double>(div))/log(2.0)); //div=64,n=6
uchar mask=0xFF<<n;
//遍历图像的每个像素
for (int i=0;i<rows;i++)
{
uchar *inData=im.ptr<uchar>(i);
uchar *outData=outImage.ptr<uchar>(i);
for (int j=0;j<cols;j++)
{
*outData++=*inData&mask+div/2;
}
}

}


普通方法: at<typename>(i,j)


void ScanImage(const Mat& im, Mat& outImage,int
div=64)

{
for (int i=0;j<im.rows;i++)
{
for (j=0;j<im.cols;j++)
{
outImage.at<Vec3b>(i,j)[0]=im.at<Vec3b>(i,j)[0]/div*div+div/2;
outImage.at<Vec3b>(i,j)[1]=im.at<Vec3b>(i,j)[1]/div*div+div/2;
outImage.at<Vec3b>(i,j)[2]=im.at<Vec3b>(i,j)[2]/div*div+div/2;
}
}
}




[title3]

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

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

image.at<Vec3b>(i,j)[k]:取出彩色图像中i行j列第k通道的颜色点。其中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;

[/title3]

安全的方法:迭代器遍历

相比用指针直接访问可能出现越界问题,迭代器绝对是非常安全的方法:

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

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开始。





[title3]
图像的邻域操作[/title3]

很多时候,我们对图像处理时,要考虑它的邻域,比如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 }


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

1,staturate_cast<typename>是一个类型转换函数,程序里是为了确保运算结果还在uchar范围内。

2,row和col方法返回图像中的某些行或列,返回值是一个Mat。

3,setTo方法将Mat对像中的点设置为一个值,Scalar(n)为一个灰度值,Scalar(a,b,c)为一个彩色值。

部分出处:http://www.cnblogs.com/ronny/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: