图像处理,从开始我们就接触了Mat类,这一个图像容器类,同时也是个矩阵类,那么如何访问图像的像素呢?或者说如何去操作这个矩阵呢?普遍上是说有暗中方法,一个是指针ptr,一个是AT,一个是迭代器,这个是一一来说,主要是从不同的角度说指针访问,因为这个最快,个人认为最重要。其中有vc6.0和matlab的辅助因为比较长,所以就穿插在里面,不单独说了。首先,在进行访问前,要知道像素的存储方式,下面来一张图,是最好的解释,这个是基础,因为后面在对行列进行访问的时候,你不知道存储方式,就一定会出现。
我们认为的矩阵形式是左图,计算机认识的是右图
不同维度的数组在内存的存储方式为
一、灰度图像,单通道
二、彩色图像,三通道图像
对于彩色图像来说,一般我们都说RGB,但是这里要强调一个是BGR,这个我用下面的代码来看一下,更好的理解
<span style="font-size:18px;">#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
using namespace cv;
int main()
{
Mat srcimage=imread("red.jpg");
//Mat srcimage1=imread("green.jpg");
Mat srcimage2=imread("blue.jpg");
//if(!srcimage.data)
// return 1;
Mat tempimage=srcimage.clone();
Mat tempimage1=srcimage2.clone();
int watch11,watch12,watch13,watch21,watch22,watch23;
watch11=tempimage.at<Vec3b>(0,0)[0];
watch12=tempimage.at<Vec3b>(0,0)[1];
watch13=tempimage.at<Vec3b>(0,0)[2];
watch21=tempimage1.at<Vec3b>(0,0)[0];
watch22=tempimage1.at<Vec3b>(0,0)[1];
watch23=tempimage1.at<Vec3b>(0,0)[2];
waitKey(0);
return 0;
}</span>
对面下面的图可以看到,(0 0 254)一张红色的图,只有BGR的red有数值,同时也可以看到矩阵的数值显示 0 0 254
基础也说的差不多了,那我们看看像素是怎么访问的
三、指针方式
我喜欢从指针方式开始,因为自己以前用VC6.0的,感觉很相同
<span style="font-size:18px;">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; //这里也可以用*outData下面的例子就是可以参考
}
}
} </span>
一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行,所以用进一步的提高了效率。
<span style="font-size:18px;">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;
}
}
} </span>
上面说的两种是最常见的,也是最重要的,以后的访问中会经常看到,所以要好好的看
为了进一步学习ptr指针访问,找了2个方法,都是用指针来访问,略有一点不同,可以作为参考
//三通道图像,at(y , x)索引是先行(y轴) , 后列(x轴)
//第一种方法
for(int h = 0 ; h < image.rows ; ++ h)
{
for(int w = 0 ; w < image.cols / 2 ; ++ w)
{
uchar *ptr = image.ptr<uchar>(h , w) ;
ptr[0] = 255 ;
ptr[1] = 0 ;
ptr[2] = 0 ;
}
}
imshow("color1" , image) ;
//第二种方法
for(int h = 0 ; h < image.rows ; ++ h)
{
for(int w = 0 ; w < image.cols / 2 ; ++ w)
{
Vec3b *ptr = image.ptr<Vec3b>(h , w) ;
ptr->val[0] = 0 ;
ptr->val[1] = 255 ;
ptr->val[2] = 0 ;
}
}
imshow("color2" , image) ;
为了加深印象我对照了一下vc6.0c++的程序,发现很相似,但是明显简介的多,但是思路和方法是一样的,下面这个就是vc6.0里面的一个小程序
void HuidubianhuanDib::Fei0()
{
LPBYTE p_data;
int wide,height;
p_data=this->GetData;
wide=this->GetWidth;
height=this->Getheight;
for(int j=0;j<height;j++)
for(int i=0;i<wide;i++)
{
if(*p_data!=0)
*p_data=255;
p_data++;
}
}
显然可以看到用是对data进行操作,相应的opencv中也可以用同样的方式,和上面的代码一个思路,so看看下面的代码是不是更加的清楚
#include <highgui.h>
using namespace std ;
using namespace cv ;
int main()
{
Mat image = imread("forest.jpg") ;
imshow("image" , image) ;
//三通道
uchar *data = image.data ;
for(int h = 0 ; h < image.rows ; ++ h)
{
for(int w = 0 ; w < image.cols/2 ; ++ w)
{
*data ++ = 128 ;
*data ++ = 128 ;
*data ++ = 128 ;
}
}
imshow("data" , image) ;
//单通道
image = imread("forest.jpg" , 0) ;
imshow("image" , image) ;
data = image.data ;
for(int h = 0 ; h < image.rows ; ++ h)
{
for(int w = 0 ; w < image.cols/2 ; ++ w)
{
*data ++ = 128 ;
}
}
imshow("data1" , image) ;
waitKey(0) ;
return 0 ;
}
其中ptr是成员函数,data是成员变量,单独一个用法,看下面的代码,和上面的对比就会名ptr和data了
void colorReduce2(const Mat &image, Mat &result, int div = 64)
{
int n1 = image.rows;
//int nc = image.cols * image.channels();
int nc = image.cols ;
for(int j = 0; j < n1; j++)
{
//uchar *data = image.ptr(j);
//uchar *data_in = image.data + j * image.step;
//uchar *data_out = result.data + j * result.step;
for(int i = 0; i < nc; i++)
{
uchar *data = image.data + j * image.step + i * image.elemSize(); // 这种方式不推荐使用,一方面容易出错,还不适用于带有"感兴趣区域"
//data_out[i] = data_in[i]/div *div + div/2;
data[0] = 0;
data[1] = 0;
data[2] = 0;
}
}
}
四、动态地址at
Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。下面我们通过一个图像处理中的实际来说明它的用法。
最经典的用法就是m.at<Vec3b>(i,j)[m]
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;
}
}
}
看到at,很多的时候叫访问下标,因为类似与image(i,j),这个就和矩阵的访问一样,Matlab经常出现的就是这样的,例如
I=zeros(m,n);
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
I(i,j)=0;
}
五、迭代器访问
这个这是你没有看出来什么优点,目前就是因为指针直接访问可能出现越界问题,而迭代器是非常安全的方法,用法是通过获得图像矩阵的开始和结束,然后增加迭代直至从开始到结束。
cv::Mat tempImage = srcImage.clone();
// 初始化源图像迭代器
cv::MatConstIterator_<cv::Vec3b> srcIterStart = srcImage.begin<cv::Vec3b>();
cv::MatConstIterator_<cv::Vec3b> srcIterEnd = srcImage.end<cv::Vec3b>();
// 初始化输出图像迭代器
cv::MatIterator_<cv::Vec3b> resIterStart = tempImage.begin<cv::Vec3b>();
cv::MatIterator_<cv::Vec3b> resIterEnd = tempImage.end<cv::Vec3b>();
// 遍历图像反色处理
while( srcIterStart != srcIterEnd )
{
(*resIterStart)[0] = 255 - (*srcIterStart)[0];
(*resIterStart)[1] = 255 - (*srcIterStart)[1];
(*resIterStart)[2] = 255 - (*srcIterStart)[2];
// 迭代器递增
srcIterStart++;
resIterStart++;
}
官方比较流行的是这样的代码,其中是一样的,只是个人写法的习惯
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
// accept only char type matrices
CV_Assert(I.depth() != sizeof(uchar));
const int channels = I.channels();
switch(channels)
{
case 1:
{
MatIterator_<uchar> it, end;
for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
*it = table[*it];
break;
}
case 3:
{
MatIterator_<Vec3b> it, end;
for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
{
(*it)[0] = table[(*it)[0]];
(*it)[1] = table[(*it)[1]];
(*it)[2] = table[(*it)[2]];
}
}
}
return I;
}
六、LUT函数 Look up table与计时函数getTickFrequency()
LuT用于批量进行图像像素查找、扫描、操作像素
cv::Mat inverseColor6(cv::Mat srcImage)
{
int row = srcImage.rows;
int col = srcImage.cols;
cv::Mat tempImage = srcImage.clone();
// 建立LUT 反色table
uchar LutTable[256];
for (int i = 0; i < 256; ++i)
LutTable[i] = 255 - i;
cv::Mat lookUpTable(1, 256, CV_8U);
uchar* pData = lookUpTable.data;
// 建立映射表
for( int i = 0; i < 256; ++i)
pData[i] = LutTable[i];
// 应用索引表进行查找
cv::LUT(srcImage, lookUpTable, tempImage);
return tempImage;
}
对于计算时间的问题,opencv提供了2个比较方便的函数getTickCount()与getTickFrequency(),
其中getTickCount()函数返回CPU自某个事件以来走过的时钟周期,
getTickFrequency()函数返回CPU一秒钟所走过的时钟周期数
用法
double time0=static_cast<double>(getTickCount());
//
time0=((double)getTickCount()-time0)/getTickFrequency();
cout<<''时间为:"<<time0<<"秒"<<endl;
图像识别算法交流 QQ群:145076161,欢迎图像识别与图像算法,共同学习与交流