OpenCV API应用手册(2)- 基本概念

1 cv 命名空间

所有的OpenCV类和函数都被放在cv命名空间之内,所以要想访问这些功能,必须使用cv::指定符或者using namespace cv

2 自动内存管理

OpenCV会自动处理所有的内存。
首先,使用了std::vector, Mat,和其它数据结构的函数和方法,它们的析构函数只有在适当的时候才会释放那些潜在的内存buffers。正如Mat类的情况,这意味着析构函数不总是释放缓存。它们还要考虑可能的数据共享问题。析构函数会在矩阵数据缓存相关的引用计数器上减1,这个矩阵数据缓存只有在引用计算器为0时,才会被释放。相似的,当一个Mat实例被复制时,并没有复制真实的数据。只是引用计数器+1而已,它们还是共用的一块数据缓存。可以使用Mat::clone()进行完整复制。看下面的例子:

// 创建一个8Mb矩阵
Mat A(1000, 1000, CV_64F);

// 创建相同矩阵的另一个数据头,
// 这是一个实例操作,无关矩阵的大小。
Mat B = A;
// 创建A数据矩阵的第3行的数据头,也不会发生数据的复制
Mat C = B.row(3);
// 现在,创建该矩阵的独立拷贝
Mat D = B.clone();
// 复制B的第5行给C,也就是说,拷贝A的第5行给A的第3行。
B.row(5).copyTo(C);
// 现在让A和D共享数据;
// 在A这个修改版本后,A仍然被B和C引用。
A = D;
// 现在,释放B(B的引用没有内存缓存),但是,A仍然被C引用;
// 尽管C只是源数据A的一行。
B.release();
// 最后,对C进行完全拷贝。结果,修改后的大矩阵将会被释放,
// 因为没有任何对象引用它了。
C = C.clone();

综上所述,可以看出,Mat类和其它数据结构的使用是非常简单的。但是,对于更高级别的类或者用户创建的数据类型,没有考虑内存的自动管理怎么办?对此,OpenCV提供了Ptr<>模板类,它类似于C++ TR1的std::shared_ptr指针。所以,替代使用简易指针:

T* ptr = new T(...);

而使用:

Ptr<T> ptr = new T(...);

也就是说,Ptr ptr将一个指针封装进T实例中,对应该指针就会有一个引用计数器。详细的可以查阅Ptr指针描述。

3 为输出数据自动分配内存

在大多数时候,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会被>>操作符自动分配内存,因为视频捕捉模块能够知道视频的帧率和位深度,所以操作符“>>“会自动给数据数组frame分配内存。同样的道理,cvtColor()函数会自动给数组edges分配内存,它具有和输入数组相同的大小和位深度。由于传递了转换码CV_BGR2GRAY作为参数,所以其通道数是1,意味着颜色向灰度图像转变。注意的是,在上面的循环体内,因为所有接下来的图像帧具有相同的分辨率,所以只有在第一次执行时,才会被分配内存。如果你以某种方式改变了视频的分辨率,这些数组会自动重新分配。
该技术的关键部分就是Mat::create方法。它需要想要的数组大小和类型。如果,数组已经有了指定的大小和类型,create什么也不做。否则,如果有的话,它会释放先前分配的内存数据(这部分涉及到引用计数器的自减和与0比较,是否已经到0),然后分配一个想要尺寸的新buffer。大部分函数会为每一个输出数组调用Mat::create方法,所以,自动内存分配会被执行。
值得注意的例外是cv::mixChannels,cv::RNG::fill和一些其它的函数和方法。它们不能够为输出数组分配内存,所以你必须预先准备好。

4 饱和运算

作为计算机视觉库,OpenCV处理大量的图像像素点,这些像素点经常会以比较紧凑的,8bits/channel或者16bits/channel,表格的编码方式进行编码,因此,这些像素点有有限的值范围。更进一步,对图像的某种操作,像色彩空间转换,亮度/对比度调整,锐化,复杂的插值运算(bi-cubic,Lanczos)产生的值会在值范围以外。此时,如果你就存储结果的最低8(16)位,会导致视觉的伪影,且可能影响进一步的图像分析。为了解决这个问题,就会运用所谓的饱和度运算。例如,为了存储操作结果r,为一个8位图像,你需要在(0~255)内找到最接近的值:

I(x,y)= min(max(round(r),0),255)

对于8位,16位有符号和无符号类型都是相同的规则。OpenCV库中都应用了这种处理。在C++代码里,操作方式是应用saturate_cast<>函数,类似于C++的cast操作。下面是对前面提到的公式应用saturate_cast<>的操作。

I.at<uchar>(y, x) = saturate_cast<uchar>(r);

在这里,cv::uchar是OpenCV提供的8-bit无符号整数类型。在被优化过的SIMD代码里,例如SSE2指令(如paddusb,packuswb等)被使用。它们能够帮助达到如在C++代码里相同的行为。
注意
当结果是32-bit的整数时,饱和度不会被应用。

5 固定像素类型(限制使用模板)

模板是C++的一个重要特色,它使实现更加强大,有效,还保证了数据结构和算法的安全。但是,模板大量的使用,会显著地增加编译时间和代码的大小。另外,如果当只使用模板的时候,很难将接口和实现独立开来。对于基本算法这也许是好的,但是对于计算机视觉库中一个单一算法就可能跨度几千行代码来说,并不是很好。基于此,也是为了简化和其它语言的绑定的开发,像Python,java,Matlab等,它们要么根本就没有模板,要么具有有限的模板功能,现在OpenCV的实现是基于多态和运行时调度而不是模板。在那些使用运行时调度会很慢的地方(像像素访问操作),不可能用运行时调度的地方(通用的Ptr<>实现),或仅仅是不方便的地方(saturate_cast<>))现在的实现引入了小模板类,方法,和函数。在现在的OpenCV版本中,模板的是用都是受限制的。或者可以说是,尽量减少模板的使用。
因此,OpenCV库中提供的可供操作的原始数据类型是有限的、固定的集合。也就是说,数组元素应该使用下面数据类型中的一种:

18位无符号整数(uchar28位有符号整数(schar)
316位无符号整数(ushort416位有符号整数(short532位有符号整数(int632位浮点数(float764位浮点数(double8、元组数据类型,元组里的数组类型必须是一样的,且是上面的数据类型中的一种。如果一个数组中的元素是这样的元组,就称为多通道数组与单通道数组不同,多通道数组的值是有范围的。这些通道的最大可能值由常数CV_CN_MAX定义,其被设为512

对于基本的数据类型,OpenCV用枚举形数据给出了定义:

enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 };

对于多通道(n-channel),可以使用下面的选项进行指定:

1)CV_8UC1 ... CV_64FC4 常数 (最后的数字是通道数,可以看出最大到4通道);
(2)
    CV_8UC(n) ... CV_64FC(n) or CV_MAKETYPE(CV_8U, n) ... CV_MAKETYPE(CV_64F, n) macros when the number of channels is more than 4 or unknown at the compilation time.

那么对于大于4的通道数呢?可以使用

CV_8UC(n) ... CV_64FC(n) 或 CV_MAKETYPE(CV_8U, n) ... CV_MAKETYPE(CV_64F, n) 宏

进行定义。例如:

Mat mtx(3, 3, CV_32F); // 创建一个3x3的浮点数矩阵
Mat cmtx(10, 1, CV_64FC2); // 创建一个10x1的2通道浮点数矩阵,10个矢量元素
Mat img(Size(1920, 1080), CV_8UC3); // 创建一个3通道的图像(彩色)
Mat grayscale(image.size(), CV_MAKETYPE(image.depth(), 1)); // 创建一个单通道的图像,与img具有相同的大小和通道类型

使用OpenCV不能构建具有更复杂的元素的数组了。进一步讲,每个函数或方法仅仅处理的是所有可能数组类型的一个子集。通常,算法越复杂,可支持的子集就越小。看下面这种限制的典型例子:

1、人脸检测算法只能操作8-bit的灰度或彩色图像;
2、线性代数函数和机器学习算法只能使用浮点数类型的数组;
3、基本函数,例如cv::add,支持所有的数据类型;
4、色彩空间转换函数支持 8-bit unsigned, 16-bit unsigned, 和 32-bit floating-point 类型;

6 InputArray/OutputArray

大量的OpenCV函数处理2维或多维稠密数值矩阵。通常,这些函数把Mat类作为参数,但是,大多数情况下,像一个像素点的访问,使用set::vector<> 更方便;再有,对于3x3全息矩阵等,使用Matx<>更为方便。为了避免在API函数中进行许多的拷贝操作,特定的代理类被引入。基本的“代理“类就是输入矩阵,它被用来传递只读矩阵作为函数的输入,从输入矩阵派生而来的输出矩阵被用来指定函数的输出数组。正常情况下,你不需要关心这些中间类型(你不需要明确地声明这些类形的变量)-它会自动工作。你可以假设用你经常使用的Mat, std::vector<>, Matx<>,Vec<> 或Scalar代替了InputArray/OutputArray。
当一个函数有可选的输入或者输出数组的时候,而你又不需要这个参数的时候,可以传递cv::noArray();

7 异常处理

OpenCV使用异常处理重要的信号错误。当输入数据格式正确,且属于指定的值范围,但是算法却因为某些原因不正确的时候(例如,优化算法不能收敛),它会返回一个指定的错误码(通常,就是一个布尔量)。
异常可以是cv::Exception类的实例,也可以是它的派生类。而cv::Exception就是std::exception的派生类。所以,它能够被其它的标准C++库妥善的处理。
The exception is typically thrown either using the CV_Error(errcode, description) macro, or its printf-like CV_Error_(errcode, printf-spec, (printf-args)) variant, or using the CV_Assert(condition) macro that checks the condition and throws an exception when it is not satisfied. For performance-critical code, there is CV_DbgAssert(condition) that is only retained in the Debug configuration. Due to the automatic memory management, all the intermediate buffers are automatically deallocated in case of a sudden error. You only need to add a try statement to catch exceptions, if needed:
异常抛出的方式可以有多种:(1)使用CV_Error宏;(2)类似于printf函数的CV_Error_(errcode,printf-spec,(printf-args))变量;或(3)CV_Assert(condition)宏。对于追求性能的代码,在debug阶段,可以使用CV_DbgAssert(condition)宏。由于具有自动内存管理,当发生突然错误的时候,所有的中间缓存都会被自动释放。你需要做的只是,添加一个try语句去捕获异常,

try
{
    // ... // call OpenCV
}
catch( cv::Exception& e )
{
    const char *err_msg = e.what();
    std::cout << "exception caught: " << err_msg << std::endl;
}

8 多线程和可重入性

OpenCV的实现完全是可重入的。也就说,不同的线程可以调用相同的函数,相同的类实例constant方法,或者不同类实例的相同的non-constant方法。另外,cv::Mat能够被不同的线程同时使用,因为引用计数操作使用了特定于体系架构的原子操作指令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值