OpenCV学习笔记(四十一)——再看基础数据结构core
记得我在OpenCV学习笔记(四)——新版本的数据结构core里面讲过新版本的数据结构了,可是我再看这部分的时候,我发现我当时实在是看得太马虎了。对于新版本的数据结构,我再说说。
Point_类不用多言,里面两个成员变量x,y。Point_<int>就是Point2i,也是Point,Point_<float>就是Point2f,Point_<double>就是Point2d。
Point3_类不太常用,跟Point_类差不太多,成员变量x,y,z。
Size_类成员变量width、height。Size_<int>就是Size2i,也是Size,Size_<float>就是Size2f,大家就要不臆断出来个Size2d啥的让编译器发懵了。
Rect_类有些意思,成员变量x、y、width、height,分别为左上角点的坐标和矩形的宽和高。常用的成员函数有Size()返回值为一个Size,area()返回矩形的面积,contains(Point)用来判断点是否在矩形内,inside(Rect)函数判断矩形是否在该矩形内,tl()返回左上角点坐标,br()返回右下角点坐标。值得注意的是,如果想求两个矩形的交集,并集,可以用如下格式
- Rect rect = rect1 & rect2;
- Rect rect = rect1 | rect2;
- Rect rectShift = rect + point;
- Rect rectScale = rect + size;
Matx其实是个轻量级的Mat,必须在使用前规定好大小,比如一个2*3的float型的可以声明为Matx23f。我想很容易理解的。
Vec是Matx的一个派生类,就是一个1维的Matx,跟vector很相似。比如想声明一个10个数据的float数组,可以写成Vec2f。
这样就很容易引出一个大家经常使用的数据结构了,Scalar_类,这个类其实就是一个Vec4x的一个变种,大家常用的Scalar其实就是Scalar_<double>。这样一说,大家是不是就很容易理解了,为啥很多函数的参数输入可以是Mat,也可以是Scalar了。其实OpenCV定义的InputMat,outputMat参数格式,以上的这几种数据结构都可以作为参数的。
接下来介绍一个有意思的类Range,大家可能用的不多,对它不熟悉,其实它就是为了使OpenCV的使用更像Matlab而产生的。比如Range::all()其实就是Matlab里的符号:或者...。而Range(a, b)其实就是Matlab中的a:b。有趣吧,注意a,b都需要是int型的哦,亲。
Ptr类我就不太敢介绍了,是智能指针,我也没有用过,文档里说是很类似大名鼎鼎的Boost库里的shared_ptr。希望以后有机会用一下再拿出来分享心得。
最后出场的还是最重量级的Mat,介绍几个比较重要的成员变量flag(就是我之前说过的header里的结构信息,深度信息,通道数),dims是Mat的维数,要求大于等于2,rows和cols参数代表2维矩阵的行数列数(对于更高维的矩阵,这两个参数都是-1),还有个比较常用的参数应该uchar*data,是Mat的数据指针(比较暴力的同学可以直接调用它好了,不推荐),还有个参数refconst,我理解应该就是我上一讲提到的释放内存的时候要判断这个矩阵是否是最后一个被使用的,这个参数应该就是控制跟当前矩阵结构相关的个数的。
OpenCV学习笔记(四十二)——Mat数据操作之普通青年、文艺青年、暴力青年
首先还是要感谢箫鸣朋友在我《OpenCV学习笔记(四十)——再谈OpenCV数据结构Mat详解》的留言,告诉我M.at<float>(3, 3)在Debug模式下运行缓慢,推荐我使用M.ptr<float>(i)此类方法。这不禁勾起了我测试一下的冲动。下面就为大家奉上我的测试结果。
我这里测试了三种操作Mat数据的办法,套用流行词,普通青年,文艺青年,为啥第三种我不叫2b青年,大家慢慢往后看咯。
普通青年的操作的办法通常是M.at<float>(i, j)
文艺青年一般会走路线M.ptr<float>( i )[ j ]
暴力青年通常直接强制使用我第40讲提到的M.data这个指针
实验代码如下:
- t = (double)getTickCount();
- Mat img1(1000, 1000, CV_32F);
- for (int i=0; i<1000; i++)
- {
- for (int j=0; j<1000; j++)
- {
- img1.at<float>(i,j) = 3.2f;
- }
- }
- t = (double)getTickCount() - t;
- printf("in %gms\n", t*1000/getTickFrequency());
- //***************************************************************
- t = (double)getTickCount();
- Mat img2(1000, 1000, CV_32F);
- for (int i=0; i<1000; i++)
- {
- for (int j=0; j<1000; j++)
- {
- img2.ptr<float>(i)[j] = 3.2f;
- }
- }
- t = (double)getTickCount() - t;
- printf("in %gms\n", t*1000/getTickFrequency());
- //***************************************************************
- t = (double)getTickCount();
- Mat img3(1000, 1000, CV_32F);
- float* pData = (float*)img3.data;
- for (int i=0; i<1000; i++)
- {
- for (int j=0; j<1000; j++)
- {
- *(pData) = 3.2f;
- pData++;
- }
- }
- t = (double)getTickCount() - t;
- printf("in %gms\n", t*1000/getTickFrequency());
- //***************************************************************
- t = (double)getTickCount();
- Mat img4(1000, 1000, CV_32F);
- for (int i=0; i<1000; i++)
- {
- for (int j=0; j<1000; j++)
- {
- ((float*)img3.data)[i*1000+j] = 3.2f;
- }
- }
- t = (double)getTickCount() - t;
- printf("in %gms\n", t*1000/getTickFrequency());
最后两招可以都看成是暴力青年的方法,因为反正都是指针的操作,局限了各暴力青年手段就不显得暴力了。
在Debug、Release模式下的测试结果分别为:
Debug | Release | |
普通青年 | 139.06ms | 2.51ms |
文艺青年 | 66.28ms | 2.50ms |
暴力青年1 | 4.95ms | 2.28ms |
暴力青年2 | 5.11ms | 1.37ms |
根据测试结果,我觉得箫铭说的是很可信的,普通青年的操作在Debug模式下果然缓慢,他推荐的文艺青年的路线确实有提高。值得注意的是本来后两种办法确实是一种比较2b青年的做法,因为at操作符或者ptr操作符,其实都是有内存检查的,防止操作越界的,而直接使用data这个指针确实很危险。不过从速度上确实让人眼前一亮,所以我不敢称这样的青年为2b,尊称为暴力青年吧。
不过在Release版本下,几种办法的速度差别就不明显啦,都是很普通的青年。所以如果大家最后发行程序的时候,可以不在意这几种操作办法的,推荐前两种哦,都是很好的写法,操作指针的事还是留给大神们用吧。就到这里吧~~
补充:箫铭又推荐了两种文艺青年的处理方案,我也随便测试了一下,先贴代码,再贴测试结果:
- /*********加强版********/
- t = (double)getTickCount();
- Mat img5(1000, 1000, CV_32F);
- float *pData1;
- for (int i=0; i<1000; i++)
- {
- pData1=img5.ptr<float>(i);
- for (int j=0; j<1000; j++)
- {
- pData1[j] = 3.2f;
- }
- }
- t = (double)getTickCount() - t;
- printf("in %gms\n", t*1000/getTickFrequency());
- /*******终极版*****/
- t = (double)getTickCount();
- Mat img6(1000, 1000, CV_32F);
- float *pData2;
- Size size=img6.size();
- if(img2.isContinuous())
- {
- size.width = size.width*size.height;
- size.height = 1;
- }
- size.width*=img2.channels();
- for(int i=0; i<size.height; i++)
- {
- pData2 = img6.ptr<float>(i);
- for(int j=0; j<size.width; j++)
- {
- pData2[j] = saturate_cast<float>(3.2f);
- }
- }
- t = (double)getTickCount() - t;
- printf("in %gms\n", t*1000/getTickFrequency());
测试结果:
Debug | Release | |
加强版文艺青年 | 5.74ms | 2.43ms |
终极版文艺青年 | 40.12ms | 2.34ms |
OpenCV学习笔记(四十三)——存取像素值操作汇总core
在上一讲OpenCV学习笔记(四十二)——Mat数据操作之普通青年、文艺青年、暴力青年里,对Mat内数据的各种读写操作进行了速度的比较,都是我自己想到的方法,感觉不够系统,这次整理了下思路,参考了文献,把能想到的方法进行了汇总,希望能对大家有所帮助。
1.存取单个像素值
最通常的方法就是
- img.at<uchar>(i,j) = 255;
- img.at<Vec3b>(i,j)[0] = 255;
如果你觉得at操作显得太笨重了,不想用Mat这个类,也可以考虑使用轻量级的Mat_类,使用重载操作符()实现取元素的操作。
- cv::Mat_<uchar> im2= img; // im2 refers to image
- im2(50,100)= 0; // access to row 50 and column 100
2.用指针扫描一幅图像
对于一幅图像的扫描,用at就显得不太好了,还是是用指针的操作方法更加推荐。先介绍一种上一讲提到过的
- for (int j=0; j<nl; j++)
- {
- uchar* data= image.ptr<uchar>(j);
- for (int i=0; i<nc; i++)
- {
- data[i] = 255;
- }
- }
更高效的扫描连续图像的做法可能是把W*H的衣服图像看成是一个1*(w*h)的一个一维数组,这个想法是不是有点奇葩,这里要利用 isContinuous这个函数判断图像内的像素是否填充满,使用方法如下:
- if (img.isContinuous())
- {
- nc = img.rows*img.cols*img.channels();
- }
- uchar* data = img.ptr<uchar>(0);
- for (int i=0; i<nc; i++)
- {
- data[i] = 255;
- }
更低级的指针操作就是使用Mat里的data指针,之前我称之为暴力青年,使用方法如下:
- uchar* data = img.data;
- // img.at(i, j)
- data = img.data + i * img.step + j * img.elemSize();
3.用迭代器iterator扫描图像
和C++STL里的迭代器类似,Mat的迭代器与之是兼容的。是MatIterator_。声明方法如下:
- cv::MatIterator_<Vec3b> it;
或者是:
- cv::Mat_<Vec3b>::iterator it;
扫描图像的方法如下:
- Mat_<Vec3b>::iterator it = img.begin<Vec3b>();
- Mat_<Vec3b>::iterator itend = img.end<Vec3b>();
- for (; it!=itend; it++)
- {
- (*it)[0] = 255;
- }
4.高效的scan image方案总结
还是用我们之前使用过的getTickCount、getTickFrequency函数测试速度。这里我就不一一列举我测试的结果了,直接上结论。测试发现,好的编写风格可以提高50%的速度!要想减少程序运行的时间,必要的优化包括如下几个方面:
(1)内存分配是个耗时的工作,优化之;
(2)在循环中重复计算已经得到的值,是个费时的工作,优化之;举例:
- int nc = img.cols * img.channels();
- for (int i=0; i<nc; i++)
- {.......}
- //**************************
- for (int i=0; i<img.cols * img.channels(); i++)
- {......}
后者的速度比前者要慢上好多。
(3)使用迭代器也会是速度变慢,但迭代器的使用可以减少程序错误的发生几率,考虑这个因素,可以酌情优化
(4)at操作要比指针的操作慢很多,所以对于不连续数据或者单个点处理,可以考虑at操作,对于连续的大量数据,不要使用它
(5)扫描连续图像的做法可能是把W*H的衣服图像看成是一个1*(w*h)的一个一维数组这种办法也可以提高速度。短的循环比长循环更高效,即使他们的操作数是相同的
以上的这些优化可能对于大家的程序运行速度提高并不明显,但它们毕竟是个得到速度提升的好的编程策略,希望大家能多采纳。
还有就是利用多线程也可以高效提高运行速度。OpenMP和TBB是两种流行的APT,不过对于多线程的东西,我是有些迷糊的,呵呵
5.整行整列像素值的赋值
对于整行或者整列的数据,可以考虑这种方式处理
- img.row(i).setTo(Scalar(255));
- img.col(j).setTo(Scalar(255));
这节就先介绍这么多攻略吧~希望大家喜欢
OpenCV学习笔记(四十四)——初探GPU
好久没有更新啦,感觉最近没有什么特别的收获值得和大家分享,还是有些懒,TLD结束了也没有写个blog做总结。还是和大家分享一下OpenCV的一个大家很少接触的模块吧——GPU。这个部分我接触的也是很少,只是根据教程和大家简单交流一下,如果有高手有使用心得,欢迎多多批评。
OpenCV的GPU模块只支持NVIDIA的显卡,原因是该部分是基于NVIDIA的CUDA和NVIDIA的NPP模块实现的。而该模块的好处在于使用GPU模块无需安装CUDA工具,也无需学习GPU编程,因为不需要编写GPU相关的代码。但如果你想重新编译OpenCV的GPU模块的话,还是需要CUDA的toolkit。
由于GPU模块的发展,使大部分函数使用起来和之前在CPU下开发非常类似。首先,就是把GPU模块链接到你的工程中,并包含必要的头文件gpu.hpp。其次,就是GPU模块下的数据结构,原本在cv名字空间中的现在都在gpu名字空间中,使用时可以gpu::和cv::来防止混淆。
需要再说明的是,在GPU模块中,矩阵的名字为GpuMat,而不是之前的Mat,其他的函数名字和CPU模块中相同,不同的是,现在的参数输入不再是Mat,而是GpuMat。
还有一个问题就是,对于2.0的GPU模块,多通道的函数支持的并不好,推荐使用GPU模块处理灰度的图像。有些情况下,使用GPU模块的运行速度还不及CPU模块下的性能,所以可以认为,GPU模块相对而言还不够成熟,需要进一步优化。很重要的一个原因就是内存管理部分和数据转换部分对于GPU模块而言消耗了大量的时间。
需要注意的是,在所有使用GPU模块的函数之前,最好需要调用函数gpu::getCudaEnabledDeviceCount,如果你在使用的OpenCV模块编译时不支持GPU,这个函数返回值为0;否则返回值为已安装的CUDA设备的数量。
还有一点就是使用GPU模块,需要在用CMake编译OpenCV时使其中的WITH_CUDA和WITH_TBB的宏生效,为ON。
由于我对GPU部分的熟悉程度还不行,先拿来一段sample自带的一段求矩阵转置的程序来做例子,代码如下:
- #include <iostream>
- #include "cvconfig.h"
- #include "opencv2/core/core.hpp"
- #include "opencv2/gpu/gpu.hpp"
- #include "opencv2/core/internal.hpp" // For TBB wrappers
- using namespace std;
- using namespace cv;
- using namespace cv::gpu;
- struct Worker { void operator()(int device_id) const; };
- int main()
- {
- int num_devices = getCudaEnabledDeviceCount();
- if (num_devices < 2)
- {
- std::cout << "Two or more GPUs are required\n";
- return -1;
- }
- for (int i = 0; i < num_devices; ++i)
- {
- DeviceInfo dev_info(i);
- if (!dev_info.isCompatible())
- {
- std::cout << "GPU module isn't built for GPU #" << i << " ("
- << dev_info.name() << ", CC " << dev_info.majorVersion()
- << dev_info.minorVersion() << "\n";
- return -1;
- }
- }
- // Execute calculation in two threads using two GPUs
- int devices[] = {0, 1};
- parallel_do(devices, devices + 2, Worker());
- return 0;
- }
- void Worker::operator()(int device_id) const
- {
- setDevice(device_id);
- Mat src(1000, 1000, CV_32F);
- Mat dst;
- RNG rng(0);
- rng.fill(src, RNG::UNIFORM, 0, 1);
- // CPU works
- transpose(src, dst);
- // GPU works
- GpuMat d_src(src);
- GpuMat d_dst;
- transpose(d_src, d_dst);
- // Check results
- bool passed = norm(dst - Mat(d_dst), NORM_INF) < 1e-3;
- std::cout << "GPU #" << device_id << " (" << DeviceInfo().name() << "): "
- << (passed ? "passed" : "FAILED") << endl;
- // Deallocate data here, otherwise deallocation will be performed
- // after context is extracted from the stack
- d_src.release();
- d_dst.release();
- }
以上介绍的内容不但肤浅,而且显得比较凌乱。希望高手看完后多多指正,跟我一样不太明白的朋友仅供参考。
OpenCV学习笔记(四十五)——小试随机森林(random forest)算法ml
对于随机森林算法,原理我想大家都会去看论文,推荐两个老外的网址http://www.stat.berkeley.edu/users/breiman/RandomForests/和https://cwiki.apache.org/MAHOUT/random-forests.html,第一个网址是提出随机森林方法大牛写的,很全面具体,第二个是我自己找的一个,算是一个简化版的介绍吧。说白了,随机森林分类的过程就是对于每个随机产生的决策树分类器,输入特征向量,森林中每棵树对样本进行分类,根据每个树的权重得到最后的分类结果。所有的树训练都是使用同样的参数,但是训练集是不同的,分类器的错误估计采用的是oob(out of bag)的办法。如果大家看懂了,接下来就简单咯
还是先介绍一下对应opencv的类和函数吧,之前有笼统介绍过机器学习的类,对于随机森林相关算法类有CvRTParams、CvRTrees。具体再讲解一下:
CvRTParams类包涵了随机森林训练过程中需要设置的全部参数,继承自CvDTParams。其中
max_depth表示单棵树的最大深度
min_sample_count阈值,当节点的样本数比阈值小的时候,节点就不在进行分裂
regression_accuracy回逆树时候的阈值
use_surrogates是否使用代理?(这是神马功能。。。)
max_categories最大分类的阈值
priors先验分类可能性
calc_var_importance变量重要性是否计算标志
nactive_vars每棵树选取特征子集的大小
max_num_of_trees_in_the_forest森林内树的数目上限
forest_accuracy森林训练OOB error的精度
termcrit_type森林训练阈值选取的类型
以上参数的赋值都通过CvRTParams的构造函数实现。
CvRTrees就是随机森林的主体了。包涵了train训练函数、predict预测函数、predict_prob返回预测分类标签、getVarImportance返回变量权重矩阵、get_proximity返回两训练样本之间的相似度、calc_error返回随机森林的预测误差、get_train_error返回训练误差、get_rng返回使用随机数的当前值、get_tree_count返回构造随机森林的树的数目、get_tree返回构造随机森林的其中一棵树。
接下来,结合sample自带的letter_recog.cpp讲解一下如何应用随机森林算法做字母的识别,这里选择的训练样本库是http://archive.ics.uci.edu/ml/下的一个训练集,这个网站还有很多其他的资源提供,非常好的机器学习的data库。这个训练文件letter-recognition.data有20000个训练字母,每一字母用16维的特征表示。本程序使用前16000个进行训练,后4000个进行测试。唯一让我很不爽的是这个程序是用老版本的数据结构写的,所以我决定用新结构再写一下,我对原程序进行了简化,删除了很多不相关和不是必须的部分。
其实随机森林使用起来非常简单,两个最重要的步骤无非就是train()和predict()函数,其他的函数都是用来得到测试结果。
这里把我简化并用新结构改写之后的代码下载链接奉上http://download.csdn.net/detail/yang_xian521/4134557
from: http://blog.csdn.net/yang_xian521/article/category/910716