Opencv图像识别从零到精通(6)----访问图像像素

来源:互联网 发布:好身材的女明星 知乎 编辑:程序博客网 时间:2024/05/21 09:24

              图像处理,从开始我们就接触了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,欢迎图像识别与图像算法,共同学习与交流

4 1
原创粉丝点击