赞
踩
OpenCV(开源计算机视觉库:http://opencv.org)是一个开源库,包含数百种计算机视觉算法。该文档描述了所谓的OpenCV 2.x API,它本质上是一个C++ API,而不是基于C的OpenCV 1.x API(自OpenCV 2.4发布以来,C API已被弃用,并且没有使用“C”编译器进行测试)
OpenCV 具有模块化结构,这意味着该包包含多个共享或静态库。提供以下模块:
本文档的后续章节描述了每个模块的功能。但首先,请确保熟悉库中彻底使用的常见 API 概念。
有 OpenCV 类和函数都放在命名空间中。因此,若要从代码访问此功能,请使用说明符cv
cv::
或指令using namespace cv
:
- #include "opencv2/core.hpp"...
- cv::Mat H = cv::findHomography(points1, points2, cv::RANSAC, 5);
- ...
或
- #include "opencv2/core.hpp"
- using namespace cv;...
- Mat H = findHomography(points1, points2, RANSAC, 5 );
- ...
OpenCV 会自动处理所有内存。
首先,std::vector、cv::Mat 以及函数和方法使用的其他数据结构具有在需要时释放底层内存缓冲区的析构函数。这意味着析构函数并不总是像 Mat 那样取消分配缓冲区。它们考虑了可能的数据共享。析构函数递减与矩阵数据缓冲区关联的引用计数器。当且仅当引用计数器达到零时,即当没有其他结构引用同一缓冲区时,缓冲区才会被释放。同样,当复制 Mat 实例时,不会真正复制任何实际数据。相反,引用计数器是递增的,以记住同一数据的另一个所有者。还有 cv::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 共享数据;之后是修改后的版本
- //尽管C只是原始A的一行
- A = D;
- //现在使B成为一个空矩阵(它不引用内存缓冲区),
- // 但A的修改版本仍将被C引用,
- // 最后,制作C的完整副本。结果,大修改了
- B.release();
- //最后,制作C的完整副本。结果,大修改了
- //矩阵将被解除分配,因为它不会被任何人引用
- C = C.clone();
你可以看到,垫子和其他基本结构的使用很简单。但是,在不考虑自动内存管理的情况下创建的高级类甚至用户数据类型呢?对于他们来说,OpenCV 提供了类似于 C++11 中的 std::shared_ptr 的 cv::P tr 模板类。因此,不要使用普通指针:
T* ptr = new T(...);
您可以使用:
Ptr<T> ptr(new T(...));
或:
Ptr<T> ptr = makePtr<T>(...);
Ptr<T>
封装指向 T 实例的指针和与指针关联的引用计数器。有关详细信息,请参阅 cv::P tr 描述。
OpenCV 会自动释放内存,并且大多数时候会自动为输出函数参数分配内存。因此,如果函数有一个或多个输入数组(cv::Mat 实例)和一些输出数组,则输出数组将自动分配或重新分配。输出数组的大小和类型由输入数组的大小和类型决定。如果需要,这些函数会采用额外的参数来帮助确定输出数组属性。
如下列:
- #include "opencv2/imgproc.hpp"
- #include "opencv2/highgui.hpp"
- using namespace cv;
- int main(int, char**)
- {
- VideoCapture cap(0);
- if(!cap.isOpened()) return -1;
- Mat frame, edges;
- namedWindow("edges", WINDOW_AUTOSIZE);
- for(;;)
- {
- cap >> frame;
- cvtColor(frame, edges, COLOR_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;
- }
由于视频帧分辨率和位深度对视频捕获模块是已知的,因此阵列帧由操作员自动分配。阵列边缘由 cvtColor 函数自动分配。它与输入数组具有相同的大小和位深度。通道数为 1,因为传递了颜色转换代码 cv::COLOR_BGR2GRAY,这意味着颜色到灰度的转换。请注意,在循环体的第一次执行期间,帧和边缘只分配一次,因为所有下一个视频帧都具有相同的分辨率。如果以某种方式更改视频分辨率,则会自动重新分配阵列。>>
该技术的关键组件是 cv::Mat::create 方法。它采用所需的数组大小和类型。如果数组已具有指定的大小和类型,则该方法不执行任何操作。否则,它会释放以前分配的数据(如果有)(这部分涉及递减引用计数器并将其与零进行比较),然后分配所需大小的新缓冲区。大多数函数为每个输出数组调用 cv::Mat::create 方法,因此实现了自动输出数据分配。
此方案的一些值得注意的例外是 cv::mixChannels、cv::RNG::fill 以及其他一些函数和方法。他们无法分配输出数组,因此您必须提前执行此操作。
作为一个计算机视觉库,OpenCV 处理了很多图像像素,这些像素通常以紧凑的每通道 8 位或 16 位形式编码,因此具有有限的值范围。此外,对图像的某些操作(如色彩空间转换、亮度/对比度调整、锐化、复杂插值(双立方、Lanczos))可能会产生超出可用范围的值。如果仅存储结果的最低 8 (16) 位,则会导致视觉伪影,并可能影响进一步的图像分析。为了解决这个问题,使用了所谓的饱和算术。例如,要将操作结果 r 存储到 8 位图像中,请在 0..255 范围内找到最接近的值:
I(x,y)=min(max(round(r),0),255)
类似的规则也适用于 8 位有符号类型、16 位有符号类型和无符号类型。此cv::saturate_cast<>语义在库中随处可见。在 C++ 代码中,它是使用类似于标准 C++ 强制转换操作的函数完成的。请参阅下面提供的公式的实现
I.at<uchar>(y, x) = saturate_cast<uchar>(r);
其中 cv::uchar 是 OpenCV 8 位无符号整数类型。在优化的SIMD代码中,使用了paddusb、packuswb等SSE2指令。它们有助于实现与 C++ 代码中完全相同的行为。
注意
当结果为 32 位整数时,不应用饱和度。
模板是 C++ 的一项重要功能,它支持实现非常强大、高效且安全的数据结构和算法。但是,模板的广泛使用可能会大大增加编译时间和代码大小。此外,当专门使用模板
cv::Ptr
<>
时,很难将接口和实现分开。这对于基本算法来说可能很好,但对于计算机视觉库来说却不好,因为单个算法可能跨越数千行代码。正因为如此,也为了简化其他语言(如 Python、Java、Matlab)的绑定开发,这些语言根本没有模板或模板功能有限,当前的 OpenCV 实现基于多态性和模板上的运行时调度。在那些运行时调度速度太慢(如像素访问运算符)、不可能(通用实现)或非常不方便()的地方,当前的实现引入了小模板类、方法和函数。在当前 OpenCV 版本中的其他任何地方,模板的使用都是有限的。cv::saturate_cast
<>
因此,库可以操作的一组固定的原始数据类型有限。也就是说,数组元素应具有以下类型之一:
对于这些基本类型,将应用以下枚举:
enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 };
可以使用以下选项指定多通道(n-channel)类型:
注意:
CV_32FC1 == CV_32F, CV_32FC2 == CV_32FC(2) == CV_MAKETYPE(CV_32F, 2)
, and CV_MAKETYPE(depth, n) == ((depth&7) + ((n-1)<<3)
. 这意味着常量类型是由深度(取最低 3 位)和通道数减去 1(取下一位)形成的 log2(CV_CN_MAX)
bits.
例子:
- 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(img.size(), CV_MAKETYPE(img.depth(), 1)); // make a 1-channel image of
- // the same size and same
- // channel type as img
具有更复杂元素的数组不能使用 OpenCV 构建或处理。此外,每个函数或方法只能处理所有可能的数组类型的子集。通常,算法越复杂,支持的格式子集就越小。请参阅以下此类限制的典型示例:
每个函数支持的类型子集都是根据实际需求定义的,将来可以根据用户请求进行扩展。
许多 OpenCV 函数处理密集的二维或多维数值数组。通常,此类函数cv::Mat
用作参数,但在某些情况下,std::vector<>使用起来更方便(例如,对于点集)或cv::Matx<>(用于 3x3 单调矩阵等)。为了避免 API 中出现许多重复项,引入了特殊的“代理”类。基“代理”类是 cv::InputArray。它用于在函数输入上传递只读数组。派生自 InputArray 类 cv::OutputArray 用于指定函数的输出数组。通常,你不应该关心那些中间类型(你不应该显式声明这些类型的变量)——它们都会自动工作。您可以假定始终可以使用 cv::Mat
, std::vector<>
, cv::Matx<>
, cv::Vec<>
或 cv::Scalar
. 来代替 InputArray/OutputArray。当函数具有可选的输入或输出数组,而您没有或不需要输入或输出数组时,请传递 cv::noArray().
OpenCV 使用异常来发出严重错误的信号。当输入数据格式正确且属于指定值范围,但算法由于某种原因无法成功(例如,优化算法未收敛)时,它会返回一个特殊的错误代码(通常只是一个布尔变量)。
异常可以是 cv::Exception 类或其派生的实例。反过来,cv::Exception 是 的派生词。因此,可以使用其他标准 C++ 库组件在代码中优雅地处理它。std::exception
通常使用宏CV_Error(errcode, description)
或其类似 printf 的 CV_Error_(errcode, (printf-spec, printf-args))
变体引发异常,或者使用 CV_Assert(condition) 宏来检查条件并在不满足异常时引发异常。对于性能关键代码,有 CV_DbgAssert(condition) 仅保留在调试配置中。由于自动内存管理,所有中间缓冲区都会在突然发生错误时自动释放。如果需要,只需添加 try 语句即可捕获异常:
- try
- {
- ... // call OpenCV
- }
- catch (const cv::Exception& e)
- {
- const char* err_msg = e.what();
- std::cout << "exception caught: " << err_msg << std::endl;
- }
当前的 OpenCV 实现是完全可重新输入的。也就是说,可以从不同的线程调用不同类实例的相同函数或相同方法。此外,相同的 Mat 可以在不同的线程中使用,因为引用计数操作使用特定于体系结构的原子指令。
参考文献:
1、《Introduction》 --Generated on Wed Dec 27 2023 21:56:11 for OpenCV by 编辑
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。