OpenCV像素点邻域遍历效率比较,以及访问像素点的几种方法
2017-05-17 15:18
1096 查看
OpenCV像素点邻域遍历效率比较,以及访问像素点的几种方法
前言:
以前笔者在项目中经常使用到OpenCV的算法,而大部分OpenCV的算法都需要进行遍历操作,而且很多遍历操作都是需要对目标像素点的邻域进行二次遍历操作。笔者参考了很多博文,经过了实验,在这篇博文中总结了OpenCV的遍历操作的效率。参考博文:
《OpenCV获取与设置像素点的值的几个方法 》
《【OpenCV】访问Mat中每个像素的值(新)》
《[OpenCV] 访问Mat矩阵中的元素》
《OpenCV学习笔记(四十二)——Mat数据操作之普通青年、文艺青年、暴力青年》
《OpenCV学习笔记(四十三)——存取像素值操作汇总core》
《图像噪声的抑制——均值滤波、中值滤波、对称均值滤波》
一. 均值滤波
由于笔者想要了解像素点及其邻域的遍历,所以本文用于测试的算法是均值滤波。均值滤波的方法比较简单。对待需要处理的当前像素,选择一个模板,该模板为其邻域内若干像素,然后用模板内所有像素的均值来替代原像素值。公式如下:
![](http://img.my.csdn.net/uploads/201211/05/1352113236_2958.jpg)
均值滤波模板如下图所示:
![](http://img.my.csdn.net/uploads/201211/05/1352112942_6077.jpg)
图中的模板大小选择了3 × 3矩阵,图中的1–8绿色部分的像素都是邻域内像素,黄色像素是在图像(x, y)像素处的均值滤波结果。
该方法十分简单,包括了简单的邻域像素点的操作,优缺点也十分明显:
优点:算法简单,计算速度快;
缺点:降低噪声的同时使图像产生模糊,特别是景物的边缘和细节部分。
二. 邻域遍历方法
笔者对不同邻域遍历方法使用的算法与图片如下:测试算法:均值滤波
滤波内核尺寸:3 × 3
图片尺寸:580 × 410
原图如下:
![](http://images.cnitblog.com/blog/340413/201304/23230829-17b568f50635446e8f8c5a224aff0cf3.jpg)
笔者参考了博文《【OpenCV】访问Mat中每个像素的值(新)》,从其中学习了几种遍历像素点的方法。针对邻域遍历,笔者最后总结了三种方法如下:
1. ptr与[]
Mat最直接的访问方法,是通过.ptr<>函数得到一行的指针,并用[]操作符访问某一列的像素值。源码如下:
// using .ptr and [] void MyBlur_1(Mat src, Mat& dst) { dst.create(src.size(), src.type()); // 强制将ksize变为奇数 // int ksize = ksize / 2 * 2 + 1; int ksize = 3; // kernel的半径 // kr = ksize / 2; int kr = 1; int nr = src.rows - kr; int nc = (src.cols - kr) * src.channels(); for(int j = kr; j < nr; j++) { // 获取图像三行数据的地址 const uchar* previous = src.ptr<uchar>(j - 1); const uchar* current = src.ptr<uchar>(j); const uchar* next = src.ptr<uchar>(j + 1); uchar* output = dst.ptr<uchar>(j); for(int i = 1 * src.channels(); i < nc; i++) { output[i] = cv::saturate_cast<uchar>( (previous[i - src.channels()] + previous[i] + previous[i + src.channels()] + current[i - src.channels()] + current[i] + current[i + src.channels()] + next[i - src.channels()] + next[i] + next[i + src.channels()]) / 9); } } }
2. at获取图像坐标
.at操作可以用于操作单个像素点。通常的操作如下:// 对于单通道图像 img.at<uchar>(i, j) = 255; // 对于多通道图像 img.at<cv::Vec3b>(i, j)[0] = 255;
用at实现均值滤波的代码如下:
// using at void MyBlur_2(Mat src, Mat& dst) { dst.create(src.size(), src.type()); // 强制将ksize变为奇数 // int ksize = ksize / 2 * 2 + 1; int ksize = 3; // kernel的半径 // kr = ksize / 2; int kr = 1; int nr = src.rows - kr; int nc = src.cols - kr; for(int j = kr; j < nr; j++) { for(int i = src.channels(); i < nc; i++) { dst.at<Vec3b>(j, i)[0] = cv::saturate_cast<uchar>( (src.at<Vec3b>(j-1, i-1)[0] + src.at<Vec3b>(j, i-1)[0] + src.at<Vec3b>(j+1, i-1)[0] + src.at<Vec3b>(j-1, i)[0] + src.at<Vec3b>(j, i)[0] + src.at<Vec3b>(j+1, i)[0] + src.at<Vec3b>(j-1, i+1)[0] + src.at<Vec3b>(j, i+1)[0] + src.at<Vec3b>(j+1, i+1)[0]) / 9 ); dst.at<Vec3b>(j, i)[1] = cv::saturate_cast<uchar>( (src.at<Vec3b>(j-1, i-1)[1] + src.at<Vec3b>(j, i-1)[1] + src.at<Vec3b>(j+1, i-1)[1] + src.at<Vec3b>(j-1, i)[1] + src.at<Vec3b>(j, i)[1] + src.at<Vec3b>(j+1, i)[1] + src.at<Vec3b>(j-1, i+1)[1] + src.at<Vec3b>(j, i+1)[1] + src.at<Vec3b>(j+1, i+1)[1]) / 9 ); dst.at<Vec3b>(j, i)[2] = cv::saturate_cast<uchar>( (src.at<Vec3b>(j-1, i-1)[2] + src.at<Vec3b>(j, i-1)[2] + src.at<Vec3b>(j+1, i-1)[2] + src.at<Vec3b>(j-1, i)[2] + src.at<Vec3b>(j, i)[2] + src.at<Vec3b>(j+1, i)[2] + src.at<Vec3b>(j-1, i+1)[2] + src.at<Vec3b>(j, i+1)[2] + src.at<Vec3b>(j+1, i+1)[2]) / 9 ); } } }
然而,Debug版本下,at操作要比指针的操作慢很多,所以对于不连续数据或者单个点处理,可以考虑at操作,对于连续的大量数据,尽量不要使用它。
Release版本与Debug版本的对比,以及at操作的效率,在后面会有比较。
3. data
Mat类中,对data的定义如下://! pointer to the data uchar* data;
data是Mat对象中的指针,指向存放内存中存放矩阵数据的一块内存,类型为uchar*。
又由于二维矩阵Mat中任一像素的地址为:
addr(Mi,j)=M.data+M.step[0]×i+M.step[1]×j
计算得到的便是M矩阵中像素(i, j)的地址。而又有:
M.step[0]=M.cols×M.channels
M.step[1]=M.channels
所以上式可以改写为:
addr(Mi,j)=M.data+M.cols×M.channels×i+M.channels×j
综上,用.data实现均值滤波的代码如下:
// using .data void MyBlur_3(Mat src, Mat& dst) { dst.create(src.size(), src.type()); // 强制将ksize变为奇数 // int ksize = ksize / 2 * 2 + 1; int ksize = 3; // kernel的半径 // kr = ksize / 2; int kr = 1; int nr = src.rows - kr; int nc = (src.cols - kr) * src.channels(); uchar* srcdata = (uchar*)src.data; uchar* dstdata = (uchar*)dst.data; for(int j = kr; j < nr; j++) { uchar* psrc_0 = srcdata + (j-1) * src.cols * src.channels(); uchar* psrc_1 = srcdata + j * src.cols * src.channels(); uchar* psrc_2 = srcdata + (j+1) * src.cols * src.channels(); uchar* pdst = dstdata + j * dst.cols * dst.channels(); for(int i = src.channels(); i < nc; i++) { *pdst++ = (psrc_0[i - src.channels()] + psrc_0[i] + psrc_0[i + src.channels()] + psrc_1[i - src.channels()] + psrc_1[i] + psrc_1[i + src.channels()] + psrc_2[i - src.channels()] + psrc_2[i] + psrc_2[i + src.channels()]) / 9; } } }
需要注意的是,前面说的at操作与ptr操作,都是带有内存检查,防止操作越界的,然而使用data指针比较危险,虽然在Debug版本下的速度确实让人眼前一亮。所以在《Mat数据操作之普通青年、文艺青年、暴力青年》中,博主将.data操作称之为暴力青年。
三. 不同方法效率比较
写这篇博文之前,我还以为不同的方法对效率的影响十分巨大。当然这种想法是没有错的,因为在Debug版本中确实不同的遍历方法有着很大的效率区别。但是看了博客《Mat数据操作之普通青年、文艺青年、暴力青年》中的总结后,笔者才意识到原来在Release版本下,上述方法的效率其实差别不大。1. 测试源码:
#include "cv.h" #include "highgui.h" #include <vector> #include <cmath> #include <math.h> #include <iostream> using namespace cv; using namespace std; void MyBlur_1(Mat src, Mat& dst); void MyBlur_2(Mat src, Mat& dst); void MyBlur_3(Mat src, Mat& dst); // 测试遍历像素点对均值滤波的效率 int main() { double time; double start; Mat img; img = imread("/home/grq/miska.jpg"); Mat dst1; start = static_cast<double>(getTickCount()); MyBlur_1(img, dst1); time = ((double)getTickCount() - start) / getTickFrequency() * 1000; cout << "using .ptr and []"<<endl; cout << "Time: " << time << "ms" << endl<<endl; Mat dst2; start = static_cast<double>(getTickCount()); MyBlur_2(img, dst2); time = ((double)getTickCount() - start) / getTickFrequency() * 1000; cout << "using at "<<endl; cout<< "Time: " << time << "ms" << endl <<endl; Mat dst4; start = static_cast<double>(getTickCount()); MyBlur_3(img, dst4); time = ((double)getTickCount() - start) / getTickFrequency() * 1000; cout <<"using .data"<<endl; cout << "Time: " << time << "ms" << endl<<endl; Mat dst3; start = static_cast<double>(getTickCount()); blur(img, dst3, Size(3, 3)); time = ((double)getTickCount() - start) / getTickFrequency() * 1000; cout<<"using OpenCV's blur"<<endl; cout << "Time: " << time << "ms" <<endl << endl; imshow("src", img); imshow("using .ptr and []", dst1); imshow("using at ", dst2); imshow("using .data", dst4); waitKey(0); return 0; }
2. Debug版本
运行结束后,结果输出如下:如图所示,在Debug版本下,不同的遍历方法对遍历的效率影响还是很大的。值得注意的有下面几点:
(1) 检查操作对效率的影响
实际上,at操作符与ptr操作符在Debug版本下都是有内存检查、防止操作越界的操作,而data十分简单粗暴,没有任何检查,由于它的简单粗暴所以使得data操作速度很快。所以在Debug版本下,at操作符与ptr操作符相较于data,速度还是慢了不少。另外在Debug版本下,at操作要比指针操作慢得多,所以对于不连续数据或者单个点处理,可以考虑at操作,对于连续的大量数据,尽量不要使用它。
(2) 对重复计算进行优化
在博文《OpenCV学习笔记(四十三)——存取像素值操作汇总core 》中,博主提到:“在循环中重复计算已经得到的值,是个费时的工作。”并做了对比:int nc = img.cols * img.channels(); for (int i=0; i<nc; i++) {.......} //************************** for (int i=0; i<img.cols * img.channels(); i++) {......}
博主说经过他的测试,前者明显快于后者。
笔者发现这正好是笔者的编程风格,躺着中了一枪…… 那么如果笔者把这一点改了,是不是也会有比较好的效率提升呢?作死的笔者尝试了一下:
在没有更改for循环之前,Debug版本的效率是这样的:
之后笔者对所有for循环做了上面的优化,测试结果如下:
结果显示,确实对于这几种遍历方式都是有一定的提升效果的。
3. Release版本
笔者尝试运行了一下Release版本,结果如下:作对比如下:
Method | Debug(ms) | Release(ms) |
---|---|---|
ptr | 15.226 | 3.5582 |
at | 35.370 | 2.8227 |
data | 11.642 | 2.1686 |
四. 其他遍历像素点的方法
笔者推荐博文《【OpenCV】访问Mat中每个像素的值(新) 》,博主在文中提出了十余种遍历像素点的方法,且在文章最后给出了各种方法的运行效率,可谓十分详细,所以笔者在此就不赘述了。注:博主的对不同方法的比较,在评论区也被指出都是在Debug版本下的对比,如果将程序调整至Release版本,各个方法的效率也没有太大差别。
相关文章推荐
- opencv高效访问图像像素(遍历像素的方法总结)
- OpenCV(六) Opencv中 core 核心模块详解——访问图像像素的几种方法
- OpenCV访问图像像素的方法收集以及自己实践中得体会
- PHP 数组的遍历的几种方式(以及foreach与for/while+each效率的比较)
- OpenCV访问图像像素的方法收集以及自己实践中得体会
- OpenCV2 访问各个像素点的方法(图像遍历)
- C#访问BitMap几种方法效率比较
- 统计一个大文本行数的几种方法以及效率统计(一)
- 几种字符串反转方法效率比较
- opencv2.2 Mat格式的几个参数以及几种元素存取方法的讨论
- 几种字符串反转方法效率比较
- 【原】检测是否包含特定字符串的几种方法以及性能比较
- ubuntu11.04安装opencv2.2的几种方法的比较
- [C#]几种字符串反转方法效率比较
- 统计一个大文本行数的几种方法以及效率统计(二)
- 线程同步的几种方法效率比较
- 访问google比较慢以及Google Code SVN密码获取方法
- sqlserver取得路径的几种方法以及比较
- Asp.net 几种分页方法效率比较
- javascript 的URL 编码 和 解码 的几种方法 以及比较