当前位置: 首页 > news >正文

大坪网站公司中山专业网站建设公司

大坪网站公司,中山专业网站建设公司,合肥网站设计 goz,离退休部门网站建设情况原文#xff1a;Learning Image Processing with OpenCV 协议#xff1a;CC BY-NC-SA 4.0 译者#xff1a;飞龙 本文来自【ApacheCN 计算机视觉 译文集】#xff0c;采用译后编辑#xff08;MTPE#xff09;流程来尽可能提升效率。 当别人说你没有底线的时候#xff0c;… 原文Learning Image Processing with OpenCV 协议CC BY-NC-SA 4.0 译者飞龙 本文来自【ApacheCN 计算机视觉 译文集】采用译后编辑MTPE流程来尽可能提升效率。 当别人说你没有底线的时候你最好真的没有当别人说你做过某些事的时候你也最好真的做过。 一、处理图像和视频文件 本章旨在与 OpenCV其安装和第一个基本程序进行首次接触。 我们将涵盖以下主题 面向新手的 OpenCV 简介然后是简单的分步安装库指南在用户本地磁盘中安装后快速浏览 OpenCV 的结构使用带有一些常见编程框架的库创建项目的快速秘籍如何使用函数读取和写入图像和视频最后我们描述了用于向软件项目添加丰富的用户界面的库函数包括鼠标交互绘图基元和 Qt 支持 OpenCV 简介 OpenCV开源计算机视觉最初是由英特尔开发的它是一个免费跨平台库用于实时图像处理实际上已经成为用于与计算机视觉相关的所有事物的标准工具。 第一个版本于 2000 年以 BSD 许可证发行此后其功能得到了科学界的极大丰富。 2012 年非营利组织 OpenCV.org 承担了为开发人员和用户维护支持网站的任务。 注意 在撰写本书时OpenCV 的新主要版本版本 3.0可用但仍处于 beta 状态。 在整本书中我们将介绍此新版本带来的最相关的更改。 OpenCV 适用于最流行的操作系统例如 GNU/LinuxOSXWindowsAndroidiOS 等。 第一个实现是在C编程语言中实现的 但是随着版本 2.0 的 C 实现它的受欢迎程度随之增加。 新功能使用 C 编程。 但是如今该库具有其他编程语言例如 JavaPython 和 MATLAB/Octave的完整接口。 另外还开发了用于其他语言例如 CRuby 和 Perl的包装程序以鼓励程序员采用。 为了最大程度地提高计算密集型视觉任务的性能OpenCV 包括对以下内容的支持 使用线程构建模块TBB在多核计算机上进行多线程处理-由 Intel 开发的模板库。英特尔处理器上的集成性能基元IPP的子集以提高性能。 多亏了 Intel这些原语可从 3.0 beta 版开始免费获得。使用计算统一设备架构CUDA和开放计算语言OpenCL。 OpenCV 的应用涵盖以下领域分段和识别2D 和 3D 功能工具包对象识别人脸识别运动跟踪手势识别图像拼接高动态范围HDR成像增强现实等等。 此外为了支持某些先前的应用领域还包括具有统计机器学习功能的模块。 下载并安装 OpenCV OpenCV 可从这里免费下载。 该站点提供了发行的最新版本当前为 3.0 beta和较旧的版本。 注意 如果下载的版本是不稳定版本例如当前的 3.0 beta 版本则应格外小心以免出现错误。 在这个页面上可以找到适用于每个平台的 OpenCV 版本。 可以根据最终目的从不同的存储库中获取代码和库信息 主存储库位于这个页面专用于最终用户。 它包含库的二进制版本和目标平台的可编译源。 测试数据存储库位于这个页面其中包含用于测试某些库模块目的的数据集。 贡献者仓库位于这个页面带有源代码与贡献者提供的额外功能和最先进的功能相对应。 与主干相比此代码更容易出错并且测试较少。 提示 在最新版本的 OpenCV 3.0 beta 中额外的贡献模块未包含在主包中。 它们应单独下载并通过适当的选项明确包含在编译过程中。 如果包括其中一些贡献的模块请务必谨慎因为其中一些模块依赖于 OpenCV 不附带的第三方软件。 每个模块的文档站点位于这个页面包括提供的模块。 开发库位于这个页面带有库的当前开发版本。 它适用于库主要功能的开发人员以及希望在发布最新版本之前仍使用最新更新的“急躁”用户。 而不是 GNU/Linux 和 OSX其中 OpenCV 仅作为源代码分发在 Windows 发行版中可以找到预编译的使用 Microsoft Visual C v10v11 和 v12的版本。 每个预编译的版本都可以与 Microsoft 编译器一起使用。 但是如果主要目的是使用不同的编译器框架开发项目则需要为该特定编译器例如 GNU GCC编译库。 提示 使用 OpenCV 最快的方法是使用发行版随附的预编译版本之一。 然后更好的选择是使用用于软件开发的本地平台的最佳设置来构建库的微调版本。 本章提供在 Windows 上构建和安装 OpenCV 的信息。 在这个页面和这个页面上可以找到在 Linux 上设置库的更多信息。 获取编译器并设置 CMake 使用 OpenCV 开发跨平台的一个不错的选择是使用 GNU 工具包包括 gmakeg 和 gdb。 对于大多数流行的操作系统可以轻松获得 GNU 工具包。 对于开发环境我们的首选选择包括 GNU 工具包和跨平台 Qt 框架其中包括 Qt 库和 Qt Creator 集成开发环境IDE。 Qt 框架可从这个页面免费获得。 注意 在 Windows 上安装编译器之后请记住正确设置Path环境变量为编译器的可执行文件添加路径例如 Qt 框架随附的 GNU /编译器的C:\Qt\Qt5.2.1\5.2.1\mingw48_32\bin。 在 Windows 上免费的快速环境编辑器工具可从这个页面获得提供了一种方便的方式来更改Path和其他环境变量 。 要以与编译器无关的方式管理 OpenCV 库的生成过程推荐使用 CMake 工具。 CMake 是可从这个页面上获得的免费且开源的跨平台工具。 使用 CMake 配置 OpenCV 将库的源代码下载到本地磁盘后需要为该库的编译过程配置 Makefile。 CMake 是轻松配置 OpenCV 安装过程的关键工具。 它可以从命令行使用也可以通过图形用户界面GUI版本以更加用户友好的方式使用。 使用 CMake 配置 OpenCV 的步骤总结如下 选择源目录在下面将其命名为OPENCV_SRC和目标目录[OPENCV_BUILD。 目标目录是编译后的二进制文件所在的位置。选中分组和高级复选框然后单击配置按钮。选择所需的编译器例如GNU 默认编译器MSVC 等。设置首选选项然后取消设置不需要的选项。单击配置按钮并重复步骤 4 和 5直到没有错误为止。单击生成按钮并关闭 CMake。 以下屏幕截图显示了 CMake 的主窗口其中包含源目录和目标目录以及将所有可用选项分组的复选框 预先配置步骤后的 CMake 主窗口 注意 为简便起见本文中使用OPENCV_BUILD和OPENCV_SRC分别表示 OpenCV 本地设置的目标目录和源目录。 请记住所有目录都应与您当前的本地配置相匹配。 在预配置过程中CMake 会检测到存在的编译器和许多其他本地属性以设置 OpenCV 的生成过程。 上一个屏幕截图显示了预配置过程后的 CMake 主窗口并以红色显示了分组的选项。 可以保留默认选项不变然后继续配置过程。 但是可以设置一些方便的选项 BUILD_EXAMPLES设置为使用 OpenCV 构建一些示例。BUILD_opencv_module_name设置为在构建过程中包括模块module_name。OPENCV_EXTRA_MODULES_PATH当您需要一些额外的模块时使用 在此处设置附加模块的源代码的路径例如C:/opencv_contrib-master/modules。WITH_QT启用此功能可将 Qt 功能包括在库中。WITH_IPP此选项默认为打开。 当前的 OpenCV 3.0 版本包括英特尔集成性能基元IPP的子集这些子集可加快库的执行时间。 提示 如果编译新的 OpenCV 3.0测试版请小心因为已报告一些与 IPP 包含有关的意外错误即使用此选项的默认值。 我们建议您取消设置WITH_IPP选项。 如果配置与 CMake 一起执行循环执行步骤 4 和 5没有产生任何其他错误则可以为构建过程生成最终的 Makefile。 以下屏幕截图显示了生成步骤后没有错误的 CMake 主窗口 编译和安装库 使用 CMake 生成 Makefile 的过程之后的下一步是使用适当的make工具进行的编译。 通常在目标目录在 CMake 配置步骤中设置的目录的命令行控制台上执行此工具。 例如在 Windows 中应从命令行启动编译如下所示 OPENCV_BUILDmingw32-make此命令使用 CMake 生成的 Makefile 启动构建过程。 整个编译通常需要几分钟。 如果编译没有错误结束则安装将继续执行以下命令 OPENCV_BUILDmingw32-make install此命令将 OpenCV 二进制文件复制到OPENCV_BUILD\install目录。 如果在编译过程中出现问题我们应该再次运行 CMake 来更改在配置过程中选择的选项。 然后我们应该重新生成 Makefile。 通过将库二进制文件的位置例如在 Windows 中生成的 DLL 文件位于OPENCV_BUILD\install\x64\mingw\bin添加到Path安装环境变量的末尾。 如果Path字段中没有此目录则每个 OpenCV 可执行文件的执行都会出错因为找不到库二进制文件。 要检查安装过程是否成功可以运行随库一起编译的一些示例如果使用 CMake 设置了BUILD_EXAMPLES选项。 代码示例用 C 编写可以在OPENCV_BUILD\install\x64\mingw\samples\cpp找到。 注意 安装 OpenCV 的简短说明适用于 Windows。 可以在这个页面上阅读有关 Linux 前提条件的详细说明。 尽管本教程适用于 OpenCV 2.0但几乎所有信息对于版本 3.0 仍然有效。 OpenCV 的结构 一旦安装 OpenCV OPENCV_BUILD\install目录将填充三种类型的文件 头文件它们位于OPENCV_BUILD\install\include子目录中的用于通过 OpenCV 开发新项目。库二进制文件这些是静态或动态库取决于 CMake 选择的选项具有每个 OpenCV 模块的功能。 它们位于bin子目录中例如当使用 GNU 编译器时为x64\mingw\bin。示例二进制文件这些是可执行文件并带有使用库的示例。 这些样本的来源可以在源包中找到例如OPENCV_SRC\sources\samples。 OpenCV 具有模块化的结构这意味着该包为每个模块都包含一个静态或动态DLL库。 每个模块的正式文档可以在这个页面中找到。 包中包含的主要模块是 core这定义了所有其他模块使用的基本功能以及包括重要多维数组Mat在内的基本数据结构。 highgui这提供了简单的用户界面UI功能。 使用 Qt 支持WITH_QT CMake 选项构建库可以使 UI 与此类框架兼容。 imgproc这些是图像处理功能包括滤波线性和非线性几何变换颜色空间转换直方图等。 imgcodecs: 这是一个易于使用的界面用于读取和写入图像。 注意 自从 OpenCV 3.0 以来请注意模块中的更改因为某些功能已移至新模块例如读取和写入图像功能已从highgui移至imgcodecs。 photo这包括计算摄影包括修补去噪High 动态范围HDR成像等。 stitching用于图像拼接。 videoio这是一个易于使用的界面用于视频捕获和视频编解码器。 video它为提供视频分析功能运动估计背景提取和对象跟踪。 features2d这些是功能用于特征检测角和平面对象特征描述特征匹配等。 objdetect这些是功能用于对象检测和预定义检测器实例例如脸部眼睛微笑人汽车等。 其他一些模块是calib3d相机校准flann聚类和搜索ml机器学习shape形状距离和匹配superres超分辨率video 视频分析和videostab视频稳定。 注意 从 3.0 beta 版开始新的贡献模块以单独的包opencv_contrib-master.zip分发可以从这个页面下载。 这些模块提供了的附加功能在使用它们之前应充分了解它们。 有关新版 OpenCV版本 3.0中新功能的快速概述请参考位于这个页面的文档。 使用 OpenCV 创建用户项目 在本书中我们假定 C 是用于编程图像处理应用的主要语言尽管实际上提供了其他编程语言的接口和包装器例如 PythonJavaMATLAB/Octave 等。 在本节中我们将说明如何使用易于使用的跨平台框架使用 OpenCV 的 C API 开发应用。 库的一般用法 要使用 C 开发 OpenCV 应用我们需要我们的代码 包括带有定义的 OpenCV 头文件链接 OpenCV 库二进制文件以获取最终的可执行文件 OpenCV 标头文件位于OPENCV_BUILD\install\include\opencv2目录中每个模块都有一个文件*.hpp。 头文件的包含是通过#include伪指令完成的如下所示 #include opencv2/module_name/module_name.hpp // Including the header file for each module used in the code使用此伪指令可以包含用户程序所需的每个头文件。 另一方面如果包含opencv.hpp头文件则将自动包括所有头文件如下所示 #include opencv2/opencv.hpp // Including all the OpenCVs header files in the code注意 请记住本地安装的所有模块都在OPENCV_BUILD\install\include\opencv2\opencv_modules.hpp头文件中定义该头文件在 OpenCV 的构建过程中自动生成。 #include指令的使用并不总是保证正确包含头文件因为有必要告诉编译器在哪里可以找到包含文件。 这可以通过在文件的位置传递一个特殊的参数来实现例如对于 GNU 编译器为I\location。 链接过程要求您为链接器提供库动态或静态可以在其中找到所需的 OpenCV 功能。 通常使用链接器的两种类型的参数来完成库的位置例如对于 GNU 编译器为‑Llocation和库的名称例如-lmodule_name。 注意 您可以在这个页面和这个页面中找到 GNU GCC 和make可用在线文档的完整列表。 开发新项目的工具 开发我们自己的 OpenCV C 应用的主要先决条件是 OpenCV 头文件和库二进制文件当然我们需要编译 OpenCV辅助库是进行此类编译的前提条件。 该包应使用与生成用户应用相同的编译器进行编译。C 编译器一些辅助工具可以方便地用作代码编辑器调试器项目管理器和流程管理器例如 CMake版本控制系统例如 GitMercurialSVN 等以及类检查器等。 通常这些工具一起部署在所谓的集成开发环境IDE中。任何其他辅助库可选地将需要对最终应用进行编程的任何其他辅助库例如图形统计等。 用于编程 OpenCV C 应用的最受欢迎的编译器套件是 Microsoft Visual CMSVCWindows 仅支持它与 IDE Visual Studio 集成得很好尽管它也可以与其他跨平台 IDE例如 Qt Creator 或 Eclipse集成。 当前与最新的 OpenCV 版本兼容的 MSVC 版本是 VC 10VC 11 和 VC 12Visual Studio 2010、2012 和 2013。GNU 编译器集合 GNU GCC这是由 GNU 项目开发的跨平台编译器系统。 对于 Windows此套件称为 MinGW最小 GNU GCC。 与当前 OpenCV 发行版兼容的版本是 GNU GCC 4.8。 该套件可与多个 IDE 一起使用例如 Qt CreatorCode :: BlocksEclipse 等。 对于本书介绍的示例我们使用了 Windows 的 MinGW 4.8 编译器套件以及 Qt 5.2.1 库和 Qt Creator IDE3.0.1。 跨平台 Qt 库需要使用此类库提供的新 UI 功能来编译 OpenCV。 注意 对于 Windows可以从这个页面下载 Qt 捆绑包包括 Qt 库Qt Creator 和 MinGW 套件。 捆绑包约为 700 MB。 Qt Creator 是用于 C 的跨平台 IDE它集成了我们编码应用所需的工具。 在 Windows 中它可以与 MinGW 或 MSVC 一起使用。 以下屏幕截图显示了 Qt Creator 主窗口其中包含 OpenCV C 项目的不同面板和视图 Qt Creator 的主窗口带有 OpenCV C 项目的一些视图 使用 Qt Creator 创建 OpenCV C 程序 接下来我们说明如何使用 Qt Creator IDE 创建代码项目。 特别是我们将此描述应用于 OpenCV 示例。 我们可以通过导航到文件 | Qt Creator | 新文件或文件 | 项目…然后导航到非 Qt 项目 | 普通 C 项目为任何 OpenCV 应用创建一个项目。 然后我们必须选择一个项目名称及其存储位置。 下一步是为项目在我们的情况下为 Desktop Qt 5.2.1 MinGW 32 位选择一个工具包即编译器并确定生成二进制文件的位置。 通常使用两种可能的构建配置配置文件debug和release。 这些配置文件设置适当的标志来构建和运行二进制文件。 使用 Qt Creator 创建项目时将生成两个特殊文件扩展名为.pro和.pro.user来配置生成和运行过程。 构建过程由在项目创建期间选择的工具包确定。 使用 Desktop Qt 5.2.1 MinGW 32 位套件此过程依赖于qmake和 mingw32make 工具。 使用*.pro文件作为输入qmake生成用于驱动每个配置文件即release和debug构建过程的 Makefile。 Qt Creator IDE 使用qmake工具作为 CMake 的替代品以简化软件项目的构建过程。 它可以自动从几行信息中生成 Makefile。 以下各行代表*.pro文件例如showImage.pro的示例 TARGET: showImage TEMPLATE app CONFIG console CONFIG - app_bundle CONFIG - qt SOURCES \showImage.cpp INCLUDEPATH C:/opencv300-buildQt/install/include LIBS -LC:/opencv300-buildQt/install/x64/mingw/lib \-lopencv_core300.dll \-lopencv_imgcodecs300.dll\-lopencv_highgui300.dll\-lopencv_imgproc300.dll 上一个文件说明了qmake生成适当的 Makefile 来构建项目二进制文件所需的选项。 每行以一个标记表示TARGETCONFIGSOURCESINCLUDEPATH和LIBS的标签开头后跟一个标记以添加或删除-可选值。 在此示例项目中我们使用非 Qt 控制台应用。 可执行文件为showImage.exeTARGET源文件为showImage.cppSOURCES。 由于此项目是基于 OpenCV 的应用因此最后两个标签指示此特定项目coreimgcodecshighgui和imgproc。 注意在行末尾的反斜杠表示在下一行继续。 注意 有关在 Qt 项目中开发的工具包括 Qt Creator 和qmake的详细说明请访问这个页面。 读写图像文件 图像处理依赖于获得图像例如照片或视频名望并通过在其上应用信号处理技术来“播放”图像以获得所需的结果。 在本节中我们向您展示如何使用 OpenCV 提供的功能从文件读取图像。 基本 API 概念 Mat类是在 OpenCV 中存储和处理图像的主要数据结构。 此类在core模块中定义。 OpenCV 已实现了为这些数据结构自动分配和释放内存的机制。 但是当数据结构共享相同的缓冲存储器时程序员仍应格外小心。 例如赋值运算符不将内存内容从对象Mat A复制到另一个对象Mat B 它仅复制引用内容的内存地址。 然后一个对象A或B的更改会影响两个对象。 要复制Mat对象的内存内容应使用Mat::clone()成员函数。 注意 OpenCV 中的许多函数通常使用Mat类来处理密集的单通道或多通道数组。 但是在某些情况下可以使用其他数据类型例如std::vectorMatxVec或Scalar。 为此OpenCV 提供了代理类InputArray和OutputArray它们允许将任何先前的类型用作函数的参数。 Mat类用于密集的 n 维单通道或多通道数组。 它实际上可以存储实数或复数值向量和矩阵彩色或灰度图像直方图点云等。 创建Mat对象的方法有很多最流行的是构造器其中指定数组的大小和类型如下 Mat(nrows, ncols, type, fillValue)数组元素的初始值可以由Scalar类设置为典型的四元素向量针对数组中存储的图像的每个 RGB 和透明度分量。 接下来我们向您展示Mat的用法示例如下所示 Mat img_A(4, 4, CV_8U, Scalar(255)); // White image: // 4 x 4 single-channel array with 8 bits of unsigned integers // (up to 255 values, valid for a grayscale image, for example, // 255white)DataType类定义了 OpenCV 的原始数据类型。 基本数据类型可以是boolunsigned charsigned charunsigned shortsigned shortintfloatdouble或这些原始类型之一的值的元组。 任何原始类型都可以由标识符以以下形式定义 CV_bit depth{U|S|F}C(number of channels)在前面的代码U中S和F分别代表unsignedsigned和float。 对于单通道数组将应用以下枚举以描述数据类型 enum {CV_8U0, CV_8S1, CV_16U2, CV_16S3,CV_32S4, CV_32F5, CV_64F6};注意 在此应注意这三个声明是等效的CV_8UCV_8UC1和CV_8UC(1)。 单通道声明非常适合用于灰度图像的整数数组而数组的三通道声明更适合具有三个分量例如 RGBBRGHSV 等的图像。 对于线性代数运算可以使用floatF类型的数组。 我们可以为多通道数组最多 512 个通道定义所有上述数据类型。 以下屏幕截图说明了一个通道CV_8Ugrayscale的图像内部表示以及三个通道CV_8UC3RGB表示的同一图像。 这些屏幕截图是通过放大 OpenCV 可执行文件窗口中显示的图像showImage示例而获得的 RGB 颜色和灰度的图像的 8 位表示 注意 的注意很重要要使用 OpenCV 功能正确保存 RGB 图像必须将图像存储在内存中其通道按 BGR 顺序排列。 以相同的方式当从文件中读取 RGB 图像时它以 BGR 顺序以其通道存储在内存中。 而且它需要一个辅助的第四通道alpha来操作具有 RGB 和透明性三个通道的图像。 对于 RGB 图像较大的整数值表示 alpha 通道的像素更亮或更透明。 所有 OpenCV 类和函数都在cv命名空间中因此我们在源代码中将具有以下两个选项 包括头文件之后添加using namespace cv声明这是本书所有代码示例中使用的选项。将cv::前缀附加到我们使用的所有 OpenCV 类函数和数据结构。 如果 OpenCV 提供的外部名称与常用的标准模板库STL或其他库冲突则建议使用此选项。 图像文件支持的格式 OpenCV 支持最常见的图像格式。 但是其中一些需要免费提供第三方库。 OpenCV 支持的主要格式为 Windows 位图*.bmp和*dib便携式图像格式*.pbm*.pgm*.ppm太阳栅格*.sr*.ras 需要辅助库的格式为 JPEG*.jpeg*.jpg*.jpeJPEG 2000*.jp2便携式网络图形*.pngTI​​FF*.tiff*.tifWebP*.webp。 除上述列出的格式外对于 OpenCV 3.0 版本它还包括支持以下格式的驱动程序NITFDTEDSRTM 等 由地理数据抽象库GDAL设置并带有 CMake 选项WITH_GDAL。 请注意尚未在 Windows 操作系统上对 GDAL 支持进行广泛的测试。 在 Windows 和 OSX 中默认情况下使用 OpenCV 附带的编解码器libjpeglibjasperlibpng和libtiff。 然后在这些 OS 中可以读取 JPEGPNG 和 TIFF 格式。 Linux和其他类似 Unix 的开源操作系统正在寻找系统中安装的编解码器。 可以在 OpenCV 之前安装编解码器也可以通过在 CMake 中设置适当的选项例如BUILD_JASPERBUILD_JPEGBUILD_PNG和BUILD_TIFF从 OpenCV 包中构建库。 示例代码 为了说明如何使用 OpenCV 读取和写入图像文件我们现在将描述showImage示例。 从命令行使用相应的输出窗口执行示例如下所示 bin_dir\showImage.exe fruits.jpg fruits_bw.jpgshowImage示例的输出窗口 在此示例中给出了两个文件名作为参数。 第一个是要读取的输入图像文件。 第二个是要与输入图像的灰度副本一起写入的图像文件。 接下来我们向您显示源代码及其说明 #include opencv2/opencv.hpp #include iostreamusing namespace std; using namespace cv;int main(int, char *argv[]) {Mat in_image, out_image;// Usage: cmd file_in file_out// Read original imagein_image imread(argv[1], IMREAD_UNCHANGED);if (in_image.empty()) { // Check whether the image is read or notcout Error! Input image cannot be read...\n;return -1; } // Creates two windows with the names of the imagesnamedWindow(argv[1], WINDOW_AUTOSIZE);namedWindow(argv[2], WINDOW_AUTOSIZE);// Shows the image into the previously created windowimshow(argv[1], in_image);cvtColor(in_image, out_image, COLOR_BGR2GRAY);imshow(argv[2], in_image);cout Press any key to exit...\n;waitKey(); // Wait for key press// Writing imageimwrite(argv[2], in_image);return 0; }在这里我们将#include指令与opencv.hpp头文件一起使用该头文件实际上包括所有 OpenCV 头文件。 通过包含此单个文件无需再包含其他文件。 声明使用cv命名空间后此命名空间内的所有变量和函数都不需要cv::前缀。 在main函数中要做的第一件事是检查在命令行中传递的参数数量。 然后如果发生错误将显示帮助消息。 读取图像文件 如果参数数量正确则使用imread(argv[1], IMREAD_UNCHANGED)函数将图像文件读入Mat in_image对象其中第一个参数是在命令行中传递的第一个参数argv[1] 参数是一个标志IMREAD_UNCHANGED这意味着存储在内存对象中的图像应保持不变。 imread函数根据文件内容而不是文件扩展名确定图像编解码器的类型。 imread函数的原型如下 Mat imread(const String filename, int flags IMREAD_COLOR )该标志指定所读取图像的颜色它们由imgcodecs.hpp头文件中的以下枚举定义和解释 enum { IMREAD_UNCHANGED -1, // 8bit, color or notIMREAD_GRAYSCALE 0, // 8bit, grayIMREAD_COLOR 1, // unchanged depth, colorIMREAD_ANYDEPTH 2, // any depth, unchanged colorIMREAD_ANYCOLOR 4, // unchanged depth, any colorIMREAD_LOAD_GDAL 8 // Use gdal driver };注意 从 OpenCV 3.0 版开始imread函数在imgcodecs模块中而不在highgui中就像在 OpenCV 2.x 中一样。 提示 随着几个函数和声明移入 OpenCV 3.0由于链接器未找到一个或多个声明符号和/或函数可能会出现一些编译错误。 为了弄清楚符号的定义位置*.hpp和要链接的库我们建议使用 Qt Creator IDE 进行以下技巧 将#include opencv2/opencv.hpp声明添加到代码中。 用鼠标光标在符号或函数上按F2功能键 这将打开*.hpp文件在其中声明了符号或函数。 读取输入图像文件后请检查操作是否成功。 此检查是通过in_image.empty()成员函数完成的。 如果读取图像文件时没有错误则会创建两个窗口分别显示输入和输出图像。 使用以下函数执行窗口的创建 void namedWindow(const String winname,int flags WINDOW_AUTOSIZE )OpenCV 窗口在程序中由明确的名称标识。 标志的定义及其说明由highgui.hpp头文件中的以下枚举给出 enum { WINDOW_NORMAL 0x00000000, // the user can resize the window (no constraint) // also use to switch a fullscreen window to a normal sizeWINDOW_AUTOSIZE 0x00000001, // the user cannot resize the window,// the size is constrained by the image displayedWINDOW_OPENGL 0x00001000, // window with opengl supportWINDOW_FULLSCREEN 1,WINDOW_FREERATIO 0x00000100, // the image expends as much as it can (no ratio constraint)WINDOW_KEEPRATIO 0x00000000 // the ratio of the image is respected };窗口的创建不会在屏幕上显示任何内容。 在窗口中显示图像的函数属于highgui模块是 void imshow(const String winname, InputArray mat)如果使用WINDOW_AUTOSIZE标志创建了窗口winname则图像mat将以其原始尺寸显示为。 在showImage示例中第二个窗口显示了输入图像的灰度副本。 要将彩色图像转换为灰度图像请使用imgproc模块中的cvtColor函数。 该函数实际上可以用于更改图像颜色空间。 程序中创建的任何窗口均可调整大小并从其默认设置移动。 当不再需要任何窗口时应将其销毁以释放其资源。 像示例中一样这种资源释放是在程序结束时隐式完成的。 进入固有循环的事件处理 如果我们在窗口上显示图像后没有做任何其他事情令人惊讶的是该图像将根本不会显示。 在窗口上显示图像后我们应该开始循环以获取和处理与用户与窗口交互有关的事件。 该任务由以下函数从highgui模块中执行 int waitKey(int delay0)此函数会等待一个毫秒数的按键delay 0以返回按键的代码如果延迟在没有按键的情况下结束则将返回-1。 如果delay为0或负数则该函数将一直等待直到按下某个键。 注意 请记住waitKey函数仅在至少存在一个已创建且处于活动状态的窗口时才起作用。 写入图像文件 imgcodecs模块中的另一个重要函数是 bool imwrite(const String filename, InputArray img, const vectorint paramsvectorint())此函数将图像img保存到文件filename中作为第三个可选参数它是指定编解码器参数的属性值对的向量将其保留为空以使用默认值。 编解码器由文件扩展名确定。 注意 有关编解码器属性的详细列表请查看这个页面上的imgcodecs.hpp头文件和 OpenCV API 参考。 读写视频文件 视频处理的不是运动图像而是静止图像。 视频源可以是专用摄像机网络摄像机视频文件或图像文件序列。 在 OpenCV 中VideoCapture和VideoWriter类提供了易于使用的 C API用于捕获和记录视频处理中涉及的任务。 示例代码 recVideo示例是一小段代码您可以在其中看到如何使用默认相机作为捕获设备来抓取帧对其进行边缘检测并将其保存为新的帧。 文件。 此外还创建了两个窗口以同时显示原始帧和已处理的帧。 示例代码为 #include opencv2/opencv.hpp #include iostreamusing namespace std; using namespace cv;int main(int, char **) {Mat in_frame, out_frame;const char win1[]Grabbing..., win2[]Recording...;double fps30; // Frames per secondchar file_out[]recorded.avi;VideoCapture inVid(0); // Open default cameraif (!inVid.isOpened()) { // Check errorcout Error! Camera not ready...\n;return -1;}// Gets the width and height of the input videoint width (int)inVid.get(CAP_PROP_FRAME_WIDTH);int height (int)inVid.get(CAP_PROP_FRAME_HEIGHT);VideoWriter recVid(file_out,VideoWriter::fourcc(M,S,V,C),fps, Size(width, height));if (!recVid.isOpened()) {cout Error! Video file not opened...\n;return -1;}// Create two windows for orig. and final videonamedWindow(win1);namedWindow(win2);while (true) {// Read frame from camera (grabbing and decoding)inVid in_frame;// Convert the frame to grayscalecvtColor(in_frame, out_frame, COLOR_BGR2GRAY);// Write frame to video file (encoding and saving)recVid out_frame;imshow(win1, in_frame); // Show frame in windowimshow(win2, out_frame); // Show frame in windowif (waitKey(1000/fps) 0)break;}inVid.release(); // Close camerareturn 0; }在此示例中以下函数值得快速回顾 double VideoCapture::get(int propId)这将返回VideoCapture对象的指定属性的值。 videoio.hpp头文件包含基于 DC1394IEEE 1394 数码相机规格的属性的完整列表。 static int VideoWriter::fourcc(char c1, char c2, char c3, char c4)这会将四个字符连接到一个 fourcc 代码。 在示例中MSVC 代表 Microsoft Video仅适用于 Windows。 有效的 fourcc 代码列表发布在这个页面上。 bool VideoWriter::isOpened()如果用于写入视频的对象已成功初始化则返回true。 例如使用不正确的编解码器会产生错误。 提示 要小心; 系统中有效的 fourcc 代码取决于本地安装的编解码器。 要了解本地系统中已安装的 fourcc 编解码器我们建议使用开源工具 MediaInfo该工具可在这个页面上的许多平台上使用。 VideoCapture VideoCapture::operator(Mat image)抓取解码并返回下一帧。 此方法具有等效的bool VideoCapture::read(OutputArray image)函数。 可以使用它而不是使用VideoCapture::grab()函数然后使用VideoCapture::retrieve()。 VideoWriter VideoWriter::operator(const Mat image)这将写入下一帧。 这种方法等效 void VideoWriter::write(const Mat image)函数。 在此示例中存在一个读取/写入循环其中窗口事件也被获取和处理。 waitKey(1000/fps)函数调用负责此工作 但是在这种情况下1000/fps表示返回外部循环之前要等待的毫秒数。 尽管不精确但可以为记录的视频获得每秒帧的近似量度。 void VideoCapture::release()这会释放视频文件或捕获设备。 尽管在此示例中不是明确必需的但我们将其包括在内以说明其用法。 用户交互工具 在前面的部分中我们解释了如何创建namedWindow窗口以显示imshow图像和获取/处理事件waitKey。 我们提供的示例向您展示了一种非常简单的方法用于用户通过键盘与 OpenCV 应用进行交互。 waitKey函数返回在超时之前按下的键的代码。 幸运的是OpenCV 提供了更灵活的用户交互方式例如轨迹栏和鼠标交互可以与某些绘图函数结合使用以提供更丰富的用户体验。 而且如果 OpenCV 是在 Qt 支持下CMake 的WITH_QT选项在本地编译的则可以使用一组新功能来编写更好的 UI。 在本节中我们将快速回顾在具有 Qt 支持的 OpenCV 项目中对用户界面进行编程的可用功能。 我们使用下一个名为showUI的示例来说明有关 OpenCV UI 支持的内容。 该示例在窗口中向您显示了彩色图像说明了如何使用一些基本元素来丰富用户交互。 以下屏幕快照显示了在示例中创建的 UI 元素 showUI示例的输出窗口 showUI示例的源代码没有回调函数如下 #include opencv2/opencv.hpp #include iostreamusing namespace std; using namespace cv;// Callback functions declarations void cbMouse(int event, int x, int y, int flags, void*); void tb1_Callback(int value, void *); void tb2_Callback(int value, void *); void checkboxCallBack(int state, void *); void radioboxCallBack(int state, void *id); void pushbuttonCallBack(int, void *font);// Global definitions and variables Mat orig_img, tmp_img; const char main_win[]main_win; char msg[50];int main(int, char* argv[]) {const char track1[]TrackBar 1;const char track2[]TrackBar 2;const char checkbox[]Check Box;const char radiobox1[]Radio Box1;const char radiobox2[]Radio Box2;const char pushbutton[]Push Button;int tb1_value 50; // Initial value of trackbar 1int tb2_value 25; // Initial value of trackbar 1orig_img imread(argv[1]); // Open and read the imageif (orig_img.empty()) {cout Error!!! Image cannot be loaded... endl;return -1;}namedWindow(main_win); // Creates main window// Creates a font for adding text to the imageQtFont font fontQt(Arial, 20, Scalar(255,0,0,0),QT_FONT_BLACK, QT_STYLE_NORMAL);// Creation of CallBack functionssetMouseCallback(main_win, cbMouse, NULL);createTrackbar(track1, main_win, tb1_value,100, tb1_Callback);createButton(checkbox, checkboxCallBack, 0, QT_CHECKBOX);// Passing values (font) to the CallBackcreateButton(pushbutton, pushbuttonCallBack,(void *)font, QT_PUSH_BUTTON);createTrackbar(track2, NULL, tb2_value,50, tb2_Callback);// Passing values to the CallBackcreateButton(radiobox1, radioboxCallBack,(void *)radiobox1, QT_RADIOBOX);createButton(radiobox2, radioboxCallBack,(void *)radiobox2, QT_RADIOBOX);imshow(main_win, orig_img); // Shows original imagecout Press any key to exit... endl;waitKey(); // Infinite loop with handle for eventsreturn 0; }使用 Qt 支持构建 OpenCV 时每个通过highgui模块创建的窗口都会显示默认的工具栏参见上图并具有从左到右用于平移缩放保存 并打开属性窗口。 除了上述工具栏仅 Qt 可用之外在接下来的小节中我们将注释示例中创建的不同 UI 元素以及实现它们的代码。 轨迹栏 轨迹栏是在指定窗口winname中使用createTrackbar(const String trackbarname, const String winname, int* value, int count, TrackbarCallback onChange0, void* userdata0)函数创建的具有链接的整数值value最大值count和可选值的滑块更改时将调用回调函数onChange以及回调函数的参数userdata。 回调函数本身有两个参数value由滑块选择和一个​​指向userdata的指针可选。在 Qt 支持下如果未指定窗口则是属性窗口。 在showUI示例中我们创建了两个跟踪栏第一个在主窗口中第二个在属性窗口中。 跟踪栏回调的代码为 void tb1_Callback(int value, void *) {sprintf(msg, Trackbar 1 changed. New value%d, value);displayOverlay(main_win, msg);return; } void tb2_Callback(int value, void *) {sprintf(msg, Trackbar 2 changed. New value%d, value);displayStatusBar(main_win, msg, 1000);return; }鼠标交互 始终会生成鼠标事件因此用户会与进行鼠标交互移动和单击。 通过设置适当的处理器或回调函数可以实现诸如选择拖放等操作。 回调函数onMouse通过指定窗口winname和可选参数userdata中的setMouseCallback(const String winname, MouseCallback onMouse, void* userdata0 )函数启用。 处理鼠标事件的回调函数的源代码为 void cbMouse(int event, int x, int y, int flags, void*) {// Static vars hold values between callsstatic Point p1, p2;static bool p2set false;// Left mouse button pressedif (event EVENT_LBUTTONDOWN) {p1 Point(x, y); // Set orig. pointp2set false;} else if (event EVENT_MOUSEMOVE flags EVENT_FLAG_LBUTTON) {// Check moving mouse and left button down// Check out boundsif (x orig_img.size().width)x orig_img.size().width;else if (x 0)x 0;// Check out boundsif (y orig_img.size().height)y orig_img.size().height;else if (y 0)y 0;p2 Point(x, y); // Set final pointp2set true;// Copy orig. to temp. imageorig_img.copyTo(tmp_img);// Draws rectanglerectangle(tmp_img, p1, p2, Scalar(0, 0 ,255));// Draw temporal image with rect.imshow(main_win, tmp_img);} else if (event EVENT_LBUTTONUP p2set) {// Check if left button is released// and selected an area// Set subarray on orig. image// with selected rectangleMat submat orig_img(Rect(p1, p2));// Here some processing for the submatrix//...// Mark the boundaries of selected rectanglerectangle(orig_img, p1, p2, Scalar(0, 0, 255), 2);imshow(main_win, orig_img);}return; }在showUI示例中鼠标事件用于通过回调函数cbMouse进行控制即通过在矩形区域周围绘制矩形来选择矩形区域。 在示例中此函数声明为void cbMouse(int event, int x, int y, int flags, void*)参数是事件发生的指针的位置xy事件发生的条件flags并且可选地 userdata。 注意 可用的事件标志及其相应的定义符号可以在highgui.hpp头文件中找到。 按钮 OpenCV仅具有 Qt 支持允许创建三种类型的按钮复选框QT_CHECKBOX单选框QT_RADIOBOX和按钮QT_PUSH_BUTTON。 这些类型的按钮可分别用于设置选项设置排他选项以及在按下按钮时执行操作。 这三个属性是通过createButton(const String button_name, ButtonCallback on_change, void* userdata0, int typeQT_PUSH_BUTTON, bool init_statefalse )函数在此窗口中最后一个跟踪栏之后的按钮栏中排列的属性窗口中创建的。 按钮的参数是其名称button_name在状态更改时调用的回调函数on_change还可以是回调的参数userdate按钮的类型type 以及按钮的初始状态init_state。 接下来我们向您展示示例中与按钮对应的回调函数的源代码 void checkboxCallBack(int state, void *) {sprintf(msg, Check box changed. New state%d, state);displayStatusBar(main_win, msg);return; }void radioboxCallBack(int state, void *id) {// Id of the radio box passed to the callBacksprintf(msg, %s changed. New state%d,(char *)id, state);displayStatusBar(main_win, msg);return; }void pushbuttonCallBack(int, void *font) {// Add text to the imageaddText(orig_img, Push button clicked,Point(50,50), *((QtFont *)font));imshow(main_win, orig_img); // Shows original imagereturn; }按钮的回调函数获得两个参数其状态和可选指向用户数据的指针。 在showUI示例中我们向您展示如何传递一个整数radioboxCallBack(int state, void *id)来标识按钮和一个更复杂的对象pushbuttonCallBack(int, void *font)。 绘制和显示文本 将某些图像处理的结果传达给用户的一种非常有效的方法是通过在正在处理的图形上绘制形状或/和显示文本。 通过imgproc模块OpenCV 提供了一些便捷的功能来完成诸如放置文本绘图线圆椭圆矩形多边形等任务。showUI示例说明了如何在图像上选择矩形区域并绘制矩形以标记所选区域。 以下函数在图像上绘制img该图像由两个点p1p2定义并带有指定的颜色和其他可选参数作为厚度对于填充形状为负和线型 void rectangle(InputOutputArray img, Point pt1, Point pt2,const Scalar color, int thickness1,int lineTypeLINE_8, int shift0 )除了形状的图形支持之外imgproc模块还提供了一个函数可以使用以下函数在图像上放置文本 void putText(InputOutputArray img, const String text, Point org, int fontFace, double fontScale, Scalar color, int thickness1, int lineTypeLINE_8, bool bottomLeftOriginfalse )注意 可以在core.hpp头文件中检查文本的可用字体。 在highgui模块中对 Qt 的支持增加了一些其他方法来在 OpenCV 应用的主窗口上显示文本 图像上方的文本我们使用addText(const Mat img, const String text, Point org, const QtFont font)函数获得此结果。 此函数允许您使用先前用fontQt(const String nameFont, int pointSize-1, Scalar colorScalar::all(0), int weightQT_FONT_NORMAL, int styleQT_STYLE_NORMAL, int spacing0)函数创建的字体为显示的文本选择起点。 在showUI示例中此函数用于在单击按钮时在图像上放置文本从而在回调函数中调用addText函数。状态栏上的文本使用displayStatusBar(const String winname, const String text, int delayms0 )函数在状态栏上中显示由最后一个参数delayms给出的毫秒的文本。 在showUI示例中当属性窗口的按钮和轨迹栏更改其状态时此函数在回调函数中用于显示内容丰富的文本。覆盖在图像上的文本使用displayOverlay(const String winname, const String text, int delayms0)函数将显示在图像上的文本显示最后一个参数给出的毫秒数。 在showUI示例中当主窗口跟踪栏更改其值时此函数在回调函数中用于显示内容丰富的文本。 总结 在本章中您快速了解了 OpenCV 库及其模块的主要用途。 您了解了如何在本地系统中编译安装和使用该库来开发具有 Qt 支持的 C OpenCV 应用的基础。 为了开发自己的软件我们解释了如何从免费的 Qt Creator IDE 和 GNU 编译器工具包开始。 首先本章提供了完整的代码示例。 这些示例向您展示了如何读写图像和视频。 最后本章为您提供了一个示例该示例在 OpenCV 程序中显示一些易于实现的用户界面功能例如轨迹栏按钮在图像上放置文本绘制形状等。 下一章将致力于建立主要的图像处理工具和任务这些工具和任务将为其余各章奠定基础。 二、建立图像处理工具 本章介绍将在后续章节中使用的主要数据结构和基本过程 图片类型像素存取图像的基本操作直方图 这些是我们必须对图像执行的一些最常见的操作。 此处介绍的大多数功能都在库的核心模块中。 基本数据类型 OpenCV 中的基本数据类型为Mat因为它用于存储图像。 基本上图像存储为标题和包含像素数据的存储区。 图像具有多个通道。 灰度图像具有单个通道而彩色图像通常具有三个用于红色绿色和蓝色分量的通道尽管 OpenCV 以相反的顺序存储它们即蓝色绿色和红色。 也可以使用第四个透明度alpha通道。 可以使用img.channels()检索img图像的通道数。 图像中的每个像素都使用许多位存储。 这称为图像深度。 对于灰度图像像素通常以 8 位存储因此允许 256 个灰度级整数值 0 到 255。 对于彩色图像每个像素存储在三个字节中每个彩色通道存储一个字节。 在某些操作中有必要以浮点格式存储像素。 可以使用img.depth()获得图像深度并且返回的值为 CV_8U8 位无符号整数0..255CV_8S8 位有符号整数-128..127CV_16U16 位无符号整数0..65,535CV_16S16 位有符号整数-32,768..32,767CV_32S32 位有符号整数-2,147,483,648..2,147,483,647CV_32F32 位浮点数CV_64F64 位浮点数 请注意对于灰度图像和彩色图像最常见的图像深度均为CV_8U。 可以使用convertTo方法从一种深度转换为另一种深度 Mat img imread(lena.png, IMREAD_GRAYSCALE); Mat fp; img.convertTo(fp,CV_32F);在浮点图像上执行运算是很常见的也就是说像素值是数学运算的结果。 如果使用imshow()显示该图像则将看不到任何有意义的内容。 在这种情况下我们必须将像素转换为0..255的整数范围。 convertTo函数实现了线性变换并具有两个附加参数alpha和beta分别代表比例因子和要添加的增量值。 这意味着每个像素p都将转换为 newp alpha * p beta这可用于正确显示浮点图像。 假设img图像具有m和M最小值和最大值请参阅下面的代码以了解如何获得这些值我们将使用此值 Mat m1 Mat(100, 100, CV_32FC1); randu(m1, 0, 1e6); // random values between 0 and 1e6 imshow(original, m1); double minRange,MaxRange; Point mLoc,MLoc; minMaxLoc(m1,minRange,MaxRange,mLoc,MLoc); Mat img1; m1.convertTo(img1,CV_8U,255.0/(MaxRange-minRange),-255.0/minRange); imshow(result, img1);This code maps the range of the result image values to the range 0-255. The following image shows you the result of running the code: convertTo的结果请注意左侧图像显示为白色 图像大小可以通过row和cols属性获得。 还有一个size属性可以检索这两个属性 MatSize s img.size; int rl[0]; int cl[1];除了图像本身外其他数据类型也很常见。 请参考下表 类型类型关键字示例小向量VecAB其中A可以是 2、3、4、5 或 6B可以是bsif或dVec3b rgb; rgb[0]255;最多 4 个标量ScalarScalar a; a[0]0; a[1]0;点PointAB其中A可以是2或3而B可以是if或dPoint3d p; p.x0; p.y0; p.z0;尺寸SizeSize s; s.width30; s.height40;长方形RectRect r; r.xr.y0; r.widthr.height100; 其中一些类型具有其他操作。 例如我们可以检查一个点是否位于矩形内 p.inside(r)p和r自变量分别是二维点和矩形。 请注意在任何情况下上表都不是完整的。 OpenCV 提供了更多支持结构以及相关方法。 像素级访问 要处理图像我们必须知道如何独立访问每个像素。 OpenCV 提供了许多方法来执行此操作。 在本节中我们介绍两种方法 第一个对于程序员来说很容易而第二个效率更高。 第一种方法使用at模板函数。 为了使用它我们必须指定矩阵像元的类型例如下面的简短示例 Mat src1 imread(lena.jpg, IMREAD_GRAYSCALE); uchar pixel1src1.atuchar(0,0); cout Value of pixel (0,0): (unsigned int)pixel1 endl; Mat src2 imread(lena.jpg, IMREAD_COLOR); Vec3b pixel2 src2.atVec3b(0,0); cout B component of pixel (0,0): (unsigned int)pixel2[0] endl;该示例读取灰度和彩色图像并访问(0, 0)处的第一个像素。 在第一种情况下像素类型为unsigned char即uchar。 在第二种情况下当以全色读取图像时我们必须使用Vec3b类型它是指未签名字符的三元组。 当然at函数也可以出现在分配的左侧即更改像素的值。 以下是另一个简短示例其中使用此方法将浮点矩阵初始化为 Pi 值 Mat M(200, 200, CV_64F); for(int i 0; i M.rows; i)for(int j 0; j M.cols; j)M.atdouble(i,j)CV_PI;请注意at方法不是很有效因为它必须从像素行和列中计算出确切的存储位置。 当我们逐像素处理整个图像时这可能会非常耗时。 第二种方法使用ptr函数该函数返回指向特定图像行的指针。 以下代码段获取彩色图像中每个像素的像素值 uchar R, G, B;for (int i 0; i src2.rows; i){Vec3b* pixrow src2.ptrVec3b(i); for (int j 0; j src2.cols; j){B pixrow[j][0];G pixrow[j][1];R pixrow[j][2];}}在上面的示例中ptr是用于获取指向每一行中第一个像素的指针。 使用此指针我们现在可以访问最内层循环中的每一列。 测量时间 处理图像需要花费时间比处理 1D 数据要花费的时间要多得多。 通常处理时间是决定解决方案是否可行的关键因素。 OpenCV 提供了两个测量经过时间的函数getTickCount()和getTickFrequency()。 您将像这样使用它们 double t0 (double)getTickCount(); // your stuff here ... elapsed ((double)getTickCount() – t0)/getTickFrequency();在这里elapsed以秒为单位。 图像的常用操作 下表总结了最典型的图像操作 设定矩阵值 img.setTo(0); // for 1-channel img img.setTo(Scalar(B,G,R); // 3-channel imgMATLAB 样式的矩阵初始化 Mat m1 Mat::eye(100, 100, CV_64F); Mat m3 Mat::zeros(100, 100, CV_8UC1); Mat m2 Mat::ones(100, 100, CV_8UC1)*255;随机初始化 Mat m1 Mat(100, 100, CV_8UC1); randu(m1, 0, 255);创建矩阵的副本 Mat img1 img.clone();创建矩阵的副本使用遮罩 img.copy(img1, mask);引用子矩阵不复制数据 Mat img1 img (Range(r1,r2),Range(c1,c2));图像裁剪 Rect roi(r1,c2, width, height); Mat img1 img(roi).clone(); // data copied调整图片大小 resize(img, imag1, Size(), 0.5, 0.5); // decimate by a factor of 2翻转图片 flip(imgsrc, imgdst, code); // code0 vertical flipping // code0 horizontal flipping // code0 vertical horizontal flipping 分割通道 Mat channel[3]; split(img, channel); imshow(B, channel[0]); // show blue合并通道 merge(channel,img);计算非零像素 int nz countNonZero(img);最小和最大 double m,M; Point mLoc,MLoc; minMaxLoc(img,m,M,mLoc,MLoc);平均像素值 Scalar m, stdd; meanStdDev(img, m, stdd); uint mean_pxl mean.val[0];检查图像数据是否为空 If (img.empty())cout couldnt load image;算术运算 算术运算符已重载。 这意味着我们可以像在此示例中一样对Mat图像进行操作 imgblend 0.2*img1 0.8*img2;在 OpenCV 中运算的结果值受所谓的饱和算法的影响。 这意味着最终值实际上是0..255范围内最接近的整数。 使用掩码时按位运算bitwise_and()bitwise_or()bitwise_xor()和bitwise_not()可能非常有用。 遮罩是二进制图像指示要在其中执行操作的像素而不是整个图像。 以下bitwise_and示例向您展示了如何使用 AND 运算来裁剪图像的一部分 #include opencv2/opencv.hppusing namespace cv; using namespace std;int main() {Mat img1 imread(lena.png, IMREAD_GRAYSCALE);if (img1.empty()){cout Cannot load image! endl;return -1;}imshow(Original, img1); // Original// Create mask imageMat mask(img1.rows, img1.cols, CV_8UC1, Scalar(0,0,0));circle(mask, Point(img1.rows/2,img1.cols/2), 150, 255, -1);imshow(Mask,mask);// perform ANDMat r;bitwise_and(img1,mask,r);// fill outside with whiteconst uchar white 255;for(int i 0; i r.rows; i)for(int j 0; j r.cols; j)if (!mask.atuchar(i,j))r.atuchar(i,j)white;imshow(Result,r);waitKey(0);return 0; }阅读并显示输入的图像后我们通过绘制一个填充的白色圆圈来创建遮罩。 在 AND 操作中使用此掩码。 逻辑运算仅适用于掩码值不为零的像素 其他像素不受影响。 最后在此示例中我们用白色填充结果图像的外部即圆的外部。 使用前面说明的像素访问方法之一完成此操作。 在以下屏幕截图中查看生成的图像 bitwise_and示例的结果 接下来显示另一个很酷的示例其中我们估计 Pi 的值。 让我们考虑一个正方形及其封闭的圆 它们的面积由下式给出 由此我们有 假设我们的边长未知的正方形图像和一个封闭的圆。 我们可以通过在图像中的随机位置绘制许多像素并计算落入封闭圆内的像素来估计封闭圆的面积。 另一方面正方形的面积估计为绘制的像素总数。 这样一来您可以使用前面的公式估计 Pi 的值。 以下算法对此进行了模拟 在黑色正方形图像上绘制一个实心白色封闭的圆圈。在另一个黑色正方形图像相同尺寸上在随机位置上绘制大量像素。在两个图像之间执行“与”运算并计算结果图像中的非零像素。使用等式估计 Pi。 以下是EstimatePi示例的代码 #include opencv2/opencv.hppusing namespace cv; using namespace std;int main() {const int side100;const int npixels8000;int i,j;Mat s1Mat::zeros(side, side, CV_8UC1);Mat s2s1.clone();circle(s1, Point(side/2, side/2), side/2, 255, -1);imshow(s1,s1);for (int k0;knpixels;k){i rand()%side;j rand()%side;s2.atuchar(i,j)255;}Mat r;bitwise_and(s1,s2,r);imshow(s2, s2);imshow(r, r);int Acircle countNonZero(r);int Asquare countNonZero(s2);float Pi4*(float)Acircle/Asquare;cout Estimated value of Pi: Pi endl;waitKey();return 0; }程序完全遵循上述算法。 请注意我们使用countNonZero函数对非零在这种情况下为白色像素进行计数。 对于npixels8000估计为 3.125。 npixels的值越大估计越好。 EstimatePi示例的输出 数据持久化 在 OpenCV 中除了读取和写入图像和视频的特定功能外还有一种更通用的保存/加载数据的方法。 这称为数据持久性可以将程序中对象和变量的值记录序列化在磁盘上。 这对于保存结果和加载配置数据非常有用。 主类的名称恰当地命名为FileStorage它表示磁盘上的文件。 数据实际上以 XML 或 YAML 格式存储。 这些是写入数据时涉及的步骤 调用FileStorage构造器并传递文件名和带有FileStorage::WRITE值的标志。 数据格式由文件扩展名定义即.xml.yml或.yaml。使用操作符将数据写入文件。 数据通常写为字符串值对。使用release方法关闭文件。 读取数据要求您执行以下步骤 调用FileStorage构造器并传递文件名和带有FileStorage::READ值的标志。使用[]或操作符从文件中读取数据。使用release方法关闭文件。 以下示例使用数据持久性来保存和加载跟踪栏值。 #include opencv2/opencv.hppusing namespace cv; using namespace std;Mat img1;void tb1_Callback(int value, void *) {Mat temp img1 value;imshow(main_win, temp); }int main() {img1 imread(lena.png, IMREAD_GRAYSCALE);if (img1.empty()){cout Cannot load image! endl;return -1;}int tb1_value 0;// load trackbar valueFileStorage fs1(config.xml, FileStorage::READ);tb1_valuefs1[tb1_value]; // method 1fs1[tb1_value] tb1_value; // method 2fs1.release();// create trackbarnamedWindow(main_win);createTrackbar(brightness, main_win, tb1_value,255, tb1_Callback);tb1_Callback(tb1_value, NULL);waitKey();// save trackbar value upon exitingFileStorage fs2(config.xml, FileStorage::WRITE);fs2 tb1_value tb1_value;fs2.release();return 0; }提示 使用 Qt 支持编译 OpenCV 后可以使用saveWindowParameters()函数保存窗口属性包括轨迹栏值。 一旦跟踪栏用于控制整数值就可以将其简单地添加到原始图像中使其更明亮。 程序启动时读取该值该值首次为 0并在程序正常退出时保存。 注意显示了两种等效的方法来读取tb1_值变量的值。 config.xml文件的内容是 ?xml version1.0? opencv_storage tb1_value112/tb1_value /opencv_storage直方图 一旦使用数据类型定义了图像并且我们能够访问其灰度值即像素我们可能希望获得不同灰度的概率密度函数称为直方图 。 图像直方图表示图像中各种灰度等级的出现频率。 可以对直方图进行建模以便图像可以更改其对比度级别。 这被称为直方图均衡。 直方图建模是一种通过对比度变化来增强图像的强大技术。 均衡允许较低对比度的图像区域获得较高的对比度。 下图显示了均衡图像及其直方图的示例 均衡图像直方图的示例 在 OpenCV 中可以通过void calcHist函数计算图像直方图并通过void equalizeHist函数进行直方图均衡。 图像直方图计算由十个参数定义void calcHist(const Mat* imagesint nimagesconst int* channelsInputArray maskOutputArray histint dimsconst int* histSizeconst float** rangesbool uniformtrue和bool accumulatefalse) 。 const Mat* images第一个参数是来自集合的第一个图像的地址。 这可用于处理一批图像。int nimages第二个参数是源图像的数量。const int* channels第三个输入参数是用于计算直方图的通道列表。 通道数从 0 到 2。InputArray mask这是一个可选的遮罩用于指示直方图中计数的图像像素。OutputArray hist第五个参数是输出直方图。int dims此参数允许您指示直方图的尺寸。const int* histSize此参数是每个维度中直方图大小的数组。const float** ranges此参数是每个维度中直方图箱子边界的维度数组。bool uniformtrue默认情况下布尔值为true。 它表示直方图是均匀的。bool accumulatefalse默认情况下布尔值为false。 它表明直方图是非累积的。 直方图均衡化仅需要两个参数void equalizeHist(InputArray src, OutputArray dst)。 第一个参数是输入图像第二个参数是直方图均等的输出图像。 可以计算多个输入图像的直方图。 这使您可以比较图像直方图并计算多个图像的联合直方图。 可以使用void compareHist(InputArray histImage1, InputArray histImage2, method)函数对histImage1和histImage2这两个图像直方图进行比较。 Method度量用于计算两个直方图之间的匹配。 OpenCV 中实现了四个度量即相关性CV_COMP_CORREL卡方CV_COMP_CHISQR交点或最小距离CV_COMP_INTERSECT和 Bhattacharyya 距离CV_COMP_BHATTACHARYYA。 可以计算同一彩色图像的一个以上通道的直方图。 这要归功于第三个参数。 以下各节向您展示和用于颜色直方图计算的两个示例代码ColourImageEqualizeHist和比较ColourImageComparison。 在ColourImageComparison示例中在ColourImageEqualizeHist中还显示了如何计算两个通道的直方图均衡以及 2D 直方图即色调H和饱和度S。 示例代码 以下ColourImageEqualizeHist示例向您展示如何均衡彩色图像并同时显示每个通道的直方图。 RGB 图像中每个颜色通道的直方图计算均使用histogramcalculation(InputArray Imagesrc, OutputArray histoImage)函数完成。 为此将彩色图像分为通道RG 和 B。直方图均衡化也应用于每个通道然后合并以形成均衡的彩色图像 #include opencv2/highgui/highgui.hpp #include opencv2/imgproc/imgproc.hpp #include iostream #include stdio.husing namespace cv; using namespace std;void histogramcalculation(const Mat Image, Mat histoImage) {int histSize 255;// Set the ranges ( for B,G,R) )float range[] { 0, 256 } ;const float* histRange { range };bool uniform true; bool accumulate false;Mat b_hist, g_hist, r_hist;vectorMat bgr_planes;split(Image, bgr_planes );// Compute the histograms:calcHist( bgr_planes[0], 1, 0, Mat(), b_hist, 1, histSize, histRange, uniform, accumulate );calcHist( bgr_planes[1], 1, 0, Mat(), g_hist, 1, histSize, histRange, uniform, accumulate );calcHist( bgr_planes[2], 1, 0, Mat(), r_hist, 1, histSize, histRange, uniform, accumulate );// Draw the histograms for B, G and Rint hist_w 512; int hist_h 400;int bin_w cvRound( (double) hist_w/histSize );Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );// Normalize the result to [ 0, histImage.rows ]normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );// Draw for each channelfor( int i 1; i histSize; i ){line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.atfloat(i-1)) ) , Point( bin_w*(i), hist_h - cvRound(b_hist.atfloat(i)) ), Scalar( 255, 0, 0), 2, 8, 0 );line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.atfloat(i-1)) ) , Point( bin_w*(i), hist_h - cvRound(g_hist.atfloat(i)) ), Scalar( 0, 255, 0), 2, 8, 0 );line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.atfloat(i-1)) ) , Point( bin_w*(i), hist_h - cvRound(r_hist.atfloat(i)) ), Scalar( 0, 0, 255), 2, 8, 0 );}histoImage histImage; }int main( int, char *argv[] ) {Mat src, imageq;Mat histImage;// Read original imagesrc imread( fruits.jpg);if(! src.data ){ printf(Error imagen\n); exit(1); }// Separate the image in 3 places ( B, G and R )vectorMat bgr_planes;split( src, bgr_planes );// Display resultsimshow( Source image, src );// Calculate the histogram to each channel of the source imagehistogramcalculation(src, histImage);// Display the histogram for each colour channelimshow(Colour Image Histogram, histImage );// Equalized Image// Apply Histogram Equalization to each channelequalizeHist(bgr_planes[0], bgr_planes[0]);equalizeHist(bgr_planes[1], bgr_planes[1]);equalizeHist(bgr_planes[2], bgr_planes[2]);// Merge the equalized image channels into the equalized imagemerge(bgr_planes, imageq );// Display Equalized Imageimshow( Equalized Image , imageq );// Calculate the histogram to each channel of the equalized imagehistogramcalculation(imageq, histImage);// Display the Histogram of the Equalized Imageimshow(Equalized Colour Image Histogram, histImage );// Wait until user exits the programwaitKey();return 0; }示例创建四个带有以下内容的窗口 源图像这是下图中左上角显示的。均匀彩色图像这是下图中右上角显示的。三个通道的直方图对于源图像此处 R 为红色G 为绿色B 为蓝色。 下图的左下角显示了该内容。均衡图像的 RGB 通道的直方图在下图右下角中显示。 该图显示了由于均衡过程如何延长了 RG 和 B 的最频繁强度值。 下图显示了该算法的结果 示例代码 以下ColourImageComparison示例显示了如何从同一彩色图像计算由两个通道组成的 2D 直方图。 示例代码还通过直方图匹配在原始图像和均衡图像之间执行比较。 用于匹配的度量是前面已经提到的四个度量即“相关”“卡方”“最小距离”和“Bhattacharyya 距离”。 H 和 S 颜色通道的 2D 直方图计算是通过histogram2Dcalculation(InputArray Imagesrc, OutputArray histo2D)函数完成的。 为了执行直方图比较已经为 RGB 图像计算了标准化的 1D 直方图。 为了比较直方图已将归一化。 这是在histogramRGcalculation(InputArray Imagesrc, OutputArray histo)中完成的 void histogram2Dcalculation(const Mat src, Mat histo2D) {Mat hsv;cvtColor(src, hsv, CV_BGR2HSV);// Quantize the hue to 30 -255 levels// and the saturation to 32 - 255 levelsint hbins 255, sbins 255;int histSize[] {hbins, sbins};// hue varies from 0 to 179, see cvtColorfloat hranges[] { 0, 180 };// saturation varies from 0 (black-gray-white) to// 255 (pure spectrum color)float sranges[] { 0, 256 };const float* ranges[] { hranges, sranges };MatND hist, hist2;// we compute the histogram from the 0-th and 1-st channelsint channels[] {0, 1};calcHist( hsv, 1, channels, Mat(), hist, 1, histSize, ranges, true, false );double maxVal0;minMaxLoc(hist, 0, maxVal, 0, 0);int scale 1;Mat histImg Mat::zeros(sbins*scale, hbins*scale, CV_8UC3);for( int h 0; h hbins; h )for( int s 0; s sbins; s ){float binVal hist.atfloat(h, s);int intensity cvRound(binVal*255/maxVal);rectangle( histImg, Point(h*scale, s*scale),Point( (h1)*scale - 1, (s1)*scale - 1),Scalar::all(intensity),CV_FILLED );}histo2DhistImg; }void histogramRGcalculation(const Mat src, Mat histoRG) {// Using 50 bins for red and 60 for greenint r_bins 50; int g_bins 60;int histSize[] { r_bins, g_bins };// red varies from 0 to 255, green from 0 to 255float r_ranges[] { 0, 255 };float g_ranges[] { 0, 255 };const float* ranges[] { r_ranges, g_ranges };// Use the o-th and 1-st channelsint channels[] { 0, 1 };// HistogramsMatND hist_base;// Calculate the histograms for the HSV imagescalcHist( src, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );histoRGhist_base;}int main( int argc, char *argv[]) {Mat src, imageq;Mat histImg, histImgeq;Mat histHSorg, histHSeq;// Read original imagesrc imread( fruits.jpg);if(! src.data ){ printf(Error imagen\n); exit(1); }// Separate the image in 3 places ( B, G and R )vectorMat bgr_planes;split( src, bgr_planes );// Display resultsnamedWindow(Source image, 0 );imshow( Source image, src );// Calculate the histogram of the source imagehistogram2Dcalculation(src, histImg);// Display the histogram for each colour channelimshow(H-S Histogram, histImg );// Equalized Image// Apply Histogram Equalization to each channelequalizeHist(bgr_planes[0], bgr_planes[0] );equalizeHist(bgr_planes[1], bgr_planes[1] );equalizeHist(bgr_planes[2], bgr_planes[2] );// Merge the equalized image channels into the equalized imagemerge(bgr_planes, imageq );// Display Equalized ImagenamedWindow(Equalized Image, 0 );imshow(Equalized Image, imageq );// Calculate the 2D histogram for H and S channelshistogram2Dcalculation(imageq, histImgeq);// Display the 2D Histogramimshow( H-S Histogram Equalized, histImgeq );histogramRGcalculation(src, histHSorg);histogramRGcalculation(imageq, histHSeq);/// Apply the histogram comparison methodsfor( int i 0; i 4; i ){int compare_method i;double orig_orig compareHist( histHSorg, histHSorg, compare_method );double orig_equ compareHist( histHSorg, histHSeq, compare_method );printf( Method [%d] Original-Original, Original-Equalized : %f, %f \n, i, orig_orig, orig_equ );}printf( Done \n );waitKey(); }示例使用源图像均等的彩色图像以及 2 个原始图像和均化图像的 H 和 S 通道的 2D 直方图创建四个窗口。 该算法还显示从原始 RGB 图像直方图与其自身以及与均衡后的 RGB 图像进行比较所获得的四个数值匹配参数。 对于相关和相交方法度量越高匹配越精确。 对于卡方距离和 Bhattacharyya 距离结果越少匹配越好。 下图显示了ColourImageComparison算法的输出 最后您可以参考第 3 章“校正和增强图像”以及其中的示例以涵盖此广泛主题的基本方面例如通过直方图建模来增强图像。 注意 有关更多信息请参阅OpenCV Essentials, Deniz O., Fernández M.M., Vállez N., Bueno G., Serrano I., Patón .A., Salido J. by Packt Publishing。 总结 本章涵盖并建立了应用计算机视觉中使用的图像处理方法的基础。 图像处理通常是进一步实现计算机视觉应用的第一步因此这里已涉及到基本数据类型像素级访问图像的常规操作算术运算数据持久性和直方图。 您还可以参考 Packt Publishing 的《OpenCV Essentials》的第 3 章“校正和增强图像”以涵盖该广泛主题的其他重要方面例如图像增强通过滤波的图像恢复以及几何校正。 下一章将介绍通过平滑锐化图像分辨率分析形态和几何变换修复和去噪来校正和增强图像的图像处理的其他方面。 三、校正和增强图像 本章介绍了图像增强和校正的方法。 有时有必要减少图像中的噪点或强调或抑制图像中的某些细节。 这些过程通常是通过修改像素值对它们或它们的本地邻居执行一些操作来执行的。 根据定义图像增强操作用于改善重要的图像细节。 增强操作包括降噪平滑和边缘增强。 另一方面图像校正尝试恢复损坏的图像。 在 OpenCV 中imgproc模块包含图像处理功能。 在本章中我们将介绍 图像过滤。 这包括图像平滑图像锐化以及使用图像金字塔。应用形态学操作例如扩张腐蚀打开或关闭。几何变换仿射和透视变换。修复用于重建图像的受损部分。去噪这对于减少图像捕获设备产生的图像噪声是必需的。 图像过滤 图像过滤是修改或增强图像的过程。 强调某些特征或去除图像中的其他特征是图像过滤的示例。 过滤是一种邻居操作。 邻域是所选像素周围的一组像素。 图像滤波通过对附近像素的像素值执行某些操作来确定位于位置(x, y)的特定像素的输出值。 OpenCV 为常见的图像处理操作例如平滑或锐化提供几种过滤功能。 平滑 平滑也称为“模糊”的是一种图像处理操作除其他用途外通常用于减少噪声。 通过对图像应用线性过滤器来执行平滑操作。 然后将位置(x[i], y[j])处的输出的像素值计算为位置(x[i], y[j])处的输入像素值及其附近的加权和。 线性运算中像素的权重通常存储在称为核的矩阵中。 因此过滤器可以表示为系数的滑动窗口。 像素邻域的表示 假设K为核I和O分别为输入图像和输出图像。 然后在(i, j)处的每个输出像素值计算如下 中值高斯和双边是最常用的 OpenCV 平滑过滤器。 中值滤波非常适合消除椒盐或斑点噪声而高斯滤波则是边缘检测的更好的预处理步骤。 另一方面双边滤波是一种在尊重强边缘的同时平滑图像的好技术。 为此OpenCV 中包含的函数是 void boxFilter(InputArray src, OutputArray dst, int ddepth, Size ksize, Point anchor Point(-1,-1), bool normalize true, int borderType BORDER_DEFAULT)这是一个盒子过滤器其核系数相等。 使用normalizetrue时每个输出像素值都是其核邻居的平均值所有系数均等于1 / n其中n为元素数。 使用normalizefalse时所有系数都等于 1。src参数是输入图像而滤波后的图像存储在dst中。 ddepth参数指示输出图像深度为 -1以使用与输入图像相同的深度。 核大小在ksize中指示。 anchor点指示所谓的锚点像素的位置。 默认值(-1, -1)表示锚点位于核的中心。 最后在borderType参数中指示边界类型的处理。 void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY 0, int borderTypeBORDER_DEFAULT): This is done by convolving each point in the src input array with a Gaussian kernel to produce the dst output. The sigmaX and sigmaY parameters indicate the Gaussian kernel standard deviation in X and Y directions. If sigmaY is zero, it is set to be equal to sigmaX, and if both are equal to zero, they are computed using the width and height given in ksize. 注意 卷积定义为两个函数乘积的积分其中两个函数之一先前已被反转和移位。 void medianBlur(InputArray src, OutputArray dst, int ksize)这遍历图像的每个元素并将每个像素替换为其相邻像素的中间值。 void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderTypeBORDER_DEFAULT)这类似于高斯过滤器其中考虑了相邻像素每个像素都分配有权重但每个权重上都有两个分量这与高斯过滤器使用的分量相同而另一个考虑了相邻像素和评估像素之间的强度。 此函数需要像素邻域的直径作为参数d和sigmaColor sigmaSpace值。 sigmaSpace参数的较大值表示像素邻域内的其他颜色将混合在一起从而生成较大的半均等颜色区域而sigmaSpace参数的较大值表示较远像素将彼此影响。 只要它们的颜色足够接近。 void blur(InputArray src, OutputArray dst, Size ksize, Point anchorPoint(-1,-1), int borderTypeBORDER_DEFAULT): This blurs an image using the normalized box filter. It is equivalent to using boxFilter with normalize true. The kernel used in this function is: 注意 getGaussianKernel和getGaborKernel函数可以在 OpenCV 中使用以生成自定义核然后可以将其传递给filter2D。 在所有情况下都必须外推图像边界外不存在的像素的值。 OpenCV 允许在大多数过滤器函数中指定外推方法。 这些方法是 BORDER_REPLICATE此操作重复上一个已知的像素值aaaaaa | abcdefgh | hhhhhhhBORDER_REFLECT这反映了图像边框fedcba | abcdefgh | hgfedcbBORDER_REFLECT_101这将反映图像边框而不复制边框的最后一个像素gfedcb | abcdefgh | gfedcbaBORDER_WRAP这将追加相对边框的值cdefgh | abcdefgh | abcdefgBORDER_CONSTANT这将在新边界上建立一个常数kkkkkk | abcdefgh | kkkkkk 示例代码 平滑示例之后的向您展示了如何通过GaussianBlur和medianBlur函数加载图像并对其应用高斯和中值模糊 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Apply the filtersMat dst, dst2;GaussianBlur( src, dst, Size( 9, 9 ), 0, 0);medianBlur( src, dst2, 9);// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( GAUSSIAN BLUR , WINDOW_AUTOSIZE );imshow( GAUSSIAN BLUR , dst );namedWindow( MEDIAN BLUR , WINDOW_AUTOSIZE );imshow( MEDIAN BLUR , dst2 );waitKey();return 0; }下图显示了代码的输出 来自高斯和中值模糊变换的原始和模糊图像 锐化 锐化过滤器用于突出显示图像中的边框和其他精细细节。 它们基于一阶和二阶导数。 图像的一阶导数计算图像强度梯度的近似值而二阶导数定义为该梯度的散度。 由于数字图像处理处理离散量像素值因此将一阶和二阶导数的离散版本用于锐化。 一阶导数产生较厚的图像边缘并广泛用于边缘提取。 但是由于二阶导数对精细细节的响应更好因此可用于图像增强。 用于获得导数的两种流行的运算符是 Sobel 和 Laplacian。 Sobel 运算符通过以下方式计算图像I的一阶图像导数 可以通过组合两个方向上的梯度近似值来获得 Sobel 梯度幅度如下所示 另一方面可以将图像的离散拉普拉斯算子与以下核进行卷积 为此OpenCV 中包含的函数是 void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize 3, double scale 1, double delta 0, int borderType BORDER_DEFAULT)这将根据src中的图像使用 Sobel 运算符计算一阶二阶三阶或混合图像导数。 ddepth参数指示输出图像深度即 -1 至使用与输入图像相同的深度。 籽粒大小在ksize中指示所需的导数阶数在dx和dy中指示。 可以使用scale建立计算得出的微分值的比例因子。 最后在borderType参数中指示边界类型处理并且可以在将结果存储在dst中之前将delta值添加到结果中。void Scharr(InputArray src, OutputArray dst, int ddepth, int dx, int dy, double scale 1, double delta 0, int borderType BORDER_DEFAULT )这为大小为3 x 3的核计算了一个更准确的导数。Scharr(src, dst, ddepth, dx, dy, scale, delta, borderType)等效于Sobel(src, dst, ddepth, dx, dy, CV_SCHARR, scale, delta, borderType)。void Laplacian(InputArray src, OutputArray dst, int ddepth, int ksize 1, double scale 1, double delta 0, int borderType BORDER_DEFAULT)这将计算图像的拉普拉斯算子。 除了ksize以外所有参数均与Sobel和Scharr函数中的参数相同。 当ksize 为 1 时它通过将使用Sobel计算的第二x和y导数相加来计算src中图像的拉普拉斯算子。 当ksize 1时通过用3 x 3核对图像进行滤波来计算拉普拉斯算子该核包含 -4 为中心0 为角其余为 1。 注意 getDerivKernels可以在 OpenCV 中使用以生成自定义的派生核然后可以将其传递给sepFilter2D。 示例代码 锐化示例之后的展示了如何通过Sobel和Laplacian函数从图像计算 Sobel 和 Laplacian 导数。 示例代码为 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Apply Sobel and LaplacianMat dst, dst2;Sobel(src, dst, -1, 1, 1 );Laplacian(src, dst2, -1 );// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( SOBEL , WINDOW_AUTOSIZE );imshow( SOBEL , dst );namedWindow( LAPLACIAN , WINDOW_AUTOSIZE );imshow( LAPLACIAN , dst2 );waitKey(); return 0; }下图显示了代码的输出 通过 Sobel 和 Laplacian 派生获得的轮廓 使用图像金字塔 在某些场合下无法使用固定的图像尺寸进行操作并且我们将需要具有不同分辨率的原始图像。 例如在对象检测问题中检查整个图像以尝试查找对象会花费太多时间。 在这种情况下以较小的分辨率开始搜索对象会更有效。 这种图像集称为金字塔或 mipmap因为如果图像从底部到顶部按从大到小的顺序排列则与金字塔结构类型相似 。 高斯金字塔 有两种图像金字塔高斯金字塔和拉普拉斯金字塔。 高斯金字塔 高斯金字塔是通过交替除去较低级别的行和列然后通过使用来自底层级别的邻域应用高斯过滤器来获得较高级别像素的值来创建的。 在每个金字塔步骤之后图像将其宽度和高度减小一半并且其面积是上一级图像面积的四分之一。 在 OpenCV 中可以使用pyrDownpyrUp和buildPyramid函数来计算高斯金字塔 void pyrDown(InputArray src, OutputArray dst, const Size dstsize Size(), int borderType BORDER_DEFAULT)此子采样会模糊src图像并将结果保存在dst中。 如果未使用dstsize参数设置输出图像的大小则将其计算为Size((src.cols1)/2, (src.rows1)/2)。void pyrUp(InputArray src, OutputArray dst, const Size dstsize Size(), int borderType BORDER_DEFAULT)计算pyrDown的相反过程。void buildPyramid(InputArray src, OutputArrayOfArrays dst, int maxlevel, int borderType BORDER_DEFAULT)这将为src中存储的图像构建高斯金字塔获取maxlevel新图像然后将它们存储在dst[0]中原始图像之后的dst数组中。 因此dst结果存储了maxlevel 1图像。 金字塔也用于分割。 OpenCV 提供了一个基于均值漂移分割算法第一步来计算均值漂移金字塔的函数 void pyrMeanShiftFiltering(InputArray src, OutputArray dst, double sp, double sr, int maxLevel 1, TermCriteria termcrit TermCriteria (TermCriteria::MAX_ITER TermCriteria::EPS, 5, 1)): This implements the filtering stage of the mean-shift segmentation, obtaining an image, dst, with color gradients and fine-grain texture flattened. The sp and sr parameters indicate the spatial window and the color window radii. 注意 可以在这个页面中找到有关均值漂移分割的更多信息。 拉普拉斯金字塔 拉普拉斯金字塔在 OpenCV 中没有特定的函数实现但它们是由高斯金字塔形成的。 拉普拉斯金字塔可视为边界图像其中大部分元素为零。 拉普拉斯金字塔中的第i个级别是高斯金字塔中第i个级别与第i 1个级别的扩展版本之间的差。 高斯金字塔。 示例代码 金字塔示例之后的向您展示了如何通过pyrDown函数从高斯金字塔中获取两个级别以及如何通过pyrUp从相反的操作中获取两个级别。 请注意使用pyrUp后无法获得原始图像 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Apply two times pyrDownMat dst, dst2;pyrDown(src, dst);pyrDown(dst, dst2);// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( 1st PYRDOWN , WINDOW_AUTOSIZE );imshow( 1st PYRDOWN , dst );namedWindow( 2st PYRDOWN , WINDOW_AUTOSIZE );imshow( 2st PYRDOWN , dst2 ); // Apply two times pyrUppyrUp(dst2, dst);pyrUp(dst, src);// Show the resultsnamedWindow( NEW ORIGINAL , WINDOW_AUTOSIZE );imshow( NEW ORIGINAL , dst2 );namedWindow( 1st PYRUP , WINDOW_AUTOSIZE );imshow( 1st PYRUP , dst );namedWindow( 2st PYRUP , WINDOW_AUTOSIZE );imshow( 2st PYRUP , src );waitKey(); return 0; }下图显示了代码的输出 高斯金字塔的原始层和两个层 形态操作 形态学操作根据形状处理图像。 他们将定义的“结构元素”应用于图像从而获得新图像其中通过比较位置上的输入像素值来计算位置(x[i], y[j])上的像素(x[i], y[j])及其附近地区。 根据所选的结构元素形态操作对一种特定形状或其他形状更敏感。 两种基本的形态学操作是膨胀和侵蚀。 膨胀将像素从背景添加到图像中对象的边界而侵蚀则将像素去除。 这是在其中考虑结构元素以选择要添加或删除的像素的地方。 在扩张中输出像素的值是附近所有像素的最大值。 使用腐蚀输出像素的值是附近所有像素的最小值。 膨胀和腐蚀的例子 其他图像处理操作可以通过组合扩张和腐蚀来定义例如打开和关闭操作以及形态梯度。 打开操作的定义是腐蚀然后是膨胀而关闭是相反的操作-膨胀然后是腐蚀。 因此打开时会从图像中移除小物体同时保留较大的物体而闭合用于移除小孔而同时保留较大物体其方式类似于打开。 形态梯度定义为图像的膨胀与腐蚀之间的差异。 此外还使用打开和关闭定义了另外两个操作高帽操作和黑帽操作。 在大礼帽的情况下它们被定义为源图像与其打开之间的差异在黑帽的情况下它们被定义为图像的关闭与源图像之间的差异。 所有操作都使用相同的结构元素。 在 OpenCV 中可以通过以下函数应用膨胀腐蚀打开和关闭 void dilate(InputArray src, OutputArray dst, InputArray kernel, Point anchor Point(-1,-1), int iterations 1, int borderType BORDER_CONSTANT, const Scalar borderValue morphologyDefaultBorderValue())这会使用特定的结构化元素扩大src中存储的图像并将结果保存在dst中。 kernel参数是所使用的结构元素。 anchor点指示锚点像素的位置。 (-1, -1)值表示锚点位于中心。 使用iterations可以多次应用该操作。 边界类型的处理在borderType参数中指示与前面部分中的其他过滤器相同。 最后如果使用BORDER_CONSTANT边界类型则在borderValue中指示常量。void erode(InputArray src, OutputArray dst, InputArray kernel, Point anchor Point(-1,-1), int iterations 1, int borderType BORDER_CONSTANT, const Scalar borderValue morphologyDefaultBorderValue())这会使用特定的结构元素腐蚀图像。 其参数与dilate中的参数相同。void morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor Point(-1,-1), int iterations 1, int borderType BORDER_CONSTANT, const Scalar borderValue morphologyDefaultBorderValue())这执行使用op参数定义的高级形态学操作。 可能的op值为MORPH_OPENMORPH_CLOSEMORPH_GRADIENTMORPH_TOPHAT和MORPH_BLACKHAT。Mat getStructuringElement(int shape, Size ksize, Point anchor Point(-1,-1))这将返回指定大小和形状的结构元素以进行形态学操作。 支持的类型为MORPH_RECTMORPH_ELLIPSE和MORPH_CROSS。 示例代码 以下形态学示例向您展示了如何在棋盘格中分割红色棋子如何应用二进制阈值inRange函数然后通过膨胀和腐蚀操作通过dilate和erode函数完善结果。 使用的结构是15 x 15像素的圆圈。 示例代码为 #include opencv2/opencv.hppusing namespace cv; using namespace std;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Apply the filtersMat dst, dst2, dst3;inRange(src, Scalar(0, 0, 100), Scalar(40, 30, 255), dst);Mat element getStructuringElement(MORPH_ELLIPSE,Size(15,15));dilate(dst, dst2, element);erode(dst2, dst3, element);// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( SEGMENTED , WINDOW_AUTOSIZE );imshow( SEGMENTED , dst );namedWindow( DILATION , WINDOW_AUTOSIZE );imshow( DILATION , dst2 );namedWindow( EROSION , WINDOW_AUTOSIZE );imshow( EROSION , dst3 );waitKey();return 0; }下图显示了代码的输出 原始的红色分割膨胀和腐蚀 LUT 查找表LUT在自定义过滤器中非常常见在自定义过滤器中中两个具有相同值的像素在输入中也包含相同的值。 LUT 变换根据表给出的值为输入图像中的每个像素分配一个新的像素值。 在该表中索引表示输入强度值并且由索引给出的单元格的内容表示相应的输出值。 由于实际上是针对每个可能的强度值计算变换的因此这减少了在图像上应用变换所需的时间图像通常具有比强度值的数量更多的像素。 LUT(InputArray src, InputArray lut, OutputArray dst, int interpolation 0) OpenCV 函数对 8 位有符号或src无符号图像应用查找表转换。 因此lut参数中给出的表包含 256 个元素。 lut中的通道数为 1 或src.channels。 如果src具有多个通道但lut具有单个通道则将同一lut通道应用于所有图像通道。 示例代码 下面的 LUT 示例向您展示了如何使用查找表将图像的像素强度除以 2。 在将 LUT 与以下代码结合使用之前需要对其进行初始化 uchar * M (uchar*)malloc(256*sizeof(uchar));for(int i0; i256; i){M[i] i*0.5; //The result is rounded to an integer value}Mat lut(1, 256, CV_8UC1, M);将创建一个Mat对象其中每个单元格都包含新值。 示例代码为 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Create the LUTuchar * M (uchar*)malloc(256*sizeof(uchar));for(int i0; i256; i){M[i] i*0.5;}Mat lut(1, 256, CV_8UC1, M);// Apply the LUTMat dst;LUT(src,lut,dst);// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( LUT , WINDOW_AUTOSIZE );imshow( LUT , dst );waitKey(); return 0; }下图显示了代码的输出 原始和 LUT 转换的图像 几何变换 几何变换不会更改图像内容而是会通过使它们的网格变形来使图像变形。 在这种情况下输出图像像素值的计算方法是首先通过应用相应的映射函数获得相应输入像素的坐标然后将原始像素值从获得的位置复制到新位置 这种类型的操作有两个问题 外推f[x](x, y)和f[y](x, y)可获得表示图像外部边界像素的值。 几何变换中使用的外推方法与图像过滤中使用的外推方法相同外加另一种称为BORDER_TRANSPARENT的方法。插值f[x](x, y)和f[y](x, y)通常是浮点数。 在 OpenCV 中可以在最近邻和多项式插值方法之间进行选择。 最近邻插值包括将浮点坐标舍入到最接近的整数。 支持的插值方法是 INTER_NEAREST这是前面解释的最近邻插值。INTER_LINEAR这是一种双线性插值方法。 默认情况下使用。INTER_AREA使用像素面积关系重新采样。INTER_CUBIC这是在4 x 4像素邻域上的双三次插值方法。INTER_LANCZOS4这是在8 x 8像素邻域上的 Lanczos 插值方法。 OpenCV 支持的几何变换包括仿射缩放平移旋转等和透视变换。 仿射变换 仿射变换是几何变换在应用后保留了直线上初始线的所有点。 此外还保留了从这些点中的每一个到线的末端的距离比。 另一方面仿射变换不一定会保留角度和长度。 缩放平移旋转倾斜和反射等几何变换都是仿射变换。 缩放 缩放图像可通过缩小或缩放来调整其大小。 为此OpenCV 中的函数为void resize(InputArray src, OutputArray dst, Size dsize, double fx 0, double fy 0, int interpolation INTER_LINEAR)。 除了输入图像和输出图像src和dst外它还具有一些参数来指定图像要缩放到的尺寸。 如果通过将dsize设置为不同于 0 的值来指定新图像尺寸则缩放因子参数fx和fy均为 0并且从dsize和fx计算fx和fy。 输入图像的原始大小。 如果fx和fy不同于 0并且dsize等于 0则根据其他参数计算dsize。 缩放操作可以通过其转换矩阵表示 此处s[x]和s[y]是 x 和 y 轴上的比例因子。 示例代码 以下缩放示例显示了如何通过resize函数缩放图像。 示例代码为 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Apply the scaleMat dst;resize(src, dst, Size(0,0), 0.5, 0.5);// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( SCALED , WINDOW_AUTOSIZE );imshow( SCALED , dst );waitKey(); return 0; }下图显示了代码的输出 原始和缩放图像 fx和fy均为 0.5 平移 平移只是沿着特定的方向和距离移动图像。 因此平移可以通过向量(t[x], t[y])或其转换矩阵表示 在 OpenCV 中可以使用void warpAffine( InputArray src, OutputArray dst, InputArray M, Size dsize, int flags INTER_LINEAR, int borderMode BORDER_CONSTANT, const Scalar borderValue Scalar())函数应用平移。 M参数是将src转换为dst的转换矩阵。 使用flags参数指定插值方法该参数也支持WARP_INVERSE_MAP值这意味着M是逆变换。 borderMode参数是外推方法当borderMode为BORDER_CONSTANT时borderValue为。 示例代码 平移示例向您展示如何使用warpAffine函数平移图像。 示例代码为 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Apply translationMat dst;Mat M (Mat_double(2,3) 1, 0, 200, 0, 1, 150);warpAffine(src,dst,M,src.size());// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( TRANSLATED , WINDOW_AUTOSIZE );imshow( TRANSLATED , dst );waitKey(); return 0; }下图显示了代码的输出 原始图像和置换图像。 水平位移为 200垂直位移为 150。 图像旋转 图像旋转涉及特定角度θ。 OpenCV 使用定义如下的转换矩阵在特定位置支持缩放旋转 此处x和y是旋转点的坐标 sf 是比例因子。 旋转通过warpAffine函数像平移一样应用但使用Mat getRotationMatrix2D(Point2f center, double angle, double scale)函数创建旋转变换矩阵。 M参数是将src转换为dst的转换矩阵。 如参数名称所示center是旋转的中心点angle是旋转角度沿逆时针方向scale是比例因子。 示例代码 以下旋转示例显示了如何使用warpAffine函数旋转图像。 首先通过getRotationMatrix2D( Point2f( src.cols/2, src.rows/2 ), 45, 1 )获得 45 度中心旋转矩阵。 示例代码为 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Apply the rotationMat dst;Mat M getRotationMatrix2D(Point2f(src.cols/2,src.rows/2),45,1);warpAffine(src,dst,M,src.size());// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( ROTATED , WINDOW_AUTOSIZE );imshow( ROTATED , dst );waitKey(); return 0; }下图显示了代码的输出 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oxqcgxHx-1681871326909)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/learn-imgproc-opencv/img/7659OT_03_11.jpg)] 应用原始图像和中心旋转 45 度后的图像 倾斜 倾斜变换将每个点在固定方向上移动的距离与与平行于方向的线的其有符号距离成比例。 因此它通常会扭曲几何图形的形状例如将正方形变成非正方形的平行四边形将圆形变成椭圆。 但是倾斜会保留几何图形的面积共线点的对齐方式和相对距离。 倾斜映射是直立和倾斜或斜体字母样式之间的主要区别。 偏斜也可以通过其角度θ来定义。 原稿及其与中心图像旋转 45 度 使用偏斜角度水平和垂直偏斜的转换矩阵为 由于与先前转换的相似性用于应用倾斜的函数为warpAffine。 提示 在大多数情况下有必要为输出图像添加一些大小和/或应用平移更改剪切变换矩阵上的最后一列以便完整且集中地显示输出图像。 示例代码 偏斜示例之后的向您展示了如何使用warpAffine函数使图像中的θ π/ 3水平偏斜。 示例代码为 #include opencv2/opencv.hpp #include math.husing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Apply skewMat dst;double m 1/tan(M_PI/3);Mat M (Mat_double(2,3) 1, m, 0, 0, 1, 0);warpAffine(src,dst,M,Size(src.cols0.5*src.cols,src.rows));// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( SKEWED , WINDOW_AUTOSIZE );imshow( SKEWED , dst );waitKey(); return 0; }下图显示了代码的输出 原始图像和水平倾斜时的图像 反射 由于默认情况下会在x和y轴上进行反射因此必须应用平移变换矩阵的最后一列。 然后反射矩阵为 在此t[x]是图像列数t[y]是图像行数。 与以前的转换一样用于施加反射的函数为warpAffine。 注意 其他仿射变换可以使用warpAffine函数及其对应的变换矩阵来应用。 示例代码 以下反射示例显示了使用warpAffine函数对图像进行水平垂直和组合反射的示例。 示例代码为 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Apply the reflectionsMat dsth, dstv, dst;Mat Mh (Mat_double(2,3) -1, 0, src.cols, 0, 1, 0Mat Mv (Mat_double(2,3) 1, 0, 0, 0, -1, src.rows);Mat M (Mat_double(2,3) -1, 0, src.cols, 0, -1, src.rows);warpAffine(src,dsth,Mh,src.size());warpAffine(src,dstv,Mv,src.size());warpAffine(src,dst,M,src.size());// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( H-REFLECTION , WINDOW_AUTOSIZE );imshow( H-REFLECTION , dsth );namedWindow( V-REFLECTION , WINDOW_AUTOSIZE );imshow( V-REFLECTION , dstv );namedWindow( REFLECTION , WINDOW_AUTOSIZE );imshow( REFLECTION , dst );waitKey(); return 0; }下图显示了代码的输出 XY 和两个轴上的原始图像和旋转图像 透视变换 对于透视变换虽然需要对二维图像执行但仍需要3 x 3变换矩阵。 直线在输出图像中保持直线但是在这种情况下比例会改变。 与仿射变换相比查找变换矩阵要复杂得多。 使用透视图时将使用输入图像矩阵的四个点的坐标及其在输出图像矩阵上的相应坐标来执行此操作。 通过这些点和getPerspectiveTransform OpenCV 函数可以找到透视变换矩阵。 在获得矩阵之后应用warpPerspective获得透视变换的输出。 这两个函数在这里详细说明 Mat getPerspectiveTransform(InputArray src, InputArray dst)和Mat getPerspectiveTransform(const Point2f src[], const Point2f dst[])这将返回根据src和dst计算的透视变换矩阵。void warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flagsINTER_LINEAR, int borderModeBORDER_CONSTANT, const Scalar borderValueScalar())这会将M仿射变换应用于src图像从而获得新的dst图像。 其余参数与讨论的其他几何变换相同。 示例代码 以下透视图示例向您展示了如何使用warpPerspective函数更改图像的透视图的示例。 在这种情况下需要指示从第一张图像开始的四个点的坐标并从输出指示另外四个点的坐标以通过getPerspectiveTransform计算透视变换矩阵。 选择的点是 Point2f src_verts[4];src_verts[2] Point(195, 140);src_verts[3] Point(410, 120);src_verts[1] Point(220, 750);src_verts[0] Point(400, 750);Point2f dst_verts[4];dst_verts[2] Point(160, 100);dst_verts[3] Point(530, 120);dst_verts[1] Point(220, 750);dst_verts[0] Point(400, 750);示例代码为 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);Mat dst;Point2f src_verts[4];src_verts[2] Point(195, 140);src_verts[3] Point(410, 120);src_verts[1] Point(220, 750);src_verts[0] Point(400, 750);Point2f dst_verts[4];dst_verts[2] Point(160, 100);dst_verts[3] Point(530, 120);dst_verts[1] Point(220, 750);dst_verts[0] Point(400, 750);// Obtain and Apply the perspective transformationMat M getPerspectiveTransform(src_verts,dst_verts);warpPerspective(src,dst,M,src.size());// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( PERSPECTIVE , WINDOW_AUTOSIZE );imshow( PERSPECTIVE , dst );waitKey(); return 0; }下图显示了代码的输出 透视结果带有原始图像中标记的点 修复 修复是重建图像和视频的受损部分的过程。 此过程也称为称为图像或视频插值。 基本思想是模拟古董修复者完成的过程。 如今随着数码相机的广泛使用修补已成为一种自动过程不仅可以通过删除划痕来进行图像恢复还可以用于其他任务例如去除物体或文本。 OpenCV 从版本 2.4 开始支持修复算法。 用于此目的的函数是 void inpaint(InputArray src, InputArray inpaintMask, OutputArray dst, double inpaintRadius, int flags)这将恢复源src图像中inpaintMask参数用非零值指示的区域。 inpaintRadius参数指示flags指定的算法要使用的邻域。 OpenCV 中可以使用两种方法 INPAINT_NS这是基于 Navier-Stokes 的方法INPAINT_TELEA这是 Alexandru Telea 提出的方法 最后恢复的图像存储在dst中。 注意 有关 OpenCV 中使用的修复算法的更多详细信息请参见这个页面。 提示 对于视频修复请将视频视为图像序列然后将算法应用于所有图像。 示例代码 修复示例之后的显示了如何使用inpaint函数修复在图像遮罩中指定的图像区域。 示例代码为 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Read the mask fileMat mask;mask imread(argv[2]);cvtColor(mask, mask, COLOR_RGB2GRAY);// Apply the inpainting algorithmsMat dst, dst2;inpaint(src, mask, dst, 10, INPAINT_TELEA);inpaint(src, mask, dst2, 10, INPAINT_NS);// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( MASK , WINDOW_AUTOSIZE );imshow( MASK , mask );namedWindow( INPAINT_TELEA , WINDOW_AUTOSIZE );imshow( INPAINT_TELEA , dst );namedWindow( INPAINT_NS , WINDOW_AUTOSIZE );imshow( INPAINT_NS , dst2 );waitKey(); return 0; }下图显示了代码的输出 应用修补的结果 注意 第一行包含原始图像和使用的遮罩。 第二行在左侧包含 Telea 提出的修复结果在右侧包含基于 Navier-Stokes 的方法的结果。 要获得修复遮罩并非易事。 inpainting2示例代码向您展示了一个示例该示例说明如何使用通过threshold(mask, mask, 235, 255, THRESH_BINARY)的二进制阈值从源图像中获取遮罩 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Create the maskMat mask;cvtColor(src, mask, COLOR_RGB2GRAY);threshold(mask, mask, 235, 255, THRESH_BINARY);// Apply the inpainting algorithmsMat dst, dst2;inpaint(src, mask, dst, 10, INPAINT_TELEA);inpaint(src, mask, dst2, 10, INPAINT_NS);// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( MASK , WINDOW_AUTOSIZE );imshow( MASK , mask );namedWindow( INPAINT_TELEA , WINDOW_AUTOSIZE );imshow( INPAINT_TELEA , dst );namedWindow( INPAINT_NS , WINDOW_AUTOSIZE );imshow( INPAINT_NS , dst2 );waitKey(); return 0; }下图显示了代码的输出 在不知道遮罩的情况下应用修复算法的结果 注意 第一行包含原始图像和提取的遮罩。 第二行在左侧包含 Telea 提出的修复结果在右侧包含基于 Navier-Stokes 的方法的结果。 此示例的结果表明并非总是可能获得完美的遮罩。 有时会包括图像的其他一些部分例如背景或噪点。 但是修复结果仍然可以接受因为生成的图像接近于在其他情况下获得的图像。 降噪 降噪或降噪是从模拟或数字设备获得的信号中去除噪声的过程。 本节将重点放在减少数字图像和视频的噪声上。 尽管平滑和中值滤波是对图像进行降噪的不错选择但 OpenCV 提供了其他算法来执行此任务。 这些是非本地均值和 TVL1总变异 L1算法。 非局部均值算法的基本思想是用来自多个图像子窗口的平均颜色替换像素的颜色这些子窗口与包含像素邻域的子窗口相似。 另一方面使用原始对偶优化算法实现的 TVL1 变分降噪模型将图像降噪过程视为一个变分问题。 注意 有关非局部均值和 TVL1 去噪算法的更多信息请访问这个页面和这个页面。 OpenCV 提供了四种使用非局部均值方法对彩色和灰度图像进行降噪的函数。 对于 TVL1 模型提供了一种函数。 这些函数为 void fastNlMeansDenoising(InputArray src, OutputArray dst, float h 3, int templateWindowSize 7, int searchWindowSize 21)这会将src中加载的单个灰度图像降噪。 templateWindowSize和searchWindowSize 参数是用于计算权重的模板补丁的像素大小以及用于计算给定像素的加权平均值的窗口大小。 这些应该是奇数建议值分别为 7 和 21 像素。 h参数调节算法的效果。 较大的h值可消除更多的噪点缺陷但具有消除更多图像细节的缺点。 输出存储在dst中。void fastNlMeansDenoisingColored(InputArray src, OutputArray dst, float h 3, float hForColorComponents 3, int templateWindowSize 7, int searchWindowSize 21)这是对彩色图像先前函数的修改。 它将src图像转换为 CIELAB 色彩空间然后使用fastNlMeansDenoising函数分别对 L 和 AB 分量进行降噪。void fastNlMeansDenoisingMulti(InputArrayOfArrays srcImgs, OutputArray dst, int imgToDenoiseIndex, int temporalWindowSize, float h 3, int templateWindowSize 7, int searchWindowSize 21)这使用图像序列获得去噪的图像。 在这种情况下还需要两个参数imgToDenoiseIndex和temporalWindowSize。 imgToDenoiseIndex的值是srcImgs中要去噪的目标图像索引。 最后temporalWindowSize用于确定要用于降噪的周围图像的数量。 这应该很奇怪。void fastNlMeansDenoisingColoredMulti(InputArrayOfArrays srcImgs, OutputArray dst, int imgToDenoiseIndex, int temporalWindowSize, float h 3, float hForColorComponents 3, int templateWindowSize 7, int searchWindowSize 21)基于fastNlMeansDenoisingColored和fastNlMeansDenoisingMulti函数。 这些参数将在其余函数中说明。void denoise_TVL1(const std::vectorMat observations, Mat result, double lambda, int niters)这从observations中存储的一个或多个噪声图像获得result中的去噪图像。 lambda和niters参数控制算法的强度和迭代次数。 示例代码 去噪示例后的向您展示了如何使用其中一种降噪函数对彩色图像fastNlMeansDenoisingColored)进行降噪。由于该示例使用的是无噪声图像因此需要添加一些内容。 为此使用以下代码行 Mat noisy src.clone(); Mat noise(src.size(), src.type()); randn(noise, 0, 50); noisy noise;创建的Mat元素具有与原始图像相同的大小和类型以在其上存储由randn函数产生的产生的噪声。 最后将噪声添加到克隆图像中以获得噪声图像。 示例代码为 #include opencv2/opencv.hppusing namespace cv;int main( int argc, char** argv ) {// Read the source fileMat src;src imread(argv[1]);// Add some noiseMat noisy src.clone();Mat noise(src.size(), src.type());randn(noise, 0, 50);noisy noise;// Apply the denoising algorithmMat dst;fastNlMeansDenoisingColored(noisy, dst,30,30,7,21);// Show the resultsnamedWindow( ORIGINAL , WINDOW_AUTOSIZE );imshow( ORIGINAL , src );namedWindow( ORIGINAL WITH NOISE , WINDOW_AUTOSIZE );imshow( ORIGINAL WITH NOISE , noisy );namedWindow( DENOISED , WINDOW_AUTOSIZE );imshow( DENOISED , dst );waitKey(); return 0; }下图显示了执行前一代码后产生的噪点和去噪图像 应用去噪的结果 总结 在本章中我们介绍了用于图像增强和校正的方法包括降噪边缘增强形态运算几何变换以及受损图像的恢复。 在每种情况下都提供了不同的选项以向读者提供可以在 OpenCV 中使用的所有选项。 下一章将介绍色彩空间以及如何转换色彩空间。 另外将说明基于色彩空间的分割和色彩转移方法。 四、处理色彩 颜色是响应于我们的视觉系统的激发而产生的感知结果入射光是在光谱的可见光区域中入射到视网膜上的。 图像的颜色可能包含大量信息这些信息可用于简化图像分析对象识别和基于颜色的提取。 通常在考虑定义其的色彩空间中的像素值的情况下执行这些过程。 本章将讨论以下主题 OpenCV 中使用的色彩空间以及如何将图像从一种色彩模型转换为另一种色彩模型考虑定义图片色彩空间的方式如何对图片进行分割的示例如何使用颜色转移方法将图像的外观转移到另一个 色彩空间 人类的视觉系统能够分辨出数十万种颜色。 为了获得此信息人类视网膜具有三种类型的彩色感光锥体细胞它们对入射辐射作出响应。 因此可以使用称为原色的三个数字分量生成大多数人的颜色感知。 为了根据三个或更多个特定特性来指定颜色存在许多称为颜色空间或颜色模型的方法。 在它们之间代表图像进行选择取决于要执行的操作因为根据所需的应用某些操作更合适。 例如在某些颜色空间例如 RGB中亮度会影响三个通道这对于某些图像处理操作可能是不利的。 下一节将说明 OpenCV 中使用的色彩空间以及如何将图片从一种色彩模型转换为另一种色彩模型。 颜色空间之间的转换cvtColor OpenCV 中有 150 多种颜色空间转换方法。 OpenCV 在imgproc模块中提供的函数是void cvtColor(InputArray src, OutputArray dst, int code, int dstCn0)。 该函数的参数为​​ src这是 8 位无符号16 位无符号CV_16UC或单精度浮点的输入图像。dst此是与src相同大小和深度的输出图像。code此是色彩空间转换代码。 该参数的结构为COLOR_SPACEsrc2SPACEdst。 一些示例值是COLOR_BGR2GRAY和COLOR_YCrCb2BGR。dstCn这是目标图像中的通道数。 如果此参数为 0 或省略则通道数自动从src和code派生。 此函数的示例将在接下来的部分中进行介绍。 提示 cvtColor函数只能从 RGB 转换为另一个颜色空间或从另一个颜色空间转换为 RGB因此如果读者想在 RGB 以外的两个颜色空间之间转换则必须先进行到 RGB 的转换。 OpenCV 中的各种色彩空间将在接下来的部分中进行讨论。 RGB RGB 是可加模型其中图像由三个独立图像平面或通道组成红色绿色和蓝色以及可选的透明度的第四个通道有时称为 alpha 通道。 为了指定特定的颜色每个值指示每个像素上存在的每种成分的数量较高的值对应于较亮的像素。 由于该色彩空间对应于人眼的三个感光器因此被广泛使用。 注意 OpenCV 中的默认颜色格式通常称为 RGB但实际上将其存储为 BGR 通道相反。 示例代码 以下BGRsplit示例向您展示了如何加载 RGB 图像如何以灰色和彩色分割并显示每个特定通道。 代码的第一部分用于加载和显示图片 #include opencv2/opencv.hpp #include opencv2/imgproc.hppusing namespace std; using namespace cv;vectorMat showSeparatedChannels(vectorMat channels);int main(int argc, const char** argv) {//Load the imageMat image imread(BGR.png);imshow(Picture,image);代码的下一部分将图片分成每个通道并显示 vectorMat channels;split( image, channels );//show channels in gray scalenamedWindow(Blue channel (gray), WINDOW_AUTOSIZE );imshow(Blue channel (gray),channels[0]);namedWindow(Green channel (gray), WINDOW_AUTOSIZE );imshow(Green channel (gray),channels[1]);namedWindow(Red channel (gray), WINDOW_AUTOSIZE );imshow(Red channel (gray),channels[2]);//show channels in BGRvectorMat separatedChannelsshowSeparatedChannels(channels);namedWindow(Blue channel, WINDOW_AUTOSIZE );imshow(Blue channel,separatedChannels[0]);namedWindow(Green channel, WINDOW_AUTOSIZE );imshow(Green channel,separatedChannels[1]);namedWindow(Red channel, WINDOW_AUTOSIZE );imshow(Red channel,separatedChannels[2]);waitKey(0);return 0; }值得注意使用void split(InputArray m, OutputArrayOfArrays mv) OpenCV 函数将图像m分成三个通道并将其保存在称为mv的向量中。 相反void merge( InputArrayOfArrays mv, OutputArray dst)函数用于将所有mv通道合并到一个dst图像中。 此外命名为showSeparatedChannels的函数用于创建代表每个通道的三个彩色图像。 对于每个通道该函数都会生成vectorMat aux该vectorMat aux由通道本身和两个辅助通道组成它们的所有值均设置为 0表示颜色模型的其他两个通道。 最后辅助图片被合并生成仅满足一个通道的图像。 该函数代码将在本章的其他示例中使用如下所示 vectorMat showSeparatedChannels(vectorMat channels){vectorMat separatedChannels;//create each image for each channelfor ( int i 0 ; i 3 ; i){Mat zerMat::zeros( channels[0].rows, channels[0].cols, channels[0].type());vectorMat aux;for (int j0; j 3 ; j){if(ji)aux.push_back(channels[i]);elseaux.push_back(zer);}Mat chann;merge(aux,chann);separatedChannels.push_back(chann);}return separatedChannels; }下图显示了示例的输出 原始 RGB 图像和通道分割 灰度 在灰度中每个像素的值表示为仅包含强度信息的单个值它构成了一个由不同灰度组成的图像。 使用cvtColor在 OpenCV 中在 RGB 和灰度Y之间转换的颜色空间转换代码是COLOR_BGR2GRAYCOLOR_RGB2GRAYCOLOR_GRAY2BGR和COLOR_GRAY2RGB。 这些转换的内部计算如下 注意 请注意根据上式中的不可能直接从灰度图像中检索颜色。 示例代码 Gray示例之后的显示了如何将 RGB 图像转换为灰度并显示了两张图片。 示例代码为 #include opencv2/opencv.hpp #include opencv2/imgproc.hppusing namespace cv;int main(int argc, const char** argv) {//Load the imageMat image imread(Lovebird.jpg);namedWindow(Picture, WINDOW_AUTOSIZE );imshow(Picture,image);Mat imageGray;cvtColor(image, imageGray, COLOR_BGR2GRAY);namedWindow( Gray picture, WINDOW_AUTOSIZE );imshow(Gray picture,imageGray);waitKey(0);return 0; }下图显示了代码的输出 原始 RGB 图像和灰度转换 注意 从 RGB 转换为灰度的方法的缺点是失去了原始图像的对比度。 本书的第 6 章“计算摄影”描述了脱色过程该过程在克服此问题的同时进行了相同的转换。 CIE XYZ CIE XYZ 系统使用亮度分量 Y 来描述颜色该亮度分量与人眼的亮度灵敏度有关并且由国际照明委员会CIE使用来自几个人类观察者的实验统计数据标准化了另外两个通道 X 和 Z。 此颜色空间用于报告测量仪器例如比色计或分光光度计的颜色当需要跨不同设备进行一致的颜色表示时该颜色空间将非常有用。 这种颜色空间的主要问题是颜色以不均匀的方式缩放。 这一事实导致 CIE 采用 CIE Lab 和 CIE Luv 颜色模型。 使用cvtColor在 OpenCV 中在 RGB 和 CIE XYZ 之间转换的色彩空间转换代码是COLOR_BGR2XYZCOLOR_RGB2XYZ COLOR_XYZ2BGR和COLOR_XYZ2RGB。 这些转换的计算如下 示例代码 CIExyz示例之后的显示了如何将 RGB 图像转换为 CIE XYZ 颜色空间并分别以灰色和彩色显示并显示每个特定通道。 代码的第一部分用于加载和转换图片 #include opencv2/opencv.hpp #include opencv2/imgproc.hpp using namespace std; using namespace cv;vectorMat showSeparatedChannels(vectorMat channels);int main(int argc, const char** argv) {//Load the imageMat image imread(Lovebird.jpg);imshow(Picture,image);//transform to CIEXYZcvtColor(image,image,COLOR_BGR2XYZ); 代码的下一部分在每个 CIE XYZ 通道中拆分图片并显示它们 vectorMat channels;split( image, channels );//show channels in gray scalenamedWindow(X channel (gray), WINDOW_AUTOSIZE );imshow(X channel (gray),channels[0]);namedWindow(Y channel (gray), WINDOW_AUTOSIZE );imshow(Y channel (gray),channels[1]);namedWindow(Z channel (gray), WINDOW_AUTOSIZE );imshow(Z channel (gray),channels[2]);//show channels in BGRvectorMat separatedChannelsshowSeparatedChannels(channels);for (int i0;i3;i){ cvtColor(separatedChannels[i],separatedChannels[i],COLOR_XYZ2BGR);}namedWindow(X channel, WINDOW_AUTOSIZE );imshow(X channel,separatedChannels[0]);namedWindow(Y channel, WINDOW_AUTOSIZE );imshow(Y channel,separatedChannels[1]);namedWindow(Z channel, WINDOW_AUTOSIZE );imshow(Z channel,separatedChannels[2]);waitKey(0);return 0; }下图显示了代码的输出 原始 RGB 图像和 CIE XYZ 通道分割 YCrCb 此色彩空间广泛用于视频和图像压缩方案它不是绝对的色彩空间因为它是对 RGB 色彩空间进行编码的一种方式。 Y 通道表示亮度而 Cr 和 Cb 表示红色差异RGB 色彩空间中的 R 通道与 Y 之间的差异和蓝色差异RGB 色彩空间中的 B 通道与 Y 之间的差异色度分量。 它广泛用于视频和图像压缩方案例如 MPEG 和 JPEG。 使用cvtColor在 OpenCV 中在 RGB 和 YCrCb 之间进行转换的颜色空间转换代码是COLOR_BGR2YCrCbCOLOR_RGB2YCrCbCOLOR_YCrCb2BGR和COLOR_YCrCb2RGB。 这些转换的计算如下 然后以和来看以下内容 示例代码 下面的 YCrCb 颜色示例向您展示如何将 RGB 图像转换为 YCrCb 颜色空间并以灰色和一种颜色拆分并显示每个特定的通道。 代码的第一部分用于加载和转换图片 #include opencv2/opencv.hpp #include opencv2/imgproc.hppusing namespace std; using namespace cv;vectorMat showSeparatedChannels(vectorMat channels);int main(int argc, const char** argv) {//Load the imageMat image imread(Lovebird.jpg);imshow(Picture,image);//transform to YCrCbcvtColor(image,image,COLOR_BGR2YCrCb); 代码的下一部分将图片分成每个 YCrCb 通道并显示它们 vectorMat channels;split( image, channels );//show channels in gray scalenamedWindow(Y channel (gray), WINDOW_AUTOSIZE );imshow(Y channel (gray),channels[0]);namedWindow(Cr channel (gray), WINDOW_AUTOSIZE );imshow(Cr channel (gray),channels[1]);namedWindow(Cb channel (gray), WINDOW_AUTOSIZE );imshow(Cb channel (gray),channels[2]);//show channels in BGRvectorMat separatedChannelsshowSeparatedChannels(channels);for (int i0;i3;i){cvtColor(separatedChannels[i],separatedChannels[i],COLOR_YCrCb2BGR);}namedWindow(Y channel, WINDOW_AUTOSIZE );imshow(Y channel,separatedChannels[0]);namedWindow(Cr channel, WINDOW_AUTOSIZE );imshow(Cr channel,separatedChannels[1]);namedWindow(Cb channel, WINDOW_AUTOSIZE );imshow(Cb channel,separatedChannels[2]);waitKey(0);return 0; }下图显示了代码的输出 原始 RGB 图像和 YCrCb 通道分割 HSV HSV 颜色空间属于所谓的面向色相的颜色坐标系。 这种颜色模型与人类颜色感知模型非常相似。 在其他颜色模型如 RGB中图像被视为三种基色的相加结果而 HSV 的三个通道代表色相H 给出了颜色的光谱组成的一个度量饱和度S 表示主波长的纯光比例它表示颜色与相等亮度的灰色有多远和值V 给出相对于类似照亮的白色颜色的亮度的亮度对应于色调阴影和色调的直观吸引力。 HSV 被广泛用​​于进行颜色比较因为 H 几乎是独立的光线变化。 下图显示了此颜色模型该颜色模型将每个通道表示为圆柱体的一部分 用于使用cvtColor在 OpenCV 中的 RGB 和 HSV 之间进行转换的颜色空间转换代码是COLOR_BGR2HSVCOLOR_RGB2HSVCOLOR_HSV2BGR和COLOR_HSV2RGB。 在这种情况下值得注意的是如果src图像格式为 8 位或 16 位则cvtColor首先将其转换为浮点格式并在 0 和 1 之间缩放值。 转换计算如下 如果H 0则H H 360。 最后值将转换为目标数据类型。 示例代码 下面的HSVcolor示例向您展示如何将 RGB 图像转换为 HSV 色彩空间并以灰度和 HSV 图像拆分和显示每个特定通道。 示例代码为 #include opencv2/opencv.hpp #include opencv2/imgproc.hppusing namespace std; using namespace cv;int main(int argc, const char** argv) {//Load the imageMat image imread(Lovebird.jpg);imshow(Picture,image);//transform to HSVcvtColor(image,image,COLOR_BGR2HSV);vectorMat channels;split( image, channels );//show channels in gray scalenamedWindow(H channel (gray), WINDOW_AUTOSIZE );imshow(H channel (gray),channels[0]);namedWindow(S channel (gray), WINDOW_AUTOSIZE );imshow(S channel (gray),channels[1]);namedWindow(V channel (gray), WINDOW_AUTOSIZE );imshow(V channel (gray),channels[2]);namedWindow(HSV image (all channels), WINDOW_AUTOSIZE );imshow(HSV image (all channels),image);waitKey(0);return 0; }下图显示了代码的输出 原始 RGB 图像HSV 转换和通道分割 注意 OpenCV 的imshow函数假定要显示的图像颜色是 RGB因此显示不正确。 如果您在其他颜色空间中有图像并且想要正确显示则首先必须将其转换回 RGB。 HLS HLS 颜色空间属于面向色相的颜色坐标系统例如先前说明的 HSV 颜色模型。 开发该模型以指定每个通道中的色相亮度和颜色饱和度的值。 HSV 颜色模型的区别在于HLS 定义的纯色的亮度等于中等灰色的亮度而 HSV 定义的纯色的亮度等于白色的亮度。 使用cvtColor在 OpenCV 中在 RGB 和 HLS 之间进行转换的颜色空间转换代码为COLOR_BGR2HLSCOLOR_RGB2HLSCOLOR_HLS2BGR和COLOR_HLS2RGB。 在这种情况下与 HSV 一样如果src图像格式为 8 位或 16 位则cvtColor首先将其转换为浮点格式将值缩放到 0 到 1 之间。然后转换计算如下 如果H 0则H H 360。 最后将值重新转换为目标数据类型。 示例代码 以下HLScolor示例向您展示如何将 RGB 图像转换为 HLS 色彩空间如何拆分和显示灰度中的每个特定通道以及 HLS 图像。 示例代码为 #include opencv2/opencv.hpp #include opencv2/imgproc.hppusing namespace std; using namespace cv;int main(int argc, const char** argv) {//Load the imageMat image imread(Lovebird.jpg);imshow(Picture,image);//transform to HSVcvtColor(image,image,COLOR_BGR2HLS);vectorMat channels;split( image, channels );//show channels in gray scalenamedWindow(H channel (gray), WINDOW_AUTOSIZE );imshow(H channel (gray),channels[0]);namedWindow(L channel (gray), WINDOW_AUTOSIZE );imshow(L channel (gray),channels[1]);namedWindow(S channel (gray), WINDOW_AUTOSIZE );imshow(S channel (gray),channels[2]);namedWindow(HLS image (all channels), WINDOW_AUTOSIZE );imshow(HLS image (all channels),image);waitKey(0);return 0; }下图显示了代码的输出 原始 RGB 图像HLS 转换和通道分割 CIE Lab CIE Lab 颜色空间是在 CIE Luv 之后由 CIE 标准化的第二个均匀颜色空间它是基于 CIE XYZ 空间和白色参考点得出的。 实际上它是 CIE 指定的最完整的色彩空间其创建是与设备无关的例如 CYE XYZ 模型并用作参考。 它能够描述人眼可见的颜色。 这三个通道代表颜色的亮度L品红色和绿色之间的位置a以及黄色和蓝色之间的位置b。 使用cvtColor在 OpenCV 中在 RGB 和 CIE Lab 之间进行转换的色彩空间转换代码是COLOR_BGR2LabCOLOR_RGB2LabCOLOR_Lab2BGR和COLOR_Lab2RGB。 在这个页面中解释了用于计算这些转换的过程。 示例代码 CIElab示例之后的显示了如何将 RGB 图像转换为 CIE Lab 色彩空间以灰度和 CIE Lab 分割并显示图片的每个特定通道。 示例代码为 #include opencv2/opencv.hpp #include opencv2/imgproc.hppusing namespace std; using namespace cv;int main(int argc, const char** argv) {//Load the imageMat image imread(Lovebird.jpg);imshow(Picture,image);//transform to CIE LabcvtColor(image,image,COLOR_BGR2Lab);vectorMat channels;split( image, channels );//show channels in gray scalenamedWindow(L channel (gray), WINDOW_AUTOSIZE );imshow(L channel (gray),channels[0]);namedWindow(a channel (gray), WINDOW_AUTOSIZE );imshow(a channel (gray),channels[1]);namedWindow(b channel (gray), WINDOW_AUTOSIZE );imshow(b channel (gray),channels[2]);namedWindow(CIE Lab image (all channels), WINDOW_AUTOSIZE );imshow(CIE Lab image (all channels),image);waitKey(0);return 0; }下图显示了代码的输出 原始 RGB 图像CIE Lab 转换和通道分割 CIE Luv CIE Luv 颜色空间是 CIE 标准化的第一个统一颜色空间。 它是 CIE XYZ 空间和白色参考点的简单计算转换尝试进行感知均匀性。 类似于 CIE Lab 颜色空间它的创建与设备无关。 三个通道代表颜色的亮度L及其在绿色和红色之间的位置u最后一个通道主要代表蓝色和紫色类型的颜色v。 该颜色模型具有线性加和特性因此可用于灯光的添加剂混合物。 使用cvtColor在 OpenCV 中在 RGB 和 CIE Luv 之间进行转换的颜色空间转换代码是COLOR_BGR2LuvCOLOR_RGB2LuvCOLOR_Luv2BGR和COLOR_Luv2RGB。 可以在这个页面上看到用于计算这些转换的过程。 示例代码 CIELuvcolor示例之后的显示了如何将 RGB 图像转换为 CIE Luv 色彩空间以灰度和 CIE Luv 分割并显示图片的每个特定通道。 示例代码为 #include opencv2/opencv.hpp #include opencv2/imgproc.hppusing namespace std; using namespace cv;int main(int argc, const char** argv) {//Load the imageMat image imread(Lovebird.jpg);imshow(Picture,image);//transform to CIE LuvcvtColor(image,image,COLOR_BGR2Luv);vectorMat channels;split( image, channels );//show channels in gray scalenamedWindow(L channel (gray), WINDOW_AUTOSIZE );imshow(L channel (gray),channels[0]);namedWindow(u channel (gray), WINDOW_AUTOSIZE );imshow(u channel (gray),channels[1]);namedWindow(v channel (gray), WINDOW_AUTOSIZE );imshow(v channel (gray),channels[2]);namedWindow(CIE Luv image (all channels), WINDOW_AUTOSIZE );imshow(CIE Luv image (all channels),image);waitKey(0);return 0; }下图显示了代码的输出 原始 RGB 图像CIE Luv 转换和通道分割 拜耳 拜耳像素空间合成被广泛用于带有仅一个图像传感器的数码相机。 与具有三个传感器的相机每个 RGB 通道一个传感器可以获取特定组件的所有信息不同在一台传感器相机中每个像素都被一个不同的滤色镜覆盖因此每个像素仅以此颜色进行测量。 使用拜耳方法从其邻居中推断出丢失的颜色信息。 它使您可以从一个像素交错的单一平面中获取完整的彩色图片如下所示 拜耳模式示例 注意 请注意拜耳图案由比 R 和 B 多的 G 像素表示因为人眼对绿色频率更敏感。 通过将图案在任何方向上移动一个像素可以获得对所示图案的几种修改。 在 OpenCV 中将 Bayer 转换为 RGB 的色彩空间转换代码是将第二行的第二列和第三列分别为X和Y的组件定义为COLOR_BayerXY2BGR的。 例如前一张图片的图案具有“BG”类型因此其转换代码为COLOR_BayerBG2BGR。 示例代码 以下Bayer示例向您展示如何将由从图像传感器获得的 RG Bayer 模式定义的图片转换为 RGB 图像。 示例代码为 #include opencv2/opencv.hpp #include opencv2/imgproc.hppusing namespace cv;int main(int argc, const char** argv) {//Show bayered image in colorMat bayer_color imread(Lovebird_bayer_color.jpg);namedWindow(Bayer picture in color, WINDOW_AUTOSIZE );imshow(Bayer picture in color,bayer_color);//Load bayered imageMat bayer imread(Lovebird_bayer.jpg,CV_8UC3);namedWindow(Bayer picture , WINDOW_AUTOSIZE );imshow(Bayer picture,bayer);Mat imageColor;cvtColor(bayer, imageColor, COLOR_BayerRG2BGR);namedWindow( Color picture, WINDOW_AUTOSIZE );imshow(Color picture,imageColor);waitKey(0);return 0; }下图显示了代码的输出 拜耳图案图像和 RGB 转换 基于颜色空间的分割 每个颜色空间代表一个图像该图像指示每个像素上每个通道测得的特定特性的数值。 考虑到这些特性可以使用线性边界例如三维空间中的平面和每个通道一个空间对颜色空间进行分区从而可以根据其所在的分区对每个像素进行分类因此可以选择一组具有预定义特性的像素。 这个想法可以用来分割我们感兴趣的图像对象。 OpenCV 提供void inRange(InputArray src, InputArray lowerb, InputArray upperb, OutputArray dst)函数来检查元素数组是否位于其他两个数组的元素之间。 对于基于色彩空间的分割此函数可让您获得src图像的像素集其通道值位于lowerb下边界和upperb上边界之间从而获得dst图片。 注意 lowerb和upperb边界通常定义为Scalar(x, y, z)其中xy,和z是定义为上下边界的每个通道的数值。 以下示例向您展示如何检测可以被认为是皮肤的像素。 已经观察到肤色的强度比色度的差异更大因此通常在皮肤检测中不考虑亮度成分。 由于该色彩空间对亮度的依赖性这一事实使得难以检测以 RGB 表示的图像中的皮肤因此使用 HSV 和 YCrCb 颜色模型。 值得注意的是对于这种类型的分段必须知道或获得每个通道的边界值。 HSV 分割 如先前所述HSV 被广泛用​​于进行颜色比较因为 H 几乎与光的变化无关因此在皮肤检测中很有用。 在该示例中选择下边界(0, 10, 60)和上边界(20, 150, 255)以检测每个像素中的皮肤。 示例代码为 #include opencv2/opencv.hpp #include opencv2/imgproc.hppusing namespace std; using namespace cv;int main() {//Load the imageMat image imread(hand.jpg);namedWindow(Picture, WINDOW_AUTOSIZE );imshow(Picture,image);Mat hsv;cvtColor(image, hsv, COLOR_BGR2HSV);//select pixelsMat bw;inRange(hsv, Scalar(0, 10, 60), Scalar(20, 150, 255), bw);namedWindow(Selected pixels, WINDOW_AUTOSIZE );imshow(Selected pixels, bw);waitKey(0);return 0; }下图显示代码的输出 使用 HSV 颜色空间进行皮肤检测 YCrCb 分割 YCrCb 颜色空间减少了 RGB 颜色通道的冗余并以独立的组件表示颜色。 考虑到亮度和色度分量是分开的此空间是皮肤检测的不错选择。 以下示例将 YCrCb 颜色空间用于皮肤检测并使用每个像素中的下边界(0, 33, 77)和上边界(255, 173, 177)。 示例代码为 #include opencv2/opencv.hpp #include opencv2/imgproc.hppusing namespace std; using namespace cv;int main() {//Load the imageMat image imread(hand.jpg);namedWindow(Picture, WINDOW_AUTOSIZE );imshow(Picture,image);Mat ycrcb;cvtColor(image, ycrcb, COLOR_BGR2HSV);//select pixelsMat bw;inRange(ycrcb, Scalar(0, 133, 77), Scalar(255, 173, 177), bw);namedWindow(Selected pixels, WINDOW_AUTOSIZE );imshow(Selected pixels, bw);waitKey(0);return 0; }下图显示代码的输出 使用 YCrCb 颜色空间进行皮肤检测 注意 有关的更多图像分割方法请参阅 Packt Publishing 的《OpenCV Essentials》的第 4 章。 颜色转移 图像处理中通常执行的另一项任务是修改图像的颜色特别是在需要去除主要或不希望有的偏色的情况下。 这些方法中的一种称为颜色转移该方法执行一组借用一个源图像的颜色特征的颜色校正并将源图像的外观转移到目标图像。 示例代码 以下colorTransfer示例显示了如何将颜色从源图像传输到目标图像。 此方法首先将图像色彩空间转换为 CIE Lab。 接下来它为源图像和目标图像分割通道。 之后使用均值和标准差拟合从一个图像到另一个图像的通道分布。 最后通道合并回一起并转换为 RGB。 注意 有关示例中使用的转换的完整理论详细信息请参见《图像之间的颜色转换》 。 代码的第一部分将图像转换为 CIE Lab 颜色空间同时还将图像类型更改为CV_32FC1 #include opencv2/opencv.hpp #include opencv2/imgproc.hppusing namespace std; using namespace cv;int main(int argc, const char** argv) {//Load the imagesMat src imread(clock_tower.jpg);Mat tar imread(big_ben.jpg);//Convert to Lab space and CV_32F1Mat src_lab, tar_lab;cvtColor(src, src_lab, COLOR_BGR2Lab );cvtColor(tar, tar_lab, COLOR_BGR2Lab );src_lab.convertTo(src_lab,CV_32FC1);tar_lab.convertTo(tar_lab,CV_32FC1);代码的接下来的部分执行颜色转移如前所述 //Find mean and std of each channel for each imageMat mean_src, mean_tar, stdd_src, stdd_tar;meanStdDev(src_lab, mean_src, stdd_src);meanStdDev(tar_lab, mean_tar, stdd_src);// Split into individual channelsvectorMat src_chan, tar_chan;split( src_lab, src_chan );split( tar_lab, tar_chan );// For each channel calculate the color distributionfor( int i 0; i 3; i ) {tar_chan[i] - mean_tar.atdouble(i);tar_chan[i] * (stdd_src.atdouble(i) / stdd_src.atdouble(i));tar_chan[i] mean_src.atdouble(i);}//Merge the channels, convert to CV_8UC1 each channel and convert to BGRMat output;merge(tar_chan, output);output.convertTo(output,CV_8UC1);cvtColor(output, output, COLOR_Lab2BGR );//show picturesnamedWindow(Source image, WINDOW_AUTOSIZE );imshow(Source image,src);namedWindow(Target image, WINDOW_AUTOSIZE );imshow(Target image,tar);namedWindow(Result image, WINDOW_AUTOSIZE );imshow(Result image,output);waitKey(0);return 0; }下图显示了代码的输出 夜间外观颜色转移示例 总结 在本章中我们对 OpenCV 中使用的色彩空间进行了更深入的介绍并向您展示了如何使用cvtColor函数在色彩空间之间进行转换。 此外强调了使用不同颜色模型进行图像处理的可能性以及考虑到我们需要进行的操作选择正确的颜色空间的重要性。 为此实现了基于颜色空间的分割和颜色转移方法。 下一章将介绍用于视频或一系列图像的图像处理技术。 我们将看到如何使用 OpenCV 实现视频稳定超分辨率和拼接算法。 五、视频图像处理 本章介绍与视频图像处理有关的各种技术。 尽管大多数经典图像处理都是处理静态图像但基于视频的处理正变得越来越流行且价格合理。 本章涵盖以下主题 视频稳定视频超分辨率过程图像拼接 在本章中我们将直接与视频序列或实时摄像机一起使用。 图像处理的输出可以是一组修改的图像或有用的高级信息。 大多数图像处理技术将图像视为二维数字信号并对其应用不同的技术。 在本章中将使用视频或实时摄像机的图像序列来使用不同的高级技术来制作或改进新的增强序列。 因此获得了更多有用的信息即结合了第三时间维度。 视频稳定 视频稳定是指用于减少与摄像机运动相关的模糊的一系列方法。 换句话说它补偿了任何角度移动等效于摄像机的偏航俯仰横滚以及 x 和 y 平移。 最早的图像稳定器出现在 60 年代初期。 这些系统能够略微补偿相机抖动和非自愿移动。 它们由陀螺仪和加速计控制其机制是通过改变镜头的位置来抵消或减少不必要的运动。 当前这些方法广泛用于双筒望远镜摄像机和望远镜。 有多种用于图像或视频稳定的方法本章重点介绍最广泛的方法系列 机械稳定系统这些系统在摄像机镜头上使用机械系统因此在移动摄像机时加速度计和陀螺仪会检测到运动并且系统会在运动时产生运动。 镜片。 这些系统将不在此处考虑。数字稳定系统这些通常是视频中使用的它们直接作用于从摄像机获得的图像。 在这些系统中稳定图像的表面略小于源图像的表面。 移动相机时拍摄的图像也会移动以补偿该移动。 尽管这些技术通过减小运动传感器的可用面积有效地消除了运动但是却牺牲了分辨率和图像清晰度。 视频稳定算法通常包括以下步骤 视频稳定算法的一般步骤 本章将放在 OpenCV 3.0 Alpha 中的videostab模块上该模块包含一组可用于解决视频稳定问题的函数和类。 让我们更详细地探讨一般过程。 视频稳定是通过使用 RANSAC 方法对连续帧之间的帧间运动进行第一估计来实现的。 在此步骤结束时将获得3 x 3矩阵的数组并且每个矩阵都描述了两对连续帧的运动。 全局运动估计对于此步骤非常重要它会影响稳定的最终序列的准确率。 注意 您可以在这个页面上找到有关 RANSAC 方法的更多详细信息。 第二步基于估计的运动生成新的帧序列。 执行其他处理例如平滑去模糊边界外推等以提高稳定的质量。 第三步消除烦人的不规则扰动请参见下图。 有一些方法假设了摄像机运动模型当可以对摄像机的实际运动做出一些假设时这些方法会很好地起作用。 消除不规则的扰动 在 OpenCV 示例[opencv_source_code]/samples/cpp/videostab.cpp中可以找到视频稳定程序示例。 对于以下videoStabilizer示例videoStabilizer.pro项目需要以下库lopencv_core300lopencv_highgui300lopencv_features2d300lopencv_videoio300和lopencv_videostab300。 使用 OpenCV 3.0 Alpha 的videostab模块创建了以下videoStabilizer示例 #include string #include iostream #include opencv2/opencv.hpp #include opencv2/videostab.hppusing namespace std; using namespace cv; using namespace cv::videostab;void processing(PtrIFrameSource stabilizedFrames, string outputPath);int main(int argc, const char **argv) { PtrIFrameSource stabilizedFrames;try{// 1-Prepare the input video and check itstring inputPath;string outputPath;if (argc 1)inputPath argv[1];elseinputPath .\\cube4.avi;if (argc 2)outputPath argv[2];elseoutputPath .\\cube4_stabilized.avi;PtrVideoFileSource source makePtrVideoFileSource(inputPath);cout frame count (rough): source-count() endl;// 2-Prepare the motion estimator // first, prepare the motion the estimation builder, RANSAC L2double min_inlier_ratio 0.1; PtrMotionEstimatorRansacL2 est makePtrMotionEstimatorRansacL2(MM_AFFINE); RansacParams ransac est-ransacParams(); ransac.size 3;ransac.thresh 5;ransac.eps 0.5;est-setRansacParams(ransac);est-setMinInlierRatio(min_inlier_ratio);// second, create a feature detector int nkps 1000; PtrGoodFeaturesToTrackDetector feature_detector makePtrGoodFeaturesToTrackDetector(nkps);// third, create the motion estimator PtrKeypointBasedMotionEstimator motionEstBuilder makePtrKeypointBasedMotionEstimator(est);motionEstBuilder-setDetector(feature_detector); PtrIOutlierRejector outlierRejector makePtrNullOutlierRejector();motionEstBuilder-setOutlierRejector(outlierRejector);// 3-Prepare the stabilizer StabilizerBase *stabilizer 0;// first, prepare the one or two pass stabilizerbool isTwoPass 1;int radius_pass 15;if (isTwoPass){// with a two pass stabilizerbool est_trim true;TwoPassStabilizer *twoPassStabilizer new TwoPassStabilizer();twoPassStabilizer-setEstimateTrimRatio(est_trim);twoPassStabilizer-setMotionStabilizer(makePtrGaussianMotionFilter(radius_pass));stabilizer twoPassStabilizer;}else{// with an one pass stabilizer OnePassStabilizer *onePassStabilizer new OnePassStabilizer();onePassStabilizer-setMotionFilter(makePtrGaussianMotionFilter(radius_pass));stabilizer onePassStabilizer;}// second, set up the parametersint radius 15;double trim_ratio 0.1;bool incl_constr false; stabilizer-setFrameSource(source); stabilizer-setMotionEstimator(motionEstBuilder);stabilizer-setRadius(radius);stabilizer-setTrimRatio(trim_ratio);stabilizer-setCorrectionForInclusion(incl_constr);stabilizer-setBorderMode(BORDER_REPLICATE);// cast stabilizer to simple frame source interface to read stabilized frames stabilizedFrames.reset(dynamic_castIFrameSource*(stabilizer));// 4-Processing the stabilized frames. The results are showed and saved. processing(stabilizedFrames, outputPath);}catch (const exception e){cout error: e.what() endl;stabilizedFrames.release();return -1;}stabilizedFrames.release();return 0; }void processing(PtrIFrameSource stabilizedFrames, string outputPath) {VideoWriter writer;Mat stabilizedFrame;int nframes 0; double outputFps 25;// for each stabilized frame while (!(stabilizedFrame stabilizedFrames-nextFrame()).empty()){nframes;// init writer (once) and save stabilized frameif (!outputPath.empty()){if (!writer.isOpened()) writer.open(outputPath,VideoWriter::fourcc(X,V,I,D), outputFps, stabilizedFrame.size()); writer stabilizedFrame;}imshow(stabilizedFrame, stabilizedFrame);char key static_castchar(waitKey(3));if (key 27) { cout endl; break;}}cout processed frames: nframes endl;cout finished endl; }本示例接受输入视频文件的名称作为默认视频文件名.\cube4.avi。 将显示结果视频然后将其另存为.\cube4_stabilized.avi。 请注意如何包含videostab.hpp标头和使用cv::videostab名称空间。 该示例采取了四个重要步骤。 第一步准备输入视频路径此示例使用标准命令行输入参数inputPath argv[1]选择视频文件。 如果没有输入视频文件则使用默认视频文件.\cube4.avi。 第二步建立运动估计器。 使用 OpenCVPtrMotionEstimatorRansacL2 est makePtr MotionEstimatorRansacL2 (MM_AFFINE)的智能指针Ptrobject为运动估计器创建了基于 RANSAC 的鲁棒全局 2D 方法。 有不同的运动模型可以稳定视频 MM_TRANSLATION 0MM_TRANSLATION_AND_SCALE 1MM_ROTATIO 2MM_RIGID 3MM_SIMILARITY 4MM_AFFINE 5MM_HOMOGRAPHY 6MM_UNKNOWN 7 在稳定视频的精度和计算时间之间需要权衡。 基本运动模型越不准确计算时间就越长。 但是更复杂的模型具有更好的准确率和更差的计算时间。 现在创建 RANSAC 对象RansacParams ransac est- ransacParams()并设置它们的参数ransac.sizeransac.thresh和ransac.eps。 还需要一个特征检测器来估计稳定器将使用的每个连续帧之间的运动。 本示例使用GoodFeaturesToTrackDetector方法来检测nkps 1000每帧中的显着特征。 然后它使用鲁棒的 RANSAC 和特征检测器方法使用PtrKeypointBasedMotionEstimator motionEstBuilder makePtrKeypointBasedMotionEstimator(est)类创建运动估计器并使用motionEstBuilder-setDetector (feature_detector)设置特征检测器。 RANSAC 参数sizeepsthreshprob 第三步创建需要先前运动估计器的稳定器。 您可以选择isTwoPass 1一或两遍稳定器。 如果使用两遍稳定器TwoPassStabilizer *twoPassStabilizer new TwoPassStabilizer()结果通常会更好。 但是在此示例中这在计算上较慢。 如果使用其他选项单程稳定器OnePassStabilizer *onePassStabilizer new OnePassStabilizer()结果会更糟但响应速度会更快。 稳定器需要设置其他选项才能正常工作例如源视频文件stabilizer-setFrameSource(source)和运动估计器stabilizer-setMotionEstimator(motionEstBuilder)。 它还需要将稳定器转换为简单的帧源视频以读取稳定的帧stabilizedFrames.reset(dynamic_castIFrameSource*(stabilizer))。 最后一步使用创建的稳定器稳定视频。 创建processing(PtrIFrameSource stabilizedFrames)函数来处理和稳定每个帧。 此函数需要引入一条路径来保存生成的视频string outputPath .//stabilizedVideo.avi和设置播放速度double outputFps 25。 此后此函数将计算每个稳定的帧直到不再有帧stabilizedFrame stabilizedFrames- nextFrame().empty()。 在内部稳定器首先估计每个帧的运动。 此函数创建一个视频编写器writer.open(outputPath,VideoWriter::fourcc(X,V,I,D), outputFps, stabilizedFrame.size())以 XVID 格式存储每个帧。 最后它保存并显示每个稳定的帧直到用户按下Esc键。 为了演示如何使用 OpenCV 稳定视频使用了先前的videoStabilizer示例。 该示例从命令行执行如下所示 bin_dir\videoStabilizer.exe .\cube4.avi .\cube4_stabilized.avi 注意 该cube4.avi视频可在 OpenCV 示例文件夹中找到。 它还具有大量的相机移动这对于此示例来说是完美的。 为了显示稳定结果首先请参见下图中的cube4.avi的四个帧。 这些帧之后的图显示了cube4.avi和cube4_stabilized.avi的前 10 个帧没有图的左侧和图的右侧稳定叠加。 cube4.avi视频的四个连续帧是摄像机的移动 cube4.avi和cube4_stabilizated视频的 10 个叠加帧 通过右图查看上图您可以看到由于稳定减少了摄像机运动产生的振动。 超分辨率 超分辨率是指是指通常用于从较低分辨率的图像序列中提高图像或视频空间分辨率的技术或算法。 它与传统的图像缩放技术不同传统的图像缩放技术使用单个图像来提高分辨率同时保持锐利的边缘。 相反超分辨率合并了来自同一场景的多个图像的信息以表示最初在原始图像中未捕获的细节。 从真实场景捕获图像或视频的过程需要以下步骤 采样这是连续系统从理想离散系统场景开始的变换没有混叠几何变换此是指根据摄像机的位置和镜头系统应用一组变换例如平移或旋转来理想地推断到达每个传感器的场景细节模糊这是由于镜头系统或积分期间场景中的现有运动造成的二次采样传感器仅对可使用的像素数照片进行积分 您可以在下图中看到此图像捕获过程 从真实场景捕获图像的过程 在此捕获过程中场景的细节由不同的传感器集成因此每次捕获中的每个像素都包含不同的信息。 因此超分辨率基于试图找到获得场景不同细节的不同捕获之间的关系以便创建具有更多信息的新图像。 因此超分辨率用于再生具有更高分辨率的离散场景。 可以通过各种技术来获得超分辨率从空间领域中最直观的技术到基于频谱分析的技术。 技术基本上分为光学技术使用镜头变焦等或基于图像处理的技术。 本章重点介绍基于图像处理的超分辨率。 这些方法使用低分辨率图像或其他无关图像的其他部分来推断高分辨率图像的外观。 这些算法也可以分为频域或空间域。 最初超分辨率方法仅适用于灰度图像但是已经开发出新方法来使它们适应彩色图像。 通常由于低分辨率和高分辨率图像的尺寸很大并且可能需要数百秒才能生成图像因此在空间和时间上都需要超分辨率。 为了尝试减少计算时间当前将预处理器用于负责使这些功能最小化的优化器。 另一种选择是使用 GPU 处理来改善计算时间因为超分辨率过程固有地可并行化。 本章重点介绍 OpenCV 3.0 Alpha 中的superres模块其中包含一组可用于解决分辨率增强问题的函数和类。 该模块实现了多种基于图像处理超分辨率的方法。 本章重点介绍已实现的双边总变异 L1BTVL1超分辨率方法。 超分辨率过程的主要困难是估计扭曲函数以建立超分辨率图像。 双总变异 L1 使用光流来估计翘曲函数。 注意 您可以在这个页面和这个页面找到双边 TV-L 方法的更多详细信息。 在 OpenCV 示例[opencv_source_code]/samples/gpu/super_resolution.cpp中可以找到超分辨率的基本示例。 注意 您还可以从这个页面的 OpenCV GitHub 存储库中下载此示例。 对于以下超分辨率示例项目superresolution.pro项目文件应包括以下库lopencv_core300lopencv_imgproc300lopencv_highgui300lopencv_features2d300lopencv_videoio300和lopencv_superres300才能正常工作 #include iostream #include iomanip #include string#include opencv2/core.hpp #include opencv2/core/utility.hpp #include opencv2/highgui.hpp #include opencv2/imgproc.hpp #include opencv2/superres.hpp #include opencv2/superres/optical_flow.hpp #include opencv2/opencv_modules.hppusing namespace std; using namespace cv; using namespace cv::superres;static PtrDenseOpticalFlowExt createOptFlow(string name);int main(int argc, char *argv[]) { // 1-Initialize the initial parameters// Input and output video string inputVideoName;string outputVideoName;if (argc 1)inputVideoName argv[1];elseinputVideoName .\\tree.avi;if (argc 2)outputVideoName argv[2];elseoutputVideoName .\\tree_superresolution.avi;const int scale 4;// Scale factorconst int iterations 180;// Iterations countconst int temporalAreaRadius 4;// Radius of the temporal search areastring optFlow farneback;// Optical flow algorithm// optFlow farneback;// optFlow tvl1;// optFlow brox;// optFlow pyrlk;double outputFps 25.0;// Playback speed output// 2- Create an optical flow method PtrDenseOpticalFlowExt optical_flow createOptFlow(optFlow);if (optical_flow.empty()) return -1;// 3- Create the superresolution method and set its parameters PtrSuperResolution superRes;superRes createSuperResolution_BTVL1();superRes-set(opticalFlow, optical_flow);superRes-set(scale, scale);superRes-set(iterations, iterations);superRes-set(temporalAreaRadius, temporalAreaRadius);PtrFrameSource frameSource;frameSource createFrameSource_Video(inputVideoName);superRes-setInput(frameSource);// Not use the first frameMat frame;frameSource-nextFrame(frame);// 4- Processing the input video with the superresolution// Show the initial optionscout Input : inputVideoName frame.size() endl;cout Output : outputVideoName endl;cout Playback speed output : outputFps endl;cout Scale factor : scale endl;cout Iterations : iterations endl;cout Temporal radius : temporalAreaRadius endl;cout Optical Flow : optFlow endl;cout endl;VideoWriter writer;double start_time,finish_time;for (int i 0;; i){cout [ setw(3) i ] : ;Mat result;// Calculate the processing timestart_time getTickCount(); superRes-nextFrame(result);finish_time getTickCount();cout (finish_time - start_time)/getTickFrequency() secs, Size: result.size() endl;if (result.empty()) break;// Show the result imshow(Super Resolution, result);if (waitKey(1000) 0) break;// Save the result on output fileif (!outputVideoName.empty()){ if (!writer.isOpened())writer.open(outputVideoName, VideoWriter::fourcc(X, V, I, D), outputFps, result.size());writer result;}}writer.release();return 0; }static PtrDenseOpticalFlowExt createOptFlow(string name) {if (name farneback) return createOptFlow_Farneback();else if (name tvl1) return createOptFlow_DualTVL1();else if (name brox)return createOptFlow_Brox_CUDA();else if (name pyrlk)return createOptFlow_PyrLK_CUDA();elsecerr Incorrect Optical Flow algorithm - name endl;return PtrDenseOpticalFlowExt(); }本示例创建一个程序超分辨率以获取具有超分辨率的视频。 它采用输入视频的路径或使用默认视频路径.\tree.avi。 显示所得的视频并将其另存为.\tree_superresolution.avi。 首先包含superres.hpp和superres/optical_flow.hpp标头并使用cv::superres命名空间。 该示例遵循四个重要步骤。 第一步设置初始参数。 输入视频路径使用标准输入inputVideoName argv[1]选择视频文件如果没有输入视频文件则使用默认视频文件。 输出视频路径还使用输入标准outputVideoName argv[2]选择输出视频文件如果它没有输出视频文件则使用默认输出视频文件.\tree_superresolution同时也设置输出回放速度double outputFps 25.0。 superresolution方法的其他重要参数是比例因子const int scale 4迭代count(const int iterations 100时间搜索区域的半径const int temporalAreaRadius 4和光流算法string optFlow farneback。 。 第二步创建光流方法以检测显着特征并针对每个视频帧跟踪它们。 已经创建了一种新方法static PtrDenseOpticalFlowExt createOptFlow(string name)以便在不同的光流方法之间进行选择。 您可以在 FarnebackTVL1Brox 和 Pyrlk 光流方法之间进行选择。 编写了新的方法static PtrDenseOpticalFlowExt createOptFlow(string name)以创建光学流动方法来跟踪特征。 两个最重要的方法是 FarnebackcreateOptFlow_Farneback()和 TV-L1createOptFlow_DualTVL1()。 第一种方法基于 Gunner Farneback 的算法该算法计算帧中所有点的光流。 第二种方法基于电视能量的双重公式计算两个图像帧之间的光流并采用有效的逐点阈值化步骤。 此第二种方法在计算上更有效。 不同光流方法之间的比较方法复杂度可并行化Farneback二次否TVL1线性是Brox线性是Pyrlk线性否 注意 您还可以在此处了解有关 Farneback 光流方法的更多信息。 第三步创建并设置superresolution方法。 创建此方法的一个实例PtrSuperResolution superRes该实例使用双边总变异 L1 算法superRes createSuperResolution_BTVL1()。 对于算法此方法具有以下参数 scale这是比例因子iterations这是迭代计数tau这是最速下降法的渐近值lambda这是权重参数用于平衡数据项和平滑度项alpha这是双边电视中空间分布的参数btvKernelSize这是双边电视过滤器的核大小blurKernelSize这是高斯模糊核大小blurSigma这是高斯模糊西格玛temporalAreaRadius这是时间搜索区域的半径opticalFlow这是一种密集光流算法 这些参数设置如下 superRes-set(parameter, value);仅设置以下参数 其他参数使用其默认值 superRes-set(opticalFlow, optical_flow); superRes-set(scale, scale); superRes-set(iterations, iterations); superRes-set(temporalAreaRadius, temporalAreaRadius);之后选择输入视频帧superRes-setInput(frameSource)。 最后一步处理输入视频以计算超分辨率。 对于每个视频帧计算超分辨率superRes-nextFrame(result) 这种计算在计算上非常慢因此估计处理时间可以显示进度。 最后显示每个结果帧imshow(Super Resolution, result)并保存writer result。 为了显示超分辨率的结果比较了tree.avi和tree_superresolution.avi视频的第一帧的一小部分是否有超分辨率 tree.avi和tree_superresolution.avi视频的第一帧的一部分不包括超分辨率处理 在上图的右侧部分由于超分辨率过程您可以在树的叶子和树枝中观察到更多细节。 拼接 图像拼接或照片拼接可以发现具有一定程度重叠的图像之间的对应关系。 此过程将一组图像与重叠的视场组合在一起以生成全景图像或高分辨率图像。 大多数用于图像拼接的技术需要在图像之间几乎完全重叠以产生无缝结果。 一些数码相机可以在内部拼接一组图像以构建全景图像。 下图显示了一个示例 通过拼接创建的全景图像 注意 可以在这个页面上找到前面的图像示例和有关图像拼接的更多信息。 拼接通常可以分为三个重要步骤 配准图像表示一组图像中的匹配特征以搜索使重叠像素之间差异的绝对值之和最小的位移。 直接比对方法可用于获得更好的结果。 用户还可以添加全景图的粗略模型以帮助特征匹配阶段在这种情况下结果通常更准确且计算速度更快。校准图像将重点放在最小化理想模型和相机镜头系统之间的差异不同的相机位置和光学缺陷例如失真曝光色差等。合成图像使用上一步校准的结果并将图像重新映射到输出投影。 图像之间的颜色也会进行调整以补偿曝光差异。 将图像融合在一起并进行接缝线调整以最小化图像之间接缝的可见性。 当从空间的同一点拍摄了图像片段时可以使用各种地图投影之一进行拼接。 最重要的地图投影如下所示 直线投影在这里在与全景球在一个点相交的二维平面上查看拼接图像。 无论图像上的方向如何现实中笔直的线都显示类似。 当视野开阔大约 120 度时图像在边框附近会变形。圆柱投影此处拼接图像显示 360 度水平视场和有限的垂直视场。 该投影旨在被视为好像图像被包裹在圆柱体中并从内部观看。 在 2D 平面上查看时水平线看起来是弯曲的而垂直线则保持笔直。球形投影在这里拼接图像显示了 360 度水平视野和 180 度垂直视野即整个球体。 具有这种投影的全景图像旨在像被包裹在一个球体中并从内部观看一样被观看。 在 2D 平面上查看时水平线看起来像圆柱投影一样弯曲而垂直线则保持垂直。立体投影或鱼眼投影通过将虚拟摄像机指向下方并将视场设置为足够大以显示整个地面及其上方的某些区域可以将其用于形成一个小行星全景。 将虚拟摄像机指向上方会产生隧道效果。帕尼尼投影这具有专业投影可能比常规制图投影在美学上更具优势。 此投影将同一图像中的不同投影组合在一起以微调输出全景图像的最终外观。 本章重点介绍 OpenCV 3.0 Alpha 中的stitching模块和detail子模块其中包含实现拼接器的一组函数和类。 使用这些模块可以配置或跳过某些步骤。 实现的缝合示例具有以下常规示意图 在 OpenCV 示例中有两个基本的缝合示例可以在[opencv_source_code]/samples/cpp/stitching.cpp]和[opencv_source_code]/samples/cpp/stitching_detailed.cpp]处找到。 对于以下更高级的拼接示例stitchingAdvanced.pro项目文件必须包含以下库才能正常工作lopencv_core300lopencv_imgproc300lopencv_highgui300lopencv_features2d300lopencv_videoio300, lopencv_imgcodecs300和lopencv_stitching300 #include iostream #include string #include opencv2/opencv_modules.hpp #include opencv2/core/utility.hpp #include opencv2/imgcodecs.hpp #include opencv2/highgui.hpp #include opencv2/features2d.hpp #include opencv2/stitching/detail/blenders.hpp #include opencv2/stitching/detail/camera.hpp #include opencv2/stitching/detail/exposure_compensate.hpp #include opencv2/stitching/detail/matchers.hpp #include opencv2/stitching/detail/motion_estimators.hpp #include opencv2/stitching/detail/seam_finders.hpp #include opencv2/stitching/detail/util.hpp #include opencv2/stitching/detail/warpers.hpp #include opencv2/stitching/warpers.hppusing namespace std; using namespace cv; using namespace cv::detail;int main(int argc, char* argv[]) {// Default parametersvectorString img_names; double scale 1; string features_type orb;//surf or orb features type float match_conf 0.3f; float conf_thresh 1.f; string adjuster_method ray;//reproj or ray adjuster method bool do_wave_correct true; WaveCorrectKind wave_correct_type WAVE_CORRECT_HORIZ; string warp_type spherical; int expos_comp_type ExposureCompensator::GAIN_BLOCKS; string seam_find_type gc_color; float blend_strength 5; int blend_type Blender::MULTI_BAND; string result_name panorama_result.jpg;double start_time getTickCount();// 1-Input imagesif(argc 1){for(int i1; i argc; i) img_names.push_back(argv[i]);}else{ img_names.push_back(./panorama_image1.jpg);img_names.push_back(./panorama_image2.jpg);}// Check if have enough imagesint num_images static_castint(img_names.size()); if (num_images 2) {cout Need more images endl; return -1; }// 2- Resize images and find features stepscout Finding features... endl;double t getTickCount();PtrFeaturesFinder finder;if (features_type surf) finder makePtrSurfFeaturesFinder();else if (features_type orb) finder makePtrOrbFeaturesFinder();else {cout Unknown 2D features type: features_type endl; return -1; }Mat full_img, img;vectorImageFeatures features(num_images);vectorMat images(num_images);vectorSize full_img_sizes(num_images);for (int i 0; i num_images; i){full_img imread(img_names[i]);full_img_sizes[i] full_img.size();if (full_img.empty()) {cout Cant open image img_names[i] endl; return -1; }resize(full_img, img, Size(), scale, scale);images[i] img.clone();(*finder)(img, features[i]);features[i].img_idx i;cout Features in image # i1 are : features[i].keypoints.size() endl;}finder-collectGarbage();full_img.release();img.release();cout Finding features, time: ((getTickCount() - t) / getTickFrequency()) sec endl;// 3- Match featurescout Pairwise matching endl;t getTickCount();vectorMatchesInfo pairwise_matches; BestOf2NearestMatcher matcher(false, match_conf);matcher(features, pairwise_matches);matcher.collectGarbage();cout Pairwise matching, time: ((getTickCount() - t) / getTickFrequency()) sec endl;// 4- Select images and matches subset to build panorama vectorint indices leaveBiggestComponent(features, pairwise_matches, conf_thresh);vectorMat img_subset;vectorString img_names_subset;vectorSize full_img_sizes_subset;for (size_t i 0; i indices.size(); i){img_names_subset.push_back(img_names[indices[i]]);img_subset.push_back(images[indices[i]]);full_img_sizes_subset.push_back(full_img_sizes[indices[i]]);}images img_subset;img_names img_names_subset;full_img_sizes full_img_sizes_subset;// Estimate camera parameters roughHomographyBasedEstimator estimator;vectorCameraParams cameras;if (!estimator(features, pairwise_matches, cameras)){cout Homography estimation failed. endl; return -1; }for (size_t i 0; i cameras.size(); i){Mat R;cameras[i].R.convertTo(R, CV_32F);cameras[i].R R;cout Initial intrinsic # indices[i]1 :\n cameras[i].K() endl;}// 5- Refine camera parameters globally PtrBundleAdjusterBase adjuster;if (adjuster_method reproj)// reproj method adjuster makePtrBundleAdjusterReproj();else // ray method adjuster makePtrBundleAdjusterRay();adjuster-setConfThresh(conf_thresh);if (!(*adjuster)(features, pairwise_matches, cameras)) {cout Camera parameters adjusting failed. endl; return -1; }// Find median focal lengthvectordouble focals;for (size_t i 0; i cameras.size(); i){cout Camera # indices[i]1 :\n cameras[i].K() endl;focals.push_back(cameras[i].focal);}sort(focals.begin(), focals.end());float warped_image_scale;if (focals.size() % 2 1)warped_image_scale static_castfloat(focals[focals.size() / 2]);elsewarped_image_scale static_castfloat(focals[focals.size() / 2 - 1] focals[focals.size() / 2]) * 0.5f;// 6- Wave correlation (optional)if (do_wave_correct){vectorMat rmats;for (size_t i 0; i cameras.size(); i)rmats.push_back(cameras[i].R.clone());waveCorrect(rmats, wave_correct_type);for (size_t i 0; i cameras.size(); i)cameras[i].R rmats[i];}// 7- Warp imagescout Warping images (auxiliary)... endl;t getTickCount();vectorPoint corners(num_images);vectorUMat masks_warped(num_images);vectorUMat images_warped(num_images);vectorSize sizes(num_images);vectorUMat masks(num_images);// Prepare images masksfor (int i 0; i num_images; i){masks[i].create(images[i].size(), CV_8U);masks[i].setTo(Scalar::all(255));}// Map projectionsPtrWarperCreator warper_creator;if (warp_type rectilinear) warper_creator makePtrcv::CompressedRectilinearWarper(2.0f, 1.0f);else if (warp_type cylindrical) warper_creator makePtrcv::CylindricalWarper();else if (warp_type spherical) warper_creator makePtrcv::SphericalWarper();else if (warp_type stereographic) warper_creator makePtrcv::StereographicWarper();else if (warp_type panini) warper_creator makePtrcv::PaniniWarper(2.0f, 1.0f);if (!warper_creator){ cout Cant create the following warper warp_type endl; return 1; }PtrRotationWarper warper warper_creator-create(static_castfloat(warped_image_scale * scale));for (int i 0; i num_images; i){Mat_float K;cameras[i].K().convertTo(K, CV_32F);float swa (float)scale;K(0,0) * swa; K(0,2) * swa;K(1,1) * swa; K(1,2) * swa;corners[i] warper-warp(images[i], K, cameras[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]);sizes[i] images_warped[i].size();warper-warp(masks[i], K, cameras[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);}vectorUMat images_warped_f(num_images);for (int i 0; i num_images; i)images_warped[i].convertTo(images_warped_f[i], CV_32F);cout Warping images, time: ((getTickCount() - t) / getTickFrequency()) sec endl;// 8- Compensate exposure errors PtrExposureCompensator compensator ExposureCompensator::createDefault(expos_comp_type);compensator-feed(corners, images_warped, masks_warped);// 9- Find seam masksPtrSeamFinder seam_finder;if (seam_find_type no) seam_finder makePtrNoSeamFinder();else if (seam_find_type voronoi) seam_finder makePtrVoronoiSeamFinder();else if (seam_find_type gc_color)seam_finder makePtrGraphCutSeamFinder(GraphCutSeamFinderBase::COST_COLOR);else if (seam_find_type gc_colorgrad)seam_finder makePtrGraphCutSeamFinder(GraphCutSeamFinderBase::COST_COLOR_GRAD);else if (seam_find_type dp_color) seam_finder makePtrDpSeamFinder(DpSeamFinder::COLOR);else if (seam_find_type dp_colorgrad) seam_finder makePtrDpSeamFinder(DpSeamFinder::COLOR_GRAD);if (!seam_finder){cout Cant create the following seam finder seam_find_type endl; return 1; }seam_finder-find(images_warped_f, corners, masks_warped);// Release unused memoryimages.clear();images_warped.clear();images_warped_f.clear();masks.clear();// 10- Create a blender PtrBlender blender Blender::createDefault(blend_type, false);Size dst_sz resultRoi(corners, sizes).size();float blend_width sqrt(static_castfloat(dst_sz.area())) * blend_strength / 100.f;if (blend_width 1.f) blender Blender::createDefault(Blender::NO, false);else if (blend_type Blender::MULTI_BAND){ MultiBandBlender* mb dynamic_castMultiBandBlender*(blender.get());mb-setNumBands(static_castint(ceil(log(blend_width)/log(2.)) - 1.));cout Multi-band blender, number of bands: mb-numBands() endl;}else if (blend_type Blender::FEATHER){ FeatherBlender* fb dynamic_castFeatherBlender*(blender.get());fb-setSharpness(1.f/blend_width);cout Feather blender, sharpness: fb-sharpness() endl;} blender-prepare(corners, sizes);// 11- Compositing stepcout Compositing... endl;t getTickCount();Mat img_warped, img_warped_s;Mat dilated_mask, seam_mask, mask, mask_warped;for (int img_idx 0; img_idx num_images; img_idx){cout Compositing image # indices[img_idx]1 endl;// 11.1- Read image and resize it if necessary full_img imread(img_names[img_idx]);if (abs(scale - 1) 1e-1) resize(full_img, img, Size(), scale, scale);elseimg full_img;full_img.release();Size img_size img.size();Mat K;cameras[img_idx].K().convertTo(K, CV_32F);// 11.2- Warp the current image warper-warp(img, K, cameras[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped);// Warp the current image maskmask.create(img_size, CV_8U);mask.setTo(Scalar::all(255));warper-warp(mask, K, cameras[img_idx].R, INTER_NEAREST, BORDER_CONSTANT, mask_warped);// 11.3- Compensate exposure error step compensator-apply(img_idx, corners[img_idx], img_warped, mask_warped);img_warped.convertTo(img_warped_s, CV_16S);img_warped.release();img.release();mask.release();dilate(masks_warped[img_idx], dilated_mask, Mat());resize(dilated_mask, seam_mask, mask_warped.size());mask_warped seam_mask mask_warped;// 11.4- Blending images step blender-feed(img_warped_s, mask_warped, corners[img_idx]);}Mat result, result_mask;blender-blend(result, result_mask);cout Compositing, time: ((getTickCount() - t) / getTickFrequency()) sec endl;imwrite(result_name, result);cout Finished, total time: ((getTickCount() - start_time) / getTickFrequency()) sec endl;return 0; }此示例创建一个程序以使用 OpenCV 步骤拼接图像。 它采用输入路径来选择不同的输入图像或使用默认的输入图像.\panorama_image1.jpg和panorama_image2.jpg这些将在后面显示。 最后显示所得图像并将其另存为.\panorama_result.jpg。 首先包含stitching.hpp和detail标头并使用cv::detail命名空间。 还设置了更重要的参数您可以使用这些参数配置针迹处理。 如果您需要使用自定义配置则了解拼接过程的总体图上图非常有用。 这个高级示例包含 11 个重要步骤。 第一步读取并检查输入图像。 本示例需要两个或更多图像才能工作。 第二步使用double scale 1参数调整输入图像的大小并在每个图像上找到特征 您可以使用string features_type orb参数在 Surffinder makePtrSurfFeaturesFinder()或 Orbfinder makePtrOrbFeaturesFinder()特征查找器之间进行选择。 之后此步骤将调整输入图像的大小resize(full_img, img, Size(), scale, scale)并找到特征(*finder)(img, features[i])。 注意 有关 SURF 和 ORB 描述符的更多信息请参阅 Packt Publishing 的《OpenCV Essentials》的第 5 章。 第三步匹配先前找到的特征。 使用float match_conf 0.3f参数创建一个匹配器BestOf2NearestMatcher matcher(false, match_conf)。 第四步选择图像并匹配子集以构建全景图。 然后使用vectorint indices leaveBiggestComponent(features, pairwise_matches, conf_thresh)函数选择并匹配最佳特征。 通过这些特征将创建一个新的子集以供使用。 第五步使用包调整来全局调整的参数以构建调整器PtrBundleAdjusterBase adjuster。 给定一组从不同视点描绘多个 2D 或 3D 点的图像可以将束调整定义为同时细化 2D 或 3D 坐标描述场景几何形状以及所部署的相机的相对运动和光学特性参数根据涉及所有点的相应图像投影的最优性标准来获取图像。 有两种计算束调整的方法reprojadjuster makePtrBundleAdjusterReproj()或rayadjuster makePtrBundleAdjusterRay()这些方法是通过string adjuster_method ray参数选择的。 最后将该束调整用作(*adjuster)(features, pairwise_matches, cameras)。 第六步是可选步骤bool do_wave_correct true该步骤计算波形相关性以改善相机设置。 波相关的类型通过WaveCorrectKind wave_correct_type WAVE_CORRECT_HORIZ参数选择并计算为waveCorrect(rmats, wave_correct_type)。 第七步创建需要地图投影的变形图像。 地图投影以前已经描述过可以是直线圆柱球形立体或帕尼尼。 实际上OpenCV 中实现了更多的地图投影。 可以使用string warp_type spherical参数选择地图投影。 此后创建整形器PtrRotationWarper warper warper_creator- create(static_castfloat(warped_image_scale * scale))并变形每个图像warper-warp(masks[i], K, cameras[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i])。 第八步通过创建补偿器PtrExposureCompensator compensator ExposureCompensator::createDefault(expos_comp_type)补偿曝光误差并将其应用于每个变形图像compensator-feed(corners, images_warped, masks_warped)。 第九步找到接缝口罩。 此过程将为每个全景图像搜索最佳的附件区域。 OpenCV 中实现了一些方法来执行此任务并且本示例使用string seam_find_type gc_color参数进行选择。 这些方法是NoSeamFinder不使用此方法VoronoiSeamFinderGraphCutSeamFinderBase::COST_COLORGraphCutSeamFinderBase::COST_COLOR_GRADDpSeamFinder::COLOR和DpSeamFinder::COLOR_GRAD。 第十步创建一个混合器将每个图像合成全景图。 OpenCV 中实现了两种类型的搅拌器即MultiBandBlender* mb dynamic_castMultiBandBlender*(blender.get())和FeatherBlender* fb dynamic_castFeatherBlender*(blender.get())可以使用int blend_type Blender::MULTI_BAND参数进行选择。 最后准备搅拌器blender-prepare(corners, sizes)。 最后一步合成了最终的全景图。 该步骤需要前面的步骤来配置针脚。 执行四个子步骤以计算最终全景图。 首先读取每个输入图像full_img imread(img_names[img_idx])并在必要时调整其大小resize(full_img, img, Size(), scale, scale)。 其次将这些图像与创建的变形器warper-warp(img, K, cameras[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped)变形。 第三使用创建的补偿器compensator-apply(img_idx, corners[img_idx], img_warped, mask_warped)补偿这些图像的曝光误差。 最后使用创建的混合器混合这些图像。 现在最终结果全景图已保存在string result_name panorama_result.jpg文件中。 为了向您显示高级拼接结果将两个输入图像进行拼接结果全景图如下所示 总结 在本章中您学习了如何使用 OpenCV 的三个重要模块来处理视频中的图像处理。 这些模块是视频稳定超分辨率和拼接。 每个模块还解释了一些理论基础。 在本章的每一节中都将说明用 C 开发的完整示例。 还显示了每个模块的图像结果显示了主要效果。 下一章介绍高动态范围图像并向您展示如何使用 OpenCV 处理它们。 通常在现在称为计算摄影的范围内考虑高动态范围成像。 粗略地说计算摄影是指允许您扩展数字摄影的典型功能的技术。 这可能包括硬件附加组件或修改但主要指基于软件的技术。 这些技术可能会产生“传统”数码相机无法获得的输出图像。
http://www.w-s-a.com/news/260390/

相关文章:

  • 杏坛网站制作太原做网站要多少钱呢
  • 做新闻类网站还有市场吗东莞黄页网广告
  • 地方网站做外卖专业做互联网招聘的网站有哪些
  • 网站推广公司兴田德润紧急网页升级紧急通知
  • 厦门做网站哪家强企业网站网页设计的步骤
  • 普拓网站建设济南行业网站建设
  • 燕郊 网站开发网站里的地图定位怎么做
  • 门户网站建设招标互联网创业项目概述
  • 用什么做网站比较好市场调研公司是做什么的
  • 电商网站充值消费系统绍兴网站优化
  • 深圳网站建设公司联虚拟币交易网站开发
  • 专业网站设计建设公司抖音代运营公司排名前十强
  • 做网站架构肃北蒙古族自治县建设局网站
  • 推广网站怎么建经济研究院网站建设方案
  • 网站建设商家淘宝客自建网站做还是用微信qq做
  • django做网站效率高吗涉县移动网站建设报价
  • 做外贸网站注册什么邮箱能够做渗透的网站
  • 购物网站 怎么做织梦网站会员功能
  • 北京市网站开发公司郑州联通网站备案
  • 温岭专业营销型网站建设地址wordpress小程序怎么不用认证审核
  • 网站建设主体设计要求微信公众号缴费
  • 网站建设的税率WordPress多用户建站
  • 专业门户网站的规划与建设网络培训
  • 东莞汽车总站停止营业crm管理系统在线使用
  • 深圳网站建设公司哪个网络优化是做什么的
  • 大连地区做网站自己怎么做电影网站
  • 成都APP,微网站开发手机要访问国外网站如何做
  • 网站app建设用discuz做的手机网站
  • vs 2008网站做安装包公众号登录超时
  • 银川做网站推广wordpress dux会员中心