OpenCV介绍
OpenCV 模块
OpenCV是模块化的, 以下是它所包含的模块:
core
定义包含三位数组Mat的压缩模块, 以及, 定义其他模块使用的基础方法。
imgproc
用于处理线性或者非线性图像过滤器、图象转换、颜色空间转换、直方图等的模块。
video
用于视频分析的模块, 包括:运动估计、物体跟踪等算法
calib3d
处理3d物体模块,包括:多视图几何算法、立体标定、物体姿态评估、立体匹配算法和3D重建
feature2d
处理2D物体模块,包括:突出特征检测、描述符和描述符的匹配
objdetect
物体识别和一些预定义类(比如:faces, eyes, mugs, people, cars等)
highgui
提供视频捕捉接口、图像和视频编码、一些简单UI
gpu
来自不同模块的GPU加速算法的整合
other
一些其他的模块, 比如:FLANN、谷歌测试包、Python绑定和一些其他的。
cv 命名空间
OpenCV所有的类和函数都是在cv的命名空间下定义的,所以,当我们在引用OpenCV的代码的时候,需要加上cv命名空间,也可以在开始的时候使用“using namespace”,不过,再使用的时候,需要注意的问题就是,我们的代码有可能与stl的代码出现重复,这时,我们需要用命名空间来标定我们到底引用的是哪个空间内的函数或者类。
1 #include "opencv2/core/core.hpp" 2 using namespace cv; 3 int main() 4 { 5 ///create a big 8Mb matrix 6 Mat A(1000, 1000, CV_64F); 7 8 ///create another header for the same matrix 9 Mat B = A; 10 11 ///create anoter header for the 3-rd row of A; no data is copied either 12 Mat C = B.row(3); 13 14 ///now create a separate copy of the matrix 15 Mat D = B.clone(); 16 17 ///copy the 5-th row of B to C, that is, copy the 5-th row of A 18 ///to the 3-rd row of A 19 B.row(5).copyTo(C); 20 21 ///now let A and D share the data; after that modified version 22 ///of A is still referenced by B and C 23 A = D; 24 25 ///now make B and empty matrix (which references no memory buffers), 26 ///but the modified version of A will still be referenced by C, 27 ///despite that C is just a single row of the original A 28 B.release(); 29 30 ///finallly, make a full copy of C. As a result, the big modified 31 ///matrix will be deallocated, since it is not referenced by anyone; 32 C = C.clone(); 33 }
当然这里的Mat会提供自动的内存释放功能,但是当其他的没有提供自动内存释放的数据结构我们该如何处理呢?
如果学过stl自然便会明白其“智能指针”的说法(std::shared),那么,在opencv里面也提供了类似的指针,即:
Ptr<T> ptr = new T(...)
具体的内容请参见Ptr<T>一节的描述。
输出数据的自动分配
Opencv能够自动的释放空间,当然,也能够为输出函数的参数分配空间,当一个函数有一个或者多个输入数组的时候和一些输出数组,输出数组会自动的分配和释放,输出数组的类型和大小取决与输入数组或者其他额外参数。代码如下
1 #include "cv.h" 2 #include "highgui.h" 3 using namespace cv; 4 5 int main(int argn, char**args) 6 { 7 VideoCapture cap(0); 8 if(!cap.isOpened()) return -1; 9 10 Mat frame, edges; 11 namedWindow("edages", 1); 12 for(;;) 13 { 14 cap >> frame; 15 cvtColor(frame, edges, CV_BGR2GRAY); 16 Canny(edges, edges, 0, 30, 3); 17 imshow("edges", edges); 18 if(waitKey(30) >= 0)break; 19 } 20 return 0; 21 }
分析代码,我们可以看到,刚开始在声明Mat的时候,没有任何参数,则调用默认构造函数,默认构造函数仅仅调用:
initEmpty();
那么,在initEmpty()中的实现为:
| flags = MAGIC_VAL; dims = row = cols = 0; data = datastart = dataend = datalimit = 0; refcount = 0; allocator = 0; |
可以看出,并没有分配任何空间
那么, 在执行
cap >> frame
这句话的时候,才给frame分配一些空间;在cvtColor函数的时候,应经分配空间的frame作为输入,edges作为输出,在该函数中其实是调用Mat::create()方法, 该方法的实现如下:
1 inline void Mat::create(int _rows, int _cols, int _type) 2 { 3 _type &= TYPE_MASK; 4 if( dims <= 2 && rows == _rows && cols == _cols && type() == _type && data ) 5 return; 6 int sz[] = {_rows, _cols}; 7 create(2, sz, _type); 8 }
该方法有一个判断的过程,如果类型和大小一致,那么它则部分配股空间,否则分配。
我们需要注意的是,类似与cv::mixChannels cv::RNG::fill它们没有分配空间的功能,所以,你需要提前对它们分配空间。
Saturation Arithmetics
也可以称为饱和算术子。当Opencv在以压缩编码的方式处理图像像素的时候,会涉及到8位或者16位的转换,比如在颜色空间转换、错切、复杂内差等都会涉及到超出范围的转换情况,如果用8位来保存16位的数据自然会影响以后的计算,为了解决这个问题,饱和算法应运而生,比如, r存储运算结果,我们处理的是8位图像,那么,为了找到更贴近真是结果的数值,我们使用如下函数:
I(x, y) = min(max(round(r), 0), 255)
我们称这种操作符为饱和算术子,其实很简单。
这中算法在库中非常常见在C++中使用saturate_cast<>函数来模仿标准C++的转换操作,可以参见上面提供的公式。
注意:
如果是32位的则失效。
固定像素类型和模板的限制
C++模板的坏处:
增加编译时间和代码长度
接口和实现难以分割
增加与其他语言绑定的难度
由于上面的种种,当前Opencv的实现是基于多态和运行时分发,而非模板机制,当然,这只是限制,并没有禁用。
因此,产生了一组元数据类型,如下:
1 8-bit unsigned integer(uchar) 2 8-bit signed integer(schar) 3 16-bit unsigned integer(ushort) 4 16-bit signed integer(sshort) 5 32-bit signed integer(int) 6 32-bit floating-point number(float) 7 64-bit floating-point number(double)
这些基本的类型都有对应的标号, 如下:
enum{ CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6};
除此之外还有建立在上面基础之上的元组(其实也就是数组的意思):
比如,我们在存储一幅图片的时候,不能只用类型为上面的二位数组,我们需要数组的类型是向量,比如保存的是RGB,分别用x, y, z组成的3位数组来作为二位数组的元素。因此,元组便应运而生。用官方的话来说就是多通道类型,对应的符号表示:
CV_8UC1 ... CV_64FC4 (前4位使用宏定义,后面的则使用宏函数来进行表示,通道的最大个数用CV_CN_MAX来指定), 后面的使用CV_MAKETYPE()来定义,比如:
CV_MAKETYPE(CV_32F, 100);
那么CV_MAKETYPE()是如何定义的呢? 如下;
#define CV_MAKETYPE(depth,cn) (CV_MAT_DEPTH(depth) + (((cn)-1) << CV_CN_SHIFT))
如果将宏定义展开,那么将是如下:
#define CV_MAKETYPE(depty,cn) (((depth)&7) + (((cn)-1)<<3))
其含义是,站在二进制的角度来理解,元组类型使用元数类型作为低三位,元组大小-1作为高位
比如:
cout << CV_MAKETYPE(CV_8U, 5);
低三位为: 000
高位-1为: 100
则组合起来为: 100000
则输出为: 32
拥有更复杂元素的数组将不能被OpenCV处理,除了这之外,每个函数仅仅能能够处理这些类型的一部分,通常越复杂的算法,可处理的类型就越小,一下列举出函数处理类型的限制:
人脸识别仅仅能够检测8位的灰阶或者彩色图片
线性代数相关函数和大部分机器学习算法仅仅能够处理浮点数组
颜色空间转换函数支持CV_8U、CV_16U、CV32_F
当然有些函数也能够处理所有的类型,如:
基础函数cv::add支持所有类型
这些函数所支持的类型都是根据实际需要进行定义的,当然,在未来也会根据用户的要求进一步扩展。
InputArray和OutputArray
OpenCV中的很多函数都是处理数组的,我们可以向其中传入Mat,但是,有时候,我们往往倾向与传入vector或者Matx<>,这样,在API的书写的过程之中自然带了很多的不便,为此,我们引入了InputArray和OutputArray,这样我们在制定API的时候省了不少代码,当然,当然,你也可以直接使用Mat或者vector,当一个函数有多个可选输入数组,而且,你又不想使用这个输入选项,那么,你可以使用cv::noArray()。
错误处理
OpenCV使用cv::Exception进行错误处理,它继承与std::exception,错误处理函数如下:
CV_Error(errocode, description) CV_Error(errocode, printf-spec, (printf-args)) CV_Assert(condition) CV_DbgAssert(condition)
由于Opencv使用自动内存管理,所以,我们无需太担心内存泄漏的问题,大可以使用try...catch语句进行异常处理。
try { } catch(cv::Exception& e) { const char* err_msg = e.what(); std::cout << "exception caught:" << err_msg << std::endl; }
多线程和可重入
OpenCV是可以在多线程的环境下使用的,当然Mat也是可以的,究其原因,引用计数器是原子操作。

浙公网安备 33010602011771号