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及减少循环次数来遍历图像,并采用位操作来对图像像素进行压缩。
访问图像中的像素:
一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,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;
}
}
}
{
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;
}
}
}
相比用指针直接访问可能出现越界问题,迭代器绝对是非常安全的方法:
下面的方法可以让我们来为图像中的像素声明一个迭代器:
MatIterator_<Vec3b> it;
Mat_<Vec3b>::iterator it;
如果迭代器指向一个const图像,则可以用下面的声明:
MatConstIterator<Vec3b> it; 或者
Mat_<Vec3b>::const_iterator it;
下面我们用迭代器来简化上面的colorReduce程序:
如果你想从第二行开始,则可以从image.begin<Vec3b>()+image.rows开始。
上面的程序有以下几点需要说明:
1,staturate_cast<typename>是一个类型转换函数,程序里是为了确保运算结果还在uchar范围内。
2,row和col方法返回图像中的某些行或列,返回值是一个Mat。
3,setTo方法将Mat对像中的点设置为一个值,Scalar(n)为一个灰度值,Scalar(a,b,c)为一个彩色值。
部分出处:http://www.cnblogs.com/ronny/
灰度图的存放格式:
多通道的图像中,每列并列存放通道数量的子列,如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/
相关文章推荐
- opencv中遍历图像(IplImage格式)
- 在MFC图像控件上显示opencv Mat格式图片
- opencv中Mat存储图像和遍历图像像素
- 保存OpenCV中Mat_<Vec3f>格式的图像
- OpenCV:通过Mat遍历图像的几种方法
- OpenCV图像MAT格式转换为QT中QImage并显示
- OpenCV中图像的格式Mat 图像深度
- 在MFC中显示OpenCV的Mat格式图像和打开摄像头
- opencv从零开始——6. 图像的读取和像素遍历
- [学习OpenCV攻略][013][Mat - 基本图像容器]
- Opencv--访问Mat图像中每个像素的值
- 学习《OpenCV 2 计算机视觉编程手册》第二章---使用指针遍历图像案例
- OpenCV 将Mat数据转成Video格式视频
- opencv中mat,cvmat,Iplimage结构体定义以及格式互相转换。
- OpenCv (正式篇)6----core组件--Mat(最基本的图像数据类型)
- 图像识别与处理之Opencv——Mat表达式的运算(矩阵的一些运算)
- javaCV图像处理之Frame、Mat和IplImage三者相互转换(使用openCV进行Mat和IplImage转换)
- OPENCV Mat图像在MFC PictureControl显示
- opencv 帧差法找出相差大的图像(包含访问mat的像素值)
- Opencv2系列学习笔记2(图像的遍历)