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,欢迎图像识别与图像算法,共同学习与交流
- Opencv图像识别从零到精通(6)----访问图像像素
- Opencv图像识别从零到精通(23)----轮廓
- Opencv图像识别从零到精通(26)---分水岭
- Opencv图像识别从零到精通(27)---grabcut
- Opencv图像识别从零到精通(28)----Kmeans
- Opencv图像识别从零到精通(34)---SIFI
- Opencv图像识别从零到精通(35)---SURF
- Opencv图像识别从零到精通(7)----图像平移、旋转、镜像
- Opencv图像识别从零到精通(31)----图像修补,分离合并通道
- Opencv图像识别从零到精通(3)———单图像显示和多图像显示
- Opencv图像识别从零到精通(2)-----准备知识
- Opencv图像识别从零到精通(4)----cMake与源代码与image watch
- Opencv图像识别从零到精通(8)-----灰度直方图
- Opencv图像识别从零到精通(9)----对比度亮度改变
- Opencv图像识别从零到精通(10)-----直方图均衡化与直方图拉伸
- Opencv图像识别从零到精通(11)---一个窗口多图显示
- Opencv图像识别从零到精通(13)----点线圆矩形与鼠标事件
- Opencv图像识别从零到精通(14)-----线性滤波和非线性滤波
- 东软学习第二天
- ListView 简单理解
- 华为OJ——iNOC产品部--完全数计算
- JavaScript创建对象4种方法详解
- jzoj 1579. 【普及模拟】老鼠 解题报告
- Opencv图像识别从零到精通(6)----访问图像像素
- 不属于冯诺依曼体系结构必要组成部分是:
- 华为OJ——输入n个整数,输出其中最小的k个
- 在Android studio控制台中显示输出内容
- atoi---ASCII to integer,将字符串转换成整形,经常用的转换符号
- 什么是野指针和内存泄漏?如何避免野指针
- 详解JavaScript中的Object对象
- 在C#中 ref和out的区别
- UVA133The Dole Queue