OpenCV 介绍使用

返回:OpenCV系列文章目录(持续更新中......)

上一篇:OpenCV4.9.0开源计算机视觉库使用简要说明

下一篇:OpenCV4.9.0开源计算机视觉库核心功能(核心模块)

OpenCV(开源计算机视觉库:http://opencv.org)是一个开源库,包含数百种计算机视觉算法。该文档描述了所谓的OpenCV 2.x API,它本质上是一个C++ API,而不是基于C的OpenCV 1.x API(自OpenCV 2.4发布以来,C API已被弃用,并且没有使用"C"编译器进行测试)

OpenCV 具有模块化结构,这意味着该包包含多个共享或静态库。提供以下模块:

  • 核心功能core) - 定义基本数据结构的紧凑模块,包括密集的多维数组 Mat 和所有其他模块使用的基本功能。
  • 图像处理imgproc) - 一个图像处理模块,包括线性和非线性图像过滤、几何图像变换(调整大小、仿射和透视变形、基于表格的通用重映射)、色彩空间转换、直方图等。
  • 视频分析视频) - 一个视频分析模块,包括运动估计、背景减法和对象跟踪算法。
  • 相机校准和 3D 重建calib3d) - 基本的多视图几何算法、单视图和立体相机校准、物体姿态估计、立体对应算法和 3D 重建元素。
  • 2D 特征框架features2d) - 显著特征检测器、描述符和描述符匹配器。
  • 对象检测objdetect) - 检测对象和预定义类的实例(例如,人脸、眼睛、杯子、人、汽车等)。
  • 高级 GUIhighgui) - 简单 UI 功能的易于使用的界面。
  • 视频 I/Ovideoio) - 一个易于使用的视频捕获和视频编解码器界面。
  • ...其他一些帮助程序模块,例如 FLANN 和 Google 测试包装器、Python 绑定等。

本文档的后续章节描述了每个模块的功能。但首先,请确保熟悉库中彻底使用的常见 API 概念。

API 概念

cv 命名空间

有 OpenCV 类和函数都放在命名空间中。因此,若要从代码访问此功能,请使用说明符cv``cv::或指令using namespace cv

cpp 复制代码
#include "opencv2/core.hpp"...
cv::Mat H = cv::findHomography(points1, points2, cv::RANSAC, 5);
...

cpp 复制代码
#include "opencv2/core.hpp"
using namespace cv;...
Mat H = findHomography(points1, points2, RANSAC, 5 );
...

自动内存管理

OpenCV 会自动处理所有内存。

首先,std::vector、cv::Mat 以及函数和方法使用的其他数据结构具有在需要时释放底层内存缓冲区的析构函数。这意味着析构函数并不总是像 Mat 那样取消分配缓冲区。它们考虑了可能的数据共享。析构函数递减与矩阵数据缓冲区关联的引用计数器。当且仅当引用计数器达到零时,即当没有其他结构引用同一缓冲区时,缓冲区才会被释放。同样,当复制 Mat 实例时,不会真正复制任何实际数据。相反,引用计数器是递增的,以记住同一数据的另一个所有者。还有 cv::Mat::clone 方法,用于创建矩阵数据的完整副本。请参阅以下示例:

cpp 复制代码
​// 创建一个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 模板类。因此,不要使用普通指针:

cpp 复制代码
T* ptr = new T(...);

您可以使用:

cpp 复制代码
Ptr<T> ptr(new T(...));

或:

cpp 复制代码
Ptr<T> ptr = makePtr<T>(...);

Ptr<T>封装指向 T 实例的指针和与指针关联的引用计数器。有关详细信息,请参阅 cv::P tr 描述。

自动分配输出数据

OpenCV 会自动释放内存,并且大多数时候会自动为输出函数参数分配内存。因此,如果函数有一个或多个输入数组(cv::Mat 实例)和一些输出数组,则输出数组将自动分配或重新分配。输出数组的大小和类型由输入数组的大小和类型决定。如果需要,这些函数会采用额外的参数来帮助确定输出数组属性。

如下列:

cpp 复制代码
​#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::mixChannelscv::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<>cv::saturate_cast<>时,很难将接口和实现分开。这对于基本算法来说可能很好,但对于计算机视觉库来说却不好,因为单个算法可能跨越数千行代码。正因为如此,也为了简化其他语言(如 Python、Java、Matlab)的绑定开发,这些语言根本没有模板或模板功能有限,当前的 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 };

可以使用以下选项指定多通道(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.

例子:

cpp 复制代码
​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 构建或处理。此外,每个函数或方法只能处理所有可能的数组类型的子集。通常,算法越复杂,支持的格式子集就越小。请参阅以下此类限制的典型示例:

  • 人脸检测算法仅适用于 8 位灰度或彩色图像。
  • 线性代数函数和大多数机器学习算法仅适用于浮点数组。
  • 基本函数,如cv::add,支持所有类型。
  • 色彩空间转换函数支持 8 位无符号、16 位无符号和 32 位浮点类型。

每个函数支持的类型子集都是根据实际需求定义的,将来可以根据用户请求进行扩展。

输入数组和输出数组

许多 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 语句即可捕获异常:

cpp 复制代码
​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 ​编辑

相关推荐
奔跑中的小象43 分钟前
统信UOS + 天数AI卡部署SGLang服务手册
人工智能·uos·sglang·天数智芯
DevSecOps选型指南1 小时前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
Shockang1 小时前
LangGraph 状态机实战
人工智能
刹那芳华19921 小时前
循环神经网络的从零开始实现(RNN)
人工智能·rnn·深度学习
阿童木写作1 小时前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
科技之门1 小时前
AI3D从建模到贴图、绑骨和动画的完整流程怎么做?V2Fun完整工作流指南
人工智能·3d·贴图
宇宙第一小趴菜1 小时前
二、机器学习的应用领域和发展史
人工智能·机器学习
前端开发江鸟1 小时前
我写过 MCP Server,却一直以为 MCP 只有 Tool
人工智能
天国梦2 小时前
自习室智能化升级避坑指南:天学网AI智习室方案实测与选型建议
大数据·人工智能
阿里云云原生2 小时前
可用性从 99.9% 跃升至 99.995%:畅捷通如何用 AI 重塑运维底座?
运维·网络·人工智能