OpenCV(三) 关于图片的存储

在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*uchar
  20:              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 输出矩阵。和输入矩阵有相同的尺寸和通道
 
        
 
        







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值