在OpenCV2中,对于的图片的存储已经全部转移到Mat中。对于OpenCV1的方式使用IplImage就不考虑了。因为Mat帮助自动管理我们的内存。
在(一)中我们测试环境是否搭建成功的测试程序中,我们用imread()读取硬盘上我图片,现在我们考虑图片在矩阵中存储。
首先来看一下图像像素值在内存中的保存方式。像素值是以矩阵的方式保存的,矩阵的大小取决于图像采用的颜色模型,确切的说是图像的通道数。如果是灰度图像,矩阵是这样的:(单通道)
矩阵的每一个元素代表一个像素值。而对 多通道图像 来说,一个像素值需要多个矩阵元素来存储, 矩阵中的列会包含多个子列,其子列数和通道数目相等。以常见的RGB模型来说:
而且,如果内存比较大,图像中的各行各列就可以一行一行的连接起来,形成一个长行。连续存储有助于提升图像的扫描速度,使用iscontinuous来判断矩阵是否是连续存储的。
颜色空间缩减
如果矩阵元素存储的是单通道像素,使用8位无符号来保存每个元素,那么像素可能有256个不同的值。如果是三通道的话,就会用(2的24次)一千六百多种颜色。如此多的颜色在有些时候不是必须的,而且会对算法的性能造成严重的影响。在这种情况下,最常用的做法就是颜色空间的缩减,也就是将现有的颜色空间进行映射,以获得较少的颜色数。
例如:颜色值0到9映射为0,10到19映射为10,以此类推。
将各个颜色值映射关系存储到表中,在对格像素的颜色值进行处理时,直接进行查表。
下面是对映射表的初始化:1:
2: uchar table[256] ;
3: int divideWith = 10;4: for(int i = 0 ; i < 256 ; i ++)5: table[i] = (uchar) ( divideWith * (i / divideWith)); //处理0-255的值
这里将各个像素的颜色值整除以10,然后再乘以10,这样会像上面所说的将0到9的颜色值映射为0,10到19的颜色值映射为10,以此类推。这样颜色数就减少到26*26*26大大减小了数量!
指针遍历图像 Efficient Way
1: Mat& scanImageWithPointer(Mat &img , const uchar * const table)
2: {
3: CV_Assert(img.depth () == sizeof(uchar)); //断言,只处理使用8位无符号数保存元素值的矩阵4:
5: int channels = img.channels() ; //得到通道数6:
7: int rows = img.rows ;8: int cols = img.cols * channels; //用通道数乘以矩阵的行数作为最终遍历时行数(多通道的话矩阵是有子列的)9:
10: if(img.isContinuous()) { //调用isContinuous来判断矩阵在内存中是不是连续存储的11: cols *= rows ;
12: rows = 1 ; //连续存储的话就变成一行即可!!!!!GOOD!
13: }
14:
15: uchar * p ;
16: for(int i = 0 ; i < rows ; i ++){17: p = img.ptr<uchar>(i); //获取每一行开始处的指针18: for(int j = 0 ; j < cols ; j ++){19: p[j] = table[p[j]] ; //table[256] p[j] ==(0-255),这就是查表取值了!
20: }
21: }
22: return img ;23: }
迭代遍历图像 Safe Method
1: Mat& scanImageWithIterator(Mat &img,const uchar * const table)
2: {
3: CV_Assert(img.depth () == sizeof(uchar));4:
5: const int channels = img.channels() ;6:
7: switch (channels){8: case 1:9: {
10: MatIterator_<uchar> it,end ;
11: end = img.end<uchar>() ;
12: for(it = img.begin<uchar>(); it != end ; it ++) {13: *it = table[*it] ;
14: }
15: break ;16: }
17: case 3:18: {
19: MatIterator_<Vec3b> it,end ; //Vec3b == 3*uchar20: end = img.end<Vec3b>() ;
21: for(it = img.begin<Vec3b>(); it != end ; it ++) {22: (*it)[0] = table[(*it)[0]] ;
23: (*it)[1] = table[(*it)[1]] ;
24: (*it)[2] = table[(*it)[2]] ;
25: }
26: break ;27: }
28: }
29: return img ;30: }
有两种方式来获取图像矩阵的迭代器1: cv::MatIterator_<cv::Vec3b> it = image.begin<cv::Vec3b>();
2: cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
同样的获取图像的常量迭代器也有两种方式1: cv::MatConstIterator_<cv::Vec3b> it = image.begin<cv::Vec3b>();
2: cv::Mat_<cv::Vec3b>::const_iterator end = image.end<cv::Vec3b>();
at<>遍历图像
这种方法不推荐用来遍历图像,它主要用来获取或更改图像的中随机元素的。基本用途是用来访问特定的矩阵元素(知道行数和列数)
Mat_是Mat的一个模板子类。使用Mat_可以带来一些便利。
这个类定义了一些额外的成员方法,但没有定义成员变量。它重载了操作符(),允许我们可以通过它直接存取矩阵元素。
1: Mat& scanImageWithAt(Mat& img,const uchar * const table)2: {
3: CV_Assert(img.depth () == sizeof(uchar));4: const int channels = img.channels() ;5:
6: switch (channels){7: case 1:8: {
9: for (int i = 0 ; i < img.rows ; i ++)10: for(int j = 0 ; j < img.cols ; j ++)11: img.at<uchar>(i,j) = table[img.at<uchar>(i,j)] ;
12: break ;13: }
14: case 3:15: {
16: Mat_<Vec3b> I = img ;17: for(int i = 0 ; i < I.rows ; i ++){18: for(int j = 0 ; j < I.cols ; j ++){19: I(i,j)[0] = table[I(i,j)[0]] ;
20: I(i,j)[1] = table[I(i,j)[1]] ;
21: I(i,j)[2] = table[I(i,j)[2]] ;
22: }
23: }
24: img = I ;
25: break ;26: }
27: }
28: return img ;29: }
前面提到,at<>主要是用来访问矩阵的随机元素的,下面使用该方法来给一张图像添加椒盐噪声。at(i,j)是Mat的一个函数,但是对Mat操作必须指定Mat中存储图片的类型<>,如下面的代码所示。//n添加椒盐噪声的个数 void salt(cv::Mat &image, int n) { for (int k=0; k<n; k++) { int i= rand()%image.cols; int j= rand()%image.rows; if (image.channels() == 1) { // gray-level image image.at<uchar>(j,i)= 255; } else if (image.channels() == 3) { // color image image.at<cv::Vec3b>(j,i)[0]= 255; image.at<cv::Vec3b>(j,i)[1]= 255; image.at<cv::Vec3b>(j,i)[2]= 255; } } }
效果:
LUT 图像扫描
在图像处理中,对图像的所有像素重新映射是很常见的,在OpenCV中提供一个函数来实现该该操作,不需要去扫描整个图像, operation on array :LUT。const char* imagename = "lena.jpg"; Mat image = imread(imagename); //制作表 uchar table[256] ; int divideWith = 10; for(inti = 0 ; i < 256 ; i ++) table[i] = (uchar) ( divideWith * (i / divideWith)); //表转化为Mat,单通道表! Mat lookupTable(1,256,CV_8U); uchar *p = lookupTable.data ; for(int i = 0 ; i < 256 ; i ++) p[i] = table[i] ; Mat result ; LUT(image,lookupTable,result) ; imshow("1",image); imshow("2",result); waitKey(0);
LUT的函数原型
void LUT(InputArray src, InputArray lut, OutputArray dst)
src 输入的8位矩阵
lut 256个元素的查找表,为了应对多通道输入矩阵,
查找表要么是单通道(此时,输入矩阵的多个通道使用相同的查找表),
要么和输入矩阵有相同的通道数
dst 输出矩阵。和输入矩阵有相同的尺寸和通道