访问Mat图像中每个像素的值

来源:互联网 发布:淘宝特色馆是干什么的 编辑:程序博客网 时间:2024/06/04 08:19

以下例子源自《The OpenCV Tutorials --Release 2.4.2》2.2 How to scan images, lookup tables and time measurement with OpenCV


图像容器Mat

还是先看Mat的存储形式。Mat和Matlab里的数组格式有点像,但一般是二维向量,如果是灰度图,一般存放<uchar>类型;如果是RGB彩色图,存放<Vec3b>类型。
单通道灰度图数据存放格式:

多通道的图像中,每列并列存放通道数量的子列,如RGB三通道彩色图:

注意通道的顺序反转了:BGR。通常情况内存足够大的话图像的每一行是连续存放的,也就是在内存上图像的所有数据存放成一行,这中情况在访问时可以提供很大方便。可以用 isContinuous()函数来判断图像数组是否为连续的。

访问图像中的像素


高效的方法:C操作符[ ]

最快的是直接用C风格的内存访问操作符[]来访问:
[cpp] view plaincopy
  1. Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)  
  2. {  
  3.     // accept only char type matrices  
  4.     CV_Assert(I.depth() != sizeof(uchar));  
  5.     int channels = I.channels();  
  6.     int nRows = I.rows ;  
  7.     int nCols = I.cols* channels;  
  8.     if (I.isContinuous())  
  9.     {  
  10.         nCols *= nRows;  
  11.         nRows = 1;  
  12.     }  
  13.     int i,j;  
  14.     uchar* p;  
  15.     for( i = 0; i < nRows; ++i)  
  16.     {  
  17.         p = I.ptr<uchar>(i);  
  18.         for ( j = 0; j < nCols; ++j)  
  19.         {  
  20.             p[j] = table[p[j]];  
  21.         }  
  22.     }  
  23.     return I;  
  24. }  
注意:书中这段代码是有问题的,前面写成了 
[cpp] view plaincopy
  1. int nRows = I.rows * channels;  
  2. int nCols = I.cols;  
一般情况 isContinous为true,运行不会出错,但你可以注释掉那个if,会有访问越界的问题。
这种访问形式就是在每行定义一个指针,然后在内存上直接连续访问。如果整个数组在内存上都是连续存放的,那么只需要定义一个指针就可以访问所有的数据!如单通道的灰度图访问方式如下:
[cpp] view plaincopy
  1. uchar* p = I.data;  
  2. for( unsigned int i =0; i < ncol*nrows; ++i)  
  3.     *p++ = table[*p];  

安全的方法:迭代器iterator

相比用指针直接访问可能出现越界问题,迭代器绝对是非常安全的方法:
[cpp] view plaincopy
  1. Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)  
  2. {  
  3.     // accept only char type matrices  
  4.     CV_Assert(I.depth() != sizeof(uchar));  
  5.     const int channels = I.channels();  
  6.     switch(channels)  
  7.     {  
  8.     case 1:  
  9.         {  
  10.             MatIterator_<uchar> it, end;  
  11.             for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)  
  12.                 *it = table[*it];  
  13.             break;  
  14.         }  
  15.     case 3:  
  16.         {  
  17.             MatIterator_<Vec3b> it, end;  
  18.             for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)  
  19.             {  
  20.                 (*it)[0] = table[(*it)[0]];  
  21.                 (*it)[1] = table[(*it)[1]];  
  22.                 (*it)[2] = table[(*it)[2]];  
  23.             }  
  24.         }  
  25.     }  
  26.     return I;  
  27. }  
这里我们只定义了一个迭代器,用了一个for循环,这是因为在OpenCV里迭代器会访问每一列然后自动跳到下一行,不用管在内存上是否isContinous。另外要注意的是在三通道图像中我们定义的是 <Vec3b>格式的迭代器,如果定义成uchar,则只能访问到B即蓝色通道的值。
这种方式虽然安全,但是挺慢的,一会儿就知道了。

更慢的方法:动态地址计算

这种方法在需要连续扫描所有点的应用时并不推荐,因为它更实用与随机访问。这种方法最基本的用途是访问任意的某一行某一列:
[cpp] view plaincopy
  1. Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)  
  2. {  
  3.     // accept only char type matrices  
  4.     CV_Assert(I.depth() != sizeof(uchar));  
  5.     const int channels = I.channels();  
  6.     switch(channels)  
  7.     {  
  8.     case 1:  
  9.         {  
  10.             forint i = 0; i < I.rows; ++i)  
  11.                 forint j = 0; j < I.cols; ++j )  
  12.                     I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];  
  13.             break;  
  14.         }  
  15.     case 3:  
  16.         {  
  17.             Mat_<Vec3b> _I = I;  
  18.   
  19.             forint i = 0; i < I.rows; ++i)  
  20.                 forint j = 0; j < I.cols; ++j )  
  21.                 {  
  22.                     _I(i,j)[0] = table[_I(i,j)[0]];  
  23.                     _I(i,j)[1] = table[_I(i,j)[1]];  
  24.                     _I(i,j)[2] = table[_I(i,j)[2]];  
  25.                 }  
  26.                 I = _I;  
  27.                 break;  
  28.         }  
  29.     }  
  30.     return I;  
  31. }  
因为这种方法是为随机访问设计的,所以真的是奇慢无比。。。

减小颜色空间 color space reduction

现在来介绍下上述函数对每个元素的操作,也就是用table更改像素值。这里其实是做了个减小颜色空间的操作,这在一些识别之类的应用中会大大降低运算复杂度。类如uchar类型的三通道图像,每个通道取值可以是0~255,于是就有 256*256个不同的值。我们可以通过定义:
0~9 范围的像素值为 0
10~19 范围的像素值 为 10
20~29 范围的像素值为 20
。。。。。。
着这样的操作将颜色取值降低为 26*26*26 种情况。这个操作可以用一个简单的公式:

来实现,因为C++中int类型除法操作会自动截余。 类如 Iold=14; Inew=(Iold/10)*10=(14/10)*10=1*10=10;
在处理图像像素时,每个像素需要进行一遍上述计算也需要一定的时间花销。但我们注意到其实只有 0~255 种像素,即只有256种情况。进一步可以把256种计算好的结果提前存在表中 table 中,这样每种情况不需计算直接从 table 中取结果即可。
[cpp] view plaincopy
  1. int divideWith=10;   
  2. uchar table[256];  
  3. for (int i = 0; i < 256; ++i)  
  4.     table[i] = divideWith* (i/divideWith);  
于是table[i]存放的是值为i的像素减小颜色空间的结果,这样也就可以理解上述方法中的操作:
[cpp] view plaincopy
  1. p[j] = table[p[j]];  

LUT : Look up table

OpenCV 很聪明的有个 LUT 函数就是针对这种 Look up talbe 的操作:
[cpp] view plaincopy
  1. Mat lookUpTable(1, 256, CV_8U);  
  2. uchar* p = lookUpTable.data;  
  3. forint i = 0; i < 256; ++i)  
  4.     p[i] = table[i];  
  5. for (int i = 0; i < times; ++i)  
  6.     LUT(I, lookUpTable, J);  

算法计时

为了验证几种方法的效率,可以用一个简单的计时和输出:
[cpp] view plaincopy
  1. double t;  
  2. t = (double)getTickCount();  
  3. t = 1000*((double)getTickCount() - t)/getTickFrequency();  
  4. t /= times;  

实验结果


原图:


降低颜色空间结果:


算法时间:


更清楚的时间对比表:


转载请注明出处:http://blog.csdn.net/xiaowei_cqu/article/details/7771760
实验代码下载:http://download.csdn.net/detail/xiaowei_cqu/4443761

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 旅行箱提手坏了怎么办 影棚人物后面有影子怎么办 微信运动图标不见了怎么办 逆光拍摄人黑了怎么办 单反镜头刮花了怎么办 股东各50股份不同意退股怎么办 退股没有协议他不愿意退钱怎么办 s7刷机有三星帐号id怎么办 做主播留不住人怎么办 直播间留不住人怎么办 淘宝直播间留不住人怎么办 干了一个月不发工资怎么办 16岁长白色头发怎么办 腾讯乘车码解约了怎么办 蓝洞棋牌客封号怎么办 草莓被蚂蚁吃了怎么办 脖子被种了草莓怎么办 2岁的宝宝说脏话怎么办 欠钱的人跑路了怎么办 美国非婚生子父亲不认怎么办 孕4个月肚子胀气怎么办 8个月孕妇感冒了怎么办 孕8个月感冒鼻塞怎么办 怀孕八个月感冒了怎么办 孕八个月咳嗽了怎么办 享物说同城自提怎么办 锤基意外怀孕怎么办 08 蛐蛐罐底翻砂了怎么办 剑网3中被盗号后怎么办 本人想离婚对方躲避怎么办 微信不能说语音怎么办 微信语音发不了怎么办 吃鸡语音用不了怎么办 要杀我的人见面怎么办 转晕了想吐怎么办 原地转圈头晕恶心想吐怎么办 孩子吃凉的呕吐头还晕怎么办 孩子转晕了想吐怎么办 转圈晕了想吐怎么办 我爸总是骂我妈怎么办 转圈转的想吐怎么办