简介
Opencv (Open Source Computer Vision Library: http://opencv.org)是一个包含几千个计算机视觉算法的开源库,本文档讲述的是所谓的OpenCV 2.X 接口,与基于C语言的OpenCV 1.X接口不同,这是一个基于C++的编程接口。
opencv是库组织结构,也就是说Opencv包含一些共享或者静态的库。可用的库如下 :
core - 一个定义最基本数据结构的库,包括密集的多维矩阵Mat,和一些被其他库用到的基本函数
imgproc - 图像处理库,包含线性非线性的图像滤波,图像几何变换(调整尺寸,仿射变换,透视变换,基本的基于表格的重映射),颜色空间转换,直方图等
video - 视频分析库,包括运动估计,背景差值,和物体追踪算法
calib3d - 基本的多视角几何算法,单摄像机和立体摄像机的标定,物体姿态估测,立体匹配算法和元素的三维重建。
features2d - 特征探测子,描述子,以及描述子匹配
objdetect - 对于预定义类的实例或对象的检测(如人脸,眼睛,mugs,人,汽车等)
highgui - 简单易用的视频捕捉接口,图像'视频的编解码器,以及简单的UI函数
gpu - 不同opencv库中基于GPU加速的算法
... - 其他一些帮助库,如FLANN 和Google test wrappers,python绑定等
在后续的章节中会描述各个模块的函数功能,但是一开始你应该熟悉贯穿整个库中最基本的API概念。
1.1 API 概念
cv 命名空间
所有的opencv类和函数是被放置在cv命名空间的,所以为了在你的程序中使用它们,需要用cv::来指明或者直接这样using namespace cv;
#include "opencv2/core/core.hpp"
...
cv::Mat H = cv::findHomography(points1, points2, CV_RANSAC, 5);
...
或者
#include "opencv2/core/core.hpp"
using namespace cv;
...
Mat H = findHomography(points1, points2, CV_RANSAC, 5);
...
opencv中一些现有的或者将来有的外部名可能会与STL(标准库)或者其他库冲突,这种情况下,需要用明确的命名空间指示来解决冲突问题:
Mat a(100, 100, CV_32F);
randu(a, Scalar::all(1), Scalar::all(std::rand()));
cv::log(a, a);
a /= std::log(2.);
自动内存管理
opencv自动处理所有的内存。
首先,std::vector,Mat,和其他被函数或方法用到的数据结构会有在必要时释放相关内存的析构函数,析构函数并不总去释放内存,拿Mat来举例子,考虑到可能的数据共享,析构函数会递减矩阵数据缓冲区的引用计数器,而只有在引用计数器为0时,也就是没有其他的结构指向该缓冲区,缓冲区才会被释放,类似地,当一个Mat实例被复制,实际数据并没有真正被拷贝,相反,缓冲区的引用计数器递增来表明这个缓冲区被另一个拥有者所分享。当然也有Mat::clone方法是创建矩阵数据的整个拷贝,见下面的例子:
// create a big 8Mb matrix
Mat A(1000, 1000, CV_64F);
// create another header for the same matrix;
// this is an instant operation, regardless of the matrix size.
Mat B = A;
// create another header for the 3-rd row of A; no data is copied either
Mat C = B.row(3);
// now create a separate copy of the matrix
Mat D = B.clone();
// copy the 5-th row of B to C, that is, copy the 5-th row of A
// to the 3-rd row of A.
B.row(5).copyTo(C);
// now let A and D share the data; after that the modified version
// of A is still referenced by B and C.
A = D;
// now make B an empty matrix (which references no memory buffers),
// but the modified version of A will still be referenced by C,
// despite that C is just a single row of the original A
B.release();
// finally, make a full copy of C. As a result, the big modified
// matrix will be deallocated, since it is not referenced by anyone
C = C.clone();
从中你可以看出Mat和其他基本结构的应用是非常简单的,但是那些没有自动内存管理机制的高层的类和用户数据类型又怎么样呢,对于它们,opencv提供了类似于std::shared_ptr的Ptr<>模板类,所以替代纯粹指针的使用:
T* ptr = new T(...);
you can use:
Ptr<T> ptr = new T(...);
Ptr<T>ptr封装了指向T实例的指针,和与指针相关的引用计数器。
输出数据的自动内存分配
opencv自动释放内存,同样大部分时候也为输出函数的参数自动分配内存,所以如果一个函数有一个或多个输入数组(cv::Mat 实例)和一些输出数组,输出数组会被自动分配或重分配内存。输出数组的大小和类型由输入数组决定。如果需要,函数还会接受额外参数来表明输出数组的属性
例子:
#include "cv.h"
#include "highgui.h"
using namespace cv;
int main(int, char**)
{
VideoCapture cap(0);
if(!cap.isOpened()) return -1;
Mat frame, edges;
namedWindow("edges",1);
for(;;)
{
cap >> frame;
cvtColor(frame, edges, CV_BGR2GRAY);
GaussianBlur(edges, edges, Size(7,7), 1.5, 1.5);
Canny(edges, edges, 0, 30, 3);
imshow("edges", edges);
if(waitKey(30) >= 0) break;
}
return 0;
}
frame数组会由操作符>>根据视频帧的分辨率和位深来自动分配内存,edges数组则由cvtColor函数自动分配,它的大小和位深是与输入数组一样的,通道数为1因为转换码CV_BGR2GRAY(表示彩色向灰度的转换)。需要记住的是frame和edges只有在第一次循环时会被分配内存,这也是由于下一个视频帧具有同样的分辨率和位深。如果你改变了视频的分辨率,这些数组也会自动被重新分配。
这个技术的关键组件就是Mat::create方法,它接受期望的大小和类型,如果数组已经有特定的大小和类型,那么该方法什么也不做,否则它就会释放先前的分配的数据(递减引用计数器并与0比较),然后申请分配一块新的指定大小的事缓冲区。大部分函数都会为每一个输出数组调用Mat::create方法,这也是解释了输出数组的自动内存分配机制。
一些需要注意的特例就是cv::mixChannels, cv::RNG::fill和其他一些函数和方法,它们并不会为输出数组分配内存,所以你需要事先分配。
饱和算术
作为一个计算机视觉库,opencv经常要处理的图像像素点往往是紧凑的,8位或才16位每通道形式的数据,这样就要求限制数据的取值范围,进一步说,图像的特定操作(如颜色空间转换,亮度/对比度调整,锐化,复杂的内插(bi-cubic, Lanczos))会产生超出可接受的数据范围,如果你只能够存储结果的低8位抑或是16位,这会导致视觉缺陷或者更进一步影响到后续的分析处理,为了解决这个问题,所谓的饱和算术派上用场了,如,为了存储r(8位的图像数据),以下操作会让你在0~255内找到最接近真实值的值。
I(x; y) = min(max(round(r); 0); 255)
同样的规则也被用来处理有符号8位,有符号16位和无符号类型,这样的语义在库中随处可见,在C++语言中,用的是类标准C++中类型转换操作的saturate_cast<>,看下面对于 上面方程的实现:
I.at<uchar>(y, x) = saturate_cast<uchar>(r);
cv::uchar是opencv中8位无符号类型,
固定的像素类型,限制使用模板
模板是c++中用来实现强大,高效并且安全数据结构和算法的伟大特征,但是过度的模板使用又会导致编译时间变长和代码量变大,此外,it is difficult to separate an interface and implementation when templates are used exclusively(??),当然这对基本的算法没什么问题,但是对于一个算法就拥有几千行代码的计算机视觉库来说,是不适用的,正因为如此,也因为简化与其他语言的绑定开发(像python,jave,matlab这些压根就没有模板的语言),当前的opencv是基于多态性的运行时动态绑定的实现,在这些动态绑定过慢(like
pixel access operators),、不可能(generic Ptr<> implementation)或者压根就不方便(saturate_cast<>())的地方,opencv实现就介绍小的模板类、方法和函数,而opencv 的其他地方则限制了模板的使用。
因此,计算机视觉库可以操作的数据类型是一组有限的、固定的基本数据类型。那就是数组元素需要是以下类型:
• 8-bit unsigned integer (uchar)
• 8-bit signed integer (schar)
• 16-bit unsigned integer (ushort)
• 16-bit signed integer (short)
• 32-bit signed integer (int)
• 32-bit floating-point number (float)
• 64-bit floating-point number (double)
• 元组数据类型,元组里的元素的类型(以上一种)是一致的,图像数组就是元组的一种,被叫作多通道数组,以区分单通道数组。最大的可能通道数是用宏CV_CN_MAX定义的,当前版本设置为512
对于这此基本的类型,下面的枚举可以使用:
enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 };
多通道类型可以用下面选项来指定:
• CV_8UC1 ... CV_64FC4 常数(通道数据1-4)
• CV_8UC(n) ... CV_64FC(n) or CV_MAKETYPE(CV_8U, n) ... CV_MAKETYPE(CV_64F, n) 当通道数大于4或者编译时不可知即变量可用这些宏
注意:CV_32FC1 == CV_32F, CV_32FC2 == CV_32FC(2) == CV_MAKETYPE(CV_32F, 2), and
CV_MAKETYPE(depth, n) == ((x&7)<<3) + (n-1).
Mat mtx(3, 3, CV_32F); // make a 3x3 floating-point matrix
Mat cmtx(10, 1, CV_64FC2); // make a 10x1 2-channel floating-point
// matrix (10-element complex vector)
Mat img(Size(1920, 1080), CV_8UC3); // make a 3-channel (color) image
// of 1920 columns and 1080 rows.
Mat grayscale(image.size(), CV_MAKETYPE(image.depth(), 1)); // make a 1-channel image of
// the same size and same
更复杂元素的数组是不能被opencv构造或处理的,或者说,各个函数或方法只能处理上述数组类型的子集,一个算法越复杂,那么它所支持的类型子集就越小,看下面典型例子:
人脸检测算法只能处理8位的灰度图像或者彩色图像
线性代数函数和大部分机器学习算法只能处理浮点型数据
基本函数,如cv::add,支持所有类型
彩色空间转换函数支持8位无符号,16位无符号,和32位浮点类型。
各个函数的支持类型的子集根据实际应用而设定并且可以根据用户需求在以后来扩展
输入数组和输出数组
很多opencv函数能够处理2维或多维数值数组,通常,这些函数以cpp:class:Mat作为参数,但是在一些场合,用std::vector<>(如点集)或Matx<>(如3X3单应矩阵?)要更为方便一些,为了避免API的重复,特殊的“代理”类被引入,由InputArray派生的OutputArray是用来为函数指定一个输出数组。通常,你需要关心这些中间类型(但你不需要明确申明这些类型的变量):这些都是会自动完成任务的。你可以假设除了InputArray/OutputArray,你总可以用Mat, std::vector<>,
Matx<>, Vec<> or Scalar。当一个函数有一个可选择的输入或者输出数组时,而你又不必或者不想定义该数组,你可以传入cv::noArray()。
错误处理
opencv利用异常来触发关键错误,当输入数据是正确的类型并且数据在指定范围内,但是算法由于某些原因没有成功,函数会返回特定的错误代码(通常,是一个布尔变量)
异常可以通过cv::Exception类来实例化,cv::Exception是由std::exception派生的,所以它很容易被标准C++库组件处理。
try
{
... // call OpenCV
}
catch( cv::Exception& e )
{
const char* err_msg = e.what();
std::cout << "exception caught: " << err_msg << std::endl;
}
多线程和重入
当前版本的OPENCV是完全支持重入的,也就是说,同样的函数,同样的类实例的常数方法,或者同样的不同类实例的非常数方法可以被不同的线程调用,当然,同样的cv::Mat也可以在不同的线程中调用,因为引用计数器的操作是基于架构的原子指令