OpenCV3 和 Qt5 计算机视觉:1~5

一、OpenCV 和 Qt 简介

在最基本的形式和形状中,“计算机视觉”是一个术语,用于标识用于使数字设备具有视觉感觉的所有方法和算法。 这意味着什么? 好吧,这就是听起来的确切含义。 理想情况下,计算机应该能够通过标准相机(或与此相关的任何其他类型的相机)的镜头看到世界,并且通过应用各种计算机视觉算法,它们应该能够检测甚至识别并计数人脸。 图像中的对象,检测视频馈送中的运动,然后执行更多操作,这些操作乍一看只能是人类的期望。 因此,要了解计算机视觉的真正含义,最好知道计算机视觉旨在开发方法以实现所提到的理想,使数字设备具有查看和理解周围环境的能力。 值得注意的是,大多数时间计算机视觉和图像处理可以互换使用(尽管对这个主题的历史研究可能证明应该相反)。 但是,尽管如此,在整本书中,我们仍将使用“计算机视觉”一词,因为它是当今计算机科学界中更为流行和广泛使用的术语,并且因为正如我们将在本章稍后看到的那样,“图像处理”是 OpenCV 库的模块,我们还将在本章的后续页面中介绍,并且还将在其完整的一章中介绍它。

计算机视觉是计算机科学中当今最受欢迎的学科之一,它被用于各种应用,从检测癌变组织的医疗工具到可以帮助制作所有闪亮音乐视频和电影的视频编辑软件,以及军事级目标检测器可帮助在地图上找到交通标志检测器的特定位置,以帮助无人驾驶汽车找到路。 好吧,很显然,我们无法完成为计算机视觉命名的所有可能性,但是我们可以确定,这是一个有趣的话题,并且将会存在很长时间。 还值得一提的是,计算机视觉领域的工作和职业市场正在迅速扩大,并且它正在日趋增长。

在计算机视觉开发人员和专家使用的最受欢迎的工具中,有两个最著名的开源社区框架,OpenCV 和 Qt 也在您的书名中。 每天,世界各地成千上万的开发人员,从成熟的公司到创新的初创公司,都使用这两个框架为各种行业(例如,我们提到的行业)构建应用,这正是您将学到的东西。 这本书。

在本章中,我们将介绍以下主题:

  • 引入 Qt,这是一个开放源代码和跨平台的应用开发框架
  • 引入 OpenCV,一个开源的跨平台计算机视觉框架
  • 如何在 Windows,MacOS 和 Linux 操作系统上安装 Qt
  • 如何从 Windows,MacOS 和 Linux 操作系统上的源代码构建 OpenCV
  • 配置您的开发环境以结合使用 Qt 和 OpenCV 框架来构建应用
  • 使用 Qt 和 OpenCV 构建您的第一个应用

需要什么?

这是在本章引言中所说的之后最明显的问题,但是对于它的答案也是我们学习计算机视觉的第一步。 本书适用于熟悉 C++ 编程语言并希望开发功能强大且外观精美的计算机视觉应用的开发人员,而这些应用可以在不同的操作系统上很好地完成工作,而无需付出太多努力。 本书旨在带您踏上激动人心的旅程,遍历计算机视觉的不同主题,着重于动手练习并一次一步地发展所学内容。

具有足够 C++ 经验的任何人都知道,使用原始 C++ 代码并取决于特定于 OS 的 API 来编写视觉上丰富的应用并非易事。 因此,几乎每个 C++ 开发人员(或至少是认真从事 C++ 工作的认真的开发人员)都使用一个或另一个框架来简化此过程。 Qt 是最广为人知的 C++ 框架。 实际上,它是首选,或者不是首选。 另一方面,如果您的目标是开发一个处理图像或可视化数据集的应用,则 OpenCV 框架可能是第一个(也是最受欢迎的)地址。 因此,这就是为什么本书着重介绍 Qt 和 OpenCV 的结合的原因。 如果不使用 Qt 和 OpenCV 等强大的框架,就不可能为不同的台式机和移动平台开发性能最佳的计算机视觉应用。

总结所讲内容,请确保您至少具有 C++ 编程语言的中级知识。 如果您不熟悉类,抽象类,继承,模板或指针之类的术语,请考虑首先阅读有关 C++ 的书。 对于所有其他主题,尤其是涵盖的所有动手实践主题,本书保证为您包括的所有示例和教程提供清晰的解释(或参考特定的文档页面)。 当然,要获得对 Qt 和 OpenCV 中如何实现模块和类的非常详细和深入的了解,您需要熟悉更多的资源,研究,有时甚至是核心数学计算,或者对如何使用 Qt 和 OpenCV 进行低级理解。 计算机或操作系统在现实世界中的性能,这完全超出了本书的范围。 但是,对于本书中涵盖的所有算法和方法,您将简要了解它们的用途,使用方式,时间和位置以及足够的指导原则,以使您可以继续深入研究。

Qt 简介

您已经听说过它,甚至可能在不知道的情况下使用它。 它是许多世界著名的商业和开源应用的基础,例如 VLC Player,Calibre 等。 Qt 框架被大多数所谓的财富 500 强公司使用,我们甚至无法开始定义它在世界上许多应用开发团队和公司中的使用和流行程度。 因此,我们将从介绍开始,然后从那里开始。

首先,让我们简单介绍一下 Qt 框架,以使我们步入正轨。 清晰地了解整个事情,没有什么比让您更喜欢框架了。 因此,Qt 框架是一个开放源代码应用开发框架,目前由 Qt 公司构建和管理,它广泛用于创建外观丰富且跨平台的应用,这些应用可以在很少的不同操作系统或设备上运行,而无需任何负担。 为了进一步分解,开源是其中最明显的部分。 这意味着您可以访问 Qt 的所有源代码。 通过丰富的外观,我们意味着 Qt 框架中存在足够的资源和功能来编写非常漂亮的应用。 对于最后一部分,跨平台,这基本上意味着,例如,如果使用针对 Microsoft Windows 操作系统的 Qt 框架模块和类开发应用,则可以按原样针对 MacOS 或 Linux 对其进行编译和构建。 ,而无需更改任何代码(几乎),只要您在应用中不使用任何非 Qt 或平台特定的库即可。

在撰写本书时,Qt 框架(或以下简称 Qt)的版本为 5.9.X,其中包含许多模块,几乎可用于开发应用的任何目的。 Qt 将这些模块分为以下四个主要类别:

  • Qt Essentials
  • Qt 附加组件
  • 附加模块
  • 技术预览模块

让我们看看它们是什么以及它们包括什么,因为在整本书中我们将与之打交道。

Qt Essentials

这些是 Qt 承诺可在所有受支持平台上使用的模块。 它们基本上是 Qt 的基础,并且包含几乎所有 Qt 应用使用的大多数类。 Qt Essential 模块包括所有通用模块和类。 确实要注意通用一词,因为这正是这些模块的用途。 以下是用于快速研究现有模块并供以后参考的简要列表:

模块

说明

Qt Core

这些是其他模块使用的核心非图形类。

Qt GUI

这些是图形用户界面(GUI)组件的基类。 这些包括 OpenGL。

Qt Multimedia

这些是音频,视频,广播和照相机功能的类。

Qt Multimedia Widgets

这些是基于小部件的类,用于实现多媒体功能。

Qt Network

这些是使网络编程更轻松,更可移植的类。

Qt QML

这些是 QML 和 JavaScript 语言的类。

Qt Quick

这是一个声明性框架,用于使用自定义用户界面构建高度动态的应用。

Qt Quick Controls

这些是可重用的基于 Qt Quick 的 UI 控件,用于创建经典的桌面样式用户界面。

Qt Quick Dialogs

这些类型可以在 Qt Quick 应用中创建系统对话框并与之交互。

Qt Quick Layouts

这些布局是用于在用户界面中安排基于 Qt Quick 2 的项目的项目。

Qt SQL

这些是使用 SQL 进行数据库集成的类。

Qt Test

这些是用于单元测试 Qt 应用和库的类。

Qt Widgets

这些是使用 C++ 小部件扩展 Qt GUI 的类。

有关更多信息,请参考这里。

请注意,不可能涵盖本书中的所有模块和所有类,并且可能不是一个好主意,并且在大多数情况下,我们将坚持我们需要的任何模块和类; 但是,到本书结尾时,您将很容易自己独自探索 Qt 中所有众多强大的模块和类。 在接下来的章节中,您将学习如何在您的项目中包括模块和类,因此,现在,让我们不要花太多时间来烦恼,而只专注于了解 Qt 的真正含义以及它在我们的脑海中所包含的内容。

Qt 附加组件

这些模块可能在所有平台上都可用或不可用。 这意味着它们用于开发特定功能,而不是 Qt Essentials 的通用性质。 这些类型的模块的一些示例是 Qt 3D,Qt 打印支持,Qt WebEngine,Qt 蓝牙等等。 您可以始终参考 Qt 文档以获取这些模块的完整列表,实际上,它们太多了,因此无法在此处列出。 在大多数情况下,您只需看一下就可以简要了解模块的用途。

有关此的更多信息,您可以参考这里。

附加模块

这些模块提供了附加功能,并获得了 Qt 的商业许可。 是的,您猜对了,这些模块仅在 Qt 的付费版本中可用,而在 Qt 的开源版本和免费版本中不提供,但它们的主要目的是帮助我们完成非常具体的任务,本书完全不需要。 您可以使用 Qt 文档页面获取列表。

有关此的更多信息,您可以参考这里。

技术预览模块

顾名思义,这些模块通常是在不能保证在所有情况下都能正常工作的状态下提供的。 它们可能包含也可能不包含 bug 或其他问题,并且它们仍在开发中,并且作为测试和反馈目的的预览。 一旦开发了模块并使其变得足够成熟,它就可以在前面提到的其他类别中使用,并且已从技术预览类别中删除。 在撰写本书时,这些类型的模块的一个示例是 Qt Speech,该模块旨在增加对 Qt 应用中文本到语音的支持。 如果您希望成为一名成熟的 Qt 开发人员,那么始终关注这些模块总是一个好主意。

有关此的更多信息,您可以参考这里。

Qt 支持的平台

当我们谈论开发应用时,该平台可能具有许多不同的含义,包括 OS 类型,OS 版本,编译器类型,编译器版本和处理器的架构(32 位,64 位,ARM 等)。 Qt 支持许多(如果不是全部)知名平台,并且通常足够快以在新平台发布时赶上它们。 以下是在撰写本书时(Qt 5.9)Qt 支持的平台列表。 请注意,您可能不会使用这里提到的所有平台,但可以使您了解 Qt 到底有多强大和跨平台:

平台

编译器

注解

Windows

Windows 10(64 位)

MSVC 2017,MSVC 2015,MSVC 2013,MinGW 5.3

Windows 10(32 位)

MSVC 2017,MSVC 2015,MSVC 2013,MinGW 5.3

Windows 8.1(64 位)

MSVC 2017,MSVC 2015,MSVC 2013,MinGW 5.3

Windows 8.1(32 位)

MSVC 2017,MSVC 2015,MSVC 2013,MinGW 5.3

Windows 7(64 位)

MSVC 2017,MSVC 2015,MSVC 2013,MinGW 5.3

Windows 7(32 位)

MSVC 2017,MSVC 2015,MSVC 2013,MinGW 5.3

MinGW 构建的 gcc 5.3.0(32 位)

Linux/X11

openSUSE 42.1(64 位)

GCC 4.8.5

RedHat 企业版 Linux 6.6(64 位)

GCC 4.9.1

devtoolset-3

RedHat 企业版 Linux 7.2(64 位)

GCC 5.3.1

devtoolset-4

Ubuntu 16.04(64 位)

Canonical 提供的 GCC

(Linux 32/64 位)

GCC 4.8,GCC 4.9,GCC 5.3

MacOS

macOS 10.10、10.11、10.12

苹果提供的 Clang

嵌入式平台:嵌入式 Linux,QNX,INTEGRITY

嵌入式 Linux

GCC

ARM Cortex-A,具有基于 GCC 的工具链的英特尔板

QNX 6.6.0、7.0(armv7le 和 x86)

QNX 提供的 GCC

主机:RHEL 6.6(64 位),RHEL 7.2(64 位),Windows 10(64 位),Windows 7(32 位)

INTEGRITY 11.4.x

由 Green Hills 提供的 INTEGRITY

主机:64 位 Linux

移动平台:Android,iOS,通用 Windows 平台(UWP)

通用 Windows 平台(UWP)(x86,x86_64,armv7)

MSVC 2017,MSVC 2015

主机:Windows 10

iOS 8、9、10(armv7,arm64)

苹果提供的 Clang

macOS 10.10 主机

Android(API 等级:16)

Google 提供的 GCC,MinGW 5.3

主机:RHEL 7.2(64 位),macOS 10.12,Windows 7(64 位)

参考这里

正如您将在下一节中看到的那样,我们将在 Windows 上使用 Microsoft Visual C++ 2015(或从此处开始,简称为 MSVC 2015)编译器,因为 Qt 和 OpenCV(您将在后面学习)都高度支持它。 我们还将在 Linux 上使用 GCC,在 MacOS 操作系统上使用 Clang。 所有这些都是免费和开源的工具,或者由操作系统提供者提供。 尽管我们的主要开发系统将是 Windows,但只要 Windows 与其他版本之间存在差异,我们就会介绍 Linux 和 MacOS 操作系统。 因此,整本书中的默认屏幕截图将是 Windows 的默认屏幕截图,并在它们之间存在任何严重差异的地方提供 Linux 和 MacOS 屏幕截图,而不仅仅是路径,按钮颜色等等之间的细微差异。

Qt Creator

Qt Creator 是用于开发 Qt 应用的 IDE集成开发环境)的名称。 在本书中,我们还将使用 IDE 来创建和构建项目。 值得注意的是,可以使用任何其他 IDE(例如 Visual Studio 或 Xcode)创建 Qt 应用,并且 Qt Creator 并不是构建 Qt 应用的要求,而是 Qt 框架安装程序随附的轻量级功能强大的 IDE。 默认。 因此,它具有的最大优势是易于与 Qt 框架集成。

以下是 Qt Creator 的屏幕截图,显示了处于代码编辑模式的 IDE。 在下一章中将介绍有关如何使用 Qt Creator 的详细信息,尽管我们也将在本章稍后的部分中尝试一些测试,而无需过多介绍:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kkOvmk3L-1681869945426)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/61cbbcd2-ea76-4767-8bf2-2098174f0f8b.png)]

OpenCV 简介

现在,是时候介绍 OpenCV,开源计算机视觉库或框架(如果需要的话)了,因为 OpenCV 本身可以互换使用它们,并且在本书中也可能会发生。 但是,在大多数情况下,我们只会坚持使用 OpenCV。 好吧,让我们先听听它的真正含义,然后在需要的地方对其进行分解。

OpenCV 是一个开放源代码和跨平台的库,用于开发计算机视觉应用。 着眼于速度和性能,它在各种模块中包含数百种算法。 这些模块也分为两种类型:MainExtra模块。 OpenCV 主要模块只是 OpenCV 社区内构建和维护的所有模块,它们是 OpenCV 提供的默认包的一部分。

这与 OpenCV 的额外模块形成了鲜明的对比,后者的模块或多或少是第三方库和包装程序的包装器,这些第三方库和接口将它们集成到 OpenCV 构建中。 以下是一些不同模块类型的示例,并分别对其进行了简要说明。 值得注意的是,OpenCV 中模块的数量(有时甚至是顺序)可以随着时间而改变,因此,最好记住的一点是,只要有可能,只要访问 OpenCV 文档页面即可。 错位,或者如果某些东西不在以前。

主要模块

这是一些 OpenCV 主模块的示例。 请注意,它们只是 OpenCV 中的少数几个模块(可能是使用最广泛的模块),而涵盖所有这些模块不在本书的讨论范围内,但是对 OpenCV 包含的内容有所了解是很有意义的,就像我们在本章前面的 Qt 中看到的东西。 他们来了:

  • 核心功能或简称为core模块包含所有其他 OpenCV 模块使用的所有基本结构,常量和函数。 例如,在此模块中定义了臭名昭著的 OpenCV Mat类,在本书的其余部分中,我们几乎将在每个 OpenCV 示例中使用该类。 第 4 章,“MatQImage”将涵盖此和密切相关的 OpenCV 模块以及 Qt 框架的相应部分。
  • 图像处理或imgproc模块包含许多用于图像过滤,图像转换的算法,顾名思义,它用于一般图像处理。 我们将在第 6 章,“OpenCV 中的图像处理”中介绍此模块及其功能。
  • 2D 特征框架模块或features2d包含用于特征提取和匹配的类和方法。 它们将在第 7 章,“特征和描述符”中进行详细介绍。
  • 视频模块包含用于主题的算法,例如运动估计,背景减法和跟踪。 该模块以及 OpenCV 的其他类似模块,将在第 9 章,“视频分析”中介绍。

额外模块

如前所述,额外模块主要是第三方库的包装器,这意味着它们仅包含集成这些模块所需的接口或方法。 示例附加模块将是text模块。 此模块包含用于在图像或 OCR光学字符识别)中使用文本检测的接口,并且您还将需要这些第三方模块来进行此项工作,因此不涉及这些第三方模块。 作为本书的一部分,但您始终可以查看 OpenCV 文档以获取额外模块的更新列表及其使用方式。

有关此的更多信息,您可以参考这里。

OpenCV 支持的平台:如前所述,在应用开发的情况下,平台不仅仅是操作系统。 因此,我们需要知道 OpenCV 支持哪些操作系统,处理器架构和编译器。 OpenCV 是高度跨平台的,几乎像 Qt 一样,您可以为所有主要操作系统(包括 Windows,Linux,macOS,Android 和 iOS)开发 OpenCV 应用。 稍后我们将看到,我们将在 Windows 上使用 MSVC 2015(32 位)编译器,在 Linux 上使用 GCC,在 MacOS 上使用 Clang。 还需要注意的是,我们需要自己使用 OpenCV 的源代码来构建 OpenCV,因为目前,尚未为上述编译器提供预构建的二进制文件。 但是,正如您稍后将看到的,如果您具有正确的工具和说明,则可以轻松地为任何操作系统构建 OpenCV。

安装 Qt

在本节中,我们将执行必需的步骤,以在您的计算机上设置完整的 Qt SDK(软件开发工具包)。 我们将从在 Windows 操作系统上设置 Qt 开始,并在需要时记下 Linux(在我们的情况下为 Ubuntu,但对于所有 Linux 发行版几乎相同)和 MacOS 操作系统。 所以,让我们开始吧。

准备安装 Qt

为了能够安装和使用 Qt,我们需要首先创建一个 Qt 帐户。 尽管这不是强制性的,但仍强烈建议您这样做,因为您可以访问与此单一,统一和免费帐户有关的所有 Qt。 对于要安装的 Qt 的任何最新版本,您将需要 Qt 帐户凭据,只有在创建 Qt 帐户后才能使用。 为此,首先,您需要使用自己喜欢的浏览器访问 Qt 网站。 链接在这里。

这是它的屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QDTI6m05-1681869945427)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/60ac20d4-7d32-462c-b005-5671d08c06f8.png)]

在这里,您必须使用“登录”按钮下方的“创建 Qt 帐户”页面来使用您的电子邮件地址。 该过程几乎与网上任何类似的帐户创建过程相同。 可能会要求您输入验证码图像以证明您不是机器人,或者单击电子邮件中的激活链接。 完成 Qt 要求的过程后,您将拥有自己的 Qt 帐户用户,即您的电子邮件和密码。 请记录下来,因为稍后将需要它。 从现在开始,我们将其称为您的 Qt 帐户凭据。

在哪里获取?

至此,我们开始下载 Qt 开发所需的工具。 但是,从哪里开始呢? Qt 通过 Qt 下载网页维护所有正式发布的版本。 这里是一个链接。

如果打开浏览器并导航到上一个网页,将会看到一个非常简单的网页(类似于文件浏览器程序),然后您需要从中自行选择合适的文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fBOjL6eM-1681869945427)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/f660b231-48ae-4b25-a118-552ea9fcdffe.png)]

Qt 在此处发布了其所有官方工具,并且您将看到,Last Modify列将一直在变化。 有些条目不是很常见,有些则更多。 目前,我们不会详细介绍每个文件夹包含的内容以及它们的用途,但是正如您将在本书后面看到的那样,我们所需的几乎所有工具都在一个安装文件中,和qt文件夹下。 因此,通过单击每个条目,导航到以下文件夹:qt/5.9/5.9.1/

您会发现浏览器的网址中添加了相同的内容。

您应该注意,访问此页面时可能会有一个较新的版本,或者该版本可能不再可用,因此您需要从前面提到的 Qt 下载页面开始,然后逐步进入最新的[ Qt version文件夹。 或者,您可以使用 Qt 下载主页中的存档链接始终访问以前的 Qt 版本。

以下是您需要从前面的文件夹下载的文件: 对于 Windowsqt-opensource-windows-x86-5.9.1.exe 对于 macOSqt-opensource-mac-x64-5.9.1.dmg 对于 Linuxqt-opensource-linux-x64-5.9.1.run

这些是预先构建的 Qt 库,并包含每个提到的操作系统的完整 Qt SDK。 这意味着您无需自己构建 Qt 库即可使用它们。 这些安装文件通常包括以下内容以及我们将使用的工具:

  • Qt Creator(版本 4.3.1)
  • 每个操作系统支持的所有编译器和架构的预构建库:
    • Windows 桌面,Windows Mobile
    • Linux 桌面
    • MacOS 桌面和 iOS
    • Android(在所有平台上)

Windows 用户:Qt 安装包还包括其中包含的 MinGW 编译器,但是由于我们将使用另一个编译器,即 MSVC 2015,因此您实际上与它没有任何关系。 尽管安装它不会造成任何伤害。

如何安装?

您需要通过执行下载的安装文件来开始安装。 如果您使用的是 Windows 或 MacOS 操作系统,则只需运行下载的文件。 但是,如果您使用的是 Linux,则可能需要先使下载的.run文件成为可执行文件,然后才能实际运行它。 可以在 Linux 上执行以下命令以使安装程序文件可执行:

代码语言:javascript
复制
chmod +x qt-opensource-linux-x64-5.9.1.run

或者,您可以简单地右键单击.run文件,并使用属性对话框使其可执行:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RLfoDLL8-1681869945428)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/6cc75c84-7645-4be9-9dbf-7787a676c7e8.png)]

请注意,即使没有下载任何内容,您仍然需要可以正常使用的互联网连接,这只是为了确认您的 Qt 帐户凭据。 运行安装程序将为您显示以下一系列需要完成的对话框。 只要对话框上的说明足够,请确保已阅读并提供所需的内容,然后按“下一步”,“同意”或类似按钮继续操作。 如以下屏幕快照所示,您需要提供 Qt 帐户凭据才能继续安装。 这些对话框在所有操作系统上都是相同的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDY29W8z-1681869945428)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/2b7735a9-b235-4884-a098-d362c9b6eda4.png)]

其余对话框未在此处显示,但是它们几乎是不言自明的,并且,如果您曾经在任何计算机上安装任何应用,那么您肯定会看到类似的对话框,并且通常不需要对其进行介绍。

Windows 用户

安装 Windows 版 Qt 时,请确保在“选择组件”对话框上,选中“msvc2015 32 位”选项旁边的复选框。 其余的是可选的,但是值得注意的是,安装所有平台(或在 Qt 中称为 Kits)通常需要太多空间,并且在某些情况下会影响 Qt Creator 的性能。 因此,只需确保选择您将真正使用的任何东西。 就本书而言,它只是您绝对需要的 msvc2015 32 位选项。

Windows 用户要注意的重要事项:您还需要安装至少启用了 C++ 桌面开发功能的 Visual Studio 2015。 Microsoft 为 Visual Studio 提供了不同类型的许可证。 您可以下载社区版本用于教育目的,这对于本书的示例来说绝对足够,并且是免费提供的,但是使用 Enterprise,Professional 或其他类型的 Visual Studio 也可以,只要它们具有 MSVC 2015 32 位编译器。

MacOS 用户

如果您没有在 Mac 上安装 XCode,则在为 Mac OS 安装 Qt 时,将面临以下对话框(或一个非常类似的对话框,具体取决于您使用的 MacOS 的版本)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q2ayThl2-1681869945428)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/3db86241-bef3-4207-89e3-92bafcca2317.png)]

不幸的是,仅单击“安装”按钮是不够的,尽管看起来很明显,但是安装按钮比安装 Xcode 花费的时间少得多。 您仍然需要通过单击“获取 Xcode”按钮直接从 App Store 获取 Xcode 来确保在 Mac 上安装了 Xcode,或者在安装 Qt 时会遇到以下问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-61BctAdY-1681869945428)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/83c22d3c-2ade-479d-822b-f61a6e93e5b5.png)]

使用 App Store 安装最新版本的 Xcode(在撰写本书时,Xcode 8.3.3 已可用),然后继续进行 Qt 安装。

在“选择组件”对话框上,确保至少选择 MacOS 版本。 您不需要其余的组件,但是安装它们不会造成伤害,除了它可能占用您计算机的大量空间之外。

Linux 用户

在为 Linux 安装 Qt 时,在“选择组件”对话框上,确保选择(至少)桌面 GCC(32 位或 64 位,具体取决于您的操作系统)。 您会注意到,默认情况下将安装 Qt Creator,并且不需要检查任何选项。

安装完成后,您将在计算机上安装以下应用:

  • Qt Creator:这是我们在整本书中将用来构建应用的主要 IDE。
  • Qt Assistant:此应用用于查看 Qt 帮助文件。 它提供了查看 Qt 文档的有用功能。 尽管如此,Qt Creator 还提供了上下文相关的帮助,并且还具有自己的内置且非常方便的帮助查看器。
  • Qt Designer:这用于使用 Qt 小部件设计 GUI。 同样,Qt Creator 也内置了此设计器,但是如果您更喜欢使用其他 IDE 而不是 Qt Creator,则仍可以使用 Designer 来帮助 GUI 设计过程。
  • Qt Linguist:如果您要构建多语言应用,这将是非常有用的帮助。 Qt Linguist 有助于简化翻译并将翻译后的文件集成到您的版本中。

对于 Windows 和 MacOS 用户,这是 Qt 安装故事的结尾,但是 Linux 用户仍然需要多做一些事情,即安装应用开发,构建工具以及一些 Linux 所需的运行时库。 Qt 始终使用操作系统提供的编译器和构建工具。 默认情况下,Linux 发行版通常不包括那些工具,因为它们仅由开发人员使用,而未被普通用户使用。 因此,要安装它们(如果尚未安装),可以从终端运行以下命令:

代码语言:javascript
复制
sudo apt-get install build-essential libgl1-mesa-dev

您可以始终参考 Qt 文档页面以获取所有 Linux 发行版所需的命令,但是,在本书中,我们假定发行版为 Ubuntu/Debian。 但是,请注意,通常,所有 Linux 发行版的命令在模式上都非常相似。

有关此的更多信息,您可以参考这里。

测试 Qt 的安装

您现在可以安全地运行 Qt Creator 并使用它创建出色的应用。 现在,让我们确保我们的 Qt 安装正确运行。 现在就不用理会细节了,因为我们将在本书的过程中介绍所有细节,尤其是如果您认为自己不了解幕后的真实情况,请不要担心。 只需运行 Qt Creator 并按下显示的大New Project按钮,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iN5IvLjI-1681869945429)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/af055e80-4a21-4800-8f41-442231a3d06d.png)]

在接下来出现的窗口中,选择“应用”,“Qt Widgets 应用”,然后单击“选择”,如以下屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iFF1uTuW-1681869945429)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/f8cadd85-d247-4ebf-80dc-17a0f2f0020d.png)]

在下一个窗口中,您需要提供一个名称和文件夹(将在其中创建测试项目),然后单击“下一步”继续。 如果要为 Qt 项目使用专用文件夹,请确保选中“用作默认项目位置”复选框。 您只需执行一次,然后所有项目将在该文件夹中创建。 现在,我们只需要输入名称和路径,因为我们仅要测试 Qt 安装,然后单击“下一步”。 您将看到与以下屏幕快照类似的内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zHWchdwc-1681869945429)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/762b92eb-d656-47f6-a459-af6db76b22f7.png)]

在下一个窗口中,您需要选择一个所谓的 Kit 来构建您的应用。 选择一个以Desktop Qt 5.9.1开头的名称,然后单击“下一步”。 根据在 Qt 安装过程中选择的组件,您在这里可能有多个选择,并且取决于系统上安装的操作系统和编译器,您可能有多个工具包,其名称以Desktop开头,因此确保选择我们将在本书中使用的编译器,如下所示:

  • Windows 上的 msvc2015 32 位
  • MacOS 上的 Clang
  • Linux 上的 GCC

根据前面提到的工具选择正确的工具包后,可以单击“下一步”继续进行操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FgrLhZ6w-1681869945429)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/d3909ddb-764f-45b9-8a9d-2d5da50c355c.png)]

您真的不需要麻烦接下来出现的两个窗口,只需单击“下一步”就足以测试我们的 Qt 安装。 第一个窗口使创建新类更加容易,第二个窗口使您可以选择版本控制工具并跟踪代码中的更改:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WI9d2I4C-1681869945430)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/e4ba211e-2cc0-45a2-8fad-525be8bae0a9.png)]

在最后一个窗口上单击“完成”按钮后,您将进入 Qt Creator 中的“编辑”模式。 我们将在下一章介绍 Qt Creator 的不同方面,因此,现在,只需单击“运行”按钮(或按Ctrl + R)即可开始编译(并清空)您的测试应用,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S1NZ0RCI-1681869945430)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/d98e09b0-ad61-43fb-a545-7588ab79b165.png)]

根据计算机的速度,完成构建过程将需要一些时间。 片刻之后,您应该看到测试(以及第一个)Qt 应用正在运行。 它只是一个空的应用,与下面的屏幕快照类似,其目的是确保我们的 Qt 安装能够按预期运行。 显然,在不同的操作系统上,空的 Qt 应用看上去可能与此略有不同,并且不同的视觉选项可能会影响整个颜色或窗口的显示方式。 不过,您新建的应用应该看起来与此处看到的窗口完全相同(或非常相似):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MmvvUDrb-1681869945430)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/b603d19a-b7a5-4156-bb2f-7b345c225b67.png)]

如果您的应用没有出现,请确保再次阅读说明。 另外,请确保没有任何冲突的 Qt 安装或其他可能干扰 Qt 安装的设置。 有关 Qt Creator 或其他 Qt 工具意外行为的答案,请始终参考文档页面和 Qt 社区。 长期以来,Qt 一直是一个开源项目,它已经成长为庞大而忠实的用户群体,他们渴望在互联网上分享他们的知识并回答 Qt 同行用户面临的问题。 因此,密切关注 Qt 社区是个好主意,因为您已经拥有可用于访问 Qt 论坛的统一 Qt 帐户。 这是您为继续进行 Qt 安装过程而创建的用户名和密码。

安装 OpenCV

在本章的这一部分,您将学习如何使用其源代码构建 OpenCV。 如您将在后面看到的,并且与本节的标题相反,我们并不是真正地以类似于 Qt 安装的方式来安装 OpenCV。 这是因为 OpenCV 通常不为所有编译器和平台提供预构建的二进制文件,而实际上它根本不为 MacOS 和 Linux 提供预构建的二进制文件。 在 OpenCV 的最新 Win 包中,仅包含适用于 64 位 MSVC 2015 的预构建二进制文件,这些二进制文件与我们将使用的 32 位版本不兼容,因此,自己构建 OpenCV 来了解如何进行安装是一个非常好的主意。 它还具有构建适合您需要的 OpenCV 框架库的优势。 您可能要排除一些选项以简化 OpenCV 安装,或者可能要为其他编译器(例如 MSVC 2013)进行构建。因此,有很多理由需要您自己从源代码构建 OpenCV。

准备构建 OpenCV

互联网上的大多数开放源代码框架和库,或者至少是希望保持 IDE 中立的库和库(这意味着可以使用任何 IDE 进行配置和构建的项目,以及不依赖于特定 IDE 的项目而工作),使用 CMake 或类似的make系统。 我猜这也会回答诸如“到底为什么我需要 CMake?”和“为什么他们不能仅仅提供库并使用它完成?”之类的问题,或类似的其他问题。 因此,我们需要 CMake 能够使用源配置和构建 OpenCV。 CMake 是一个开放源代码和跨平台应用,它允许配置和构建开放源代码项目(或应用,库等),并且您可以在上一节中提到的所有操作系统上下载和使用它。 在撰写本书时,可以从 CMake 网站下载页面下载 CMake 3.9.1 版。

在继续前进之前,请确保下载并安装在计算机上。 CMake 安装没有什么特别的需要注意的,除了您必须确保安装 GUI 版本这一事实外,这是我们将在下一部分中使用的内容,并且它是提供的链接中的默认选项。 较早。

从哪里获得 OpenCV?

OpenCV 在其网站的“发布”页面下维护其官方和稳定版本:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BCp1Yn9E-1681869945430)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/324afce6-fba7-400b-997e-476f4d4e00ee.png)]

在这里,您始终可以找到适用于 Windows,Android 和 iOS 的最新版本的 OpenCV 源代码,文档和预构建的二进制文件。 随着新版本的发布,它们会添加到页面顶部。 在撰写本书时,版本 3.3.0 是 OpenCV 的最新版本,这就是我们将使用的版本。 因此,事不宜迟,您应该继续进行操作,并通过单击 3.3.0 版的“源”链接来下载源。 将source zip文件下载到您选择的文件夹中,将其提取出来,并记下提取的路径,因为稍后我们将使用它。

如何构建?

现在,我们拥有构建 OpenCV 所需的所有工具和文件,我们可以通过运行 CMake GUI 应用来启动该过程。 如果正确安装了 CMake,则应该能够从桌面,开始菜单或扩展坞运行它,具体取决于您的操作系统。

Linux 用户应在终端中运行以下命令,然后再继续进行 OpenCV 构建。 这些基本上是 OpenCV 本身的依赖关系,需要在配置和构建它之前就位:

代码语言:javascript
复制
sudp apt-get install libgtk2.0-dev and pkg-config 

运行 CMake GUI 应用后,需要设置以下两个文件夹:

  • “源代码在哪里”文件夹应设置为您下载和提取 OpenCV 源代码的位置
  • 可以将“生成二进制文件的位置”文件夹设置为任何文件夹,但是通常在源代码文件夹下创建一个名为build的子文件夹并将其选择为二进制文件文件夹

设置这两个文件夹后,您可以通过单击“配置”按钮继续前进,如以下屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-buoV5aND-1681869945430)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/cc3d2eb5-459b-4b2d-a083-c8728000a9e1.png)]

单击配置按钮将启动配置过程。 如果构建文件夹尚不存在,可能会要求您创建该文件夹,您需要通过单击“是”按钮来对其进行回答。 如果您仍然觉得自己只是在重复书中的内容,请不要担心。 当您继续阅读本书和说明时,所有这些都会陷入。 现在,让我们仅关注在计算机上构建和安装 OpenCV。 考虑到此安装过程并不像单击几个“下一步”按钮那样简单,并且一旦开始使用 OpenCV,一切都会变得有意义。 因此,在接下来出现的窗口中,选择正确的生成器,然后单击“完成”。 有关每个操作系统上正确的生成器类型,请参阅以下说明:

Windows 用户:您需要选择Visual Studio 142015。请确保您未选择 ARM 或 Win64 版本或其他 Visual Studio 版本。 MacOS 和 Linux 用户:您需要选择Unix Makefile

您将在 CMake 中看到一个简短的过程,完成后,您将能够设置各种参数来配置您的 OpenCV 构建。 有许多参数需要配置,因此我们将直接影响那些直接影响我们的参数。

确保选中BUILD_opencv_world选项旁边的复选框。 这将允许将所有 OpenCV 模块构建到单个库中。 因此,如果您使用的是 Windows,则只有一个包含所有 OpenCV 功能的 DLL 文件。 正如您将在后面看到的那样,当您要部署计算机视觉应用时,这样做的好处是仅使用一个 DLL 文件即可。 当然,这样做的明显缺点是您的应用安装程序的大小会稍大一些。 但是同样,易于部署将在以后证明更加有用。

更改构建参数后,您需要再次单击“配置”按钮。 等待重新配置完成,最后单击“生成”按钮。 这将使您的 OpenCV 内部版本可以编译。 在下一部分中,如果使用 Windows,MacOS 或 Linux 操作系统,则需要执行一些不同的命令。 因此,它们是:

Windows 用户:转到您先前在 CMake 中设置的 OpenCV 构建文件夹(在我们的示例中为c:\dev\opencv\build)。 应该有一个 Visual Studio 2015 解决方案(即 MSVC 项目的类型),您可以轻松地执行和构建 OpenCV。 您也可以立即单击 CMake 上“生成”按钮旁边的“打开项目”按钮。 您也可以只运行 Visual Studio 2015 并打开您刚为 OpenCV 创建的解决方案文件。

打开 Visual Studio 之后,需要从 Visual Studio 主菜单中选择“批量生成”。 就在Build下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p7YQk38a-1681869945431)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/93a01570-7e06-4113-b9c1-9adb6fc68bf3.png)]

确保在Build列中为ALL_BUILDINSTALL启用了复选框,如以下屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-USx6bmC3-1681869945431)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/4f696460-eea4-4f6b-8bcf-107d4fc5bf1d.png)]

对于 MacOS 和 Linux 用户:在切换到在 CMake 中选择的Binaries文件夹后,运行终端实例并执行以下命令。 要切换到特定文件夹,您需要使用cd命令。 进入 OpenCV 构建文件夹(应该是打开 CMake 时选择的家庭文件夹)之后,需要执行以下命令。 系统将要求您提供管理密码,只需提供密码,然后按Enter即可继续构建 OpenCV:

代码语言:javascript
复制
 sudo make

这将触发构建过程,并且可能需要花费一些时间,具体取决于您的计算机速度。 等到所有库的构建完成后,进度条将达到 100%。

漫长的等待之后,仅剩下一个命令可以为 MacOS 和 Linux 用户执行。 如果您使用的是 Windows,则可以关闭 Visual Studio IDE 并继续进行下一步。

MacOS 和 Linux 用户:构建完成后,在关闭终端实例之前,请在仍位于 OpenCV build文件夹中的情况下执行以下命令:

代码语言:javascript
复制
sudo make install

对于非 Windows 用户,这最后一个命令将确保您的计算机上已安装 OpenCV,并且可以完全使用。 如果您没有错过本节中的任何命令,则可以继续进行。 您已经准备好使用 OpenCV 框架来构建计算机视觉应用。

配置 OpenCV 的安装

还记得我们提到过 OpenCV 是一个框架,您将学习如何在 Qt 中使用它吗? 好吧,Qt 提供了一种非常易于使用的方法,可以在您的 Qt 项目中包括任何第三方库,例如 OpenCV。 为了能够在 Qt 中使用 OpenCV,您需要使用一种特殊的文件,称为 PRO 文件。 PRO 文件是用于添加第三方模块并将其包含在 Qt 项目中的文件。 请注意,您只需要执行一次此操作,在本书的其余部分中,您将在所有项目中使用此文件,因此,它是 Qt 配置中非常关键(但非常简单)的一部分。

首先在您选择的文件夹中创建一个文本文件。 我建议使用与 OpenCV 构建相同的文件夹,因为这可以帮助确保将所有与 OpenCV 相关的文件都放在一个文件夹中。 但是,从技术上来讲,该文件可以位于计算机上的任何位置。 将文件重命名为opencv.pri并使用任何文本编辑器将其打开,然后在 PRO 文件中写入以下内容:

Windows 用户:到目前为止,您的 OpenCV 库文件应该位于您先前在 CMake 上设置的 OpenCV 构建文件夹中。 build文件夹中应该有一个名为install的子文件夹,其中包含所有必需的 OpenCV 文件。 实际上,现在您可以删除所有其他内容,如果需要在计算机上保留一些空间,则只保留这些文件,但是将 OpenCV 源代码保留在计算机上始终是一个好主意,我们将在最后几章中特别需要它,并且将涵盖更高级的 OpenCV 主题。 因此,这是 PRO 文件中需要的内容(请注意路径分隔符,无论使用什么操作系统,都始终需要在 PRO 文件中使用/):

代码语言:javascript
复制
INCLUDEPATH += c:/dev/opencv/build/install/include 
Debug: { 
LIBS += -lc:/dev/opencv/build/install/x86/vc14/lib/opencv_world330d 
} 
Release: { 
LIBS += -lc:/dev/opencv/build/install/x86/vc14/lib/opencv_world330 
} 

无需说明,在前面的代码中,如果在 CMake 配置期间使用了其他文件夹,则需要替换路径。

MacOS 和 Linux 用户:只需将以下内容放入opencv.pri文件中:

代码语言:javascript
复制
 INCLUDEPATH += /usr/local/include 
 LIBS += -L/usr/local/lib \ 
    -lopencv_world 

Windows 用户还有一件事,那就是将 OpenCV DLLs文件夹添加到PATH环境变量中。 只需打开“系统属性”窗口,然后在PATH中添加一个新条目。 它们通常用;隔开,因此之后只需添加一个新的即可。 请注意,此路径仅与 Windows 操作系统相关,并且可以在其中找到 OpenCV 的DLL文件,从而简化了构建过程。 Linux 和 MacOS 的用户不需要为此做任何事情。

测试 OpenCV 的安装

最糟糕的时刻已经过去,我们现在准备深入研究计算机视觉世界,并开始使用 Qt 和 OpenCV 构建令人兴奋的应用。 尽管最后一步称为“测试 OpenCV”,但实际上它是您将要编写的第一个 Qt + OpenCV 应用,就像乍看起来一样简单。 在本节中,我们的目的不是打扰任何事情的工作方式以及幕后发生的事情,而只是确保我们正确地配置了所有内容,并避免在本书后面的内容中浪费与配置相关的问题。 如果您已按照说明进行了所有操作并以正确的顺序执行了所有指令,那么到现在为止,您不必担心任何事情,但是最好验证,这就是我们现在要做的。

因此,我们将使用一个非常简单的应用来验证我们的 OpenCV 安装,该应用将从硬盘读取图像文件并仅显示它。 同样,不要打扰任何与代码相关的细节,因为我们将在接下来的章节中介绍所有这些细节,而只是专注于手头的任务,即测试我们的 OpenCV 安装。 首先运行 Qt Creator,然后创建一个新的控制台应用。 在测试 Qt 安装之前,您已经完成了非常相似的任务。 您需要遵循完全相同的说明,除了必须使用 Qt Widget 之外,还必须确保选择Qt Console Application。 像以前一样重复所有类似的步骤,直到最终进入 Qt Creator 编辑模式。 如果询问您有关构建系统的信息,请选择qmake,默认情况下应选择qmake,因此您只需要继续前进即可。 确保为您的项目命名,例如QtCvTest。 这次,不用单击“运行”按钮,而是双击项目的 PRO 文件,您可以在 Qt Creator 屏幕左侧的资源管理器中找到该文件,然后在项目的 PRO 文件末尾添加以下行 :

代码语言:javascript
复制
    include(c:/dev/opencv/opencv.pri) 

请注意,实际上,这是应始终避免的硬编码类型,正如我们将在后面的章节中看到的那样,我们将编写适用于所有操作系统的更复杂的 PRO 文件。 无需更改任何一行; 但是,由于我们只是在测试我们的 OpenCV 安装,因此现在可以进行一些硬编码来简化一些事情,而不会因更多配置细节而使您不知所措。

因此,回到我们正在做的事情,当您通过按Ctrl + S保存 PRO 文件时,您会注意到快速的过程并在项目浏览器和opencv.pri文件将出现在资源管理器中。 您可以随时从此处更改opencv.pri的内容,但是您可能永远不需要这样做。 忽略类似注释的行,并确保您的 PRO 文件与我在此处的文件相似:

代码语言:javascript
复制
 QT += core 
 QT -= gui 
 CONFIG += c++11 
 TARGET = QtCvTest 
 CONFIG += console 
 CONFIG -= app_bundle 
 TEMPLATE = app 
 SOURCES += main.cpp 
 DEFINES += QT_DEPRECATED_WARNINGS 
 include(c:/dev/opencv/opencv.pri) 

项目的 PRO 文件中的这一行简单的代码基本上是本章所有工作的结果。 现在,我们只需在我们要使用 Qt 和 OpenCV 构建的每个计算机视觉项目中包含此简单代码段,就可以将 OpenCV 添加到我们的 Qt 项目中。

在接下来的章节中,我们将学习 Qt 中的 PRO 文件以及之前代码的所有内容。 但是,现在让我们继续前进,知道该文件负责我们项目的配置。 因此,最后一行几乎是不言自明的,仅表示我们要向我们的 Qt 项目添加 OpenCV 包含头文件和库。

现在,您实际上可以编写一些 OpenCV 代码。 打开您的main.cpp文件并更改其内容,使其与此类似:

代码语言:javascript
复制
 #include <QCoreApplication> 
 #include "opencv2/opencv.hpp" 
 int main(int argc, char *argv[]) 
 { 
    QCoreApplication a(argc, argv); 
    using namespace cv; 
    Mat image = imread("c:/dev/test.jpg"); 
    imshow("Output", image); 
    return a.exec(); 
 } 

默认情况下,您的main.cpp文件应该已经具有前面代码中的大部分内容,但是您会注意到顶部的include行和负责从我的计算机读取和显示测试图像的三行。 您可以替换任何其他图像的路径(只需确保暂时保留 JPG 或 PNG 文件),确保该图像文件存在并且可访问非常重要,否则,即使安装是正确的,我们的测试仍然可能失败。 整个代码几乎是不言自明的,但是再一次,由于我们只是测试我们的 OpenCV 版本,因此您现在不应该理会这些代码,因此只需按“运行”按钮以显示您的图像文件即可。 您应该在计算机上看到类似于以下屏幕截图的内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sQtc0h0R-1681869945431)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/17265ab6-8412-4937-8e97-06202d21059b.png)]

总结

在本章中,向您介绍了计算机视觉的一般概念以及 Qt 和 OpenCV 框架,并了解了它们的整体模块化结构,还简要了解了它们在所有平台上跨平台的重要性。 两者都支持。 您还学习了如何在计算机上安装 Qt 以及如何使用其源代码构建 OpenCV。 到目前为止,除了本章中提到的标准构建之外,您应该有足够的信心甚至可以尝试一些其他配置来构建 OpenCV。 通过简单地查看它们包含的文件夹和文件,探索这些巨大框架的一些未知而又深入的部分总是一个好主意。 最后,您学习了如何配置开发计算机以使用 Qt 和 OpenCV 构建应用,甚至还构建了第一个应用。 在下一章中,您将首先构建控制台应用,然后继续构建 Qt 小部件应用,以了解有关 Qt Creator 的更多信息。 您还将了解 Qt 项目的结构以及如何在 Qt 和 OpenCV 框架之间创建跨平台集成。 下一章将是本书中实际计算机视觉开发和编程示例的开始,并将为整本书中的动手示例奠定基础。

二、创建我们的第一个 Qt 和 OpenCV 项目

自从 Qt 和 OpenCV 框架引入开源社区和世界以来已经有很长时间了,但是直到最近人们才开始意识到将两者结合使用的好处,并且这种结合在计算机视觉专家中很受欢迎。 。 我们很幸运,因为我们处于这两个框架的发展历史的一个阶段,在这两个框架中,它们都已经足够成长,可以很容易地组合在一起,而几乎不需要付出任何努力。 这些框架的稳定性也不再是问题,它们有时被用来构建在非常敏感的硬件上运行的应用。 即使在互联网上进行简短搜索也可以证明这一点。 正如我们将在本章中了解到的那样,Qt Creator 已成为几乎完全成熟的 IDE,它提供了非常简单的机制来使用 OpenCV 集成和构建计算机视觉应用。 现在,我们已经了解了第 1 章,“OpenCV 和 Qt 简介”中遇到的所有安装和配置,我们将仅专注于使用 Qt 和 OpenCV 构建应用。

在本章中,我们将通过学习有关 Qt Creator IDE 以及如何使用它来创建项目的方式开始动手工作,因为在本书的其余部分和所构建的任何内容中,我们实际上都使用 Qt Creator。 您将了解它提供的所有好处,并了解为什么它在其简单性,外观和感觉上都是非常强大的 IDE。 您将了解 Qt Creator 的设置和详细信息,以及如何更改它们以满足您的需求。 您还将了解 Qt 项目文件,源代码,用户界面等。 在下一章第 3 章,“创建综合的 Qt + OpenCV 项目”时,我们将在下一章中使用 Qt Creator 构建应用时的所有幕后细节。 ,但在本章中,我们还将介绍一些有用的细节,以使您对真实项目的结构有清晰的了解。 在创建应用的上下文中将涵盖所有这些主题,以便您通过重复本章在此处学习的相同任务来更好地理解。

您应该注意,本章将学到的内容将帮助您节省大量时间,但是只有当您在计算机上真正地重复了所有这些并且实际上总是尝试使用它时,才会如此。 您可以使用 Qt Creator 进行 C++ 编程,甚至适用于非 Qt 应用。

最后,我们将通过创建一个实际的计算机视觉应用并将一些基本的图像处理算法应用于图像来结束本章。 本章的目的是使您为本书的其余部分做好准备,并使您熟悉本书中将要遇到的一些关键字,例如信号,插槽,小部件等。

在本章中,我们将介绍以下主题:

  • 配置和使用 Qt Creator IDE
  • 创建 Qt 项目
  • Qt Creator 中的小部件
  • 创建跨平台的 Qt + OpenCV 项目文件
  • 使用 Qt Creator 设计用户界面
  • 使用 Qt Creator 编写用户界面代码

什么是 Qt Creator?

Qt Creator 与 Qt 框架不是一回事。 是的,这是对的; 它只是由 Qt 框架创建的 IDE。 这使许多对这些术语有些陌生的人感到困惑。 那么,这到底意味着什么? 在一个非常基本的定义中,这意味着您可以使用 Qt Creator 或任何其他 IDE 来创建 Qt 应用。 在某个时刻,当 Qt 框架充斥着类和函数时,负责 Qt 的人们决定使用出色的 Qt 框架本身来创建 IDE,瞧! 一个没有操作系统和 C++ 编译器类型的 IDE 诞生了。 Qt Creator 是一个 IDE,它支持与 Qt 框架更好地集成,它是开源的(基本上意味着您可以免费使用它),它是跨平台的,并且几乎包含了 IDE 所需的所有工具。 这是 Qt Creator 中的欢迎模式的屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TOFmQRdr-1681869945431)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/ea50a35d-77a4-4b97-b23d-19f54a2a8421.png)]

请注意,我们一定会使用所有 Qt Creator 功能,但是在深入研究之前先知道它的功能是一个好主意。 以下是 Qt Creator 的一些最重要的功能:

  • 使用会话管理多个 IDE 状态
  • 管理多个 Qt 项目
  • 设计用户界面
  • 编辑代码
  • 在所有 Qt 支持的平台上构建和运行应用
  • 调试应用
  • 上下文相关帮助

根据您认为重要的内容,您可能可以在列表中添加更多项,但是前面的列表中提到的本质上是 IDE(集成开发环境)的定义,它应该是提供应用开发所需的和绝对必要的所有功能的工具。 另外,您始终可以查阅 Qt 文档以了解 Qt Creator 的其他功能。

IDE 概览

在本节中,我们将漫步 Qt Creator 的不同部分。 搬到新地方时,最好先了解周围的环境和周围环境。 您一开始可能不会注意到这些差异,但是实际上,这是非常相似的情况,您将逐渐意识到。 您将在整本书中使用 Qt Creator 环境,并且在阅读本书的整个过程中,基本上都将使用它,并且以后希望在您的职业生涯中长期从事个人项目, 或研究。 因此,让我们开始散步,开始接触事物,看看真正发生了什么。

让我们回到本章的第一张图片。 您在此处看到的是 Qt Creator 的初始屏幕,或者稍后将看到,它是 Qt Creator 的欢迎模式。 您可能会注意到,即使您安装了相同版本的 Qt,此处的图标和颜色还是与计算机上的略有不同。 不用担心,正如您稍后将看到的那样,它只是一个主题,您将学习如何根据自己的样式和喜好对其进行更改。 实际上,您会在整本书中看到来自 Qt 不同主题的屏幕截图,但请记住,这只是外观和感觉,与功能无关。 Qt Creator 的设计使其可以在其中的不同模式之间极其快速和轻松地进行切换。 切换到每种模式几乎完全改变了 Qt GUI 主要区域中的内容,并且完全达到了自己独特的目的。 让我们看看 Qt 支持哪些模式以及它们的用途。

Qt Creator 模式

Qt Creator 具有六种不同的模式,可以帮助您打开项目,编辑代码,设计用户界面等。 让我们浏览以下列表,然后尝试查看它们的确切含义:

  • 欢迎
  • 编辑
  • 设计
  • 调试
  • 项目
  • 帮助

我相信您已经注意到,在我们进一步详细介绍它们之前,您可以使用 Qt Creator 屏幕左侧的按钮在不同模式之间进行切换,如以下屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FLcJEkJA-1681869945432)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/ba90b615-0204-4c41-a271-1de7d5d319af.png)]

对于 Qt Creator 所做的几乎所有事情,都有专用的键盘快捷键,并且在不同模式之间进行切换也是这种情况。 您可以通过简单地将鼠标悬停在屏幕上而不用花一会儿时间来了解屏幕上所有内容的快捷键,然后会弹出一个提示框,告诉您有关此内容的更多信息,因此我们不会涵盖快捷键序列的完整列表,因为您可以使用上述方法轻松找到最新的热键。 如下面的屏幕截图所示,我将鼠标光标停留在“设计模式”按钮上,它确实告诉了我按钮的用途(即切换到设计模式)和键盘快捷键,即Ctrl + 3

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sq6zHvNu-1681869945432)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/e321da07-1de6-4b16-88d9-33a51b33ab59.png)]

现在,我们将了解有关 Qt Creator 中不同模式及其用途的更多信息。 您应该注意,仅列出并遍历 Qt Creator 中每个功能的细节不在本书的讨论范围之内,但是我们肯定会涵盖本书中使用的 Qt Creator 的所有方面。 Qt Creator 和有关 Qt 的几乎所有内容都在迅速发展,最好始终关注文档页面并亲自尝试新功能或更改的功能。

欢迎模式

这是打开 Qt Creator 时的初始模式,可以始终使用左侧的Welcome按钮将其切换为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ioTuGHrB-1681869945432)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/76f02d4d-e9b0-4c38-a9a7-0dd8216e6ce5.png)]

关于此模式,最重要的注意事项是它实际上具有三种不同的子模式,分别提到如下:

  • 项目
  • 示例
  • 指南

项目

此屏幕(或“欢迎”模式的子模式)可用于使用“新建项目”按钮创建新的 Qt 项目。 在第 1 章,“OpenCV 和 Qt 简介”中,您已经非常简短地体验了它的完成方式。 如果单击“打开项目”按钮,也可以打开计算机上保存的所有项目。 还有一个包含“最近的项目”的列表,该列表非常有用,既可以提醒您正在处理的内容,又可以作为访问它们的快捷方式。 在此模式下也有可见的会话,这是 Qt Creator 的一些最有趣的功能。 会话用于存储 IDE 的状态,并在以后需要时恢复该状态。 在本书中,我们不会理会会话,但是如果正确使用它们,它们将非常方便,并且可以在开发过程中节省大量时间。

借助示例,在 Qt Creator 中了解会话将非常简单。 假设您正在某个项目上,并且在 Qt Creator 中打开了一些项目,或者在代码中设置了一些断点,依此类推。 诸如此类的所有信息都存储在所谓的会话中,并且可以通过在会话之间进行切换来轻松地恢复。

如果单击“新建项目”按钮,将显示“新建项目”窗口,该窗口允许您根据要开发的内容选择项目类型(或模板)。 稍后您将看到,我们将仅使用Applications/Qt Widgets ApplicationLibrary/C++ Library选项,因为遍历所有可能的 Qt 项目模板不在本书的范围之内。 但是,如下面的屏幕快照所示,“新建项目”窗口包含三个部分,您只需选择它们,就可以对每种项目类型获得非常有用的描述。 使用第一个列表和第二个列表单击任何项​​目类型后(在下面的屏幕截图中),它们的描述将出现在第三个窗格中。 这是选择Qt Widgets Application项目类型时出现的描述(请参见下图,尤其是 3 号窗格):

  • 为桌面创建 Qt 应用,包括基于 Qt Designer 的主窗口
  • 预选用于构建应用的桌面 Qt(如果有)
  • 支持平台:台式机

如您所见,它包含了非常有用的洞察力,有助于您了解此类型的模板适用于哪种项目。 尝试遍历所有各种选项以熟悉项目类型。 知道可能的项目类型是一个好主意,即使您不会立即使用它们。 以下是“新建项目”窗口的屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OvZiHOXg-1681869945432)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/1a704eb5-fafe-4688-9271-ea7f003b598e.png)]

示例

这是我在 Qt Creator 中最喜欢的部分之一,毫无疑问,这是了解 Qt 并了解如何使用它的最重要的地方之一。 这里有很多带有解释的示例,只需单击一下即可进行构建。 示例中还有一个搜索栏,可用于使用搜索关键字搜索不同的示例。

指南

当前,这部分内容与示例非常相似,因为它旨在训练 Qt 开发人员,但主要区别在于它包含视频演示和示例。 确保您不时浏览它们,以获取有关新功能以及如何使用它们的最新信息。

在进入下一个 Qt Creator 模式(即编辑模式)之前,我们需要创建一个新项目。 在本章的其余部分中,我们将使用此示例,因为其余模式需要一个项目供我们使用。 现在您已经熟悉了欢迎模式,您可以继续创建一个新的 Qt Widgets 应用。 当我们测试 Qt 和 OpenCV 安装时,您已经在第 1 章,“OpenCV 和 Qt 简介”中创建了一个项目。 您需要重复完全相同的步骤。 这次,只需确保将项目命名为Hello_Qt_OpenCV即可。 这是您需要采取的步骤:

  • 在“欢迎”模式中单击“新建项目”按钮,或按 Ctrl + N
  • 选择“应用”,然后在“新建项目”窗口中选择“Qt Widgets 应用”。
  • 将项目名称设置为Hello_Qt_OpenCV,然后选择要在其中创建文件夹。如果您之前已做过此操作,并且选中了“用作默认项目位置”复选框,则无需在此处更改任何有关“在其中创建”的内容。 然后,单击“下一步”。
  • 选择唯一的桌面工具包选项,具体取决于您的操作系统。 然后,单击“下一步”。
  • 保持类信息不变。 默认情况下,它应该是MainWindow,这是确定的,然后单击“下一步”。
  • 在项目管理页面上时,只需单击完成。 现在,您的项目已经准备就绪,您可以按照本章的其余示例和主题进行操作。

编辑模式

使用 Qt Creator 时,编辑模式可能是您将花费大部分时间的模式。 它主要用于代码编辑以及有关 Qt 项目基于文本的源文件的所有内容。 您始终可以使用屏幕右侧的“编辑”按钮切换到“编辑”模式,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ltPbwlSJ-1681869945432)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/9eda3087-a836-460b-8b0f-f368ed6dde5d.png)]

首先,让我们看一下下面的屏幕快照,该屏幕快照涉及在编辑模式下可见的不同窗格。 如您所见,共有三个部分。 以1突出显示的部分是主编码区域,2是左侧边栏,3是右侧边栏。 默认情况下,只有左侧边栏是可见的,但是您可以使用屏幕底部每一侧箭头所指向的小按钮来打开或关闭每个边栏。 关于每个窗格(侧边栏和中心的主要编码区域)要注意的最重要事实是,可以使用每个窗格顶部的箭头指出的按钮来拆分,复制或更改它们的模式。 侧:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7pAOC7Eg-1681869945433)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/89569636-6963-485c-b3e8-95b8df8901ea.png)]

主代码编辑区是一个轻量级的代码编辑器,它允许代码完成,代码突出显示和上下文相关帮助,这些基本上就是您将要使用的最重要的内容。 稍后将看到,您可以使用首选的颜色,字体等进行配置。 您也可以使用顶部的“拆分”按钮拆分代码编辑器区域,并同时处理多个文件。 尝试键入一些您知道的代码,类或任何 C++ 代码,并随代码完成一起玩,还可以尝试通过在代码编辑器中的鼠标光标位于 Qt 类上时按F1来使用上下文相关帮助。 这些工具将在很长一段时间内成为您最好的朋友,尤其是当您将来开始处理自己的项目时。

您可以为左右两侧的侧栏中的每个窗格选择以下不同的模式:

  • 项目:这包含打开的项目及其包含文件的列表。
  • 打开:这些文档仅显示您已经打开的文件。 您可以通过单击每个按钮旁边的X按钮将其关闭。
  • 书签:显示您在代码中创建的所有书签。 使用此窗格和功能可以在编程期间以及以后在测试和调试代码时节省大量时间。
  • 文件系统:这基本上是文件浏览器窗格。 请注意,此窗格显示项目文件夹中的所有文件(如果您选中窗格中的相关复选框,甚至会显示隐藏文件),还可以用于浏览计算机上的其他文件夹,而不仅仅是当前项目。
  • 类视图:可用于查看当前项目中类的层次结构。
  • 大纲:与“类视图”不同,它显示了当前开源文件中所有方法和符号的层次结构,而不是整个项目。 在前面的屏幕截图中,该窗格是右侧栏上激活的窗格。
  • 测试:这将显示项目中所有可用的测试。
  • 类型和包含层次结构:从其标题可以猜到,它可以用于查看类的层次结构和包含的标头的层次结构。

重要的是要注意,根据您的编程习惯,您可能会经常使用某些窗格,而很少使用某些其他窗格,因此请确保将其设置为适合您自己的样式和需求,并在编程时节省大量时间。

设计模式

这是您进行所有用户界面设计的方式。 您可以使用 Qt Creator 屏幕左侧的“设计”按钮切换到“设计”模式。 请注意,如果此按钮显示为灰色(表示该按钮处于非活动状态),则需要首先选择一个用户界面文件(*.ui),因为使用设计器只能打开ui文件。 为此,您可以双击左窗格(“项目”窗格)中的mainwindow.ui文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E099qAb2-1681869945433)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/f0a45ac6-e045-4bb2-a531-83acb65c4af4.png)]

设计模式包含功能强大的 GUI 设计器所需的所有工具。 它具有 WYSIWYG所见即所得)类型的 GUI 编辑器,允许添加,删除,编辑或编写可从中添加或删除的 Qt 窗口小部件的代码。 用户界面。

Qt 小部件是 Qt 用户界面上最基本的组件类型。 基本上,用户界面上的所有内容(包括整个窗口本身),例如按钮,标签,文本框,都是 Qt 窗口小部件。 Qt 窗口小部件都是QWidget类的所有子类,这使它们可以接收用户输入事件(例如,鼠标和键盘事件),并在用户界面上自行绘制(或绘制)。 因此,必须从QWidget类中子类化任何具有可视外观并打算放在用户界面上的 Qt 类。 在整本书中,您将学习许多 Qt 小部件类,但是一些示例将是QPushButtonQProgressBarQLineEdit等。 他们的目的几乎可以从他们的名字中立即辨认出来。 请注意,所有 Qt 类(没有任何明显的例外)的名称都以Q(大写)开头。

在设计模式下有一个 Qt Creator 的屏幕截图(如下所示)。 如此处所示,它与我们在“编辑”模式下看到的内容非常相似,屏幕分为三个主要部分。 您可以在中间的主要区域以任何方式拖放,调整大小,删除或直观地编辑用户界面。 在屏幕的左侧,有可以添加到用户界面的小部件列表。 您应该尝试拖放其中的一些(基本上是其中的任何一个),只是为了使设计师大致上满意并更好地了解其工作原理。 在本书的后面,我们将设计很多不同的用户界面,并逐步为您介绍许多功能,但是最好自己尝试一些设计并至少熟悉一些功能,这是一个很好的主意。 这一切的感觉。 在屏幕右侧,您可以在用户界面上查看小部件的分层视图,并修改每个小部件的属性。 因此,如果继续进行操作,并在用户界面中添加了一些小部件,您会注意到,无论何时选择其他小部件,属性及其值都会根据该特定小部件而变化。 在这里,您可以编辑设计者可用的小部件的所有属性:

与大多数其他 IDE 一样,您通常可以通过许多不同的途径来实现相同的目标。 例如,您可以使用编辑器从代码中设置窗口小部件的大小,甚至可以采用不推荐的方式在文本编辑器中修改其 UI 文件。 您应该能够根据自己的特定需求做出决定,因为没有一种方法是最好的,而且它们都只是在不同情况下适用。 通常,最好在用户界面编辑器中设置初始属性,并根据需要在整个代码中更新它们的值。 您将在本章的后面部分中对此进行了解。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDUgnwf3-1681869945433)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/9035e46e-8d1a-4f31-bae1-81152c3d5b48.png)]

在用户界面设计器中央部分的底部,您可以看到“动作编辑器”和“信号与插槽编辑器”。 要了解它们的工作原理,以及实际上 Qt 的工作原理,我们需要首先了解 Qt 中的信号和插槽。 因此,从定义信号和插槽的概念开始我们的第一次相遇开始,然后再通过一个真实的示例进行体验,这是一个非常好的主意。

Qt 框架对标准 C++ 编程的最重要添加是信号和插槽机制,这也是使 Qt 如此易于学习且功能强大的原因。 这绝对也是 Qt 与其他框架之间最重要的区别。 可以将它视为 Qt 对象和类之间的消息传递方法(或顾名思义,只是发出信号)。 每个 Qt 对象都可以发出可以连接到另一个(或相同)对象中的插槽的信号。 让我们通过一个简单的例子进一步分解它。 QPushButton是一个 Qt 小部件类,您可以将其添加到 Qt 用户界面中以创建按钮。 它包含许多信号,包括明显的按下信号。 另一方面,在我们创建Hello_Qt_OpenCV项目时自动创建的MainWindow(以及所有 Qt 窗口)包含一个名为close的插槽,可用于简单地关闭项目的主窗口 。 我相信您可以想象如果将按钮的按下信号连接到窗口的关闭插槽会发生什么。 有很多方法可以将信号连接到插槽,因此,从现在开始,在本书的其余部分中,只要需要在示例中使用它们,我们就会学习它们的每一种。

设计用户界面

从这里开始学习如何将 Qt 小部件添加到用户界面,并使它们对用户输入和其他事件做出反应。 Qt Creator 提供了非常简单的工具来设计用户界面并为其编写代码。 您已经看到了设计模式下可用的不同窗格和工具,因此我们可以从示例开始。 通过选择mainwindow.ui文件(这是我们从编辑模式进入主窗口的用户界面文件),确保首先切换到设计模式(如果尚未进入设计模式)。

在设计模式下,您可以在用户界面上查看可使用的 Qt 小部件列表。 从这些图标和名称可以立即识别出大多数这些小部件的用途,但是仍然有一些特定于 Qt 的小部件。 这是默认情况下 Qt Creator 中代表所有可用布局和小部件的屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OejjIXH0-1681869945433)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/b2a3d25a-3b8b-4c34-bd86-82756775c599.png)]

以下是对 Qt Creator 设计模式(或从现在开始简称为 Designer)中可用小部件的简要说明,如前面的屏幕快照所示。 在设计器模式下,小部件基于其行为的相似性进行分组。 在继续进行列表操作时,请自己亲自尝试设计器中的每个功能,以感觉到将它们放置在用户界面上时的外观。 为此,您可以使用设计器模式将每个窗口小部件拖放到窗口上:

  • 布局:这些布局用于管理窗口小部件的显示方式。 在外观上,它们是不可见的(因为它们不是QWidget子类),并且它们仅影响添加到它们的小部件。 请注意,布局根本不是小部件,它们是用来管理小部件的显示方式的逻辑类。 尝试在用户界面上放置任何布局小部件,然后在其中添加一些按钮或显示小部件,以查看其布局如何根据布局类型进行更改。 查看每个示例图片以了解它们的行为。
    • 垂直布局:它们用于具有垂直布局,即一列小部件。 (此布局的等效 Qt 类称为QVBoxLayout)。 以下是它的屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uVFS1eC4-1681869945433)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/03a51bca-a3cd-471b-a05b-3f51dbd80cec.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d1wYi6TQ-1681869945434)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/277d207f-a502-4427-b93f-edfbb10acda1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QxrQCue7-1681869945434)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/0ed52881-f4f9-4d6a-a812-5fccd5d1523f.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aUHLNexV-1681869945434)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/01e370dc-fbb7-4018-b736-302ef72ceb75.png)]

  • 分隔符:类似于布局,它们在视觉上不可见,但会影响将其他窗口小部件添加到布局时的显示方式。 请参阅示例图像,并确保自己尝试在小部件之间尝试使用两个垫片。 间隔符的类型为QSpacerItem,但是通常,它们绝不能直接在代码中使用。
    • 水平分隔符:这些可用于在一行中的两个小部件之间插入一个空格:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Li1prl3E-1681869945434)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/01e464f9-6116-4ff8-b3f6-e79437eed75e.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8U6hCQuR-1681869945434)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/c90d84a7-39e2-4d85-9943-e79f63649ebd.png)]

  • 按钮:这些只是按钮。 它们用于提示操作。 您可能会注意到,单选按钮和复选框也在该组中,这是因为它们都继承自QAbstractButton类,该类是一个抽象类,提供了类按钮小部件所需的所有接口。
  • 按钮:这些按钮可用于在用户界面上添加带有文本和/或图标的简单按钮(此小部件的等效 Qt 类称为QPushButton)。
  • 工具按钮:这些按钮与按钮非常相似,但通常添加到工具栏中

Qt 窗口共有 3 种不同类型的条(实际上,一般来说是 Windows),它们在小部件工具箱中不可用,但是可以通过右键单击 Windows 中的窗口来创建,添加或删除它们。 设计器模式,然后从右键菜单中选择相关项目。 它们是:

1.菜单栏(QMenuBar) 2.工具栏(QToolBar) 3.状态栏(QStatusBar

菜单栏是显示在窗口顶部的典型水平主菜单栏。 菜单中可以有任意数量的项目和子项目,每个项目和子项目都可以触发一个动作(QAction)。 您将在接下来的章节中了解有关操作的更多信息。 以下是菜单栏示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vehc4Hlc-1681869945435)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/3f2ff1ec-225f-482f-8382-324c193748a7.png)]

工具栏是一个可移动面板,其中可以包含与特定任务相对应的工具按钮。 这是一个示例工具栏。 请注意,它们可以在 Qt 窗口内移动甚至移出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pFin1c0k-1681869945435)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/8ed42c6f-df0f-459f-a29d-692cd7a0cd04.png)]

状态栏是底部的一个简单的水平信息栏,对于大多数基于窗口的应用是通用的。 每当在 Qt 中创建一个新的主窗口时,这三种类型的条形都将添加到该窗口中。 请注意,一个窗口上只能有一个菜单栏和一个状态栏,但是可以有任意数量的状态栏。 如果不需要它们,则需要将它们从“设计器”窗口右侧的对象层次结构中删除。 现在您已经熟悉了 Qt 中的三个不同的条形,可以从“Qt 欢迎”模式中的示例中搜索Application Example,以进一步了解它们,以及是否可以进一步自定义它们。

  • 命令链接按钮:这是 Windows Vista 风格的命令链接按钮。 它们基本上是用于在向导中代替单选按钮的按钮,因此,当按下命令链接按钮时,这类似于使用单选框选择一个选项,然后在向导对话框中单击“下一步”。 (此小部件的等效 Qt 类称为QCommandLinkButton)。
  • 对话框按钮框:如果您希望按钮适应对话框中的操作系统样式,这将非常有用。 它有助于以一种更适合系统当前样式的方式在对话框上显示按钮(此小部件的等效 Qt 类称为QDialogButtonBox)。
  • 项目视图(基于模型):这基于模型视图控制器(MVC)设计模式; 它们可用于表示不同类型容器中的模型数据。

如果您完全不熟悉 MVC 设计模式,那么我建议您在这里停顿一下,首先通读一本综合性的文章,以确保至少对它是什么以及如何使用 MVC(尤其是 Qt)有一个基本的了解。 Qt 文档中名为“模型/视图编程”的文章,您可以从 Qt Creator 中的“帮助”模式访问该文章。 出于本书的目的,我们不需要非常详细的信息和对 MVC 模式的理解。 但是,由于它是非常重要的架构,您肯定会在以后的项目中遇到它,因此我建议您花一些时间来学习它。 不过,在第 3 章,“创建全面的 Qt + OpenCV 项目”中,我们将介绍 Qt 和 OpenCV 中使用的不同设计模式,但我们将主要关注本书的目的,因为它是一个非常全面的主题,并且遍历本书中所有可能的设计模式将完全没有用。

  • 项目小部件(基于项目):这类似于基于模型的项目视图,不同之处在于它们不是基于 MVC 设计模式,并且它们提供了简单的 API 来添加,删除或修改他们的项目
    • 列表小部件:类似于列表视图,但是具有基于项目的 API,可以添加,删除和修改其项目(此小部件的等效 Qt 类称为QListWidget
    • 树形小部件:这类似于树形视图,但具有基于项目的 API,可以添加,删除和修改其项目(此小部件的等效 Qt 类称为QTreeWidget
    • 表格小部件:这类似于表视图,但是具有基于项目的 API,用于添加,删除和修改其项目(此窗口小部件的等效 Qt 类称为QTableWidget
  • 容器:这些容器用于在用户界面上对小部件进行分组。 容器可以包含小部件,因为可以从其标题中猜测
    • 分组框:这是一个带有标题和边框的简单分组框(此小部件的等效 Qt 类称为QGroupBox)。
    • 滚动区域:这提供了一个可滚动区域,非常适合显示由于屏幕尺寸小或可见数据量大而无法完全看到的内容(此小部件的等效 Qt 类称为QScrollArea) 。
    • 工具箱:可用于将小部件分组在不同选项卡的列中。 选择每个选项卡将显示(扩展)其包含的小部件,并隐藏(折叠)其他选项卡的内容。 (此小部件的等效 Qt 类称为QToolBox)。
    • 选项卡小部件:可用于在选项卡式页面中显示不同组的小部件。 通过单击每个页面(或一组窗口小部件)的相关选项卡(此窗口小部件的等效 Qt 类称为QTabWidget),可以切换到该页面。
    • 堆叠式窗口小部件:与“标签”窗口小部件类似,但是始终只有一页(或窗口小部件组)可见。 当您希望将不同的用户界面设计到一个文件中并根据用户操作在它们之间进行切换(使用代码)时,此功能特别有用(此小部件的等效 Qt 类称为QStackedWidget)。
    • 框架:可用作我们要为其构建框架的小部件的占位符。 此窗口小部件也是具有框架的所有窗口小部件的基类(此窗口小部件的等效 Qt 类称为QFrame)。
    • 小部件:与QWidget类相同,它是所有 Qt 小部件的基本类型。 这个小部件几乎不包含任何内容,当我们要创建自己的小部件类型(除了现有的 Qt 小部件)时,它很有用。
    • MDI 区域:可用于在窗口或 Qt 小部件(此小部件的等效 Qt 类称为QMdiArea)内创建所谓的“多文档接口”。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mDaDc92-1681869945435)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/160c60f3-c13b-419f-ba4f-e3a3c132ac63.png)]

要使用 Designer 在 MDI 区域内创建新窗口,只需在空白处单击鼠标右键,然后从菜单中选择“添加子窗口”。 同样,“下一个子窗口”,“上一个子窗口”,“级联”,“平铺”和“子窗口/删除”都是仅在右键单击“MDI 区域”小部件时才有效的选项。

QAxWidget仅适用于 Windows OS 上的用户。 但是,即使在 Windows 上,仅将QAxWidget添加到窗口中也无法使它正常工作,因为它依赖于称为axcontainer的 Qt 模块。 目前,您可以跳过将此小部件添加到窗口的操作,但是在本章稍后介绍了如何向您的 Qt 项目中添加不同的 Qt 模块之后,您可以稍后再次尝试。

  • 输入小部件:听起来完全一样。 您可以使用以下小部件获取用户输入数据。
    • 组合框:有时称为下拉列表; 它可以用来选择列表中的选项,而屏幕上的空间却很少。 任何时候,只有选定的选项可见。 用户甚至可以输入自己的输入值,具体取决于其配置。 (此小部件的等效 Qt 类称为QComboBox):
    • 字体组合框:类似于组合框,但可用于选择字体系列。 字体列表是使用计算机上的可用字体创建的。
    • 行编辑:可用于输入和显示单行文本(此小部件的等效 Qt 类称为QLineEdit)。
    • 文本编辑:可用于输入和显示多行富文本格式。 重要的是要注意,这个小部件实际上是成熟的 WYIWYG 富文本编辑器(此小部件的等效 Qt 类称为QTextEdit)。
    • 纯文本编辑:可用于查看和编辑多行文本。 可以将其视为类似于记事本的简单小部件(此小部件的等效 Qt 类称为QPlainTextEdit)。
    • 旋转框:用于输入整数或离散的值集,例如月份名称(此小部件的等效 Qt 类称为QSpinBox)。
    • 双重旋转框:类似于旋转框,但是它接受双精度值(此小部件的等效 Qt 类称为QDoubleSpinBox)。
    • 时间编辑:可用于输入时间值。(此小部件的等效 Qt 类称为QTimeEdit)。
    • 日期编辑:可用于输入日期值(此小部件的等效 Qt 类称为QDateEdit)。
    • 日期/时间编辑:可用于输入日期和时间值(此小部件的等效 Qt 类称为QDateTimeEdit)。
    • 拨盘:类似于滑块,但具有圆形和类似拨盘的形状。 它可用于输入指定范围内的整数值(此小部件的等效 Qt 类称为QDial)。
    • 水平/垂直条:可用于添加水平和垂直滚动功能(此小部件的等效 Qt 类称为QScrollBar)。
    • 水平/垂直滑块:可用于输入指定范围内的整数值(此小部件的等效 Qt 类称为QSlider)。
    • 按键序列编辑:可用于输入键盘快捷键(此小部件的等效 Qt 类称为QKeySequenceEdit)。

不应将此与QKeySequence类混淆,该类根本不是小部件。 QKeySequenceEdit用于从用户那里获取QKeySequence。 在拥有QKeySequence之后,我们可以将其与QShortcutQAction类结合使用以触发不同的函数/插槽。 本章稍后将介绍信号/插槽的介绍。

  • 显示小部件:可用于显示输出数据,例如数字,文本,图像,日期等:
    • 标签:可用于显示数字,文本,图像或电影(此小部件的等效 Qt 类称为QLabel)。
    • 文本浏览器:它与Text Edit小部件几乎相同,但是具有在链接之间导航的附加功能(此小部件的等效 Qt 类称为QTextBrowser)。
    • 图形视图:可用于显示图形场景的内容(此小部件的等效 Qt 类称为QGraphicsView)。

我们整本书中将使用的最重要的小部件可能是图形场景(或QGraphicsScene),它将在第 5 章,“图形视图框架”中进行介绍。

请注意,OpenGL 是计算机图形学中一个完全独立且高级的主题,它完全不在本书的讨论范围内。 但是,如前所述,最好了解 Qt 中存在的工具和小部件,以便您进行进一步的研究。

QML 的简介将在第 12 章, “Qt Quick 应用”中介绍。 现在,请确保我们没有在用户界面中添加任何QQuickWidget小部件,因为我们需要向项目中添加其他模块才能使其正常工作。 本章将介绍如何向 Qt 项目添加模块。

Hello_Qt_OpenCV

现在,我们可以开始为Hello_Qt_OpenCV项目设计用户界面。 明确项目规格清单,然后根据需求设计用户友好的 UI,然后在纸上画出用户界面(或者如果不是大项目,请牢记在心)始终是一个好主意。 ,最后开始使用 Designer 创建它。 当然,此过程需要使用现有 Qt 小部件的经验以及创建自己的小部件的足够经验,但这最终会发生,您只需要继续练习即可。

因此,首先,让我们回顾一下我们需要开发的应用的规范。 比方说:

  • 此应用必须能够将图像作为输入(接受的图像类型必须至少包括*.jpg*.png*.bmp文件)。
  • 此应用必须能够应用模糊过滤器。 用户必须能够选择中值模糊或高斯模糊类型来过滤输入图像(使用默认的一组参数)。
  • 此应用必须能够保存输出图像和输出图像的文件类型(或扩展名,换句话说),并且它必须可由用户选择(*.jpg*.png*.bmp)。
  • 用户应该能够选择在保存时查看输出图像。
  • 重新启动应用时,应保留并重新加载用户界面上设置的所有选项,包括模糊过滤器类型以及最后打开和保存的图像文件。
  • 当用户要关闭应用时,应提示他们。

对于我们的情况,这应该足够了。 通常,您不应超额交付或交付不足。 这是设计用户界面时的重要规则。 这意味着您应确保所有要求均已成功满足,同时,您还没有在要求列表中添加不需要(或不需要)的任何内容。

对于这样的需求(或规格)列表,可能有无数的用户界面设计; 但是,这是我们将创建的一个。 请注意,这是我们的程序在执行时的外观。 显然,标题栏和样式可能会有所不同,具体取决于操作系统,但这基本上就是:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4dFW0NOW-1681869945435)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/a9d09830-17fe-4be0-9970-b3dc3631da9c.png)]

它看起来很简单,它包含了该任务所需的所有组件,并且界面几乎是不言自明的。 因此,将要使用此应用的人实际上并不需要了解很多功能,他们只需猜测所有输入框,单选按钮,复选框等的用途。

在 Designer 上查看时,这是相同的 UI:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XwJHndS5-1681869945435)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/207946ce-73a4-4581-9ece-2e4eed7e2511.png)]

现在该为我们的项目创建用户界面了:

  1. 要创建此用户界面,首先需要从主窗口中删除菜单栏,状态栏和工具栏,因为我们不需要它们。 右键单击顶部的菜单栏,然后​​选择“删除菜单栏”。 接下来,右键单击窗口上的任意位置,然后选择“删除状态栏”。 最后,右键单击顶部的工具栏,然后单击“删除工具栏”。
  2. 现在,在您的窗口中添加一个水平布局; 这是上一图像顶部可见的布局。 然后,在其中添加标签,行编辑和按钮,如上图所示。
  3. 通过双击标签并输入Input Image :来更改标签的文本。 (这与选择标签并使用屏幕右侧的属性编辑器将文本属性值设置为Input Image :相同。)

几乎所有具有text属性的 Qt 小部件都允许使用其文本进行这种类型的编辑。 因此,从现在开始,当我们说Change the text of the widget X to Y时,这意味着双击并设置文本或使用设计器中的属性编辑器。 我们可以很容易地将此​​规则扩展到属性编辑器中可见的窗口小部件的所有属性,并说Change the W of X to Y。 在这里,显然,W是设计者的属性编辑器中的属性名称,X是小部件名称,Y是需要设置的值。 这将在设计 UI 时为我们节省大量时间。

  1. 添加一个组框,然后添加两个单选按钮,类似于上图所示。
  2. 接下来,添加另一个水平布局,然后在其中添加LabelLine EditPush Button。 这将是在复选框正上方的底部看到的布局。
  3. 最后,在窗口中添加一个复选框。 这是底部的复选框。
  4. 现在,根据前面的图像,更改窗口上所有小部件的文本。 您的 UI 即将准备就绪。 现在,您可以通过单击屏幕左下方的“运行”按钮来尝试运行它。 确保您没有按下带有错误的“运行”按钮。 这是一个:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7meOW0Jx-1681869945436)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/b123b1f9-9824-41bf-9842-6e16c91cdb9d.png)]

这将产生与您之前看到的相同的用户界面。 现在,如果您尝试调整窗口的大小,您会注意到在调整窗口大小或最大化窗口时,所有内容都保持原样,并且它不会响应应用大小的更改。 要使您的应用窗口响应大小更改,您需要为centralWidget设置布局。 还需要对屏幕上的分组框执行此操作。

Qt 小部件均具有centralWidget属性。 这是 Qt 设计器中特别用于 Windows 和容器小部件的东西。 使用它,您可以设置容器或窗口的布局,而无需在中央窗口小部件上拖放布局窗口小部件,只需使用设计器顶部的工具栏即可:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0MrWe7Cd-1681869945436)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/59ecd372-42c2-438d-b3bc-e00bf89a2ea6.png)]

您可能已经注意到工具栏中的四个小按钮(如前面的屏幕快照所示),它们看起来与左侧小部件工具箱中的布局完全一样(如下所示):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ND2UwLw5-1681869945436)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/86f528aa-36f4-4362-9238-282c7e1ddf73.png)]

因此,让我们就整本书中的简单快速解释达成另一条规则。 每当我们说Set the Layout of X to Y时,我们的意思是首先选择小部件(实际上是容器小部件或窗口),然后使用顶部工具栏上的布局按钮选择正确的布局类型。

  1. 根据前面信息框中的描述,选择窗口(这意味着,单击窗口上的空白而不是任何小部件上的空白)并将其布局设置为Vertical
  2. 对组框执行相同操作; 但是,这一次,将布局设置为水平。 现在,您可以尝试再次运行程序。 如您现在所见,它会调整其所有小部件的大小,并在需要时移动它们,以防更改窗口大小。 窗口内的组框也发生了同样的情况。
  3. 接下来需要更改的是小部件的objectName属性。 这些名称非常重要,因为在 C++ 代码中使用它们来访问窗口上的小部件并与其进行交互。 对于每个小部件,请使用以下屏幕截图中显示的名称。 请注意,该图像显示了对象层次结构。 您还可以通过双击对象层次结构窗格中的小部件来更改objectName属性:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-plbRq50b-1681869945436)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/c4cb60b1-3d8d-4fad-b567-1a356158cf90.png)]

从理论上讲,您可以为objectName属性使用任何 C++ 有效的变量名,但实际上,最好始终使用有意义的名称。 考虑对本书中使用的变量或小部件名称遵循相同或相似的命名约定。 它基本上是 Qt 开发人员遵循的命名约定,它还有助于提高代码的可读性。

编写 Qt 项目的代码

现在,我们的用户界面已经完全设计好了,我们可以从为应用编写代码开始。 现在,我们的应用基本上只不过是一个用户界面,它实际上什么也不做。 我们首先需要将 OpenCV 添加到我们的项目中。 在第 1 章,“OpenCV 和 Qt 简介”中,您已经看到了将 OpenCV 添加到 Qt 项目的简短介绍。 现在,我们将采取进一步的措施,并确保可以按照第 1 章中的说明正确安装和配置 OpenCV,并且可以在所有三个主要操作系统上编译和构建我们的项目,而无需进行任何更改。“OpenCV 和 Qt 简介”。

因此,首先在代码编辑器中打开项目的 PRO 文件,这将是 Qt Creator 中的“编辑”模式。 您可能已经注意到,它称为Hello_Qt_OpenCV.pro。 您需要在该文件的末尾添加以下代码:

代码语言:javascript
复制
   win32: { 
      include("c:/dev/opencv/opencv.pri") 
   }

unix: !macx {
CONFIG += link_pkgconfig
PKGCONFIG += opencv
}

unix: macx {
INCLUDEPATH += "/usr/local/include"
LIBS += -L"/usr/local/lib" \
-lopencv_world
}

注意右括号前的代码; win32表示 Windows 操作系统(仅适用于桌面应用,不适用于 Windows 8、8.1 或 10 特定应用),unix: !macx表示 Linux 操作系统,unix: macx表示 MacOS 操作系统。

您的PRO文件中的这段代码允许 OpenCV 包含在内并在您的 Qt 项目中可用。 还记得我们在第 1 章,“OpenCV 和 Qt 简介”中创建了一个PRI文件吗? Linux 和 MacOS 用户可以将其删除,因为在那些操作系统中不再需要该文件。 只有 Windows 用户可以保留它。

请注意,在 Windows OS 中,您可以将前面的include行替换为 PRO 文件的内容,但这在实践中并不常见。 另外,值得提醒的是,您需要在PATH中包含 OpenCV DLLs 文件夹,否则当您尝试运行它时,应用将崩溃。 但是,它仍然可以正确编译和构建。 要更加熟悉 Qt PRO 文件的内容,可以在 Qt 文档中搜索qmake并阅读有关内容。 不过,我们还将在第 3 章,“创建综合的 Qt + OpenCV 项目”中进行简要介绍。

我们不会讨论这些代码行在每个操作系统上的确切含义,因为这不在本书的讨论范围之内,但是值得注意并足以知道何时构建应用(换句话说,编译和编译)。 链接),这些行将转换为所有 OpenCV 头文件,库和二进制文件,并包含在您的项目中,以便您可以轻松地在代码中使用 OpenCV 函数。

现在我们已经完成了配置工作,让我们开始为用户界面上的每个需求及其相关的小部件编写代码。 让我们从inputPushButton开始。

从现在开始,我们将使用其唯一的objectName属性值引用用户界面上的任何窗口小部件。 将它们视为可以在代码中使用以访问这些小部件的变量名。

这是我们项目的编码部分所需的步骤:

  1. 再次切换到设计器,然后右键单击inputPushButton。 然后,从出现的菜单中选择“转到插槽…”。 将显示的窗口包括此小部件发出的所有信号。 选择pressed(),然后单击确定:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tp5M6uWe-1681869945436)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/fdabfeaf-d0a1-47d7-966b-d384aa1a8c31.png)]

  1. 您会注意到,您是从设计器自动转到代码编辑器的。 另外,现在mainwindow.h文件中添加了新函数。
  2. mainwindow.h中,添加了以下内容:
代码语言:javascript
复制
        private slots:
void on_inputPushButton_pressed();

这是自动添加到mainwindow.cpp的代码:

代码语言:javascript
复制
    void MainWindow::on_inputPushButton_pressed()
{

} </code></pre></div></div><p>因此,显然需要在刚刚创建的<code>on_inputPushButton_pressed()</code>函数中编写负责<code>inputPushButton</code>的代码。 如本章前面所述,这是将信号从小部件连接到另一个小部件上的插槽的多种方法之一。 让我们退后一步,看看发生了什么。 同时,请注意刚刚创建的函数的名称。 <code>inputPushButton</code>小部件具有一个称为被按下的信号(因为它是一个按钮),该信号仅在被按下时才发出。 在我们的单个窗口小部件(<code>MainWindow</code>)中创建了一个新插槽,称为<code>on_inputPushButton_pressed</code>。 很方便,想到的第一个问题是,如果我自己在<code>mainwindow.h</code>和<code>mainwindow.cpp</code>中编写了这些代码行,而不是右键单击小部件并选择“转到插槽”,将会发生什么情况? ,答案是,这是完全一样的。 因此,总而言之,每当<code>inputPushButton</code>小部件发出按下信号时,Qt 都会自动理解它需要在<code>on_inputPushButton_pressed()</code>中执行代码。 在 Qt 开发中,这被称为<strong>按名称</strong>连接插槽,它仅遵循以下约定自动将信号连接至插槽<code>on_objectName_signal(parameters)</code>。</p><p>在此,<code>objectName</code>应该替换为发送信号的小部件的<code>objectName</code>属性的值,<code>signal</code>替换为信号名称,<code>parameters</code>替换为确切的信号编号和参数类型。</p><p>现在我们知道如何将窗口上的窗口小部件的信号连接到窗口本身的插槽,或者换句话说,既然我们知道必须添加一个函数并为窗口小部件的信号编写代码,我们可以节省一些时间,并避免使用诸如<code>The code for the signal X of the widget Y</code>之类的句子进行重复说明,这意味着要使用我们刚刚学习的方法添加一个负责信号的时隙。 因此,在本例中,作为第一个示例,让我们为<code>inputPushButton</code>小部件的<code>pressed</code>信号编写代码。</p><p>根据应用的要求,我们需要确保用户可以打开图像文件。 成功打开图像文件后,我们会将路径写入<code>inputLineEdit</code>小部件的<code>text</code>属性,以便用户可以看到他们选择的完整文件名和路径。 首先让我们看一下代码的外观,然后逐步介绍它:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    void MainWindow::on_inputPushButton_pressed() 
{ 
  QString fileName = QFileDialog::getOpenFileName(this, 
    &#34;Open Input Image&#34;, 
    QDir::currentPath(), 
    &#34;Images (*.jpg *.png *.bmp)&#34;); 
   if(QFile::exists(fileName)) 
   { 
     ui-&gt;-&gt;setText(fileName); 
   } 
} </code></pre></div></div><p>要访问用户界面上的小部件或其他元素,只需使用<code>ui</code>对象。 例如,可以通过<code>ui</code>类并通过编写以下行来简单地访问用户界面中的<code>inputLineEdit</code>小部件:</p><p><code>ui-&gt; inputLineEdit</code></p><p>第一行实际上是大代码的简化版本。 正如您将在本书中学习的那样,Qt 提供了许多方便的函数和类来满足日常编程需求,例如将它们打包成非常短的函数。 首先让我们看看我们刚刚使用了哪些 Qt 类:</p><ul class="ul-level-0"><li><code>QString</code>:这可能是 Qt 最重要和广泛使用的类别之一。 它代表 Unicode 字符串。 您可以使用它来存储,转换,修改字符串以及对字符串进行无数其他操作。 在此示例中,我们仅使用它来存储<code>QFileDialog</code>类读取的文件名。</li><li><code>QFileDialog</code>:可以用来选择计算机上的文件或文件夹。 它使用底层操作系统 API,因此对话框的外观可能有所不同,具体取决于操作系统。</li><li><code>QDir</code>:此类可用于访问计算机上的文件夹并获取有关它们的各种信息。</li><li><code>QFile</code>:可用于访问文件以及从文件中读取或写入文件。</li></ul><p>前面提到的将是对每个类的非常简短的描述,并且如您从前面的代码中所见,它们每个都提供了更多的功能。 例如,我们仅在<code>QFile</code>中使用了静态函数来检查文件是否存在。 我们还使用了<code>QDir</code>类来获取当前路径(通常是应用从中运行的路径)。 代码中唯一需要更多说明的是<code>getOpenFileName</code>函数。 第一个参数应该是<code>parent</code>小部件。 这在 Qt 中非常重要,它用于自动清除内存,如果出现对话框和窗口,则要确定父窗口。 这意味着每个对象在销毁子对象时也应负责清理其子对象,如果是窗户,则由其父窗口打开它们。 因此,通过将<code>this</code>设置为第一个参数,我们告诉编译器(当然还有 Qt)此类负责<code>QFileDialog</code>类实例。 <code>getOpenFileName</code>函数的第二个参数显然是文件选择对话框窗口的标题,下一个参数是当前路径。 我们提供的最后一个参数可确保仅显示应用需求中的三种文件类型:<code>*.jpg</code>,<code>*.png</code>和<code>*.bmp</code>文件。</p><p>仅当首先将其模块添加到您的项目中,然后将其头文件包含在您的源文件中时,才可以使用任何 Qt 类。 要将 Qt 模块添加到 Qt 项目,您需要在项目的<code>PRO</code>文件中添加类似于以下内容的行:</p><p><code>QT += module_name1 module_name2 module_name3 ...</code></p><p><code>module_name1</code>等可以替换为可以在 Qt 文档中找到的每个类的实际 Qt 模块名称。

您可能已经注意到项目的 PRO 文件中已经存在以下代码行:

QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

这仅表示coregui模块应包含在您的项目中。 它们是两个最基本的 Qt 模块,包括许多 Qt 基础类。 第二行表示,如果您使用的 Qt 框架的主要版本号高于四个,则还应包含widgets模块。 这是因为以下事实:在 Qt 5 之前,widgets模块是gui模块的一部分,因此无需将其包含在PRO文件中。
至于包含文件,它始终与类名本身相同。 因此,在我们的情况下,我们需要在源代码中添加以下类,以使前面的代码起作用。 最好的位置通常是头文件的顶部,因此在我们的例子中就是mainwindow.h文件。 确保在顶部具有以下类别:

#include <QMainWindow>
#include <QFileDialog>
#include <QDir>
#include <QFile>

尝试一下,然后运行程序以查看结果。 然后,将其关闭并再次返回到设计器。 现在,我们需要将代码添加到outputPushButton小部件。 只需重复与inputPushButton相同的过程,但是这次,在outputPushButton上进行此操作,并为其编写以下代码:

代码语言:javascript
复制
    void MainWindow::on_outputPushButton_pressed()
{
QString fileName = QFileDialog::getSaveFileName(this,
"Select Output Image",
QDir::currentPath(),
".jpg;;.png;;*.bmp");
if(!fileName.isEmpty())
{
ui->outputLineEdit->setText(fileName);
using namespace cv;
Mat inpImg, outImg;
inpImg = imread(ui->inputLineEdit->text().toStdString());
if(ui->medianBlurRadioButton->isChecked())
cv::medianBlur(inpImg, outImg, 5);
else if(ui->gaussianBlurRadioButton->isChecked())
cv::GaussianBlur(inpImg, outImg, Size(5, 5), 1.25);
imwrite(fileName.toStdString(), outImg);
if(ui->displayImageCheckBox->isChecked())
imshow("Output Image", outImg);
}
}

您还需要向项目添加OpenCV标头。 将它们添加到mainwindow.h文件顶部的添加 Qt 类头的位置,如下所示:

代码语言:javascript
复制
    #include "opencv2/opencv.hpp"  

现在,让我们回顾一下我们刚刚编写的代码,看看它的真正作用。 如您所见,这一次,我们在QFileDialog类和标题中使用了getSaveFileName函数,并且过滤器也有所不同。 这是必需的,以便用户在要保存输出图像时分别选择每种图像类型,而不是在打开它们时看到所有图像。 这次,我们也没有检查文件的存在,因为这将由QFileDialog自动完成,因此仅检查用户是否确实选择了某项就足够了。 在以下几行中,我们编写了一些特定于 OpenCV 的代码,在接下来的章节中,我们将越来越多地了解这些功能。 在第 1 章,“OpenCV 和 Qt 简介”中,您还以很小的剂量使用了它,因此,它们到现在为止还不完全陌生。 但是,我们将再次简短地讨论它们,并继续介绍 IDE 和Hello_Qt_OpenCV应用。

所有OpenCV函数都包含在cv名称空间中,因此我们确保我们是 OpenCV namespace cvusing。 然后,为了读取输入图像,我们使用了imread函数。 这里要注意的重要一点是 OpenCV 使用 C++ std::string类,而 Qt 的QString应该转换为该格式,否则,当您尝试运行该程序时会遇到错误。 只需使用QStringtoStdString函数即可完成。 注意,在这种情况下,QStringinputLineEdit小部件的text()函数返回的值。

接下来,根据选择的过滤器类型,我们使用medianBlurgaussianBlur函数进行简单的 OpenCV 过滤。

请注意,在这种情况下,我们为这些 OpenCV 函数使用了一些默认参数,但是如果我们使用旋转小部件从用户那里获得它们,那就更好了。 还是滑块部件? 也许是一个不错的拨盘小部件? 完成本章后,请尝试一下。 这个想法很简单,它旨在帮助您学习如何在这些框架中自己发现新的可能性。 尽管如此,您将在第 3 章,“创建全面的 Qt + OpenCV 项目”中学习如何使用许多小部件,甚至创建自己的小部件。

最后,已过滤的输出图像outImg被写入所选文件。 根据displayImageCheckBox小部件设置的条件也会显示它。

到这个时候,我们还有两个要求。 首先是,在关闭程序时将所有小部件的状态保存在窗口中,并在重新打开程序时将其重新加载。 另一个要求(最后一个要求)是在用户想要关闭程序时提示他们。 让我们从最后一个开始,因为这意味着我们需要知道如何编写在关闭窗口时需要执行的代码。 这非常简单,因为 Qt 的QMainWindow类(我们的窗口所基于的类)是QWidget,并且它已经具有一个虚拟 C++ 函数,我们可以覆盖和使用它。 只需将以下代码行添加到您的MainWindow类中:

代码语言:javascript
复制
    protected:
void closeEvent(QCloseEvent *event);

这应该进入mainwindow.h文件中的类定义。 专用插槽前的线路似乎是个好地方。 现在,切换到mainwindow.cpp并将以下代码段添加到文件末尾:

代码语言:javascript
复制
    void MainWindow::closeEvent(QCloseEvent *event)
{
int result = QMessageBox::warning(this,
"Exit",
"Are you sure you want to close this program?",
QMessageBox::Yes,
QMessageBox::No);
if(result == QMessageBox::Yes)
{
event->accept();
}
else
{
event->ignore();
}
}

我想您已经注意到我们现在又引入了两个 Qt 类,这意味着我们也需要将它们的包含标头添加到mainwindow.h。 考虑以下:

  • QMessageBox:根据消息的目的,它可以用于显示带有简单图标,文本和按钮的消息
  • QCloseEvent:这是许多 Qt 事件(QEvent)类之一,其目的是传递有关窗口关闭事件的参数

该代码几乎是不言自明的,因为您已经知道警告函数的第一个参数是什么。 这是用来告诉 Qt 我们的MainWindow类负责此消息框。 记录用户选择的结果,然后,基于此结果,关闭事件被接受或忽略,非常简单。 除此之外,我们仍然需要保存设置(小部件上的文本以及复选框和单选框的状态)并加载它们。 如您所知,保存设置的最佳位置是closeEvent函数。 在代码的event->accept();行之前怎么样? 让我们向MainWindow类添加两个私有函数,一个私有函数加载名为loadSettings的设置,另一个私有函数保存名为saveSettings的设置。 在本章中,我们将学习最后一个 Qt 类,它称为QSettings。 因此,首先将其包含行添加到mainwindow.h中,然后将以下两个函数定义添加到MainWindow类中,再次在Ui::MainWindow *ui;行正下方的mainwindow.h中,在私有成员中:

代码语言:javascript
复制
    void loadSettings();
void saveSettings();

这是loadSettings函数所需的代码:

代码语言:javascript
复制
    void MainWindow::loadSettings()
{
QSettings settings("Packt",
"Hello_OpenCV_Qt",
this);
ui->inputLineEdit->setText(settings.value("inputLineEdit",
"").toString());
ui->outputLineEdit->setText(settings.value("outputLineEdit",
"").toString());
ui->medianBlurRadioButton
->setChecked(settings.value("medianBlurRadioButton",
true).toBool());
ui->gaussianBlurRadioButton
->setChecked(settings.value("gaussianBlurRadioButton",
false).toBool());
ui->displayImageCheckBox
->setChecked(settings.value("displayImageCheckBox",
false).toBool());
}

这是给saveSettings的:

代码语言:javascript
复制
    void MainWindow::saveSettings()
{
QSettings settings("Packt",
"Hello_OpenCV_Qt",
this);
settings.setValue("inputLineEdit",
ui->inputLineEdit->text());
settings.setValue("outputLineEdit",
ui->outputLineEdit->text());
settings.setValue("medianBlurRadioButton",
ui->medianBlurRadioButton->isChecked());
settings.setValue("gaussianBlurRadioButton",
ui->gaussianBlurRadioButton->isChecked());
settings.setValue("displayImageCheckBox",
ui->displayImageCheckBox->isChecked());
}

在构造它时,需要为QSettings类提供组织名称(仅作为示例,我们使用"Packt")和应用名称(在我们的情况下为"Hello_Qt_OpenCV")。 然后,它只记录您传递给setValue函数的所有内容,并使用value函数将其返回。 我们所做的只是将要保存的所有内容传递给setValue函数,例如单行编辑小部件中的文本等,然后在需要时重新加载它。 请注意,QSettings像这样使用时,会自己照顾存储位置,并使用每个操作系统的默认位置来保留特定于应用的配置。

现在,只需将loadSettings函数添加到MainWindow类的构造器中。 您应该具有一个如下所示的构造器:

代码语言:javascript
复制
    ui->setupUi(this);
loadSettings();

event->accept()之前,将saveSettings函数添加到closeEvent即可。 现在,我们可以尝试第一个应用。 让我们尝试运行和过滤图像。 选择两个过滤器中的每一个,看看它们的区别是什么。 尝试使用该应用并查找其问题。 尝试通过向其添加更多参数来对其进行改进,依此类推。 以下是该应用运行时的屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rmKHM3rm-1681869945437)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/802ca071-f806-4b88-a793-79d4753b30e0.png)]

尝试关闭它,并使用我们的退出确认代码查看一切是否正常。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-43T8BdL6-1681869945437)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/3d9ac50d-0203-4e84-b410-36a70d9dc138.png)]

我们编写的程序显然并不完美,但是它列出了您从 Qt Creator IDE 入门到本书各章所需要了解的几乎所有内容。 Qt Creator 中还有另外三个Modes尚未见过,我们将把调试模式和项目模式留给第 12 章,“Qt Quick 应用”,其中我们将深入研究构建,测试和调试计算机视觉应用的概念。 因此,让我们简要地通过 Qt Creator 的非常重要的“帮助”模式以及Options之后,结束我们的 IDE 之旅。

帮助模式

使用左侧的帮助按钮切换到 Qt Creator 的帮助模式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F9BC27ng-1681869945437)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/4e4a85a0-824e-4b03-a901-c3dca818993f.png)]

关于 Qt Creator 帮助模式的最重要的事情是,您可以使用它来找出正确的事实,除了可以从字面上搜索与 Qt 相关的所有内容并查看每个类和模块的无数示例之外, 每个类都需要的模块。 为此,只需切换到索引模式并搜索要在应用中使用的 Qt 类。 这是一个例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CgFeaHoT-1681869945437)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/986b0acf-0507-43d1-8b74-0b7a0ba0f9eb.png)]

如您所见,可以使用索引并搜索QMessageBox类的文档页面。 注意描述之后的前两行:

代码语言:javascript
复制
    #include <QMessageBox>
QT += widgets

这基本上意味着,为了在项目中使用QMessageBox,必须在源文件中包含QMessageBox标头,并将小部件模块添加到PRO文件中。 尝试搜索本章中使用的所有类,然后在文档中查看其示例。 Qt Creator 还提供了非常强大的上下文相关帮助。 您只需在任何 Qt 类上用鼠标单击F1,它的文档页面都将在编辑模式下的代码编辑器中获取:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p2UAW7DS-1681869945437)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/be421df1-8073-4360-a36a-f78d3d685d1b.png)]

Qt Creator 选项窗口

您可以通过单击工具,然后单击选项,从其主菜单访问 Qt Creator 选项。 Qt Creator 允许非常高级别的自定义,因此您会发现 Qt Creator 的“选项”页面和选项卡具有很多要配置的参数。 对于大多数人(包括我自己)来说,Qt Creator 的默认选项几乎可以满足他们需要做的所有事情,但是有些任务在不知道如何配置 IDE 的情况下将无法完成。 考虑以下屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-boW61T73-1681869945438)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/d9c5fbd0-b530-4bc6-870d-12166f59a36a.png)]

您可以使用左侧的按钮在页面之间切换。 每个页面包含许多选项卡,但它们都与同一组相关。 以下是每组选项的主要用途:

  • 环境:该设置通常包含与 Qt Creator 外观有关的设置。 您可以在此处更改主题(在本章开头提到),字体和文本大小,语言以及所有设置。

  • 文本编辑器:这组设置包括与代码编辑器相关的所有内容。 在这里,您可以更改设置,例如代码突出显示,代码完成等。
  • FakeVim:适用于熟悉 Vim 编辑器的人。 在这里,他们可以在 Qt Creator 中启用 Vim 样式的代码编辑并进行配置。
  • 帮助:可以猜到,它包含与 Qt Creator 的帮助模式和上下文相关帮助功能有关的所有选项。
  • C++:在这里,您可以找到与 C++ 编码和代码编辑相关的设置。
  • Qt Quick:可在此处找到影响 Qt Quick 设计器和 QML 代码编辑的选项。 我们将在第 12 章,“Qt Quick 应用”中学习有关 QML 的更多信息。
  • 生成并运行:这可能是 Qt Creator 中最重要的选项页面。 此处的设置直接影响您的应用构建和运行体验。 我们将在,“链接和部署”中配置一些设置,在这里您将了解有关 Qt 中的静态链接的信息。
  • 调试器:包含与 Qt Creator 中的调试模式相关的设置。 您将在第 10 章,“调试和测试”中了解更多信息。
  • 设计器:可用于配置 Qt Creator 模板项目和其他与“设计”模式相关的设置。
  • 分析器:包括与 Clang 代码分析器,QML 分析器等相关的设置。 涵盖它们超出了本书的范围。
  • 版本控制:Qt 与许多版本控制系统(例如 Git 和 SVN)提供了非常可靠的集成。 在这里,您可以配置 Qt Creator 中与版本控制相关的所有设置。
  • 设备:如您在第 12 章,“Qt Quick 应用”中所看到的,将在其中使用它来配置 Qt Creator 进行 Android 开发,包括与设备相关的所有设置。
  • 代码粘贴:这可以用于配置 Qt Creator 可以用于诸如代码共享之类的任务的某些第三方服务。
  • Qbs:涵盖 Qbs 完全超出了本书的范围,我们将不需要它。
  • 测试设置:包含与 Qt 测试相关的设置,等等。 我们将在第 10 章(调试和测试)中介绍 Qt 测试,您将在其中学习如何为 Qt 应用编写单元测试。

除此之外,您始终可以使用 Qt Creator 的过滤器工具立即在“选项”窗口中找到所需的设置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uAfdydgo-1681869945438)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/e1bc1c80-c2e9-4cb7-8e56-49eb86cc4b21.png)]

总结

本章不仅仅介绍 Qt Creator,而且正是我们所需要的,以便轻松地继续下一章,专注于构建内容,而不是重复的说明和配置技巧。 我们学习了如何使用 Qt Creator 来设计用户界面并为用户界面编写代码。 我们介绍了一些使用最广泛的 Qt 类,以及它们如何包装在不同的模块中。 通过了解不同的 Qt Creator 模式并同时构建应用,我们现在可以自己练习更多,甚至可以改善编写的应用。 下一章将是我们在其中构建可扩展的基于插件的计算机视觉应用框架的一章,该书将在本书的其余部分中继续进行,直到最后一章为止。 在下一章中,我们将学习 Qt 和 OpenCV 中的不同设计模式,以及如何使用类似的模式来构建易于维护和扩展的应用。

三、创建一个全面的 Qt + OpenCV 项目

由于某些随机情况,专业应用永远不会成为专业。 它们从一开始就是这样设计的。 当然,说起来容易做起来难,但是如果您已经知道如何创建可以轻松扩展,维护,扩展和定制的应用的黄金法则,这仍然很容易。 黄金法则只是一个简单的概念,幸运的是 Qt 框架已经具有实现的手段,并且正在以模块化方式构建应用。 请注意,从这个意义上讲,模块化不仅意味着库或不同的源代码模块,而且在意义上说模块化是指应用的每个职责和功能都是独立于其他职责创建和构建的。 实际上,这正是 Qt 和 OpenCV 本身创建的方式。 即使来自不同背景的不同开发人员也可以很容易地扩展模块化的应用。 模块化的应用可以扩展为支持许多不同的语言,主题(样式或外观),或者更好的是,许多不同的功能。

在本章中,我们将承担一项非常重要且至关重要的任务,即为使用 Qt 和 OpenCV 框架的综合计算机视觉应用构建基础结构(或架构)。 您将学习如何创建 Qt 应用,这些应用即使在部署(交付给用户)后也可以扩展。 这实际上意味着很多事情,包括如何向应用添加新语言,如何向应用添加新样式,以及最重要的是如何构建基于插件的 Qt 应用,可以通过向其添加新插件来对其进行扩展。

我们将通过遍历 Qt 项目的结构和包含的文件,来了解构建 Qt 应用时通常会发生什么情况。 然后,我们将了解 Qt 和 OpenCV 中一些使用最广泛的设计模式,以及这两个框架如何享受使用这些设计模式的优势。 然后,我们将学习如何创建可以使用插件扩展的应用。 我们还将学习有关在应用中添加新样式和新语言的信息。 在本章结束时,我们将能够创建一个全面的计算机视觉应用的基础,该应用是跨平台,多语言,基于插件的,并且具有可自定义的外观。 此基础应用将在接下来的两章中扩展:第 4 章,“MatQImage”和第 5 章,“图形视图框架”,然后在本书的其余部分中使用插件,尤其是在第 6 章,“OpenCV 中的图像处理”之后,我们将开始深入研究计算机视觉主题和 OpenCV 库。

在本章中,我们将介绍以下主题:

  • Qt 项目的结构和 Qt 构建过程
  • Qt 和 OpenCV 中的设计模式
  • Qt 应用中的样式
  • Qt 应用中的语言
  • 如何使用 Qt Linguist 工具
  • 如何在 Qt 中创建和使用插件

背景

在第 2 章,“创建我们的第一个 Qt 和 OpenCV 项目”中,您学习了如何创建一个简单的 Qt + OpenCV 应用Hello_Qt_OpenCV。 该项目几乎包括 Qt 提供的所有基本功能,尽管我们没有过多地讨论如何将项目构建到具有用户界面和(几乎可以接受)行为的应用中。 在本节中,您将了解单击“运行”按钮时幕后发生的情况。 这将帮助我们更好地了解 Qt 项目的结构以及项目文件夹中每个文件的用途。 首先,打开项目文件夹,并逐个浏览几个文件。 因此,我们在Hello_Qt_OpenCV文件夹中包含以下内容:

代码语言:javascript
复制
    Hello_Qt_OpenCV.pro 
    Hello_Qt_OpenCV.pro.user 
    main.cpp 
    mainwindow.cpp 
    mainwindow.h 
    mainwindow.ui 

Hello_Qt_OpenCV.pro列表中的第一个文件基本上是构建项目时 Qt 处理的第一个文件。 这称为 Qt 工程文件,内部的 Qt 程序qmake负责处理该文件。 让我们看看它是什么。

#qmake工具

qmake工具是一个程序,可使用*.pro文件中的信息来帮助创建 makefile。 这仅意味着,qmake使用非常简单的语法(相对于其他make系统中更复杂的语法),生成了正确编译和构建应用所需的所有命令,并将所有生成的文件放入Build文件夹中 。

生成 Qt 项目时,它将首先创建一个新的生成文件夹,默认情况下,该文件夹与项目文件夹位于同一级别。 在我们的情况下,此文件夹应具有类似于build-Hello_Qt_OpenCV-Desktop_Qt_5_9_1_*-Debug的名称,其中*可以不同,具体取决于平台,您可以在项目文件夹所在的同一文件夹中找到它。 Qt(使用qmake和其他一些您将在本章中学习的工具)和 C++ 编译器生成的所有文件都位于此文件夹及其子文件夹中。 这称为项目的Build文件夹。 这也是创建和执行应用的地方。 例如,如果您使用的是 Windows,则可以在Build文件夹的debugrelease子文件夹中找到Hello_Qt_OpenCV.exe文件(在许多其他文件中)。 因此,从现在开始,我们将该文件夹(及其子文件夹)称为Build文件夹。

例如,我们已经知道在 Qt PRO 文件中包含以下行会导致在我们的应用中添加 Qt 的coregui模块:

代码语言:javascript
复制
    QT += core gui 

让我们在Hello_Qt_OpenCV.pro文件中进一步浏览; 以下几行立即引起注意:

代码语言:javascript
复制
    TARGET = Hello_Qt_OpenCV 
    TEMPLATE = app 

这些行仅表示TARGET名称为Hello_Qt_OpenCV,这是我们项目的名称,TEMPLATE类型app表示我们的项目是一个应用。 我们还有以下内容:

代码语言:javascript
复制
    SOURCES += \ 
        main.cpp \ 
        mainwindow.cpp 
    HEADERS += \ 
        mainwindow.h 
    FORMS += \ 
        mainwindow.ui 

很明显,这就是在项目中包含头文件,源文件和用户界面文件(窗体)的方式。 我们甚至将自己的代码添加到 PRO 文件中,如下所示:

代码语言:javascript
复制
    win32: { 
      include("c:/dev/opencv/opencv.pri") 
    } 
    unix: !macx{ 
      CONFIG += link_pkgconfig 
      PKGCONFIG += opencv 
    } 
    unix: macx{ 
      INCLUDEPATH += "/usr/local/include" 
      LIBS += -L"/usr/local/lib" \ 
    -lopencv_world 
   } 

您已经了解到,这就是 Qt 如何查看 OpenCV 并将其用于 Qt 项目中。 在 Qt 帮助索引中搜索qmake Manual,以获取有关qmake中所有可能的命令和功能的更多信息,以及有关其工作方式的更多详细信息。

qmake处理完我们的 Qt 项目文件后,它开始寻找项目中提到的源文件。 自然地,每个 C++ 程序在其源文件之一(不在头文件中)中都具有main函数(单个且唯一的main函数),我们的应用也不例外。 我们的应用的main函数是由 Qt Creator 自动生成的,它位于main.cpp文件中。 让我们打开main.cpp文件,看看其中包含什么:

代码语言:javascript
复制
    #include "mainwindow.h" 
    #include <QApplication> 
    int main(int argc, char *argv[]) 
    { 
      QApplication a(argc, argv); 
      MainWindow w; 
      w.show(); 
      return a.exec(); 
    } 

前两行用于包含我们当前的mainwindow.h标头和QApplication标头文件。 QApplication类是负责控制应用的控制流,设置等的主要类。 在main函数内,您在此处看到的是 Qt 如何创建Event Loop以及其底层信号/时隙机制和事件处理系统如何工作的基础:

代码语言:javascript
复制
    QApplication a(argc, argv); 
    MainWindow w; 
    w.show(); 
    return a.exec(); 

为了最简单地描述它,创建了QApplication类的实例,并将应用参数(通常通过命令行或终端传递)传递给名为a的新实例。 然后,创建我们的MainWindow类的实例,然后将其显示出来。 最后,调用QApplication类的exec()函数,以便应用进入主循环,并保持打开状态直到关闭窗口。

要了解事件循环的工作原理,请尝试删除最后一行,看看会发生什么。 当您运行应用时,您可能会注意到该窗口实际上只显示了很短的时间,然后立即关闭。 这是因为我们的应用不再具有事件循环,它立即到达应用的末尾,并且所有内容都从内存中清除,因此该窗口被关闭。 现在,将那行写回去,正如您所期望的,窗口保持打开状态,因为仅当在代码中的某个地方(任何地方)调用了exit()函数时,exec()函数才返回,并且它返回由exit()设置的值 ]。

现在,让我们继续下三个文件,它们具有相同的名称,但扩展名不同。 它们是mainwindow标头,源和用户界面文件。 现在,您将在第 2 章,“创建我们的第一个 Qt 和 OpenCV 项目”中了解负责代码和应用程​​序用户界面的实际文件。 这给我们带来了另外两个 Qt 内部工具,称为元对象编译器用户界面编译器

元对象编译器(MOC)

我们已经知道,标准 C++ 代码中没有信号和插槽之类的东西。 那么,如何通过使用 Qt 在 C++ 代码中拥有这些附加功能呢? 这还不是全部。 稍后您将学习到,您甚至可以向 Qt 对象添加新属性(称为动态属性),并执行类似的许多其他操作,这不是标准 C++ 编程的功能。 好了,这些可以通过使用名为moc的 Qt 内部编译器来使用。 在将 Qt 代码实际传递给真正的 C++ 编译器之前,moc工具会处理您的类头文件(在我们的示例中为mainwindow.h文件),以生成启用上述 Qt 特定功能所需的代码。 您可以在Build文件夹中找到这些生成的源文件。 他们的名字以moc_开头。

您可以在 Qt 文档中阅读有关moc工具的全部信息,但是这里值得一提的是moc搜索具有 Qt 类定义且包含Q_OBJECT宏的所有头文件。 该宏必须始终包含在想要支持信号,插槽和其他 Qt 支持功能的 Qt 类中。

这是我们在mainwindow.h文件中所包含的内容:

代码语言:javascript
复制
    ... 
    class MainWindow : public QMainWindow 
    { 
      Q_OBJECT 
      public: 
       explicit MainWindow(QWidget *parent = 0); 
     ~MainWindow(); 
    ... 

如您所见,我们的自动生成的类头文件在其私有部分中已经具有Q_OBJECT宏。 因此,这基本上是创建作为QObject(或任何其他 Qt 对象)的子类的类(不仅是窗口类,而且一般是任何 Qt 类)的标准方法,该类将支持 Qt 支持的功能,例如信号和插槽。

现在,让我们继续看看如何通过 C++ 代码访问 Qt 用户界面文件中的小部件。 如果您尝试以Edit模式或任何其他文本编辑器查看mainwindow.ui文件,则实际上它们是仅包含属性和一些其他信息的 XML 文件,这些信息仅与小部件的显示方式有关。 答案在于最终的 Qt 内部编译器,您将在本章中对其进行了解。

用户界面编译器(UIC)

每当构建带有用户界面的 Qt 应用时,就会执行称为uic的 Qt 内部工具来处理*.ui文件并将其转换为可在 C++ 代码中使用的类和源代码。 在我们的情况下,mainwindow.h被转换为ui_mainwindow.h文件,您可以再次在Build文件夹中找到该文件。 您可能已经注意到了这一点,但是让我们提到您的mainwindow.cpp文件已经包含了此头文件。 检查文件的最顶部,您会发现以下两个包含行:

代码语言:javascript
复制
    #include "mainwindow.h" 
    #include "ui_mainwindow.h" 

您已经知道mainwindow.h文件的位置和位置(在Project文件夹中),并且刚刚了解到ui_mainwindow.h实际上是位于Build文件夹内的生成的源文件。

如果查看ui_mainwindow.h文件的内容,您会注意到一个名为Ui_MainWindow的类,它具有两个函数:setupUiretranslateUisetupUi函数已自动添加到mainwindow.h函数中的MainWindow类构造器中。 该函数仅负责根据mainwindow.ui文件中的设置在用户界面上进行所有设置。 您将在本章稍后的内容中了解retranslateUi函数以及在制作多语言 Qt 应用时如何使用它。

将所有 Qt 生成的文件都保存在Build文件夹中之后,将它们传递给 C++ 编译器,就像其他任何 C++ 程序一样,进行编译,然后链接到Build文件夹中以创建我们的应用。 Windows 用户应注意,使用 Qt Creator 运行应用时,所有 DLL 文件路径均由 Qt Creator 解析,但是如果尝试从Build文件夹中运行程序,则会遇到多个错误消息,并且您的应用会崩溃或根本就不会启动。 您将在第 10 章,“调试和测试”中学习如何解决此问题,在这里您将学习如何正确地将应用交付给用户。

设计模式

即使我们假设本书的读者不是“设计模式丹尼尔”,但提醒自己为什么存在设计模式以及为什么成功的框架(例如 Qt)广泛使用不同的设计模式仍然是一个很好的主意。 好吧,首先,设计模式只是软件开发任务的许多解决方案之一,它不是唯一的解决方案; 实际上,在大多数情况下,它甚至不是最快的解决方案。 但是,设计模式绝对是解决软件开发问题的最结构化的方式,它有助于确保对添加到程序中的所有内容都使用一些预定义的类似于模板的结构。

设计模式具有用于各种问题的名称,例如创建对象,它们如何运行,如何处理数据等等。 埃里克·伽玛(Eric Gamma),理查德·赫尔姆(Richard Helm),拉尔夫·E·约翰逊(Ralph E. Johnson)和约翰·弗利斯赛德(John Vlissides)(称为四人帮)在其名为《设计模式:可重用的面向对象软件基础》的书中描述了许多最广泛使用的设计模式。 被认为是计算机科学中设计模式的实际参考书。 如果您不熟悉设计模式,那么在继续本章之前,您应该花一些时间来学习该主题。 了解软件开发中的反模式也是一个好主意。 如果您是本主题的新手,可能会因发现某些反模式的普遍性而感到惊讶,并且确保始终避免使用反模式至关重要。

以下是 Qt 和 OpenCV 框架中使用的一些最重要的设计模式(按字母顺序排列),以及实现这些设计模式的类或函数的简要说明和一些示例。 请密切注意下表中的“示例案例”列,以概述与每个设计模式相关的某些类或函数。 尽管如此,在本书的学习过程中以及各种示例中,您将学习动手实践中使用的类:

由于 OpenCV 框架的性质以及它不是用于构建日常生活应用,复杂的用户界面等的通用框架,因此它无法实现 Qt 使用的所有设计模式, 而且,相比之下,OpenCV 仅实现了这些模式的一小部分。 尤其是由于 OpenCV 追求速度和效率的目标,在大多数情况下,首选全局函数和类似底层的实现。 然而,只要速度和效率不是目标,就有一些 OpenCV 类可以实现设计模式,例如抽象工厂。 有关示例,请参见“示例案例”列。

设计模式

说明

示例案例

抽象工厂

这可用于创建所谓的工厂类,该工厂类能够以各种可能的方式创建对象并控制新对象的创建,例如防止对象具有超过定义数量的实例。

在本章中,我们将学习如何使用这种设计模式来编写基于插件的 Qt 应用。DescriptorMatcher抽象类中的create()函数是 OpenCV 中此设计模式的示例。

命令

使用此设计模式,可以用对象表示动作。 这允许诸如组织动作顺序,记录动作,还原动作等功能。

QAction:此类允许创建特定的动作并将其分配给小部件。例如,可以使用QAction类创建带有图标和文本的Open File操作,然后可以将其分配给主菜单项和键盘快捷键(例如Ctrl + O),依此类推。

组合

这用于创建由子对象组成的对象。 当制作可以由许多简单对象组成的复杂对象时,这特别有用。

QObject:这是所有 Qt 类的基础。QWidget:这是所有 Qt 小部件的基础。任何具有树状设计架构的 Qt 类都是组合模式的示例。

外观(或外观)

通过提供更简单的接口,可以将其用于封装操作系统(或与此相关的任何系统)的低级功能。 包装器和适配器设计模式的定义非常相似。

QFile:这些可用于读取/写入文件。基本上,所有 Qt 类都是围绕操作系统的低级 API 的包装,它们都是外观设计模式的示例。

蝇量(或桥接或私有实现)

此设计模式的目标是避免数据复制并在相关对象之间使用共享数据(除非另有需要)。

QString:可用于存储和操作 Unicode 字符串。实际上,许多 Qt 类都喜欢这些设计模式,这些设计模式有助于在需要对象副本时将指针传递到共享数据空间,从而导致更快的对象复制和更少的内存空间使用。 当然,具有更复杂的代码。

备忘

这可用于保存和(以后)加载对象的状态。

这种设计模式等同于编写一个能够存储 Qt 对象的所有属性并还原它们以创建新属性的类。

元对象(或反射)

在此设计模式中,所谓的元对象用于描述对象的详细信息,以便更健壮地访问该对象。

QMetaObject:这仅包含有关 Qt 类的元信息。涵盖 Qt 的元对象系统的细节不在本书的讨论范围之内,但是简单地说,每个 Qt 程序都首先使用 Qt 元对象编译器(MOC)进行编译以生成所需的元对象,然后再由实际的对象进行编译。 C++ 编译器。

单态

这允许同一类的多个实例以相同的方式行为。 (通常,通过访问相同的数据或执行相同的功能。)

QSettings:用于提供应用设置的保存/加载。我们已经在第 2 章,“创建我们的第一个 Qt 和 OpenCV 项目”中使用了QSettings类,以加载和保存相同类的两个不同实例。

MVC(模型视图控制器)

这是一种广泛使用的设计模式,用于将应用或数据存储机制(模型)的实现与用户界面或数据表示形式(视图)和数据操作(控制器)分开。

QTreeView:这是模型视图的树状实现。QFileSystemModel:用于基于本地文件系统的内容获取数据模型。QFileSystemModel(或任何其他QAbstractItemModel)与QTreeView(或任何其他QAbstractItemView)的组合可以导致 MVC 设计模式的实现。

观察者(或发布/订阅)

此设计模式用于使对象可以监听(或观察)其他对象的变化并做出相应的响应。

QEvent:这是所有 Qt 事件类的基础。将QEvent(及其所有众多子类)视为观察者设计模式的低级实现。 另一方面,Qt 支持signal和slot机制,这是使用观察者设计模式的更方便,更高级的方法。我们已经在第 2 章,“创建第一个 Qt 和 OpenCV 项目”中使用了QCloseEvent(QEvent的子类)。

序列化器

在创建可用于读取或写入其他对象的类(或对象)时使用此模式。

QTextStream:可用于在文件或其他 IO 设备中读取和写入文本。QDataStream:可用于从 IO 设备和文件读取或写入二进制数据。

单例

这可用于将类限制为仅单个实例。

QApplication:可用于以各种方式处理 Qt 小部件应用。确切地说,QApplication中的instance()函数(或全局qApp指针)是单例设计模式的示例。OpenCV 中的cv::theRNG()函数(用于获取默认的随机数生成器(RNG))是单例实现的示例。 请注意,RNG 类本身不是单例。

参考文献:

代码语言:javascript
复制
Design Patterns: Elements of Reusable Object-Oriented Software, by Eric Gamma, Richard Helm, Ralph E. Johnson and John Vlissides (referred to as the Gang of Four)

An Introduction to Design Patterns in C++ with Qt, second Edition, by Alan Ezust and Paul Ezust

通常,前面的列表不应该被视为设计模式的完整列表,因为它仅关注 Qt 和 OpenCV 设计模式,而仅针对本书而言就足够了。 如果您对该主题感兴趣,请考虑阅读提到的参考书,但是正如前面所提到的,就本书而言,您只需要上述清单即可。

检查上一个列表中提到的每个类的文档页面是一个很好的主意。 您可以为此使用 Qt Creator 帮助模式,并在索引中搜索每个类,查看每个类的代码示例,甚至尝试自己使用它们。 这不仅是学习 Qt 的最佳方法,而且是学习不同设计模式的实际实现和行为的最佳方法之一。

Qt 资源系统

在下一部分中,您将学习如何向我们的应用添加样式和多语言支持,但是在此之前,我们必须熟悉 Qt 资源系统。 简而言之,这是 Qt 中将字体,图标,图像,翻译文件,样式表文件等资源文件添加到我们的应用(和库)中的方法。

Qt 支持使用.qrc文件(资源收集文件)进行资源管理,这些文件只是 XML 文件,其中包含有关需要包含在我们的应用中的资源文件的信息。 让我们看一个简单的示例,并在我们的Hello_Qt_openCV应用中包含一个图标,以更好地了解 Qt 资源系统的工作方式:

  1. 确保已在 Qt Creator 中打开了Hello_Qt_OpenCV项目。 选择文件,然后选择新建文件或项目。 在新文件窗口中,确保从左侧的第二个列表中选择 Qt,然后选择 Qt 资源文件。 考虑以下屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-my6OsWCX-1681869945438)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/72357ae5-bf43-49a7-8c5e-173b9b345065.png)]

  1. 单击“选择…”按钮,然后在下一个屏幕中,将名称设置为resources。 默认情况下,该路径应设置为您的项目文件夹,因此请保持原样。 单击下一步,然后单击完成。 最后,您将在项目中添加一个名为resources.qrc的新文件。 如果您在 Qt Creator 中打开此文件(通过右键单击并选择“在编辑器中打开”),将在 Qt Creator 中显示资源编辑器。
  2. 在这里,您可以使用“添加”按钮打开以下两个选项:

新增档案

添加前缀

在这里,文件就是您要添加到项目中的任何文件。 但是,前缀基本上是一个伪文件夹(如果需要,则是一个容器),其中包含许多文件。 请注意,这不一定表示项目文件夹中的文件夹或子文件夹,而仅仅是表示形式和对资源文件进行分组的一种方式。

  1. 首先单击“添加前缀”,然后在“前缀”字段中输入images
  2. 然后,单击“添加文件”,然后选择所需的图像文件(就我们的示例而言,您计算机上的任何.jpg文件都可以)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TRovuUxF-1681869945438)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/bb8c362c-4c51-40cb-9827-ca4aeb7a2898.png)]

在此示例中,我们使用了与第 1 章,“Qt 和 OpenCV 简介”和第 2 章,“创建我们的第一个 Qt 和 OpenCV 项目”。 请注意,资源文件应位于项目文件夹中或其中的子文件夹中。 否则,您将得到确认,如以下屏幕截图所示; 如果是这种情况,请单击复制,然后将资源文件保存在项目文件夹中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5MhUNArm-1681869945438)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/886a03d2-124f-4cbe-94bd-5238b0912134.png)]

而已。 现在,当您构建并运行Hello_Qt_OpenCV应用时,图像文件将包含在应用中,并且可以像操作系统上存在的文件一样进行访问。 但是,该路径与常规文件路径有些不同。 在我们的示例中,test.jpg文件的路径如下:

您可以在 Qt Creator 中展开.qrc文件,然后右键单击每个资源文件,然后选择“复制路径”或“复制 URL”选项以复制每个文件的路径或 URL。 只要需要常规路径,就可以使用该路径;只要需要资源文件的 URL(Qt 中的QUrl类),URL 就会有用。 需要特别注意的是,由于 Qt 资源系统是 Qt 的内部功能,因此 OpenCV 可能无法使用这些路径并访问资源中的文件。 但是,这些文件仅供应用本身使用(通常用于与用户界面相关的任务),因此您可能永远都不需要将它们与 OpenCV 一起使用。

现在,您可以通过将新图像文件设置为按钮图标来尝试新图像文件。 尝试选择用户界面上的任何按钮,然后在属性编辑器中找到icon属性,然后通过按下旁边的小下拉按钮选择“选择资源”。 现在,您只需选择添加为按钮图标的图像即可:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-73ZFEFai-1681869945439)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/a2d4517a-4f2d-41df-8428-7d427667aeb7.png)]

这基本上是关于如何将图标设置为支持图标的 Qt 小部件的教程。 当您想在应用中包含任何其他类型的资源并在运行时使用它时,逻辑完全相同。 您只需要假设 Qt 资源系统是某种辅助文件系统,然后使用其中的文件,就像在文件系统上使用常规文件一样。

设置应用的样式

Qt 支持使用QStyle类和 Qt 样式表的应用中的样式。 QStyle是 Qt 中所有样式的基类,它封装了 Qt 用户界面的样式。 涵盖QStyle类不在本书的讨论范围内,但仍应注意,创建QStyle的子类并在其中实现不同的样式函数最终是更改外观的最有效方法。 Qt 应用。 但是,Qt 还提供样式表来样式应用。 Qt 样式表的语法几乎与 HTML CSS级联样式表)相同,这是网页样式中不可分割的一部分。

CSS 是一种样式语言,可用于定义用户界面上对象的外观。 通常,使用 CSS 文件有助于将网页的样式与基础实现分开。 Qt 在其样式表中使用了一种非常相似的方法来描述小部件的外观。 如果您熟悉 CSS 文件,那么 Qt 样式表对您来说将是小菜一碟; 但是,即使您是第一次接触该概念,也请放心,这是一种易于,简单且快速学习的方法。

让我们通过一个简单的示例来确切地了解什么是样式表以及如何在 Qt 中使用它。 让我们再次回到我们的Hello_Qt_OpenCV项目。 打开项目并转到设计器。 选择窗口上的任何窗口小部件,或单击一个空的位置以选择窗口窗口小部件本身,然后可以找到一个名为styleSheet的属性。 基本上,每个 Qt 窗口小部件(或QWidget子类)都包含一个styleSheet属性,可以将其设置为定义每个窗口小部件的外观。

单击inputPushButton小部件,并将其styleSheet属性设置为以下内容:

代码语言:javascript
复制
    border: 2px solid #222222;
border-radius: 10px;
background-color: #9999ff;
min-width: 80px;
min-height: 35px;

outputPushButton做同样的事情; 但是,这次,在styleSheet属性中使用以下命令:

代码语言:javascript
复制
    border: 2px solid #222222;
border-radius: 10px;
background-color: #99ff99;
min-width: 80px;
min-height: 35px;

在设计器中设置这些样式表时,您将看到两个按钮的新外观。 这就是 Qt 中简单的样式。 唯一需要做的就是知道可以将哪种样式更改应用于任何特定的窗口小部件类型。 在前面的示例中,我们可以更改边框的外观,背景色和QPushButton的最小可接受大小。 要大致了解可以将哪种样式应用于任何窗口小部件,可以在 Qt 帮助模式下阅读《Qt 样式表参考》。 它应该已经在您的计算机上,您可以随时从“帮助”索引中脱机访问它。 在那里,您将找到 Qt 小部件的所有可能样式,其中包含清晰的示例,您可以根据自己的需求进行复制和修改,以及想要在应用中拥有什么样的外观。 这是我们刚刚使用的两个简单样式表的结果。 如您所见,我们现在的浏览按钮外观有所不同:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wsGQ2IXC-1681869945439)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/2a93ef2e-35fb-4859-8364-ba49e99aa684.png)]

在前面的示例中,我们还避免在样式表中设置适当的样式规则。 Qt 样式表中的样式规则由选择器和声明组成。 选择器指定将使用样式的小部件,而声明只是样式本身。 同样,在前面的示例中,我们仅使用了一个声明,并且选择器是(隐式地)获得样式表的小部件。 这是一个例子:

代码语言:javascript
复制
    QPushButton
{
border: 2px solid #222222;
border-radius: 10px;
background-color: #99ff99;
min-width: 80px;
min-height: 35px;
}

在这里,QPushButton(或者实际上是{之前的所有内容)是选择器,并且{}之间的代码部分是声明。

现在,让我们看一下在 Qt 中设置样式表时的一些重要概念。

选择器类型

以下是可以在 Qt 样式表中使用的选择器类型。 明智而有效地使用它们可以极大地减少样式表所需的代码量,并改变 Qt 应用的外观:

选择器类型

范例

说明

通用

这些都是小部件

类型

QPushButton

这些是指定类型及其子类的窗口小部件

属性

[text='Browse']

这些是具有指定属性设置为指定值的小部件

.QPushButton

这些是具有指定类型的小部件,但不是其子类

ID

#inputPushButton

这些是具有指定类型和objectName的小部件

后继

QDialog QPushButton

这些小部件是另一个小部件的后代(子代)

子项

QDialog > QPushButton

这些小部件是另一个小部件的直接子代

子小部件

更好的是,子小部件是复杂小部件内的子小部件。 一个示例是QPinBox小部件上的向下和向上箭头按钮。 可以使用::运算符选择它们,如以下示例所示:

代码语言:javascript
复制
    QSpinBox::down-button 

始终记得参考 Qt Creator 帮助模式下的“Qt 样式表参考”文章,以获取(或多或少)每个小部件的子控件的完整列表。 Qt 是一个不断发展的框架,并且会定期添加新功能,因此没有比它自己的文档更好的参考了。

伪状态

每个窗口小部件都可以具有某些伪状态,例如,悬停,按下等。 可以使用:运算符在样式表中选择它们,如以下示例所示:

代码语言:javascript
复制
    QRadioButton:!hover { color: black } 

就像子控件一样,请始终参考 Qt Creator 帮助模式下的 Qt 样式表参考,以获取每个小部件的适用伪状态列表。

级联

您可以为整个应用,父窗口小部件或子窗口小部件设置样式表。 在前面的示例中,我们仅设置了两个子小部件的样式表。 每个窗口小部件的样式都将取决于级联规则,这意味着每个窗口小部件还将获得在父窗口小部件或应用中设置的样式规则(如果为其设置了样式表)。 我们可以利用这一事实来避免重复设置整个应用或每个小部件中特定窗口通用的样式规则。

现在,让我们尝试MainWindow中的以下样式表,它在一个简单的示例中结合了您学到的所有知识。 确保删除所有先前设置的样式表(对于两个“浏览”按钮),并在窗口小部件的stylesheet属性中简单地使用以下内容:

代码语言:javascript
复制
    *
{
font: 75 11pt;
background-color: rgb(220, 220, 220);
}
QPushButton, QLineEdit, QGroupBox
{
border: 2px solid rgb(0, 0, 0);
border-radius: 10px;
min-width: 80px;
min-height: 35px;
}
QPushButton
{
background-color: rgb(0, 255, 0);
}
QLineEdit
{
background-color: rgb(0, 170, 255);
}
QPushButton:hover, QRadioButton:hover, QCheckBox:hover
{
color: red;
}
QPushButton:!hover, QRadioButton:!hover, QCheckBox:!hover
{
color: black;
}

如果立即运行应用,则可以看到外观上的更改。 您还将注意到,即使“关闭确认”对话框小部件的样式也已更改,其原因仅仅是因为我们在其父窗口中设置了样式表。 这是屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kcwzRl3o-1681869945439)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/6b77d252-7787-4787-bf5a-b7a6109c7d14.png)]

不用说,您可以通过将样式表保存在文本文件中,然后在运行时加载并设置样式表来执行相同的操作,就像我们在本章后面为综合计算机视觉应用的基础构建时所做的那样。 如本章前面所述,您甚至可以在应用中存储默认样式表(请参阅 Qt 资源系统),并默认加载它,如果在自定义文件的特定位置存储了自定义文件,则可以跳过该样式表。 电脑。 这样,您可以轻松拥有可定制的应用。 您甚至可以拆分任务,并要求专业设计师为您提供样式表,以便您可以在应用中使用它。 这实质上就是 Qt 应用中样式的简单程度。

要获得更多特定于样式表的语法和帮助,最好始终关注 Qt Creator 帮助模式下的样式表语法文章,因为 Qt 样式表基本上是特定于 Qt 的,并且在某些情况下与标准 CSS 有所不同。

多国语言支持

在本节中,您将学习如何使用 Qt 框架创建支持多种语言的应用。 实际上,所有这些都归结为非常易于使用的单个类。 QTranslator类是主要的 Qt 类,负责处理输出(显示)文本的国际化。 您只需要确保以下几点:

  1. 在构建项目时,请使用默认语言(例如英语)。 这意味着,只需对显示的所有内容使用默认语言的句子和单词即可。
  2. 确保tr()函数中包含代码中的所有文字语句,或者具体来说,选择其他语言时需要翻译的所有文字语句。

例如,在代码中,如果您需要编写诸如Open Input Image之类的文字语句(就像在Hello_Qt_OpenCV示例中所做的那样),只需将其传递给tr函数并编写tr("Open Input Image")。 设计器不是这种情况,仅适用于代码内文字字符串。 在设计器中设置属性时,只需使用文字字符串即可。

  1. 确保在项目文件中指定翻译文件。 为此,您需要使用TRANSLATIONS来指定它们,就像项目文件中的SOURCESHEADERS一样。

例如,如果要在应用中使用德语和土耳其语翻译,请将以下内容添加到项目(*.PRO)文件中:

代码语言:javascript
复制
        TRANSLATIONS = translation_de.ts translation_tr.ts 

确保对每个翻译文件始终使用清晰的名称。 即使您可以随意命名,也最好使用包含的语言代码来命名它们(tr表示土耳其语,de表示德语,等等),如前面的示例所示。 这也有助于 Qt Linguist 工具(您将在后面学习)了解翻译的目标语言。

  1. 使用 Qt 的lupdate工具创建 TS 文件(如果已经存在,则更新它们)。 lupdate是 Qt 工具,可在所有源代码和 UI 文件中搜索可翻译的文本,然后创建或更新上一步中提到的 TS 文件。 负责翻译应用的人员可以使用 Qt Linguist 工具轻松打开 TS 文件,并专注于通过简单的用户界面来翻译应用。

lupdate位于 Qt 安装的bin文件夹内。 例如,在 Windows OS 上,它的路径类似于此:

代码语言:javascript
复制
 C:\Qt\Qt5.9.1\5.9.1\msvc2015\bin 

您可以从 Qt Creator 中在项目中执行lupdate,只需在主菜单中单击“工具/外部/语言学家/更新翻译”(lupdate)。 Windows 用户的重要注意事项:如果在运行lupdate后遇到任何问题,则可能是由于 Qt 安装故障所致。 要解决此问题,只需使用开发环境的命令提示符运行lupdate。 如果按照本书中的说明进行操作,则可以从开始菜单执行Developer Command Prompt for VS2015,然后使用cd命令切换到项目文件夹,然后运行lupdate,如此处所示(示例案例为Hello_Qt_OpenCV 我们之前创建的项目):

代码语言:javascript
复制
        C:\Qt\Qt5.9.1\5.9.1\msvc2015\bin\lrelease.exe Hello_Qt_OpenCV.pro 

运行此命令后,如果进入项目文件夹,您会注意到现在已经创建了先前在项目文件中指定的 TS 文件。 随着您的应用越来越大,定期运行lupdate非常重要,它可以提取需要翻译的新字符串,并进一步扩展多语言支持。

  1. 使用 Qt Linguist 工具翻译所有必需的字符串。 它已安装在您的计算机上,因为它是默认 Qt 安装的一部分。 只需选择文件/打开,然后从Project文件夹中选择所有 TS 文件(刚创建的)并打开它们。 如果到目前为止,您已经遵循了有关Hello_Qt_OpenCV项目的所有说明,那么在 Qt Linguist 中打开 TS 文件后,您应该看到以下屏幕:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zOydL6B6-1681869945439)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/3ef9dca9-121f-4ff6-99eb-1f039a1eca37.png)]

Qt Linguist 允许快速,轻松地翻译项目中所有可翻译的元素。 只需使用显示的所有语言为每个项目编写翻译,然后使用顶部的工具栏将其标记为Done。 在退出 Qt Linguist 工具之前,请确保进行保存。

  1. 使用转换后的 TS 文件创建压缩的 QM 文件和二进制 Qt 语言文件。 为此,您需要使用 Qt lrelease工具。

使用lreleaselupdate相同,您在前面的步骤中了解到。 只需将所有lupdate命令替换为lrelease,就可以了。

  1. 将 QM 文件(二进制语言文件)添加到您的应用资源中。

您已经学习了如何使用 Qt 资源系统。 只需创建一个称为Translations的新前缀,然后在该前缀下添加新创建的 QM 文件。 如果正确完成,则项目中应包含以下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BLiXoKvl-1681869945439)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/621d6ca1-afc4-4254-9576-869ddc8238ba.png)]

  1. 现在,您可以开始使用QTranslator类在您的应用中使用多种语言,还可以在运行时在各种语言之间切换。 让我们再次回到示例项目Hello_Qt_OpenCV。 在应用中使用翻译器有多种方法,但是,现在,我们将从最简单的方法开始。 首先将QTranslator包含文件添加到mainwindow.h文件中,并在MainWindow类中定义两个私有QTranslator对象,如下所示:
代码语言:javascript
复制
        QTranslator *turkishTranslator;
QTranslator *germanTranslator;
  1. 在调用loadSettings函数之后,将以下内容添加到MainWindow构造器代码中:
代码语言:javascript
复制
        turkishTranslator = new QTranslator(this);
turkishTranslator
->load(":/translations/translation_tr.qm");
germanTranslator = new QTranslator(this);
germanTranslator
->load(":/translations/translation_de.qm");
  1. 现在,是时候向我们的Hello_Qt_OpenCV项目添加一个主菜单,并允许用户在这些语言之间进行切换了。 您只需在 Qt Creator 设计模式下右键单击窗口,然后选择“创建菜单栏”即可。 然后,在顶部菜单栏中添加一个名为Language的项目。 只需单击并键入以下内容,即可向其中添加三个子项目:

现在,您应该有一个类似于以下内容的主菜单:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Eei0Vpc-1681869945440)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/9722d5ae-2b7c-4dca-b49a-c59e484845e7.png)]

在设计器的底部,您可以找到动作编辑器。 显然,您现在在此处有三个条目,这些条目是在创建主菜单时自动创建的。 它们每个都对应于您在主菜单中输入的每种语言名称。

  1. 右键单击“土耳其语”,然后选择“转到插槽”,然后从列表中选择trigger(),然后单击“确定”。 换句话说(如您在第 2 章,“创建我们的第一个 Qt 和 OpenCV 项目”中所了解的),为actionTurkish对象的触发插槽编写以下代码行:
代码语言:javascript
复制
        void MainWindow::on_actionTurkish_triggered()
{
qApp->installTranslator(turkishTranslator);
}
  1. actionGerman对象添加以下行。 基本上,重复说明,但将其适应于actionTurkish对象:
代码语言:javascript
复制
        void MainWindow::on_actionGerman_triggered()
{
qApp->installTranslator(germanTranslator);
}
  1. 并对actionEnglish对象执行相同的操作。 这次,您需要从应用中删除翻译器,因为英语是我们应用的默认语言:
代码语言:javascript
复制
        void MainWindow::on_actionEnglish_triggered()
{
qApp->removeTranslator(turkishTranslator);
qApp->removeTranslator(germanTranslator);
}
  1. 好了,我们现在就可以在我们的 Qt 应用中找到所有与翻译有关的内容,除了我们需要确保屏幕上的项目已重新翻译并基本上已重新加载。 为此,我们需要使用QMainWindow类的changeEvent。 每次使用前面的installTranslatorremoveTranslator函数安装或移除翻译器时,Language Change事件都会发送到应用中的所有窗口。 要捕获此事件,并确保在语言更改时重新加载窗口,我们需要在程序中覆盖changeEvent函数。 只需将以下代码行添加到mainwindow.h文件中MainWindow类的受保护成员,就在您先前定义closeEvent的位置之后:
代码语言:javascript
复制
        void changeEvent(QEvent event); 
  1. 现在,将以下代码片段添加到您的mainwindow.cpp文件中:
代码语言:javascript
复制
        void MainWindow::changeEvent(QEvent event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this);
}
else
{
QMainWindow::changeEvent(event);
}
}

前面的代码只是意味着,如果change事件是Language Change,则重新转换窗口,否则,一切都应照常进行。 retranslateUi函数是使用UIC生成的(请参阅本章的UIC部分),它只是根据应用中最新安装的QTranslator对象设置正确翻译的字符串。

而已。 现在,您可以运行您的应用,并尝试在两种语言之间切换。 我们已经完成了第一个真正的多语言应用。 重要的是要注意,您在本节中学到的内容基本上适用于每个 Qt 应用,并且是制作多语言应用的标准方法。 在应用中使用不同语言的更为定制的方式将遵循几乎相同的指令集,但是如果不使用资源文件将语言文件内置到应用中,则如果从磁盘上的某个位置加载语言会更好。 。 这具有更新翻译甚至添加新语言(具有更多代码)的优势,而不必重建应用本身。

创建和使用插件

在应用中使用插件是扩展应用的最强大方法之一,人们日常生活中使用的许多应用都受益于插件的功能。 插件只是一个库(Windows 上为.dll,Linux 上为.so等),可以在运行时加载并使用它来执行特定任务,但是当然,它不能像独立应用一样执行,这取决于使用它的应用。 在本书中,我们还将使用插件来扩展计算机视觉应用。

在本节中,我们将学习如何创建一个示例应用(称为Image_Filter),该应用简单地加载和使用计算机上指定文件夹中的插件。 但是,在此之前,我们将学习如何在 Qt 中创建同时使用 Qt 和 OpenCV 框架的插件,因为我们的插件很可能需要使用 OpenCV 库来做一些计算机视觉魔术。 所以,让我们开始吧。

首先,我们需要定义一组接口,以便我们的应用与插件对话。 与 C++ 中的接口等效的是带有纯虚函数的类。 因此,我们基本上需要一个接口,其中包含我们期望在插件中提供的所有功能。 通常,这就是创建插件的方式,这也是第三方开发人员可以为他人开发的应用编写插件的方式。 是的,他们知道插件的接口,只需要用实际执行某些功能的实际代码填充即可。

接口

该接口比乍看之下要重要得多。 是的,它基本上是一个什么都不做的类,但是,它列出了我们的应用在所有时间所需的所有插件的草图。 因此,我们需要确保从一开始就将所有必需的功能都包含在插件接口中,否则,以后几乎不可能添加,删除或修改功能。 由于目前我们正在处理一个示例项目,因此看上去可能并不那么严重,但是在实际项目中,这些通常是决定应用可扩展性的一些关键因素。 因此,既然我们知道了接口的重要性,就可以开始为示例项目创建一个接口。

打开 Qt Creator,并确保没有打开任何项目。 现在,从主菜单中选择“文件/新文件”或“项目”。 在出现的窗口中,从左侧列表(底部的列表)中选择 C++,然后选择C++ Header File。 输入cvplugininterface作为文件名,然后继续进行操作,直到进入代码编辑器模式。 将代码更改为以下内容:

代码语言:javascript
复制
    #ifndef CVPLUGININTERFACE_H
#define CVPLUGININTERFACE_H
#include <QObject>
#include <QString>
#include "opencv2/opencv.hpp"
class CvPluginInterface
{
public:
virtual ~CvPluginInterface() {}
virtual QString description() = 0;
virtual void processImage(const cv::Mat &inputImage,
cv::Mat &outputImage) = 0;
};

#define CVPLUGININTERFACE_IID "com.amin.cvplugininterface"
Q_DECLARE_INTERFACE(CvPluginInterface, CVPLUGININTERFACE_IID)
#endif // CVPLUGININTERFACE_H

您可能已经注意到,类似于以下代码的行会自动添加到使用 Qt Creator 创建的任何头文件中:

代码语言:javascript
复制
    #ifndef CVPLUGININTERFACE_H
#define CVPLUGININTERFACE_H
...
#endif // CVPLUGININTERFACE_H

这些只是确保在应用编译期间每个头文件仅被包含(并被处理)一次。 基本上,还有许多其他方法可以在 C++ 中实现相同的目标,但这是最广泛接受和使用的方法,尤其是 Qt 和 OpenCV 框架都获得了最高程度的跨平台支持。 与 Qt Creator 一起使用时,始终会自动将其添加到头文件中,而无需执行任何其他工作。

前面的代码基本上是 Qt 中插件接口所需的一切。 在示例接口中,我们只有两种简单类型的函数,我们需要插件来支持,但是正如我们稍后将要看到的那样,为了支持参数,语言等,我们需要的还不止这些。 但是,对于我们的示例,这应该足够了。

对于一般的 C++ 开发人员来说,一个非常重要的注意事项是前一个接口中的第一个公共成员,它在 C++ 中被称为虚拟析构器,它是许多人忘记包括而又不太注意的最重要方法之一, 因此,最好查看一下它的真实含义并记住它以避免内存泄漏,尤其是在使用 Qt 插件时:

代码语言:javascript
复制
         virtual ~CvPluginInterface() {} 

基本上,任何具有虚拟方法并打算多态使用的 C++ 基类都必须包含虚拟析构器。 这有助于确保即使使用基类的指针访问子类中的析构器也可以调用它们(多态)。 不幸的是,对于大多数 C++ 编译器,在犯此常见 C++ 编程错误时,您甚至都不会收到警告。

因此,我们的插件接口包含一个名为description()的函数,该函数旨在返回任何插件的描述以及有关该插件的有用信息。 它还包含一个名为processImage的函数,该函数将OpenCV Mat类作为输入,并返回一个作为输出。 显然,在此函数中,我们希望每个插件执行某种图像处理,过滤等操作,然后将结果提供给我们。

之后,我们使用Q_DECLARE_INTERFACE宏将类定义为接口。 如果不包含此宏,Qt 将无法将我们的类识别为插件接口。 CVPLUGININTERFACE_IID应该是具有类似包名称格式的唯一字符串,但是您基本上可以根据自己的喜好进行更改。

确保将cvplugininterface.h文件保存到您选择的任何位置,然后将其关闭。 现在,我们将创建一个使用此接口的插件。 让我们使用先前在第 2 章,“创建我们的第一个 Qt 和 OpenCV 项目”时看到的 OpenCV 函数:medianBlur

插件

现在,我们将创建一个名为median_filter_plugin的插件,该插件使用我们的CvPluginInterface接口类。 首先从主菜单中选择“文件”,然后选择“新建文件”或“项目”。 然后,选择“库”和“C++ 库”,如以下屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bPf2pyPk-1681869945440)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/3c960b11-343c-4601-9096-d5390935722e.png)]

确保选择“共享库”作为“类型”,然后输入median_filter_plugin作为名称,然后单击“下一步”。 选择套件类型为桌面,然后单击前进。 在“选择所需的模块”页面中,确保仅选中QtCore,然后继续单击“下一步”(最后是“完成”),而不更改任何选项,直到最终进入 Qt Creator 的代码编辑器。

我们基本上创建了一个 Qt 插件项目,并且您可能已经注意到,插件项目的结构与到目前为止我们尝试过的所有应用项目非常相似(除了它没有 UI 文件),这是因为插件实际上不能与应用相同,只是它不能单独运行。

现在,将我们在上一步中创建的cvplugininterface.h文件复制到新创建的插件项目的文件夹中。 然后,只需在“项目”窗格中右键单击项目文件夹,然后从弹出菜单中选择“添加现有文件”,即可将其添加到项目中,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4N1f3IZw-1681869945440)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/09784656-31f1-4aba-ac38-30ec71d92381.png)]

我们需要告诉 Qt 这是一个插件,而不仅仅是任何库。 为此,我们需要将以下内容添加到我们的.PRO文件中。 您可以将其添加到项目中的任何位置,但是将其添加到TEMPLATE = lib行是一个好习惯:

代码语言:javascript
复制
    CONFIG += plugin 

现在,我们需要将 OpenCV 添加到我们的插件项目中。 到现在为止,这对您来说应该是小菜一碟。 只需将以下内容添加到插件的.PRO文件中,就像之前使用Hello_Qt_OpenCV项目所做的那样:

代码语言:javascript
复制
    win32: {
include("c:/dev/opencv/opencv.pri")
}
unix: !macx{
CONFIG += link_pkgconfig
PKGCONFIG += opencv
}
unix: macx{
INCLUDEPATH += "/usr/local/include"
LIBS += -L"/usr/local/lib" \
-lopencv_world
}

当您向*.PRO文件中添加一些代码,或者使用 Qt Creator 主菜单(和其他用户界面快捷方式)添加新的类或 Qt 资源文件时,手动运行qmake是一个很好的习惯。 如果您发现 Qt Creator 与项目内容不同步。 您可以通过从“项目”窗格的右键菜单中选择“运行qmake”来轻松完成此操作,如以下屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l8de5cQU-1681869945440)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/d98f4e88-7fdd-470a-89d5-4f7efac2936a.png)]

好了,场景已经设定好了,我们可以开始为第一个 Qt + OpenCV 插件编写代码了。 正如您将在接下来的章节中看到的那样,我们将使用插件向我们的应用添加类似的功能; 这样,我们将只关注开发插件,而不用为添加的每个功能修改整个应用。 因此,熟悉和熟悉此过程的这一步骤非常重要。

首先打开median_filter_plugin.h文件并进行如下修改:

代码语言:javascript
复制
    #ifndef MEDIAN_FILTER_PLUGIN_H
#define MEDIAN_FILTER_PLUGIN_H
#include "median_filter_plugin_global.h"
#include "cvplugininterface.h"
class MEDIAN_FILTER_PLUGINSHARED_EXPORT Median_filter_plugin:
public QObject, public CvPluginInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "com.amin.cvplugininterface")
Q_INTERFACES(CvPluginInterface)
public:
Median_filter_plugin();
~Median_filter_plugin();
QString description();
void processImage(const cv::Mat &inputImage,
cv::Mat &outputImage);
};
#endif // MEDIAN_FILTER_PLUGIN_H

创建median_filter_plugin项目时,大多数上述代码是自动生成的。 这就是基本 Qt 库类定义的外观。 但是,正是我们的添加使它变成了一个有趣的插件。 让我们回顾一下前面的代码,看看真正添加到该类中的内容:

  1. 首先,我们包含了cvplugininterface.h头文件。

  2. 然后,我们确保Median_filter_plugin类继承了QObjectCvPluginInterface
  3. 之后,我们添加了 Qt 所需的宏,以便将我们的库识别为插件。 这仅表示以下三行代码,它们对应于您在本章之前学过的第一个Q_OBJECT宏,并且默认情况下应存在于任何 Qt 类中以允许 Qt 特定的功能(例如信号和插槽)。 下一个是Q_PLUGIN_METADATA,它需要在插件的源代码中恰好出现一次,用于添加有关插件的元数据; 最后一个Q_INTERFACES需要声明在插件中实现的接口。 这是必需的宏:
代码语言:javascript
复制
        Q_OBJECT 
        Q_PLUGIN_METADATA 
        Q_INTERFACES 
  1. 然后,我们在类中添加了descriptionprocessImage函数的定义。 在这里,我们将真正定义插件的功能,而不是仅拥有声明而不是实现的接口类。
  2. 最后,我们可以将所需的更改和实际实现添加到median_filter_plugin.cpp文件中。 确保将以下三个函数添加到median_filter_plugin.cpp文件的底部:
代码语言:javascript
复制
       Median_filter_plugin::~Median_filter_plugin() 
       {}
   QString Median_filter_plugin::description() 
   { 
     return &#34;This plugin applies median blur filters to any image.&#34; 
     &#34; This plugin&#39;s goal is to make us more familiar with the&#34; 
     &#34; concept of plugins in general.&#34;; 
   } 
   void Median_filter_plugin::processImage(const cv::Mat &amp;inputImage, 
     cv::Mat &amp;outputImage) 
  { 
    cv::medianBlur(inputImage, outputImage, 5); 
  } </code></pre></div></div><p>我们刚刚添加了类析构器的实现:<code>description</code>和<code>processImage</code>函数。 如您所见,<code>description</code>函数返回有关插件的有用信息,在这种情况下,它没有复杂的帮助页面,而只是几句话。 <code>processImage</code>函数仅将<code>medianBlur</code>应用于图像,您已经(简要地)在第 2 章,“创建我们的第一个 Qt 和 OpenCV 项目”中使用了该图像。</p><p>现在,您可以在右键单击项目后或从主菜单上的“生成”条目中单击“重建”。 这将创建一个插件文件,我们将在下一节中使用它,通常在与项目处于同一级别的文件夹下。 这是<code>Build</code>文件夹,在第 2 章,“创建我们的第一个 Qt 和 OpenCV 项目”中引入了该文件夹。</p><p>取决于操作系统,插件文件的扩展名可以不同。 例如,在 Windows 上应为<code>.dll</code>,在 MacOS 和 Linux 上应为<code>.dylib</code>或<code>.so</code>,依此类推。</p><h2 id="eelfd" name="%E6%8F%92%E4%BB%B6%E5%8A%A0%E8%BD%BD%E5%99%A8%E5%92%8C%E7%94%A8%E6%88%B7">插件加载器和用户</h2><p>现在,我们将使用在本书上一节中创建的插件。 让我们从创建一个新的 Qt Widgets 应用项目开始。 我们命名为<code>Plugin_User</code>。 创建项目后,首先将 OpenCV 框架添加到<code>*.PRO</code>文件中(您已经足够了解此内容),然后继续创建类似于以下内容的用户界面:</p><ol class="ol-level-0"><li>显然,您需要修改<code>mainwindow.ui</code>文件,对其进行设计以使其看起来像这样,并设置所有对象名称,如以下屏幕快照所示:</li></ol><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4jNQJLpR-1681869945441)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/08d03304-663a-49d2-bc89-b68d12bb16fa.png)]</p><p>确保使用与上图中相同的布局类型。</p><ol class="ol-level-0"><li> 接下来,将<code>cvplugininterface.h</code>文件添加到该项目的文件夹中,然后使用“添加现有文件”选项将其添加到项目中,就像创建插件时一样。
  • 现在,我们可以开始为用户界面编写代码,以及加载,检查和使用插件所需的代码。 首先,将所需的标头添加到mainwindow.h文件,如下所示:
  • 代码语言:javascript
    复制
            #include <QDir> 
            #include <QFileDialog> 
            #include <QMessageBox> 
            #include <QPluginLoader> 
            #include <QFileInfoList> 
            #include "opencv2/opencv.hpp" 
            #include "cvplugininterface.h" 
    1. 然后,在};之前,向MainWindow类的私有成员添加一个函数,这似乎是个好地方:
    代码语言:javascript
    复制
            void getPluginsList();  
    1. 现在,切换到mainwindow.cpp并将以下定义添加到文件顶部,紧接在任何现有#include行之后:
    代码语言:javascript
    复制
            #define FILTERS_SUBFOLDER "/filter_plugins/" 
    1. 然后,将以下函数添加到mainwindow.cpp,这基本上是getPluginsList函数的实现:
    代码语言:javascript
    复制
            void MainWindow::getPluginsList() 
            { 
              QDir filtersDir(qApp->applicationDirPath() + 
                FILTERS_SUBFOLDER); 
              QFileInfoList filters = filtersDir.entryInfoList( 
              QDir::NoDotAndDotDot | 
              QDir::Files, QDir::Name); 
              foreach(QFileInfo filter, filters) 
              { 
                if(QLibrary::isLibrary(filter.absoluteFilePath())) 
              { 
                QPluginLoader pluginLoader( 
                    filter.absoluteFilePath(), 
                    this); 
                if(dynamic_cast<CvPluginInterface*>( 
                    pluginLoader.instance())) 
                { 
                    ui->filtersList->addItem( 
                        filter.fileName()); 
                    pluginLoader 
                        .unload(); // we can unload for now 
                } 
                else 
                { 
                    QMessageBox::warning( 
                        this, tr("Warning"), 
                        QString(tr("Make sure %1 is a correct" 
                        " plugin for this application<br>" 
                        "and it's not in use by some other" 
                        " application!")) 
                        .arg(filter.fileName())); 
                } 
              } 
              else 
              { 
                QMessageBox::warning(this, tr("Warning"), 
                    QString(tr("Make sure only plugins" 
                        " exist in plugins folder.<br>" 
                        "%1 is not a plugin.")) 
                        .arg(filter.fileName())); 
              } 
              }
    
          if(ui-&gt;filtersList-&gt;count() &lt;= 0) 
          { 
            QMessageBox::critical(this, tr(&#34;No Plugins&#34;), 
            tr(&#34;This application cannot work without plugins!&#34; 
            &#34;&lt;br&gt;Make sure that filter_plugins folder exists &#34; 
            &#34;in the same folder as the application&lt;br&gt;and that &#34; 
            &#34;there are some filter plugins inside it&#34;)); 
            this-&gt;setEnabled(false); 
          } 
        } </code></pre></div></div><p>首先让我们看看这个函数的作用。 前面的函数,我们将在<code>MainWindow</code>类的构造器中调用该函数:</p><ul class="ul-level-0"><li>首先,假设在名为<code>filter_plugins</code>的子文件夹中存在插件,并且该子文件夹与应用可执行文件位于同一文件夹中。 (稍后,我们需要在该项目的<code>build</code>文件夹内手动创建此文件夹,然后将在上一步中构建的插件复制到该新创建的文件夹中。)以下是用于获取过滤器插件的直接路径的信息。 子文件夹:</li></ul><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">          qApp-&gt;applicationDirPath() + FILTERS_SUBFOLDER</code></pre></div></div><ul class="ul-level-0"><li>接下来,它使用<code>QDir</code>类的<code>entryInfoList</code>函数从文件夹中提取<code>QFileInfoList</code>。 <code>QFileInfoList</code>类本身基本上是一个<code>QList</code>类,其中包含<code>QFileInfo</code>项(<code>QList&lt;QFileInfo&gt;</code>),每个<code>QFileInfo</code>项都提供有关磁盘上文件的信息。 在这种情况下,每个文件都是一个插件。</li><li>之后,通过遍历<code>foreach</code>循环中的文件列表,它检查<code>plugins</code>文件夹中的每个文件,以确保仅接受插件(库)文件,请使用以下函数:</li></ul><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">             QLibrary::isLibrary </code></pre></div></div><ul class="ul-level-0"><li>然后检查通过上一步的每个库文件,以确保它与我们的插件接口兼容。 我们不仅会允许任何库文件作为插件被接受,因此我们将以下代码用于此目的。</li></ul><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">            dynamic_cast&lt;CvPluginInterface*&gt;(pluginLoader.instance())</code></pre></div></div><ul class="ul-level-0"><li>如果库在上一步中通过了测试,则认为该库是正确的插件(与<code>CvPluginInterface</code>兼容),已添加到我们窗口的列表小部件中,然后被卸载。 我们可以在需要时简单地重新加载和使用它。</li><li>在每个步骤中,如果有问题,则使用<code>QMessageBox</code>向用户显示有用的信息。 同样,最后,如果列表为空,则意味着没有插件可使用,窗口上的窗口小部件被禁用,应用不可用。</li></ul><ol class="ol-level-0"><li>不要忘记在<code>setupUi</code>调用之后从<code>MainWindow</code>构造器调用此函数。</li><li>我们还需要编写<code>inputImgButton</code>的代码,该代码用于打开图像文件。 这里是:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        void MainWindow::on_inputImgButton_pressed() 
        { 
          QString fileName = 
             QFileDialog::getOpenFileName( 
             this, 
             tr(&#34;Open Input Image&#34;), 
             QDir::currentPath(), 
             tr(&#34;Images&#34;) + &#34; (*.jpg *.png *.bmp)&#34;); 
             if(QFile::exists(fileName)) 
            { 
             ui-&gt;inputImgEdit-&gt;setText(fileName); 
            } 
        } </code></pre></div></div><p>我们之前已经看过这段代码,不需要解释。 它只是允许您打开图像文件并确保已正确选择它。</p><ol class="ol-level-0"><li>现在,我们将为<code>helpButton</code>编写代码,该代码将在插件中显示<code>description</code>函数的结果:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">       void MainWindow::on_helpButton_pressed() 
       { 
         if(ui-&gt;filtersList-&gt;currentRow() &gt;= 0)
        { 
         QPluginLoader pluginLoader( 
           qApp-&gt;applicationDirPath() +
           FILTERS_SUBFOLDER +
           ui-&gt;filtersList-&gt;currentItem()-&gt;text());
           CvPluginInterface *plugin = 
             dynamic_cast&lt;CvPluginInterface*&gt;(
           pluginLoader.instance()); 
           if(plugin) 
           { 
             QMessageBox::information(this, tr(&#34;Plugin Description&#34;), 
                plugin-&gt;description()); 
           } 
           else 
           { 
            QMessageBox::warning(this, tr(&#34;Warning&#34;),
            QString(tr(&#34;Make sure plugin %1&#34; &#34; exists and is usable.&#34;)) 
           .arg(ui-&gt;filtersList-&gt;currentItem()-&gt;text())); 
           }
        }
        else
        { 
          QMessageBox::warning(this, tr(&#34;Warning&#34;), QString(tr(&#34;First 
            select a filter&#34; &#34; plugin from the list.&#34;)));
        } 
      }</code></pre></div></div><p>我们使用<code>QPluginLoader</code>类从列表中正确加载插件,然后使用<code>instance</code>函数获取其实例,最后,我们将通过接口在插件中调用该函数。</p><ol class="ol-level-0"><li>相同的逻辑也适用于<code>filterButton</code>。 唯一的区别是这一次,我们将调用实际的过滤函数,如下所示:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">      void MainWindow::on_filterButton_pressed() 
      {
        if(ui-&gt;filtersList-&gt;currentRow() &gt;= 0 &amp;&amp; 
          !ui-&gt;inputImgEdit-&gt;text().isEmpty()) 
        { 
          QPluginLoader pluginLoader(qApp-&gt;applicationDirPath() +
            FILTERS_SUBFOLDER + 
            ui-&gt;filtersList-&gt;currentItem()-&gt;text()); 
            CvPluginInterface *plugin = 
              dynamic_cast&lt;CvPluginInterface*&gt;(
                pluginLoader.instance()); 
              if(plugin)
              { 
               if(QFile::exists(ui-&gt;inputImgEdit-&gt;text()))
               { 
                using namespace cv;
                Mat inputImage, outputImage;
                inputImage = imread(ui-&gt;inputImgEdit-&gt;
                text().toStdString()); 
                plugin-&gt;processImage(inputImage, outputImage); 
                imshow(tr(&#34;Filtered Image&#34;).toStdString(),
                   outputImage); 
               } 
               else
               { 
                 QMessageBox::warning(this, 
                  tr(&#34;Warning&#34;), 
                  QString(tr(&#34;Make sure %1 exists.&#34;)) 
                  .arg(ui-&gt;inputImgEdit-&gt;text()));
               }
              } 
              else
              { 
               QMessageBox::warning(this, tr(&#34;Warning&#34;), 
               QString(tr(
               &#34;Make sure plugin %1 exists and is usable.&#34; )) 
               .arg(ui-&gt;filtersList-&gt;currentItem()-&gt;text())); 
              }
             } 
           else
           {
            QMessageBox::warning(this, tr(&#34;Warning&#34;), 
            QString(tr( &#34;First select a filter plugin from the list.&#34; ))); 
        }
      }</code></pre></div></div><p>使用<code>QMessageBox</code>或其他类型的信息提供功能,始终让用户了解正在发生的事情以及可能发生的问题,这一点很重要。 如您所见,它们通常甚至比正在执行的实际任务花费更多的代码,但这对于避免应用崩溃至关重要。 默认情况下,Qt 不支持异常处理,并相信开发人员将使用足够的<code>if</code>和<code>else</code>指令来处理所有可能的崩溃情况。 关于前面的代码示例的另一个重要说明是<code>tr</code>函数。 请记住,始终将其用于文字字符串。 这样,以后您可以轻松地使您的应用成为多语言。 即使您不打算支持多种语言,也要习惯于在文字字符串中添加<code>tr</code>函数,这是一个好习惯。 它不会造成任何伤害。</p><p>现在,我们准备运行我们的<code>Plugin_User</code>应用。 如果现在运行它,我们将看到一条错误消息(将其放置在自己的位置),并且将被警告没有适当的插件。 为了能够使用我们的<code>Plugin_User</code>应用,我们需要执行以下操作:</p><ol class="ol-level-0"><li>在<code>Plugin_User</code>项目的生成文件夹中创建一个名为<code>filter_plugins</code>的文件夹。 这是创建项目可执行文件的文件夹。</li><li>复制我们构建的插件文件(该文件将是<code>median_filter_plugin</code>项目的<code>build</code>文件夹内的库文件),然后将其粘贴到第一步中的<code>filter_plugins</code>文件夹中。 如前所述,取决于操作系统,诸如可执行程序之类的插件文件将具有扩展名。</li></ol><p>现在,尝试运行<code>Plugin_User</code>,一切都会正常。 您应该能够在列表中看到单个插件,将其选中,单击帮助按钮以获取有关它的信息,然后单击过滤器按钮以将插件中的过滤器应用于图像。 如以下屏幕截图所示:</p><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EFJq1lQR-1681869945441)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/efc2002d-4608-41dc-ba23-0cbb94bd0869.png)]</p><p>尝试创建另一个名为<code>gaussian_filter_plugin</code>的插件,并遵循与<code>median_filter_plugin</code>完全相同的指令集,仅这次,使用在第 2 章,“Qt 和 OpenCV 项目”中看到的<code>gaussianBlur</code>函数。 然后构建它并将其放在<code>filter_plugins</code>文件夹中,然后再次运行<code>Plugin_User</code>应用。 另外,请尝试放置一些随机库文件(和其他非库文件)以测试我们在这些情况下编写的应用。</p><p>这里要注意的一个非常重要的事情是,您必须确保不要将以调试模式构建的插件与以发布模式构建的应用一起使用,反之亦然。 还有其他重要规则适用于加载插件,例如,使用 Qt 较高版本构建的插件不能与使用 Qt 较低版本构建的应用一起使用。 使用较低的 Qt 主版本号构建的插件不能与使用较高的 Qt 主版本号构建的应用一起使用。 有关插件及其用法的更新信息,请始终参阅 Qt 文档或 Qt Creator 帮助模式下的“部署插件”文章。</p><h2 id="72agv" name="%E5%BB%BA%E7%AB%8B%E5%9F%BA%E7%A1%80">建立基础</h2><p>您在本章中学到的所有内容都旨在使您准备开始构建全面的计算机视觉应用,该应用将执行以下操作:</p><ul class="ul-level-0"><li>使用插件扩展其功能</li><li>使用 Qt 样式表自定义其外观</li><li>支持多种语言</li></ul><p>因此,从现在开始,我们将考虑您在本章,前几章中学到的所有事实,为应用奠定基础,例如:</p><ul class="ul-level-0"><li>我们的应用将能够保存和加载所有用户首选项和设置。 我们将通过使用<code>QSettings</code>类来实现它,您已经学习了如何使用它。</li><li>最好有一个集中的单一 Qt 样式表来照顾我们应用的整体外观,并且更好的是从磁盘加载而不是将其嵌入应用本身。</li></ul><p>为此,除非用户从应用的设置页面中手动选择主题,否则我们将简单地假定应用具有本机外观。 主题将是 Qt 样式表,保存在应用可执行文件所在的同一文件夹中的<code>themes</code>文件夹中,样式表文件的扩展名将为<code>thm</code>。 所选主题(或准确地说,样式表)将在运行时从磁盘加载。</p><ul class="ul-level-0"><li>支持多种语言至关重要。 我们将创建一个应用,该应用无需扩展应用即可扩展支持的语言。</li></ul><p>这可以通过将 Qt 二进制语言文件放在应用可执行文件所在的文件夹中的<code>languages</code>文件夹中来完成。 我们可以使用系统默认语言并加载用户的语言(如果我们有其翻译和二进制语言文件); 否则,我们可以加载默认语言,例如英语。 我们还可以允许用户在运行时通过从设置页面中选择应用来更改其语言。</p><ul class="ul-level-0"><li>我们将构建一个支持处理单个图像和视频帧的计算机视觉应用。</li></ul><p>为了实现这一点,我们需要有一个与本章中所看到的非常相似的插件接口(<code>CvPluginInterface</code>),该接口将图像作为输入并产生输出。 然后,我们将需要以本章中所看到的确切方式加载和使用这些插件。 我们将假定插件位于名为<code>cvplugins</code>的文件夹中,该文件夹将存在于我们的应用可执行文件所在的文件夹中。</p><p>除此之外,我们需要考虑计算机视觉应用中即将出现的一些障碍。 在构建应用时始终遥遥领先,这一点很重要; 否则,您将陷入尴尬的境地而无路可走。 因此,它们是:</p><ul class="ul-level-0"><li>在我们的应用中,我们将不断处理图像和视频。 不仅来自文件,还来自摄像机或来自网络(例如互联网)的供稿通道的输入。 我们将在第 4 章,“<code>Mat</code>和<code>QImage</code>”中介绍所有内容。</li><li>没有适当的工具来查看和播放图像,涉及计算机视觉的应用将一无所获。 本主题和所有相关主题将在第 5 章,“图形视图框架”中介绍。</li><li>稍后,在第 9 章“视频分析”中,我们的计算机视觉应用将需要处理和处理视频,这不仅意味着单张图像,而且连续的一组图像(或帧)也会影响过程的结果。 这显然不能通过本章中看到的插件接口来实现,我们将需要创建在单独线程中工作的插件。 我们将在第 8 章,“多线程”中保留该主题,您将在这里学习 Qt 中的并行处理机制。 之后,我们将能够创建适合视频处理的新插件接口,然后在我们的应用中使用它。</li></ul><p>现在,您可以使用 Qt Creator 创建一个 Qt Widgets 应用并将其命名为<code>Computer_Vision</code>。 我们将扩展此应用,直到第 9 章,“视频分析”结束,然后我们将逐步介绍所有新概念。 从本章,前几章中学到的知识中,您应该能够自己创建上述基础列表中的前三项(对主题,语言和插件的支持),强烈建议您至少尝试这样做; 但是,在接下来的两章中,我们将扩展此基础应用。 稍后,在第 5 章,“图形视图框架”结束时,将为您提供下载<code>Computer_Vision</code>的完整基础项目的链接。 。 该基础项目将包含一个<code>MainWindow</code>类,该类能够加载和显示其中包含 GUI 的插件。 在该项目中,您还将找到一个插件接口(类似于本章中看到的接口),但是具有更多的功能,可以实现以下功能:</p><ul class="ul-level-0"><li>获取插件标题</li><li>获取插件的描述(帮助)信息</li><li>获取插件特有的 GUI(Qt 容器小部件)</li><li>获取插件的类型,无论是处理并返回任何图像还是仅在其 GUI 中显示信息</li><li>将图像传递到插件并获得结果</li></ul><p>该基础项目源代码将包含与您在本章中看到的功能类似的功能,以设置样式,更改语言等。</p><h2 id="bp6dl" name="%E6%80%BB%E7%BB%93">总结</h2><p>在您作为开发人员的职业生涯或研究工作期间,您会经常遇到<em style="font-style:italic">可持续</em>这个词。 本章的目的是向您介绍一般创建可持续应用的基本概念,尤其是使用 Qt 和 OpenCV 创建计算机视觉应用的基本概念。 现在,您已经熟悉创建插件,这意味着您可以创建一个应用,该应用可以由第三方开发人员(当然,也可以是您自己)创建的可重用库进行扩展,而无需重建核心应用。 在本章中,您还学习了自定义 Qt 应用的外观和感觉以及如何创建多语言 Qt 应用。</p><p>这是一个漫长但充满希望的章节。 如果您已完成所有示例和分步说明,那么现在您应该已经熟悉使用 Qt 框架进行跨平台应用开发的一些最关键的技术。 在本章中,您了解了 Qt 中的样式表和样式表,以及它提供的一些重要功能来开发美观的应用。 然后,我们继续学习创建多语言应用。 在全球社区中,以及在我们的应用可访问世界每个角落(由于在线应用商店等)的时代,构建支持多种语言的应用是必须要做的,而不仅仅是在大多数情况下都是首选情况。 在学习了多语言应用开发之后,我们进入了插件主题,并通过动手实例和经验学习了其所有基础知识。 我们创建的项目看起来很简单,它包含构建插件以及使用它们的应用的所有重要方面。</p><p>在第 4 章,“<code>Mat</code>和<code>QImage</code>”中,您将了解 OpenCV <code>Mat</code>和 Qt <code>QImage</code>类(以及相关的类),这两个框架都可以处理图像数据。 您将了解所有不同的读取方法(从文件,照相机等)以及写入图像,将它们相互转换并最终在 Qt 应用中显示它们的方法。 到目前为止,我们使用 OpenCV 中的<code>imshow</code>函数仅在默认窗口中显示结果图像。 在第 4 章,“<code>Mat</code>和<code>QImage</code>”中,这将成为历史,因为您将学习如何将 OpenCV <code>Mat</code>转换为<code>QImage</code>类,然后将其正确显示在 Qt 小部件。</p><h2 id="50hvn" name="%E5%9B%9B%E3%80%81Mat%E5%92%8CQImage">四、<code>Mat</code>和<code>QImage</code></h2><p>在第 3 章,“创建全面的 Qt + OpenCV 项目”中,我们了解了创建全面且可持续的应用的基本规则,这些应用看起来很吸引人,支持多种语言,并且通过使用 Qt 中的插件系统易于扩展。 现在,我们将通过学习负责处理计算机视觉数据类型的类和结构,来进一步扩展有关计算机视觉应用基础的知识库。 了解 OpenCV 和 Qt 框架所需的基本结构和数据类型是了解在应用中执行时处理它们的基础计算机视觉功能如何执行的第一步。 OpenCV 是旨在提高速度和性能的计算机视觉框架。 另一方面,Qt 是一个不断发展的应用开发框架,具有大量的类和函数。 这就是为什么他们俩都需要一组定义明确的类和结构来处理要在计算机视觉应用中处理,显示甚至保存或打印的图像数据的原因。 始终使自己熟悉有关 Qt 和 OpenCV 中现有结构的有用详细信息,这始终是一个好习惯。</p><p>您已经使用过 OpenCV 的<code>Mat</code>类来简要地读取和处理图像。 正如您将在本章中了解到的,即使<code>Mat</code>是负责处理 OpenCV 中图像数据的主要类(至少在传统上是这样),<code>Mat</code>类也有一些变体, 实用的功能,其中某些功能甚至是特定功能所必需的,您将在接下来的章节中学习这些功能。 在 Qt 框架的情况下,即使<code>QImage</code>是 Qt 中用于处理图像数据的主要类,也没有太大不同,还有更多的类(有时具有惊人的相似名称)用于支持计算机。 视觉并处理图像数据,视频等。</p><p>在本章中,我们将从最关键的 OpenCV 类<code>Mat</code>开始,然后继续研究不同的变体(其中有些是<code>Mat</code>的子类),最后向您介绍新的<code>UMat</code>类, 这是该框架的 OpenCV 3 补充。 我们将学习使用新的<code>UMat</code>类(实际上是<code>Mat</code>兼容)而不是<code>Mat</code>类的优点。 然后,我们将进入 Qt 的<code>QImage</code>类,并学习如何通过将这两种数据类型相互转换在 OpenCV 和 Qt 之间传递图像数据。 我们还将学习<code>QPixmap</code>,<code>QPainter</code>以及许多其他 Qt 类,对于要从事计算机视觉领域的任何人,所有这些都是必不可少的类。</p><p>最后,我们将学习 OpenCV 和 Qt 框架从文件,摄像机,网络订阅源等读取,写入和显示图像和视频的多种方式。 正如您将在本章末了解到的那样,始终最好根据所需的计算机视觉任务来决定哪种类最适合我们,因此在处理图像数据输入或输出时,我们应该对手头的各种选项有足够的了解 。</p><p>我们将在本章中介绍的主题包括:</p><ul class="ul-level-0"><li><code>Mat</code>类,其子类和新的<code>UMat</code>类的简介</li><li><code>QImage</code>简介和计算机视觉中使用的主要 Qt 类</li><li>如何读取,写入和显示图像和视频</li><li>如何在 Qt 和 OpenCV 框架之间传递图像数据</li><li>如何在 Qt 中创建自定义小部件并使用<code>QPainter</code>对其进行绘制</li></ul><h2 id="8eiik" name="%E5%85%B3%E4%BA%8EMat%E7%B1%BB">关于<code>Mat</code>类</h2><p>在前面的章节中,您非常简要地体验了 OpenCV 框架的<code>Mat</code>类,但是现在我们将更深入地进行研究。 从矩阵中借用其名称的<code>Mat</code>类是<code>n</code>维数组,能够在单个或多个通道中存储和处理不同的数学数据类型。 为了进一步简化,让我们看一下计算机视觉中的图像。 计算机视觉中的图像是像素矩阵(因此为二维数组),具有指定的宽度(矩阵中的列数)和高度(矩阵中的行数)。 此外,灰度图像中的像素可以用一个数字(因此是单个通道)表示,最小值(通常为 0)表示黑色,而最大值(通常为 255)是一个字节可能出现的最大值 )代表白色,而介于两者之间的所有值则对应于不同的灰色强度。 请看以下示例图像,它们只是较大的灰度图像的放大部分。 每个像素都标记有我们刚才提到的强度值:</p><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z4ggpElB-1681869945441)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/23fa03a4-1bc7-4f87-9a94-2c2228731da6.png)]</p><p>同样,标准 RGB 彩色图像中的像素具有三个不同的元素,而不是一个(因此具有三个通道),分别对应于红色,蓝色和绿色值。 看下面的图像例如:</p><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kzTZGd6T-1681869945441)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/62f55b80-51e1-432b-a64d-c65db81fca73.png)]</p><p>如上图所示,在简单的图像查看器程序中,放大(缩放)的图像可以显示出负责图像的像素。 考虑每个<code>Mat</code>类中的单个元素,可以直接访问,修改和使用它们。 图像的这种矩阵状表示使某些功能最强大的计算机视觉算法可以轻松地处理图像,测量所需值甚至生成新图像。</p><p>这是上一个示例图片中放大的区域的另一种表示形式。 每个像素都标有基础的红色,绿色和蓝色值:</p><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IXbOG1wN-1681869945441)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/ec954004-7fcb-4677-82c0-cc383f8848de.png)]</p><p>考虑图像数据和像素的大小写有助于理解<code>Mat</code>类,并且正如我们稍后将要看到的,<code>Mat</code>类和一般的 OpenCV 中的大多数函数都假定<code>Mat</code>是图像, 但是,必须注意<code>Mat</code>可以包含任何数据(不仅是图像),并且实际上在 OpenCV 中有<code>Mat</code>用于传递图像以外的数据数组的情况。 我们将在第 6 章,“OpenCV 中的图像处理”中学习一些相关示例。</p><p>由于一般而言,目前<code>Mat</code>类和 OpenCV 函数的数学细节并不符合我们的最大兴趣,因此,我们将以给定的介绍就足够了,并着重于<code>Mat</code>类及其在 OpenCV 中的基础方法。</p><h2 id="9qlf9" name="%E6%9E%84%E9%80%A0%E5%99%A8%EF%BC%8C%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95">构造器,属性和方法</h2><p>构造<code>Mat</code>类的方法很多。 在撰写本书时,<code>Mat</code>类具有二十多种不同的构造器。 其中一些只是便捷的构造器,但是例如为了创建三个或更多维数组,还需要其他一些函数。 以下是一些使用最广泛的构造器以及如何使用它们的示例:</p><p>创建一个<code>10x10</code>矩阵,每个元素一个通道 8 位无符号整数(或字节):</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    Mat matrix(10, 10, CV_8UC(1)); </code></pre></div></div><p>创建相同的矩阵,并使用<code>0</code>的值初始化其所有元素:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    Mat matrix(10, 10, CV_8UC(1), Scalar(0); </code></pre></div></div><p>前面代码中所示的构造器中的第一个参数是行数,第二个参数是矩阵中的列数。 但是,第三个参数非常重要,它将类型,位数和通道数混合到一个宏中。 这是宏的模式和可以使用的值:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    CV_&lt;bits&gt;&lt;type&gt;C(&lt;channels&gt;) </code></pre></div></div><p>让我们看看宏的每个部分都用于:</p><p><code>&lt;bits&gt;</code>可以替换为:</p><ul class="ul-level-0"><li><code>8</code>:用于无符号和有符号整数</li><li><code>16</code>:用于无符号和有符号整数</li><li><code>32</code>:用于无符号和有符号整数以及浮点数</li><li><code>64</code>:用于无符号和有符号浮点数</li></ul><p><code>&lt;type&gt;</code>可以替换为:</p><ul class="ul-level-0"><li><code>U</code>:用于无符号整数</li><li><code>S</code>:用于有符号整数</li><li><code>F</code>:用于带符号的浮点数</li></ul><p>从理论上讲,<code>&lt;channels&gt;</code>可以用任何值代替,但是对于一般的计算机视觉函数和算法,它不会高​​于 4。</p><p>如果使用的通道数不超过四个,则可以省略<code>&lt;channels&gt;</code>参数的左括号和右括号。 如果通道数仅为一个,则也可以完全省略<code>&lt;channels&gt;</code>和前面的<code>C</code>。 为了获得更好的可读性和一致性,最好使用在前面和后面的示例中使用的标准模式,并且在使用这种广泛使用的宏的方式中保持一致也是一种良好的编程习惯。</p><p>创建一个边长为 10 且具有两个<code>double</code>类型(64 位)通道元素的多维数据集(三维数组),并使用<code>1.0</code>的值初始化所有值。 显示如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    int sizes[] = {10, 10, 10}; 
    Mat cube(3,  sizes, CV_64FC(2), Scalar::all(1.0)); </code></pre></div></div><p>您还可以稍后使用<code>Mat</code>类的<code>create</code>方法来更改其大小和类型。 这是一个例子:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    Mat matrix; 
    // ... 
    matrix.create(10, 10, CV_8UC(1)); </code></pre></div></div><p><code>Mat</code>类的先前内容无关紧要。 基本上,将全部删除(安全清理,并将分配的内存分配回操作系统),并创建一个新的<code>Mat</code>类。</p><p>您可以创建一个<code>Mat</code>类,该类是另一个<code>Mat</code>类的一部分。 这称为感兴趣的<strong>区域</strong>(<strong>ROI</strong>),当我们需要访问图像的一部分,就好像它是独立图像时,它特别有用。 例如,当我们只想过滤图像的一部分时。 这是创建 ROI <code>Mat</code>类的方法,该类包含一个<code>50x50</code>像素宽的正方形,从图像的<code>(X = 25, Y = 25)</code>位置开始:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    Mat roi(image, Rect(25,25,50,50)); </code></pre></div></div><p>在<code>OpenCV</code>中指定<code>Mat</code>的大小时,通常以行和列(高度和宽度)为单位,这有时会使习惯于首先看到宽度的人感到困惑,因为其他框架在很多情况下就是这样。 原因仅仅是<code>OpenCV</code>中图像的矩阵方法。 如果您更喜欢后者,则可以在创建<code>Mat</code>类时在 OpenCV 中使用<code>Size</code>类。</p><p>在本节的示例中,除非另有明确说明,否则假设使用<code>imread</code>函数使用前面章节中的测试图像获取<code>Mat</code>类型的<code>image</code>变量。 这将有助于我们获取<code>Mat</code>类所需的信息,但是,本章稍后将在<code>imread</code>和类似函数中看到更多信息。</p><p>让我们看一下下图,以更好地理解 OpenCV <code>Mat</code>类中 ROI,大小和位置的概念。 如下图所示,图像的左上角被视为图像中坐标系的原点。 因此,原点的位置是<code>(0, 0)</code>。 类似地,图像的右上角具有位置值<code>(width - 1, 0)</code>,其中宽度可以用列数代替。 考虑到这一点,图像的右下角将具有<code>(width-1,height-1)</code>的位置值,依此类推。 现在,让我们考虑基于如下所示的区域创建一个<code>Mat</code>类。 我们可以使用前面看到的方法,但是我们需要使用<code>Rect</code>类提供 ROI 的左上角及其宽度和高度:</p><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1FGWqBgh-1681869945442)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/e93b4977-be31-4624-ad9a-b101416804a9.png)]</p><p>重要的是要注意,使用先前的方法创建 ROI <code>Mat</code>类时,对 ROI 像素的所有更改都会影响原始图像,因为创建 ROI 不会执行原始<code>Mat</code>类内容的深层复制 。 如果出于任何原因想要将<code>Mat</code>类复制到新的(且完全独立的)<code>Mat</code>中,则需要使用<code>clone</code>函数,如以下示例所示:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    Mat imageCopy = image.clone(); </code></pre></div></div><p>假设<code>Mat</code>图像包含先前的图像(来自先前的章节),您可以使用以下示例代码选择图像中看到的 ROI,并使突出显示区域中的所有像素均为黑色:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    4: Mat roi(image, Rect(500, 138, 65, 65)); 
    roi = Scalar(0);</code></pre></div></div><p>您还可以选择<code>Mat</code>中的一个或多个行或列,其方式与我们对 ROI 进行的方式非常相似,除了需要使用<code>row</code>,<code>rowRange</code>,<code>column</code>或<code>colRange</code>在<code>Mat</code>类中起作用。 这是如何做:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    Mat r = image.row(0); // first row 
    Mat c = image.row(0); // first column </code></pre></div></div><p>这是另一个使用<code>rowRange</code>和<code>colRange</code>函数的示例,这些函数可用于选择一系列行和列,而不是仅选择一行。 以下示例代码将在图像的中心产生一个<code>+</code>符号,其厚度为 20 个像素:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    Mat centralRows = image.rowRange(image.rows/2 - 10, 
       image.rows/2 + 10); 
    Mat centralColumns = image.colRange(image.cols/2 - 10, 
       image.cols/2 + 10); 
    centralRows = Scalar(0); 
    centralColumns = Scalar(0); </code></pre></div></div><p>这是在我们的测试图像上执行的结果:</p><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zRHRzsAk-1681869945442)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/0606e3f0-05fd-4c97-bf66-5613db70810c.png)]</p><p>当使用前面提到的方法提取 ROI 并将其存储在新的<code>Mat</code>类中时,可以使用<code>locateROI</code>函数获取父图像的大小和 ROI 在父对象内部的图片的左上角位置。 这是一个例子:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    Mat centralRows = image.rowRange(image.rows/2 - 10, 
      image.rows/2 + 10); 
    Size parentSize; 
    Point offset; 
    centralRows.locateROI(parentSize, offset); 
    int parentWidth = parentSize.width; 
    int parentHeight = parentSize.height; 
    int x = offset.x; 
    int y = offset.y; </code></pre></div></div><p>执行此代码后,<code>parentWidth</code>将包含图像的宽度,<code>parentHeight</code>将包含图像的高度,<code>x</code>和<code>y</code>将包含<code>centralRows</code>在父对象中的左上位置 <code>Mat</code>或换句话说就是图像。</p><p><code>Mat</code>类还包含许多有用的属性和函数,可用于获取有关任何单个<code>Mat</code>类实例的信息。 信息丰富的意思是指提供有关每个像素,通道,颜色深度,宽度和高度的详细信息的成员,以及更多类似的成员。 这些成员包括:</p><ul class="ul-level-0"><li><code>depth</code>:包含<code>Mat</code>类的深度。 深度值对应于<code>Mat</code>类的类型和位数。 因此,它可以是以下值之一:    <ul class="ul-level-1"><li><code>CV_8U</code>:8 位无符号整数</li><li><code>CV_8S</code>:8 位有符号整数</li><li><code>CV_16U</code>:16 位无符号整数</li><li><code>CV_16S</code>:16 位有符号整数</li><li><code>CV_32S</code>:32 位有符号整数</li><li><code>CV_32F</code>:32 位浮点数</li><li><code>CV_64F</code>:64 位浮点数</li></ul></li><li><code>channels</code>:它仅包含<code>Mat</code>类的每个元素中的通道数。 对于标准图像,该值通常为三个通道。</li><li><code>type</code>:这将包含<code>Mat</code>类的类型。 这与本章前面创建<code>Mat</code>类所使用的类型常量相同。</li><li><code>cols</code>:这对应于<code>Mat</code>类中的列数或图像宽度。</li><li><code>rows</code>:这对应于<code>Mat</code>类中的行数或图像高度。</li><li><code>elemSize</code>:可用于获取<code>Mat</code>类中每个元素的大小(以字节为单位)。</li><li><code>elemSize1</code>:无论通道数如何,均可用于获取<code>Mat</code>类中每个元素的大小(以字节为单位)。 例如,在三通道图像中,<code>elemSize1</code>将包含<code>elemSize</code>的值除以三。</li><li><code>empty</code>:如果<code>Mat</code>类中没有元素,则返回<code>true</code>,否则返回<code>false</code>。</li><li><code>isContinuous</code>:可用于检查<code>Mat</code>的元素是否以连续方式存储。 例如,只有一个单行的<code>Mat</code>类始终是连续的。</li></ul><p>使用<code>create</code>函数创建的<code>Mat</code>类始终是连续的。 重要的是要注意,在这种情况下,<code>Mat</code>类的二维表示是使用<code>step</code>值来处理的。 这意味着在连续的元素数组中,每步数的元素对应于二维表示中的一行。</p><ul class="ul-level-0"><li><code>isSubmatrix</code>:如果<code>Mat</code>类是另一个<code>Mat</code>类的子矩阵,则返回<code>true</code>。 在前面的示例中,在所有使用其他图像创建 ROI 的情况下,此属性将返回<code>true</code>,并且在父<code>Mat</code>类中为<code>false</code>。</li><li><code>total</code>:这将返回<code>Mat</code>类中的元素总数。 例如,在图像中,此值等于宽度乘以图像的高度。</li><li><code>step</code>:返回与<code>Mat</code>类中的一个步骤相对应的元素数。 例如,在标准图像(非连续存储的图像)中,<code>step</code>包含<code>Mat</code>类的宽度(或<code>cols</code>)。</li></ul><p>除了提供信息的成员之外,<code>Mat</code>类还包含许多用于访问其单个元素(或像素)(并对其执行操作)的函数。 它们包括:</p><ul class="ul-level-0"><li><code>at</code>:这是一个模板函数,可用于访问<code>Mat</code>类中的元素。 访问图像中的元素(像素)特别有用。 这是一个例子。 假设我们在名为<code>image</code>的<code>Mat</code>类中加载了标准的三通道彩色图像。 这意味着<code>image</code>的类型为<code>CV_8UC(3)</code>,然后我们可以简单地编写以下内容以访问位置<code>X,Y</code>的像素,并将其颜色值设置为<code>C</code>:</li></ul><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        image.at&lt;Vec3b&gt;(X,Y) = C;</code></pre></div></div><p>OpenCV 提供<code>Vec</code>(向量)类及其变体,以便于数据访问和处理。 您可以使用以下<code>typedef</code>创建和命名自己的<code>Vec</code>类型:</p><p><code>typedef Vec&lt;Type, C&gt; NewType;</code>
    

    例如,在前面的代码中,您可能已经定义了自己的 3 字节向量(例如 QCvVec3B),并用它代替Vec3b,并使用以下代码:

    typedef Vec<quint8,3> QCvVec3B;

    不过,您可以使用at函数:

    typedef Vec<uchar, 2> Vec2b;
    typedef Vec<uchar, 3> Vec3b;
    typedef Vec<uchar, 4> Vec4b;
    typedef Vec<short, 2> Vec2s;
    typedef Vec<short, 3> Vec3s;
    typedef Vec<short, 4> Vec4s;
    typedef Vec<ushort, 2> Vec2w;
    typedef Vec<ushort, 3> Vec3w;
    typedef Vec<ushort, 4> Vec4w;
    typedef Vec<int, 2> Vec2i;
    typedef Vec<int, 3> Vec3i;
    typedef Vec<int, 4> Vec4i;
    typedef Vec<int, 6> Vec6i;
    typedef Vec<int, 8> Vec8i;
    typedef Vec<float, 2> Vec2f;
    typedef Vec<float, 3> Vec3f;
    typedef Vec<float, 4> Vec4f;
    typedef Vec<float, 6> Vec6f;
    typedef Vec<double, 2> Vec2d;
    typedef Vec<double, 3> Vec3d;
    typedef Vec<double, 4> Vec4d;
    typedef Vec<double, 6> Vec6d;

    • beginend:它们可用于使用类似 C++ STL 的迭代器来检索和访问Mat类中的元素。
    • forEach:可用于在Mat类的所有元素上并行运行函数。 该函数需要提供一个函数对象,函数指针或 Lambda。

    Lambda 仅在 C++ 11 和更高版本上可用,如果您还没有这样做,它们就是切换到 C++ 11 和更高版本的重要原因。

    以下三个示例代码使用前面的代码中提到的访问方法实现了相同的目标,它们都通过将每个像素值除以5使图像更暗。 首先,使用at函数:

    代码语言:javascript
    复制
        for(int i=0; i<image.rows; i++)
    {
    for(int j=0; j<image.cols; j++)
    {
    image.at<Vec3b>(i, j) /= 5;
    }
    }

    接下来,使用具有beginend函数的类 STL 迭代器:

    代码语言:javascript
    复制
        MatIterator_<Vec3b> it_begin = image.begin<Vec3b>();
    MatIterator_<Vec3b> it_end = image.end<Vec3b>();
    for( ; it_begin != it_end; it_begin++)
    {
    *it_begin /= 5;
    }

    最后,使用[lambda]提供的forEach函数:

    代码语言:javascript
    复制
        image.forEach<Vec3b>([](Vec3b &p, const int *)
    {
    p /= 5;
    });

    这是生成的较暗的图像,对于所有前面的三个代码来说都是相同的:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B6w3ChLK-1681869945442)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/fa9341fd-e421-4105-8629-82638aba89c6.png)]

    正如您已经注意到的,Mat是具有许多方法的类,并且很显然,因为它是使用 OpenCV 和图像时的基本构建块。 除了函数和属性,您之前已经看过,在继续进行下一部分之前,我们还需要了解一些其他函数。 他们来了:

    • adjustROI:此函数可用于轻松更改子矩阵(或准确地说是 ROI 矩阵)的大小。
    • clone:这是创建Mat类的深层副本的广泛使用的函数。 一个示例情况是,您可能希望过滤或处理图像,但仍保留原始图像的副本以供以后比较。
    • convertTo:可用于更改Mat类的数据类型。 此函数还可以选择缩放图像。
    • copyTo:此函数可用于将全部(或图像的一部分)复制到另一个Mat
    • ptr:可用于获取指针并访问Mat中的图像数据。 根据重载的版本,您可以获得一个指向特定行或图像中任何其他位置的指针。
    • release:此函数在Mat析构器中调用,并且基本上负责Mat类所需的内存清理任务。
    • reserve:可用于为许多指定的行保留存储空间。
    • reserveBuffer:类似于reserve,但是它为多个指定字节保留了存储空间。
    • reshape:当我们需要更改通道数以获取矩阵数据的不同表示形式时,这很有用。 一个示例情况是将具有单个通道且每个元素中的每个具有三个字节的Mat(例如Vec3b)转换为具有每个元素中的每个字节具有一个字节的三通道Mat。 显然,这样的转换(或精确地调整形状)将导致目标Mat中的行计数乘以三。 之后,可以使用所得矩阵的转置在行和列之间进行切换。 稍后,您将学习t或转置函数。
    • resize:可用于更改Mat类中的行数。
    • setTo:可用于将矩阵中的所有或某些元素设置为指定值。

    最后但并非最不重要的一点是,Mat类提供了一些方便的方法来处理矩阵运算,例如:

    • cross:计算两个三元素矩阵的叉积。
    • diag:从矩阵中提取对角线。
    • dot:计算两个矩阵的点积。
    • eye:这是一个静态函数,可用于创建单位矩阵。
    • inv:创建逆矩阵。
    • mul:计算两个矩阵的逐元素乘法或除法。
    • ones:这是另一个静态函数,可用于创建一个矩阵,其所有元素都持有值1
    • t:此函数可用于获取Mat类的转置矩阵。 有趣的是,该函数等同于镜子和图像旋转 90 度。 有关更多信息,请参见后续图像。
    • zeroes:可用于创建一个矩阵,其所有元素的值为零。 这等于给定宽度,高度和类型的全黑图像。

    在以下屏幕截图中,左侧的图像是原始图像,而右侧的图像是生成的转置图像。 由于转置矩阵的转置与原始矩阵相同,因此我们也可以说左侧的图像是右侧的图像的转置结果。 这是在图像上执行Mat类的t函数的示例结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VMojrCP1-1681869945442)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/b5a538a1-76e3-4187-a37e-b3c45ccf6260.png)]

    同样重要的是要注意,Mat类也可以进行所有标准算术运算。 例如,与其像前面的例子中讨论Mat类中的访问方法那样,不将所有像素一一分割,我们可以编写以下代码:

    代码语言:javascript
    复制
        Mat darkerImage = image / 5; // or image * 0.2 

    在这种情况下,矩阵中的每个元素(或图像,如果需要)将进行完全相同的操作。

    Mat_<_Tp>

    Mat_<_Tp>类是具有相同成员的Mat类(和模板类)的子类,但是当在<indexentry content="Mat class:Mat_ class" dbid="256603" state="mod">编译时已知矩阵(或图像中的元素)的类型时,它会非常有用。 与Mat类的at函数相比,它还提供了一种更好的访问方法(可以说更具可读性)。 这是一个简短的示例:

    代码语言:javascript
    复制
        Mat_<Vec3b> imageCopy(image); // image is a Mat class
    imageCopy(10, 10) = Vec3b(0,0,0); // imageCopy can use ()

    如果您对类型小心,可以将Mat_<_Tp>类传递给接受Mat类的任何函数,而不会出现任何问题。

    Matx<_Tp, m, n>

    Matx类仅用于在编译时具有已知类型,宽度和高度的<indexentry content="Mat class:Matx class" dbid="256603" state="mod">小矩阵的情况。 它具有类似于Mat的方法,并提供矩阵运算,再次类似于Mat。 通常,您可以使用刚刚学习的相同Mat类代替Matx,因为它提供了更多的灵活性和可使用的功能。

    UMat

    UMat类是新引入的Mat类,在 3.0 之前的 OpenCV 版本中不可用。 使用新的UMat类(或统一的Mat类)的优势主要取决于运行它的平台上是否存在OpenCL层。 我们不会详细介绍这些细节,但是应该足以注意到OpenCL(带有 L,不要与我们自己的 OpenCV 混淆)是一个允许 CPU,GPU 和系统上的其他计算资源一起工作(有时甚至是并行工作)的框架,来实现共同的计算目标。 因此,简单地说,如果它存在于平台上,则将UMat类传递给 OpenCV 函数将导致调用基础OpenCL指令(假设它们在特定功能中实现),从而获得计算机视觉应用的更高性能。 否则,将UMat简单地转换为Mat类,并调用标准的仅 CPU 实现。 与以前版本的 OpenCV 不同,所有的OpenCL实现都位于ocl命名空间中,并且与ocl命名空间完全分离,这允许统一的抽象(这是U的来源),并使其更易于使用更快的OpenCL实现。 标准实现。

    因此,最好始终使用UMat类而不是Mat类,尤其是在具有底层OpenCL实现的 CPU 密集型函数中。 只要我们不使用较旧的 OpenCV 版本,就不会有问题。 只是请注意,在需要在MatUMat之间进行显式转换的情况下(正如您稍后将看到的,在某些情况下是必需的),每个类都提供了一个可用于转换它的函数。 到另一个:

    代码语言:javascript
    复制
        Mat::getUMat
    UMat::getMat

    对于这两个函数,都需要一个访问标志,该标志可以是:

    • ACCESS_READ
    • ACCESS_WRITE
    • ACCESS_RW
    • ACCESS_FAST

    在本书的整个过程中,我们将尽可能地交替使用MatUMat类。 UMatOpenCL实现是一种日益增长的 OpenCV 现象,习惯使用它具有巨大的优势。

    InputArrayOutputArryInputOutputArray

    您会注意到,大多数 OpenCV 函数都接受这些类型的参数,而不是Mat及其类似的数据类型。 这些是用于提高可读性和数据类型支持的代理数据类型。 这只是意味着您可以将以下任何数据类型传递给 OpenCV 函数,除了InputArrayOutputArrayInputOutputArray数据类型:

    • Mat
    • Mat_<T>
    • Matx<T, m, n>
    • std::vector<T>
    • std::vector<std::vector<T> >
    • std::vector<Mat>
    • std::vector<Mat_<T> >
    • UMat
    • std::vector<UMat>
    • double

    注意,OpenCV 像Mat或类似的类一样对待标准 C++ 向量(std::vector)。 或多或少显而易见的原因是它们的基础数据结构或多或少都是相同的。

    永远不要显式创建InputArrayOutputArryInputOutputArray。 只需传递前面提到的一种类型,一切都会好起来的。

    使用 OpenCV 读取图像

    既然我们已经了解了 OpenCV 中的Mat类,我们可以继续学习如何读取图像并为图像填充Mat类以进一步处理它。 正如您在前几章中简要看到的那样,imread函数可用于从磁盘读取图像。 这是一个例子:

    代码语言:javascript
    复制
        Mat image = imread("c:/dev/test.jpg", IMREAD_GRAYSCALE |
    IMREAD_IGNORE_ORIENTATION);

    imread仅将 C++ std::string类作为第一个参数,将ImreadModes标志作为第二个参数。 如果由于某种原因无法读取图像,则返回空的Mat类(data == NULL),否则,将返回Mat类,其中填充了具有第二个参数中指定的类型和颜色的图像像素。 根据平台中某些图像类型的可用性,imread可以读取以下图像类型:

    • Windows 位图:*.bmp*.dib
    • JPEG 文件:*.jpeg*.jpg*.jpe
    • JPEG 2000 文件:*.jp2
    • 便携式网络图形:*.png
    • WebP:*.webp
    • 便携式图像格式:*.pbm*.pgm*.ppm*.pxm*.pnm
    • SUN 栅格:*.sr*.ras
    • TIFF 文件:*.tiff*.tif
    • OpenEXR 图像文件:*.exr
    • Radius HDR:*.hdr*.pic
    • Gdal 支持的栅格和向量地理空间数据

    您可以看到ImreadModes枚举表示可以传递给imread函数的可能标志。 在我们的示例中,我们使用了以下内容:

    代码语言:javascript
    复制
        IMREAD_GRAYSCALE | IMREAD_IGNORE_ORIENTATION 

    这意味着我们希望将图像加载为灰度图像,并且还希望忽略存储在图像文件的 EXIF 数据部分中的方向信息。

    OpenCV 还支持读取多页图像文件。 因此,您需要使用imreadmulti函数。 这是一个简单的例子:

    代码语言:javascript
    复制
        std::vector<Mat> multiplePages;
    bool success = imreadmulti("c:/dev/multi-page.tif", multiplePages,
    IMREAD_COLOR);

    除了imreadimreadmulti,OpenCV 还支持使用imdecode函数从存储缓冲区读取图像。 如果图像未存储在磁盘上或需要从网络读取数据流中,则此函数特别有用。 用法与imread函数几乎相同,除了需要为其提供数据缓冲区而不是文件名。

    使用 OpenCV 写入图像

    OpenCV 中的imwrite函数可用于将图像写入磁盘上的文件。 它使用文件名的扩展名来确定图像的格式。 要在imwrite函数中自定义压缩率和类似设置,您需要使用ImwriteFlagsImwritePNGFlags等。 这是一个简单的示例,展示了如何在设置了渐进模式且质量相对较低(较高的压缩率)的情况下将图像写入 JPG 文件:

    代码语言:javascript
    复制
        std::vector<int> params;
    params.push_back(IMWRITE_JPEG_QUALITY);
    params.push_back(20);
    params.push_back(IMWRITE_JPEG_PROGRESSIVE);
    params.push_back(1); // 1 = true, 0 = false
    imwrite("c:/dev/output.jpg", image, params);

    如果要使用默认设置,则可以完全省略params并只需输入:

    代码语言:javascript
    复制
        imwrite("c:/dev/output.jpg", image, params); 

    有关imwrite函数中支持的文件类型的相同列表,请参见上一节中的imread函数。

    除了imwrite,OpenCV 还支持使用imencode函数将图像写入内存缓冲区。 与imdecode相似,在图像需要传递到数据流而不是保存到文件中的情况下,这尤其有用。 用法与imwrite函数几乎相同,除了需要为其提供数据缓冲区而不是文件名。 在这种情况下,由于未指定文件名,因此imdecode还需要扩展图像以决定输出格式。

    在 OpenCV 中读写视频

    OpenCV 提供了一个简单易用的类,称为VideoCapture,可从磁盘上保存的文件,捕获设备,摄像机或网络视频流(例如,RTSP 上的 RTSP 地址)读取视频(或图像序列)。 互联网)。 您可以简单地使用open函数来尝试从任何提到的源类型打开视频,然后使用read函数将传入的视频帧捕获为图像。 这是一个例子:

    代码语言:javascript
    复制
        VideoCapture video;
    video.open("c:/dev/test.avi");
    if(video.isOpened())
    {
    Mat frame;
    while(true)
    {
    if(video.read(frame))
    {
    // Process the frame ...
    }
    else
    {
    break;
    }
    }
    }
    video.release();

    如果要加载图像序列,只需要将文件名替换为文件路径模式。 例如,image_%02d.png将读取文件名如image_00.pngimage_01.pngimage_02.png等的图像。

    对于来自网络 URL 的视频流,只需提供 URL 作为文件名即可。

    关于我们的示例要注意的另一重要事项是,它不是一个完整且可以立即使用的示例。 您会发现,如果尝试一下,无论何时程序进入while循环,都将阻止 GUI 更新,并且您的应用甚至可能崩溃。 使用 Qt 时,对此的快速解决方案是通过在循环内添加以下代码来确保还处理了 GUI(和其他)线程:

    代码语言:javascript
    复制
        qApp->processEvents(); 

    稍后,我们将在第 8 章,“多线程”和第 9 章,“视频分析”中了解有关此问题的更正确的解决方法。

    除了我们学到的知识外,VideoCapture类还提供两个重要函数,即setget。 这些可用于配置该类的众多参数。 有关可配置参数的完整列表,请参考VideoCaptureProperties枚举。

    这是一个永不过时的技巧。 您也可以使用 Qt Creator 代码完成功能,并只需编写CAP_PROP_,因为所有相关参数均以此开头。 基本上,该技巧也适用于查找任何函数,枚举等。 在不同的 IDE 中使用这些技巧通常不会在书中讨论,但在某些情况下可能意味着节省大量时间。 以前面提到的内容为例,例如,您可以在 Qt Creator 代码编辑器中编写VideoCaptureProperties,然后按住Ctrl按钮并单击。 这将带您到枚举的源,并且您可以查看所有可能的枚举,并且如果幸运的话,源代码中的文档正在等待着您。

    这是一个简单的示例,可读取视频中的帧数:

    代码语言:javascript
    复制
        double frameCount = video.get(CAP_PROP_FRAME_COUNT); 

    这是另一个将视频中抓帧器的当前位置设置为帧号100的示例:

    代码语言:javascript
    复制
        video.set(CAP_PROP_POS_FRAMES, 100); 

    在使用上与VideoCapture类几乎相同,您可以使用VideoWriter类将视频和图像序列写入磁盘。 但是,在编写带有VideoWriter类的视频时,需要更多一些参数。 这是一个例子:

    代码语言:javascript
    复制
        VideoWriter video;
    video.open("c:/dev/output.avi", CAP_ANY, CV_FOURCC('M','P', 'G',
    '4'), 30.0, Size(640, 480), true);
    if(video.isOpened())
    {
    while(framesRemain())
    {
    video.write(getFrame());
    }
    }
    video.release();

    在此示例中,framesRemaingetFrame函数是虚构函数,用于检查是否还有要写入的剩余函数,并获取帧(Mat)。 如示例所示,在这种情况下需要提供一个捕获 API(由于它是可选的,因此我们在VideoCapture中将其省略)。 此外,在打开用于写入的视频文件时,必须具有FourCC代码, FPS每秒帧)和帧大小。 可以使用OpenCV中定义的CV_FOURCC宏输入FourCC代码。

    有关可能的FourCC代码的列表,请参见这里。 请务必注意,某些FourCC代码及其相应的视频格式可能在平台上不可用。 在将应用部署到客户时,这一点很重要。 您需要确保您的应用可以读写需要支持的视频格式。

    OpenCV 中的 HighGUI 模块

    OpenCV 中的 HighGUI 模块负责制作快速简单的 GUI。 在本书的第 3 章,“创建全面的 Qt + OpenCV 项目”中,我们已经使用了模块imshow中广泛使用的函数之一来快速显示图像。 但是,当我们要了解 Qt 和用于处理 GUI 创建的更复杂的框架时,我们将完全跳过此模块,而转至 Qt 主题。 但是,在此之前,值得引用 OpenCV 文档中 HighGUI 模块的当前介绍:

    “虽然 OpenCV 设计用于全面应用,并且可以在功能丰富的 UI 框架(例如 Qt,WinForms 或 Cocoa)中使用,或者根本不使用任何 UI,但有时需要快速尝试功能并可视化结果。这就是 HighGUI 模块的设计目标。”

    正如您将在本章稍后了解的那样,我们还将停止使用imshow函数,并坚持使用 Qt 功能以正确,一致地显示图像。

    Qt 中的图像和视频处理

    Qt 使用几种不同的类来处理图像数据,视频,照相机和相关的计算机视觉主题。 在本节中,我们将学习它们,并学习如何在 OpenCV 和 Qt 类之间进行链接,以获得更灵活的计算机视觉应用开发体验。

    QImage

    Qt QImage可能是 Qt 中最重要的与计算机视觉相关的类,它是处理图像数据的主要 Qt 类,它提供对图像的像素级访问,以及许多其他处理图像数据的函数。 我们将介绍其构造器和函数的最重要子集,尤其是使用OpenCV时最重要的子集。

    QImage包含许多不同的构造器,这些构造器允许从文件或原始图像数据或空白图像创建和处理其像素的QImage。 我们可以创建一个具有给定大小和格式的空QImage类,如以下示例所示:

    代码语言:javascript
    复制
        QImage image(320, 240, QImage::Format_RGB888); 

    这将创建一个320x240像素(宽度和高度)的标准 RGB 彩色图像。 您可以参考QImage::Format枚举(使用QImage类文档)以获取受支持格式的完整列表。 我们还可以传递QSize类而不是值,并编写以下代码:

    代码语言:javascript
    复制
        QImage image(QSize(320, 240), QImage::Format_RGB888); 

    下一个构造器也是从 OpenCV Mat类创建QImage的方法之一。 这里要注意的重要一点是,OpenCV Mat类中的数据格式应与QImage类中的数据格式兼容。 默认情况下,OpenCV 以 BGR 格式(不是 RGB)加载彩色图像,因此,如果我们尝试使用该格式构建QImage,则会在通道数据中输入错误。 因此,我们首先需要将其转换为 RGB。 这是一个例子:

    代码语言:javascript
    复制
        Mat mat = imread("c:/dev/test.jpg");
    cvtColor(mat, mat, CV_BGR2RGB);
    QImage image(mat.data,
    mat.cols,
    mat.rows,
    QImage::Format_RGB888);

    在此示例中,cvtColor函数是 OpenCV 函数,可用于更改Mat类的色彩空间。 如果我们省略该行,我们将得到一个QImage,它的蓝色和红色通道已互换。

    可以使用我们将要看到的下一个QImage构造器来创建先前代码的正确版本(以及将Mat转换为QImage的推荐方法)。 它还需要一个bytesPerLine参数,这是我们在Mat类中了解的step参数。 这是一个例子:

    代码语言:javascript
    复制
        Mat mat = imread("c:/dev/test.jpg");
    cvtColor(mat, mat, CV_BGR2RGB);
    QImage image(mat.data,
    mat.cols,
    mat.rows,
    mat.step,
    QImage::Format_RGB888);

    使用此构造器和bytesPerLine参数的优点是我们还可以转换连续存储在内存中的图像数据。

    下一个构造器也是从磁盘上保存的文件读取QImage的方法。 这是一个例子:

    代码语言:javascript
    复制
        QImage image("c:/dev/test.jpg"); 

    请注意,Qt 和 OpenCV 支持的文件类型彼此独立。 这仅表示一个提到的框架可能根本不支持文件类型,在这种情况下,读取特定文件类型时需要选择另一个框架。 默认情况下,Qt 支持读取以下图像文件类型:

    格式

    说明

    支持

    BMP

    Windows 位图

    读/写

    GIF

    图形交换格式(可选)

    JPG

    联合摄影专家组

    读/写

    JPEG

    联合摄影专家组

    读/写

    PNG

    便携式网络图形

    读/写

    PBM

    便携式位图

    PGM

    便携式灰度图

    PPM

    便携式像素图

    读/写

    XBM

    X11 位图

    读/写

    XPM

    X11 像素图

    读/写

    供参考的表源:位于这个页面的QImage类文档。

    除了所有构造器之外,QImage包括以下成员,这些成员在处理图像时非常方便:

    • allGray:可以用来检查图像中的所有像素是否都是灰色阴影。 这基本上检查所有像素在各自通道中是否具有相同的 RGB 值。
    • bitsconstBits(仅是bitsconst版本):这些可用于访问QImage中的基础图像数据。 这可以用于将QImage转换​​为Mat以便在OpenCV中进行进一步处理。 与将Mat转换为QImage时所看到的相同,在这里我们也需要确保它们与格式兼容。 为确保这一点,我们可以添加convertToFormat函数,以确保我们的QImage是标准的三通道 RGB 图像。 这是一个例子:
    代码语言:javascript
    复制
            QImage image("c:/dev/test.jpg");
    image = image.convertToFormat(QImage::Format_RGB888);
    Mat mat = Mat(image.height(),
    image.width(),
    CV_8UC(3),
    image.bits(),
    image.bytesPerLine());

    极其重要的是要注意,当像这样传递数据时,以及像将Mat转换为QImage时所看到的那样,在 Qt 和 OpenCV 中的类之间传递了相同的内存空间。 这意味着,如果您在前面的示例中修改了Mat类,则实际上是在修改图像类,因为您只是将其数据指针传递给了Mat类。 同时这非常有用(更容易操作图像)和危险(应用崩溃),并且像这样使用 Qt 和 OpenCV 时需要小心。 如果要确保QImageMat类具有完全独立的数据,则可以使用Mat类中的clone函数或QImage中的copy函数。

    • byteCount:这将返回图像数据占用的字节数。
    • bytesPerLine:类似于Mat类中的step参数。 它为图像中每条扫描线提供字节数。 这基本上与width相同,或者更好的是byteCount/height
    • convertToFormat:可用于将图像转换为另一种格式。 在前面的bits函数示例中,我们已经看到了一个示例。
    • copy:可用于将图像的部分(或全部)复制到另一个QImage类。
    • depth:这将返回图像的深度(或每像素位数)。
    • fill:此函数可用于填充相同颜色的图像中的所有像素。

    像这样的函数以及 Qt 框架中的许多其他类似函数,可以使用QColorQt::GlobalColor这三种颜色类型,最后是与像素中的位相对应的整数值。 即使它们非常易于使用,在继续之前,花一些时间以 Qt Creator Help模式阅读其文档页面也是明智的。

    • format:可用于获取QImage中图像数据的当前格式。 正如我们在前面的示例中看到的,QImage::Format_RGB888是在 Qt 和OpenCV之间传递图像数据时最兼容的格式。
    • hasAlphaChannel:如果图像具有 Alpha 通道,则返回true。 Alpha 通道用于确定像素的透明度。
    • heightwidthsize:这些可用于获取图像的高度,宽度和尺寸。
    • isNull:如果没有图像数据,则返回true,否则返回false
    • loadloadFromDatafromData:它们可用于从磁盘或从缓冲区中存储的数据中检索图像(类似于OpenCV中的imdecode)。
    • mirrored:这实际上是一种图像处理函数,可用于垂直,水平或同时镜像(翻转)图像。
    • pixel:类似于Mat类中的at函数,pixel可用于检索像素数据。
    • pixelColor:类似于pixel,但此返回一个QColor
    • rect:这将返回一个QRect类,其中包含图像的边界矩形。
    • rgbSwapped:这是一个非常方便的函数,尤其是在使用 OpenCV 并显示图像时。 它在不更改实际图像数据的情况下交换蓝色和红色通道。 正如我们将在本章稍后看到的那样,这是在 Qt 中正确显示Mat类并避免 OpenCV cvtColor函数调用所必需的。
    • save:这些可用于将图像内容保存到文件中。
    • scaledscaledToHeightscaledToWidth:提及的所有三个函数均可用于调整图像大小以适合给定大小。 (可选)调用此函数时,可以使用以下常量之一来解决任何长宽比问题。 我们将在接下来的章节中看到更多有关此的内容。
      • Qt::IgnoreAspectRatio
      • Qt::KeepAspectRatio
      • Qt::KeepAspectRatioByExpanding
    • setPixelsetPixelColor:这些可用于设置图像中单个像素的内容。
    • setText:可用于以支持它的图像格式设置文本值。
    • text:可用于检索设置到图像的文本值。
    • transformed:顾名思义,此函数用于转换图像。 它采用QMatrixQTransform类并返回转换后的图像。 这是一个简单的例子:
    代码语言:javascript
    复制
            QImage image("c:/dev/test.jpg");
    QTransform trans;
    trans.rotate(45);
    image = image.transformed(trans);
    • trueMatrix:可用于检索用于变换图像的变换矩阵。
    • valid:这将获取一个点(X, Y),如果给定点是图像内的有效位置,则返回true,否则返回false

    QPixmap

    QPixmap类在某些方面类似于QImage,但是当我们需要在屏幕上显示图像时,可以使用QPixmap类。 QPixmap可用于加载和保存图像(就像QImage一样),但它不提供处理图像数据的灵活性,我们也仅在执行所有的修改,处理和操作之后,在需要显示任何图像时再使用它。 大多数QPixmap方法与QImage方法同名,并且基本上以相同的方式使用。 对我们来说很重要且在QImage中不存在的两个函数如下:

    • convertFromImage:此函数可用于用来自QImage 的图像数据填充QPixmap数据
    • fromImage:这是一个静态函数,基本上与convertFromImage相同

    现在,我们将创建一个示例项目,以利用我们到目前为止所学的知识进行实践。 没有真正的动手项目,本章中学习的所有令人兴奋的技术都将浪费掉,因此让我们从我们的图像查看示例应用开始:

    1. 首先在 Qt Creator 中创建一个新的 Qt Widgets 应用并将其命名为ImageViewer
    2. 然后选择mainwindow.ui,然后使用设计器删除菜单栏,状态栏和工具栏,然后在窗口上放置一个标签小部件(QLabel)。 单击窗口上的空白区域,然后按Ctrl + G将所有内容(只有标签的小部件)布置为网格。 这将确保始终调整所有大小以适合窗口。
    3. 现在,将labelalignment/Horizontal属性更改为AlignHCenter。 然后将其HorizontalVertical sizePolicy属性都更改为Ignored。 接下来,将以下include语句添加到mainwindow.h文件中:
    代码语言:javascript
    复制
            #include <QPixmap>
    #include <QDragEnterEvent>
    #include <QDropEvent>
    #include <QMimeData>
    #include <QFileInfo>
    #include <QMessageBox>
    #include <QResizeEvent>
    1. 现在,使用代码编辑器将以下受保护的函数添加到mainwindow.h中的MainWindow类定义中:
    代码语言:javascript
    复制
            protected:
    void dragEnterEvent(QDragEnterEvent *event);
    void dropEvent(QDropEvent *event);
    void resizeEvent(QResizeEvent *event);
    1. 另外,将私有的QPixmap添加到您的mainwindow.h中:
    代码语言:javascript
    复制
            QPixmap pixmap; 
    1. 现在,切换到mainwindow.cpp并将以下内容添加到MainWindow构造器中,以便在程序开始时立即对其进行调用:
    代码语言:javascript
    复制
            setAcceptDrops(true);
    1. 接下来,在mainwindow.cpp文件中添加以下函数:
    代码语言:javascript
    复制
            void MainWindow::dragEnterEvent(QDragEnterEvent *event)
    {
    QStringList acceptedFileTypes;
    acceptedFileTypes.append("jpg");
    acceptedFileTypes.append("png");
    acceptedFileTypes.append("bmp");

          if (event-&gt;mimeData()-&gt;hasUrls() &amp;&amp; 
            event-&gt;mimeData()-&gt;urls().count() == 1) 
          { 
    
            QFileInfo file(event-&gt;mimeData()-&gt;urls().at(0).toLocalFile()); 
            if(acceptedFileTypes.contains(file.suffix().toLower())) 
            { 
              event-&gt;acceptProposedAction(); 
            } 
          } 
        } </code></pre></div></div><ol class="ol-level-0"><li>应添加到<code>mainwindow.cpp</code>的另一个函数如下:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        void MainWindow::dropEvent(QDropEvent *event) 
        { 
          QFileInfo file(event-&gt;mimeData()-&gt;urls().at(0).toLocalFile()); 
          if(pixmap.load(file.absoluteFilePath())) 
          { 
           ui-&gt;label-&gt;setPixmap(pixmap.scaled(ui-&gt;label-&gt;size(), 
               Qt::KeepAspectRatio, 
               Qt::SmoothTransformation)); 
          } 
          else 
          { 
            QMessageBox::critical(this, 
               tr(&#34;Error&#34;), 
               tr(&#34;The image file cannot be read!&#34;)); 
          } 
        } </code></pre></div></div><ol class="ol-level-0"><li>最后,将以下函数添加到<code>mainwindow.cpp</code>,我们准备执行我们的应用:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        void MainWindow::resizeEvent(QResizeEvent *event) 
        { 
          Q_UNUSED(event); 
          if(!pixmap.isNull()) 
          { 
            ui-&gt;label-&gt;setPixmap(pixmap.scaled(ui-&gt;label-&gt;width()-5, 
                                              ui-&gt;label-&gt;height()-5, 
                                              Qt::KeepAspectRatio, 
                                              Qt::SmoothTransformation)); 
          } 
        } </code></pre></div></div><p>您猜对了,我们只是编写了一个显示在其中拖放图像的应用。 通过向<code>MainWindow</code>添加<code>dragEnterEvent</code>函数,我们可以检查所拖动的对象是否是文件,尤其是它是否是单个文件。 然后,我们检查了图像类型以确保它受支持。</p><p>在<code>dropEvent</code>函数中,我们只需将图像文件加载到<code>QPixmap</code>中,然后将其拖放到应用窗口中即可。 然后,将<code>QLabel</code>类的<code>pixmap</code>属性设置为<code>pixmap</code>。</p><p>最后,在<code>resizeEvent</code>函数中,我们确保无论窗口大小如何,我们的图像始终会缩放以适合具有正确纵横比的窗口。</p><p>忘记上面描述的一个简单步骤,您将面临 Qt 中的拖放编程技术的问题。 例如,没有下面的行,即在我们的<code>MainWindow</code>类的构造器中,则无论将什么函数添加到<code>MainWindow</code>类,都不会接受任何删除:</p><p><code>setAcceptDrops(true);</code></p><p>这是生成的应用的屏幕截图。 尝试将不同的图像拖放到应用窗口中,以查看会发生什么。 您甚至可以尝试拖放非图像文件以确保不接受它们:</p><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VxpRhi3W-1681869945443)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/a3bc4e9f-ca2a-46e4-a457-f311fd023fc6.png)]</p><p>这基本上是一本有关如何在 Qt 中显示图像以及如何在 Qt 应用中添加拖放功能的教程。 正如我们在前面的示例中看到的那样,可以将<code>QPixmap</code>与<code>QLabel</code>小部件一起轻松显示。 <code>QLabel</code>小部件名称有时可能会引起误解,但实际上,它不仅可以用于显示纯文本,而且可以用于显示富文本,象素图甚至电影(使用<code>QMovie</code>类)。 由于我们已经知道如何将<code>Mat</code>转换为<code>QImage</code>(反之亦然),以及如何将<code>QImage</code>转换为<code>QPixmap</code>,因此我们可以编写如下内容,以使用<code>OpenCV</code>,使用一些计算机视觉算法对其进行处理(我们将在第 6 章,“OpenCV 的图像处理”和之后章节中对此进行详细了解),然后将其转换为<code>QImage</code> 然后到<code>QPixmap</code>,最后在<code>QLabel</code>上显示结果,如以下示例代码所示:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    cv::Mat mat = cv::imread(&#34;c:/dev/test.jpg&#34;); 
    QImage image(mat.data, 
                 mat.cols, 
                 mat.rows, 
                 mat.step, 
                 QImage::Format_RGB888); 
    ui-&gt;label-&gt;setPixmap(QPixmap::fromImage(image.rgbSwapped())); </code></pre></div></div><h2 id="53l42" name="QImageReader%E5%92%8CQImageWriter%E7%B1%BB"><code>QImageReader</code>和<code>QImageWriter</code>类</h2><p><code>QImageReader</code>和<code>QImageWriter</code>类可用于对图像读写过程进行更多控制。 它们支持与<code>QImage</code>和<code>QPixmap</code>相同的文件类型,但具有更大的灵活性,当图像读取或写入过程出现问题时会提供错误消息,并且在以下情况下,您还可以设置并获得更多图像属性: 您使用<code>QImageReader</code>和<code>QImageWriter</code>类。 正如您将在接下来的章节中看到的那样,我们将在全面的计算机视觉应用中使用这些相同的类,以更好地控制图像的读写。 现在,我们只需简短的介绍就可以继续进行下一部分。</p><h2 id="1f5c7" name="QPainter%E7%B1%BB"><code>QPainter</code>类</h2><p><code>QPainter</code>类可用于在作为<code>QPaintDevice</code>类子类的任何 Qt 类上进行绘制(基本上是绘画)。 这意味着什么? 基本上,这意味着包括 Qt 小部件在内的所有东西,都具有视觉效果,可以在其上绘制某些东西。 因此,仅举几例,<code>QPainter</code>可用于绘制<code>QWidget</code>类(基本上意味着所有现有和定制的 Qt 小部件),<code>QImage</code>,<code>QPixmap</code>和许多其他 Qt 类。 您可以在 Qt Creator 帮助模式下查看<code>QPaintDevice</code>类文档页面,以获取继承<code>QPaintDevice</code>的 Qt 类的完整列表。 <code>QPainter</code>具有众多函数,其中许多名称以<code>draw</code>开头,而涵盖所有这些函数本身将需要整整一章,但是我们将看到一个基本示例,说明如何将其与<code>QWidget</code>和<code>QImage</code>一起使用。 基本上,相同的逻辑适用于可以与<code>QPainter</code>一起使用的所有类。</p><p>因此,正如您所说,您可以自己创建一个自定义 Qt 小部件,并使用<code>QPainter</code>创建(或绘制)其可视面。 实际上,这是用于创建新的 Qt 小部件的一种方法(也是一种流行的方法)。 让我们通过一个示例来帮助它沉入。我们将创建一个新的 Qt 小部件,该小部件仅显示一个闪烁的圆圈:</p><ol class="ol-level-0"><li>首先创建一个名为<code>Painter_Test</code>的 Qt Widgets 应用。</li><li>然后从主菜单中选择文件/新文件或项目。</li><li>在“新建文件或项目”窗口中,选择“C++ 和 C++ 类”,然后按“选择”。</li><li>在出现的窗口中,确保将“类名”设置为<code>QBlinkingWidget</code>,并将“基类”选择为<code>QWidget</code>。 确保选中“包括<code>QWidget</code>”复选框,并保留其余选项,如以下屏幕截图所示:</li></ol><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xgMfXcwH-1681869945443)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/3faf4dfc-97df-44ff-9152-8dd5ac8b407d.png)]</p><ol class="ol-level-0"><li>现在按下一步,然后按完成。 这将创建一个带有标题和源文件的新类,并将其添加到您的项目中。</li><li>现在,您需要覆盖<code>QBlinkingWidget</code>的<code>paintEvent</code>方法,并使用<code>QPainter</code>进行一些绘制。 因此,首先将以下<code>include</code>语句添加到<code>qblinkingwidget.h</code>文件中:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        #include &lt;QPaintEvent&gt; 
        #include &lt;QPainter&gt; 
        #include &lt;QTimer&gt; </code></pre></div></div><ol class="ol-level-0"><li>现在,将以下受保护的成员添加到<code>QBlinkingWidget</code>类中(例如,将其添加到现有的公共成员之后):</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        protected: 
         void paintEvent(QPaintEvent *event);</code></pre></div></div><ol class="ol-level-0"><li>您还需要向此类添加一个专用插槽。 因此,在先前受保护的<code>paintEvent</code>函数之后添加以下内容:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        private slots: 
          void onBlink(); </code></pre></div></div><ol class="ol-level-0"><li>最后要添加到<code>qblinkingwidget.h</code>文件中,添加以下我们将在小部件中使用的私有成员:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        private: 
         QTimer blinkTimer; 
         bool blink; </code></pre></div></div><ol class="ol-level-0"><li>现在,切换到<code>qblinkingwidget.cpp</code>并在自动创建的构造器中添加以下代码:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        blink  = false; 
        connect(&amp;blinkTimer, 
          SIGNAL(timeout()), 
          this, 
          SLOT(onBlink())); 
       blinkTimer.start(500); </code></pre></div></div><ol class="ol-level-0"><li>接下来,将以下两种方法添加到<code>qblinkingwidget.cpp</code>中:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        void QBlinkingWidget::paintEvent(QPaintEvent *event) 
        { 
          Q_UNUSED(event); 
          QPainter painter(this); 
          if(blink) 
             painter.fillRect(this-&gt;rect(), 
                QBrush(Qt::red)); 
          else 
             painter.fillRect(this-&gt;rect(), 
                QBrush(Qt::white)); 
        } 
    
        void QBlinkingWidget::onBlink() 
        { 
          blink = !blink; 
          this-&gt;update(); 
        }</code></pre></div></div><ol class="ol-level-0"><li>现在,通过打开<code>mainwindow.ui</code>切换到设计模式,然后将小部件添加到<code>MainWindow</code>类。 <code>Widget</code>的确切含义是<code>Widget</code>,它是一个空的,当您将其添加到<code>MainWindow</code>时会注意到。 请参见以下屏幕截图:</li></ol><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rTcxF8ls-1681869945443)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/bcf530a0-d73d-41cd-aff8-7385c8041759.png)]</p><ol class="ol-level-0"><li>现在,右键单击添加的<code>QWidget</code>类的空窗口小部件,然后从弹出菜单中选择“升级到”:</li></ol><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNkImWpf-1681869945443)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/ff9aa0d6-1381-4ec8-9e9a-e7301045e281.png)]</p><ol class="ol-level-0"><li>在将打开的新窗口中,称为<code>Promoted Widgets</code>窗口,将<code>Promoted</code>类名设置为<code>QBlinkingWidget</code>,然后按<code>Add</code>按钮:</li></ol><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qxj8I6Ee-1681869945443)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/4799301c-8917-40fa-b3d5-0f189831d4bf.png)]</p><ol class="ol-level-0"><li>最后,按<code>Promote</code>。 您的应用和自定义小部件已准备就绪,可以运行。 应用启动后,您将看到它每 500 毫秒(半秒)闪烁一次。</li></ol><p>实际上,这是在 Qt 中创建自定义窗口小部件的通用方法。 要创建一个新的自定义 Qt 小部件并在您的 Qt 小部件应用中使用它,您需要:</p><ol class="ol-level-0"><li>创建一个继承<code>QWidget</code>的新类。</li><li>覆盖其<code>paintEvent</code>函数。</li><li>使用<code>QPainter</code>类在其上进行绘制。</li><li>在窗口中添加<code>QWidget</code>(小部件)。</li><li>将其升级到新创建的小部件。</li></ol><p>实际上,将<code>QWidget</code>提升为定制的小部件也是从第三方开发人员(或也许是从互联网)获得小部件并希望在应用窗口中使用它时使用的方法。</p><p>在前面的示例中,我们根据闪烁变量状态使用<code>QPainter</code>的<code>fillRect</code>函数简单地每秒用红色和白色填充它。 同样,您可以使用<code>drawArc</code>,<code>drawEllipse</code>,<code>drawImage</code>和<code>QPainter</code>中的更多函数在小部件上绘制几乎所有内容。 这里要注意的重要一点是,当我们想在小部件上绘制时,我们将<code>this</code>传递给<code>QPainter</code>实例。 如果我们想使用<code>QImage</code>,我们只需要确保将<code>QImage</code>传递给它或使用<code>begin</code>函数来构造<code>QPainter</code>即可。 这是一个例子:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        QImage image(320, 240, QImage::Format_RGB888); 
        QPainter painter; 
        painter.begin(&amp;image); 
        painter.fillRect(image.rect(), Qt::white); 
        painter.drawLine(0, 0, this-&gt;width()-1, this-&gt;height()-1); 
        painter.end(); </code></pre></div></div><p>在这种情况下,所有绘图函数都必须包含在<code>begin</code>和<code>end</code>函数调用中。</p><h2 id="2r4d2" name="Qt-%E4%B8%AD%E7%9A%84%E6%91%84%E5%83%8F%E5%A4%B4%E5%92%8C%E8%A7%86%E9%A2%91%E5%A4%84%E7%90%86">Qt 中的摄像头和视频处理</h2><p>由于我们将使用 OpenCV 接口来处理图像,摄像机和视频,因此我们不会涵盖 Qt 框架提供的用于读取,查看和处理视频的所有可能性。 但是有时候,尤其是当两个框架之一提供更好或更简单的功能实现时,避免使用它确实变得很诱人。 例如,即使 OpenCV 提供了非常强大的相机处理方法,正如我们将在第 12 章,“Qt Quick 应用”中看到的那样,对于在 Android,iOS 和移动平台上处理相机而言,Qt 还有很多话要说。 因此,让我们简要介绍一下一些用于摄像头和视频处理的重要和现有 Qt 类,并保留它们,直到在第 12 章,“Qt Quick 应用”中使用它们为止。</p><p>在 Qt Creator 帮助索引中搜索 Qt Multimedia C++ 类,以获取 Qt Multimedia 模块下可用类的完整列表和更新列表,以及文档和示例。</p><p>他们来了:</p><ul class="ul-level-0"><li><code>QCamera</code>:可以访问平台上可用的摄像机。</li><li><code>QCameraInfo</code>:可用于获取有关平台上可用摄像机的信息。</li><li><code>QMediaPlayer</code>:可以用来播放视频文件和其他类型的录制媒体。</li><li><code>QMediaRecorder</code>:录制视频或其他媒体类型时,此类很有用。</li><li><code>QVideoFrame</code>:此类可用于访问相机抓取的单个帧。</li><li><code>QVideoProbe</code>:可用于监视来自摄像机或视频源的帧。 此类也可以用于在 Qt 中进一步处理帧。</li><li><code>QVideoWidget</code>:可用于显示来自摄像机或视频源的传入帧。</li></ul><p>请注意,提到的所有类都存在于 Qt 的多媒体模块中,因此要使用它们,您需要首先通过将以下行添加到 Qt 项目 PRO 文件中,以确保多媒体模块对您的项目公开:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">      QT += multimedia </code></pre></div></div><p>除了前面提到的类之外,Qt 多媒体模块还提供了许多其他类来处理视频数据。 您始终可以在 Qt Creator 帮助模式下通过在帮助索引中进行搜索来检出每个类的文档页面。 通常,新的 Qt 版本会引入新的类或对现有类的更新,因此,要成为一名真正的 Qt 开发人员,就要注意文档页面和更新,甚至可能在发现任何问题时报告错误或问题,因为 Qt 仍然是一个开源框架,它依赖于其开源用户社区的支持。</p><h2 id="46t7v" name="%E6%80%BB%E7%BB%93">总结</h2><p>本章是一个重要的里程碑,因为它介绍了将 OpenCV 和 Qt 框架链接在一起所需的概念。 在本章中,我们学习了有关<code>Mat</code>类及其变体的所有信息。 我们了解了 OpenCV 中新的透明 API,以及如何使用<code>UMat</code>类可以提高计算机视觉应用的性能。 我们还学习了读写图像和视频,还捕获了来自照相机和基于网络的视频源的视频帧。 后来,我们继续学习与计算机视觉和图像处理相关的 Qt 函数和类。 本章介绍了 Qt 中的<code>QImage</code>类,它与<code>OpenCV</code>中的<code>Mat</code>类等效。 我们还了解了<code>QPixmap</code>和<code>QPainter</code>类以及其他几个 Qt 类。 在这样做的同时,我们还学习了如何创建自定义 Qt 小部件并使用<code>QPainter</code>类绘制<code>QImage</code>类。 最后,我们以介绍视频和摄像机处理相关的 Qt 类作为本章的结尾。</p><p>在第 5 章,“图形视图框架”中,我们将通过引入一个非常强大的类<code>QGraphicsScene</code>和图形视图框架来完成 Qt 和<code>OpenCV</code>中的计算机视觉难题,它可用于以非常灵活的方式查看和操作图像数据。 第 5 章,“图形视图框架”将是进入计算机视觉和图像处理领域的最后一章,因为我们全面的计算机视觉应用将通过最重要的功能之一完成,那就是图像查看器和操纵器,我们将继续学习新的计算机视觉技巧,每次都向其添加新的插件,如我们在前几章中学到的那样。</p><h2 id="2qje" name="%E4%BA%94%E3%80%81%E5%9B%BE%E5%BD%A2%E8%A7%86%E5%9B%BE%E6%A1%86%E6%9E%B6">五、图形视图框架</h2><p>既然我们已经熟悉了 Qt 和 OpenCV 框架中计算机视觉应用的基本构建模块,那么我们可以继续学习有关计算机视觉应用中可视化部分的开发的更多信息。 谈论计算机视觉,每个用户都会立即寻找一些预览图像或视频。 以您想要的任何图像编辑器为例,它们都在用户界面上包含一个区域,该区域可立即引起注意,并且可以通过 GUI 上的其他组件轻松地通过一些边框甚至简单的线条来识别。 关于视频编辑软件以及实际上需要与视觉概念和媒体输入源配合使用的所有内容,也可以这样说。 同样,对于我们将创建的计算机视觉应用,完全相同的推理也适用。 当然,在某些情况下,过程的结果只是简单地显示为数值或通过网络发送给与过程有关的其他各方。 但是,对我们来说幸运的是,我们将看到这两种情况,因此我们需要在应用中具有类似的功能,以便用户可以预览自己打开的文件或查看生成的转换(或过滤后的)图像。 屏幕。 甚至更好,请在实时视频输出预览面板中查看某些对象检测算法的结果。 该面板基本上是一个场景,或者甚至更好,它是一个图形场景,这是本书本章要讨论的主题。</p><p>在 Qt 框架内的许多模块,类和子框架下,有一块专门用于简化图形处理的工具,称为<strong>图形视图框架</strong>。 它包含许多类,几乎所有的类都以<code>QGraphics</code>开头,并且所有这些类都可用于处理构建计算机视觉应用时可能遇到的大多数图形任务。 图形视图框架将所有可能的对象简单地分为三个主要类别,随之而来的架构允许轻松地添加,删除,修改以及显示图形对象。</p><ul class="ul-level-0"><li>场景(<code>QGraphicsScene</code>类)</li><li>视图(<code>QGraphicsView</code>小部件)</li><li>图形项目(<code>QGraphicsItem</code>及其子类)</li></ul><p>在之前的章节中,我们使用了最简单的方式同时使用 OpenCV(<code>imshow</code>函数)和 Qt 标签窗口小部件来可视化图像,这在处理显示的图像(例如选择它们,修改它们, 缩放它们,依此类推。 即使是最简单的任务,例如选择图形项目并将其拖动到其他位置,我们也必须编写大量代码并经历令人困惑的鼠标事件处理。 放大和缩小图像也是如此。 但是,通过使用图形视图框架中的类,可以更轻松地处理所有这些事情,并具有更高的性能,因为图形视图框架类旨在以高效的方式处理许多图形对象。</p><p>在本章中,我们将开始学习 Qt 的图形视图框架中最重要的类,并且重要的是,我们显然是指与构建全面的计算机视觉应用所需的类最相关的类。 本章学习的主题将完成<code>Computer_Vision</code>项目的基础,该项目是在第 3 章,“创建全面的 Qt + OpenCV 项目”的结尾创建的。 到本章末,您将能够创建一个与图像编辑软件中看到的场景相似的场景,在该场景中,您可以向场景中添加新图像,选择它们,删除它们,放大和缩小它们等等。 您还将在本章末找到<code>Computer_Vision</code>项目基础和基础版本的链接,我们将继续使用该链接,直到本书的最后几章。</p><p>在本章中,我们将介绍以下各章:</p><ul class="ul-level-0"><li>如何使用<code>QGraphicsScene</code>在场景上绘制图形</li><li>如何使用<code>QGraphicsItem</code>及其子类来管理图形项目</li><li>如何使用<code>QGraphicsView</code>查看<code>QGraphicsScene</code></li><li>如何开发放大,缩小以及其他图像编辑和查看功能</li></ul><h2 id="2e91o" name="%E5%9C%BA%E6%99%AF-%E8%A7%86%E5%9B%BE-%E9%A1%B9%E7%9B%AE%E6%9E%B6%E6%9E%84">场景-视图-项目架构</h2><p>正如引言中提到的那样,Qt 中的图形视图框架(或从现在开始简称 Qt)将可能需要处理的与图形相关的对象分为三个主要类别,即场景,视图和项目。 Qt 包含名称非常醒目的类,以处理此架构的每个部分。 尽管从理论上讲,将它们彼此分开很容易,但在实践中,它们却是交织在一起的。 这意味着我们不能不提及其他人而真正深入研究其中之一。 清除架构的一部分,您将完全没有图形。 另外,再看一下架构,我们可以看到模型视图设计模式,其中模型(在本例中为场景)完全不知道如何显示或显示哪个部分。 正如在 Qt 中所说的,这是一种基于项目的模型-视图编程方法,我们将牢记这一点,同时还要简要介绍一下它们中的每一个在实践中的含义:</p><ul class="ul-level-0"><li>场景或<code>QGraphicsScene</code>管理项目或<code>QGraphicsItem</code>的实例(其子类),包含它们,并将事件(例如,鼠标单击等)传播到项目中。</li><li>视图或<code>QGraphicsView</code>小部件用于可视化和显示<code>QGraphicsScene</code>的内容。 它还负责将事件传播到<code>QGraphicsScene</code>。 这里要注意的重要一点是<code>QGraphicsScene</code>和<code>QGraphicsView</code>都具有不同的坐标系。 可以猜到,如果放大,缩小或进行不同的相似变换,则场景上的位置将不同。 <code>QGraphicsScene</code>和<code>QGraphicsView</code>都提供了转换彼此适合的位置值的功能。</li><li>这些项目或<code>QGraphicsItem</code>子类的实例是<code>QGraphicsScene</code>中包含的项目。 它们可以是线,矩形,图像,文本等。</li></ul><p>让我们从一个简单的入门示例开始,然后继续详细讨论上述每个类:</p><ol class="ol-level-0"><li> 创建一个名为<code>Graphics_Viewer</code>的 Qt Widgets 应用,类似于在第 4 章,“<code>Mat</code>和<code>QImage</code>”中创建的项目,以了解有关在 Qt 中显示图像的信息。 但是,这一次只需向其中添加“图形视图”窗口小部件,而无需任何标签,菜单,状态栏等。 将其<code>objectName</code>属性保留为<code>graphicsView</code>。
    
  • 另外,添加与以前相同的拖放功能。 如前所述,您需要在MainWindow类中添加dragEnterEventdropEvent。 并且不要忘记将setAcceptDrops添加到MainWindow类的构造器中。 显然,这一次,您需要删除用于在QLabel上设置QPixmap的代码,因为该项目中没有任何标签。
  • 现在,将所需变量添加到mainwindow.hMainWindow类的私有成员部分,如下所示:
  • 代码语言:javascript
    复制
            QGraphicsScene scene; 

    scene基本上是我们将使用并显示在添加到MainWindow类的QGraphicsView小部件中的场景。 最有可能的是,您需要为所使用的每个类添加一个#include语句,这是代码编辑器无法识别的。 您还将获得与此相关的编译器错误,通常可以很好地提醒我们忘记将其包含在源代码中的类。 因此,从现在开始,请确保为您使用的每个 Qt 类添加一个类似于以下内容的#include指令。 但是,如果要使某个类可用就需要采取任何特殊的措施,则将在书中明确说明:

    代码语言:javascript
    复制
            #include <QGraphicsScene> 
    1. 接下来,我们需要确保我们的graphicsView对象可以访问场景。 您可以通过在MainWindow构造器中添加以下行来实现。 (步骤 5 之后的行。)
    2. 另外,您需要为graphicsView禁用acceptDrops,因为我们希望能够保留放置在窗口各处的图像。 因此,请确保您的MainWindow构造器仅包含以下函数调用:
    代码语言:javascript
    复制
            ui->setupUi(this); 
            this->setAcceptDrops(true); 
            ui->graphicsView->setAcceptDrops(false); 
            ui->graphicsView->setScene(&scene); 
    1. 接下来,在上一个示例项目的dropEvent函数中,我们设置标签的pixmaps属性,这一次,我们需要确保创建了QGraphicsItem并将其添加到场景中,或者准确地说是QGraphicsPixmapItem。 这可以通过两种方式完成,让我们来看第一种:
    代码语言:javascript
    复制
            QFileInfo file(event 
                   ->mimeData() 
                   ->urls() 
                   .at(0) 
                   .toLocalFile()); 
            QPixmap pixmap; 
            if(pixmap.load(file 
                   .absoluteFilePath())) 
            { 
              scene.addPixmap(pixmap); 
            } 
            else 
            { 
             // Display an error message 
            } 

    在这种情况下,我们仅使用了QGraphicsSceneaddPixmap函数。 另外,我们可以创建QGraphicsPixmapItem并使用addItem方法将其添加到场景中,如下所示:

    代码语言:javascript
    复制
             QGraphicsPixmapItem *item =  
                new QGraphicsPixmapItem(pixmap); 
             scene.addItem(item); 

    在这两种情况下,都不必担心项目指针,因为在调用addItem时场景将拥有它的所有权,并且场景会自动从内存中清除。 当然,如果我们要手动从场景和内存中完全删除该项目,我们可以编写一个简单的delete语句来删除该项目,如下所示:

    代码语言:javascript
    复制
            delete item; 

    我们的简单代码有一个大问题,乍看之下看不到,但是如果我们继续将图像拖放到窗口中,则每次将最新图像添加到先前图像的顶部并且不清除先前图像。 实际上,如果您亲自尝试一下,这是一个好主意。 但是,首先在写入addItem的行之后添加以下行:

    代码语言:javascript
    复制
            qDebug() << scene.items().count(); 

    您需要将以下头文件添加到mainwindow.h文件中,此文件才能起作用:

    代码语言:javascript
    复制
            #include <QDebug>

    现在,如果您运行该应用并尝试通过将其拖放到窗口中来添加图像,您会注意到,在 Qt Creator 代码编辑器屏幕底部的“应用输出”窗格中,每次放置图像时,所显示的数字增加,即sceneitemscount

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VUI92ekQ-1681869945444)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/1281f635-ff2b-4d8e-b4be-cb19736c9575.png)]

    如上例所示,使用qDebug()是许多 Qt 开发人员用来在开发过程中快速查看某些变量的值的技巧。 Qt 中的qDebug()是与std::cout类似的玩具,用于输出到控制台(或终端)。 我们将在第 10 章,“调试和测试”中了解更多有关测试和调试的信息,但现在,让我们记下qDebug()并使用它来快速解决以下问题。 我们使用 Qt 和 C++ 进行开发时的代码。

    1. 因此,要解决前面示例中提到的问题,我们显然需要先对clearscene进行添加。 因此,只需在调用任何addItem(或addPixmap等)之前添加以下内容:
    代码语言:javascript
    复制
            scene.clear(); 

    尝试再次运行您的应用,然后查看结果。 现在,将其放入我们的应用窗口后,应该只存在一个图像。 另外,记下应用的输出,您将看到显示的值始终为1,这是因为在任何时候scene中始终只保留一个图像。 在我们刚才看到的示例项目中,我们使用了 Qt 的图形视图框架中的所有现有主要部分,即场景,项目和视图。 现在,我们将详细了解这些类,同时,为我们全面的计算机视觉应用Computer_Vision项目创建强大的图形查看器和编辑器。

    场景,QGraphicsScene

    此类提供了处理多个图形项(QGraphicsItem)所需的几乎所有方法,即使在前面的示例中我们仅将其与单个QGraphicxPixmapItem一起使用。 在本节中,我们将回顾该类中一些最重要的函数。 如前所述,我们将主要关注用例所需的属性和方法,因为涵盖所有方法(尽管它们都很重要)对于本书而言都是徒劳的。 我们将跳过QGraphicsScene的构造器,因为它们仅用于获取场景的尺寸并相应地创建场景。 至于其余的方法和属性,就在这里,对于其中一些可能不太明显的示例,您可以找到一个简单的示例代码,可以使用本章前面创建的Graphics_Viewer项目进行尝试 :

    • addEllipseaddLineaddRectaddPolygon函数可以从它们的名称中猜测出来,可以用来向场景添加通用的几何形状。 它们中的一些提供了重载函数,以便于输入参数。 创建并添加到场景时,上述每个函数都会返回其对应的QGraphicsItem子类实例(如下所示)。 返回的指针可以保留,以后可用于修改,删除或以其他方式使用该项目:
      • QGraphicsEllipseItem
      • QGraphicsLineItem
      • QGraphicsRectItem
      • QGraphicsPolygonItem

    这是一个例子:

    代码语言:javascript
    复制
            scene.addEllipse(-100.0, 100.0, 200.0, 100.0, 
                    QPen(QBrush(Qt::SolidPattern), 2.0), 
                    QBrush(Qt::Dense2Pattern));
    
        scene.addLine(-200.0, 200, +200, 200, 
              QPen(QBrush(Qt::SolidPattern), 5.0)); 
    
        scene.addRect(-150, 150, 300, 140); 
    
        QVector&lt;QPoint&gt; points; 
        points.append(QPoint(150, 250)); 
        points.append(QPoint(250, 250)); 
        points.append(QPoint(165, 280)); 
        points.append(QPoint(150, 250)); 
        scene.addPolygon(QPolygon(points)); </code></pre></div></div><p>这是前面代码的结果:</p><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9pcP6EGK-1681869945444)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/6011fe12-5460-4f62-bb9f-87e21929ade9.png)]</p><ul class="ul-level-0"><li> <code>addPath</code>函数可用于将<code>QPainterPath</code>与给定的<code>QPen</code>和<code>QBrush</code>添加到场景中。 <code>QPainterPath</code>类可用于记录绘画操作,类似于我们在<code>QPainter</code>中看到的操作,并在以后使用它们。 另一方面,<code>QPen</code>和<code>QBrush</code>类具有不言自明的标题,但在本章后面的示例中我们也将使用它们。 <code>addPath</code>函数返回一个指向新创建的<code>QGraphicsPathItem</code>实例的指针。
    
  • addSimpleTextaddText函数可用于将纯文本和带格式的文本添加到场景中。 它们分别返回指向QGraphicsSimpleTextItemQGraphicsTextItem的指针。
  • 在上一示例中已经使用过的addPixmap函数可用于将图像添加到场景,并且它返回指向QGraphicsPixmapItem类的指针。
  • addItem函数仅接受任何QGraphicsItem子类并将其添加到场景中。 我们在前面的示例中也使用了此函数。
  • addWidget函数可用于将 Qt 小部件添加到场景。 除了某些特殊的小部件(即设置了Qt::WA_PaintOnScreen标志的小部件或使用外部库(例如OpenGLActive-X绘制的小部件))之外,您还可以将其他任何小部件添加到场景中,就像将其添加到场景中一样。 一个窗口。 这为使用交互式图形项创建场景提供了巨大的力量。 您绝对可以使用它来创建简单的游戏,添加对图像执行某些操作的按钮以及许多其他功能。 我们将在Computer_Vision项目中大量使用此示例,并提供足够的示例来帮助您入门,但是现在这是一个简短的示例:
  • 代码语言:javascript
    复制
            QPushButton *button = new QPushButton(Q_NULLPTR); 
            connect(button, SIGNAL(pressed()), this, SLOT(onAction())); 
            button->setText(tr("Do it!")); 
            QGraphicsProxyWidget* proxy = scene.addWidget(button); 
            proxy->setGeometry(QRectF(-200.0, -200, 400, 100.0)); 

    前面的代码只是添加了一个标题为Do it!的按钮,并将其连接到名为onAction的插槽。 每当按下场景中的此按钮时,就会调用onAction函数。 与向窗口添加按钮时完全相同:

    • setBackgroundBrushbackgroundBrushsetForegroundBrushforegroundBrush函数允许访问负责刷刷场景的backgroundforegroundQBrush类。
    • fontsetFont函数可用于获取或设置QFont类,以确定场景中使用的字体。
    • 当我们想要定义最小尺寸来决定某项是否适合绘制(渲染)时,minimumRenderSizesetMinimumRenderSize函数非常有用。
    • sceneRectsetSceneRect函数可用于指定场景的边界矩形。 这基本上意味着场景的宽度和高度,以及其在坐标系上的位置。 重要的是要注意,如果未调用setSceneRect或在QGraphicsScene的构造器中未设置矩形,则调用sceneRect将始终返回可以覆盖添加到场景的所有项目的最大矩形。 始终最好设置一个场景矩形,并根据需要在场景中进行任何更改等操作,基本上根据需要手动(使用setSceneRect)再次对其进行设置。
    • stickyFocussetStickyFocus函数可用于启用或禁用场景的粘滞聚焦模式。 如果启用了粘滞聚焦,则单击场景中的空白区域不会对聚焦的项目产生任何影响; 否则,将仅清除焦点,并且不再选择选定的项目。
    • collidingItems是一个非常有趣的功能,可用于简单地确定某项是否与其他任何项共享其区域的某个部分(或发生碰撞)。 您需要将QGraphicsItem指针与Qt::ItemSelectionMode一起传递,您将获得与项目发生冲突的QGraphicsItem实例的QList
    • createItemGroupdestroyItemGroup函数可用于创建和删除QGraphicsItemGroup类实例。 QGraphicsItemGroup基本上是另一个QGraphicsItem子类(如QGraphicsLineItem等),可用于将一组图形项分组并因此表示为单个项。
    • hasFocussetFocusfocusItemsetFocusItem函数均用于处理图形场景中当前聚焦的项目。
    • 返回与sceneRect.width()sceneRect.height()相同值的widthheight可用于获取场景的宽度和高度。 请务必注意,这些函数返回的值的类型为qreal(默认情况下与double相同),而不是integer,因为场景坐标在像素方面不起作用。 除非使用视图绘制场景,否则将其上的所有内容都视为逻辑和非视觉对象,而不是视觉对象,这是QGraphicsView类的领域。
    • 在某些情况下,与update()相同的invalidate可用于请求全部或部分重绘场景。 类似于刷新函数。
    • itemAt函数可用于在场景中的某个位置找到指向QGraphicItem的指针。
    • item返回添加到场景的项目列表。 基本上是QGraphicsItemQList
    • itemsBoundingRect可用于获取QRectF类,或仅获取可包含场景中所有项目的最小矩形。 如果我们需要查看所有项目或执行类似操作,此函数特别有用。
    • mouseGrabberItem可用于获取当前单击的项目,而无需释放鼠标按钮。 此函数返回一个QGraphicsItem指针,使用它我们可以轻松地向场景添加“拖动和移动”或类似功能。
    • removeItem函数可用于从场景中删除项目。 此函数不会删除该项目,并且调用方负责任何必需的清理。
    • render可用于渲染QPaintDevice上的场景。 这只是意味着您可以使用QPainter类(如您在第 4 章,“MatQImage”中学习的)在QImageQPrinter等类似对象上绘制场景,通过将QPainter类的指针传递给此函数。 (可选)您可以在QPaintDevice渲染目标类的一部分上渲染场景的一部分,并且还要注意宽高比的处理。
    • selectedItemsselectionAreasetSelectionArea函数结合使用时,可以帮助处理一个或多个项目选择。 通过提供Qt::ItemSelectionMode枚举,我们可以基于完全选择一个框中的项目或仅对其一部分进行选择,等等。 我们还可以为该函数提供Qt::ItemSelectionOperation枚举条目,以增加选择或替换所有先前选择的项目。
    • sendEvent函数可用于将QEvent类(或子类)发送到场景中的项目。
    • stylesetStyle函数用于设置和获取场景样式。
    • update函数可用于重绘部分或全部场景。 当场景的视觉部分发生变化时,最好将此函数与QGraphicsScene类发出的变化信号结合使用。
    • views函数可用于获取QList类,其中包含用于显示(或查看)此场景的QGraphicsView小部件。

    除了先前的现有方法外,QGraphicsScene提供了许多虚拟函数,可用于进一步自定义和增强QGraphicsScene类的行为以及外观。 因此,与其他任何类似的 C++ 类一样,您需要创建QGraphicsScene的子类,并只需添加这些虚拟函数的实现即可。 实际上,这是使用QGraphicsScene类的最佳方法,它为新创建的子类提供了极大的灵活性:

    • 可以覆盖dragEnterEventdragLeaveEventdragMoveEventdropEvent函数,以向场景添加拖放功能。 请注意,这与前面示例中将图像拖放到窗口中所做的非常相似。 这些事件中的每一个都提供足够的信息和参数来处理整个拖放过程。
    • 如果我们需要在整个场景中添加自定义背景或前景,则应覆盖drawBackgrounddrawForeground函数。 当然,对于简单的背景或前景绘画或着色任务,我们可以简单地调用setBackgroundBrushsetForegroundBrush函数,而跳过这些函数。
    • mouseDoubleClickEventmouseMoveEventmousePressEventmouseReleaseEventwheelEvent函数可用于处理场景中的不同鼠标事件。 例如,当我们在Computer_Vision项目中为场景添加放大和缩小功能时,将在本章稍后使用wheelEvent
    • 可以覆盖event以处理场景接收到的所有事件。 此函数基本上负责将事件调度到其相应的处理器,但是它也可以用于处理自定义事件或不具有便捷功能的事件,例如前面提到的所有事件。

    就像到目前为止您学过的所有类一样,无论是在 Qt 还是 OpenCV 中,本书中提供的方法,属性和函数的列表都不应被视为该类各个方面的完整列表。 最好总是使用框架的文档来学习新函数和属性。 但是,本书中的描述旨在更简单,尤其是从计算机视觉开发人员的角度出发。

    项目,QGraphicsItem

    这是场景中绘制的所有项目的基类。 它包含各种方法和属性来处理每个项目的绘制,碰撞检测(与其他项目),处理鼠标单击和其他事件,等等。 即使您可以将其子类化并创建自己的图形项,Qt 也会提供一组子类,这些子类可用于大多数(如果不是全部)日常图形任务。 以下是这些子类,在前面的示例中已经直接或间接使用了这些子类:

    • QGraphicsEllipseItem
    • QGraphicsLineItem
    • QGraphicsPathItem
    • QGraphicsPixmapItem
    • QGraphicsPolygonItem
    • QGraphicsRectItem
    • QGraphicsSimpleTextItem
    • QGraphicsTextItem

    如前所述,QGraphicsItem提供了许多函数和属性来处理图形应用中的问题和任务。 在本节中,我们将介绍QGraphicsItem中一些最重要的成员,这些成员因此可以通过熟悉前面提到的子类来帮助我们:

    • acceptDropssetAcceptDrops函数可用于使项目接受拖放事件。 请注意,这与我们在前面的示例中已经看到的拖放事件非常相似,但是这里的主要区别是项目本身可以识别拖放事件。
    • acceptHoverEventssetAcceptHoverEventsacceptTouchEventssetAcceptTouchEventsacceptedMouseButtonssetAcceptedMouseButtons函数均处理项目交互及其对鼠标单击的响应等。 这里要注意的重要一点是,一个项目可以根据Qt::MouseButtons枚举设置来响应或忽略不同的鼠标按钮。 这是一个简单的例子:
    代码语言:javascript
    复制
            QGraphicsRectItem *item = 
               new QGraphicsRectItem(0, 
                                     0, 
                                     100, 
                                     100, 
                                     this); 
            item->setAcceptDrops(true); 
            item->setAcceptHoverEvents(true); 
            item->setAcceptedMouseButtons( 
                    Qt::LeftButton | 
                    Qt::RightButton | 
                    Qt::MidButton); 
    • boundingRegion函数可用于获取描述图形项区域的QRegion类。 这是一项非常重要的函数,因为它可用于获取需要绘制(或重绘)项目的确切区域,并且与项目的边界矩形不同,因为简单地说,该项目可能仅覆盖其边界矩形的一部分,如直线等。 有关更多信息,请参见以下示例。
    • 在计算项目的boundingRegion函数时,boundingRegionGranularitysetBoundingRegionGranularity函数可用于设置和获取粒度级别。 从这个意义上讲,粒度是01之间的实数,它对应于计算时的预期详细程度:
    代码语言:javascript
    复制
            QGraphicsEllipseItem *item = 
                new QGraphicsEllipseItem(0, 
                                         0, 
                                         100, 
                                         100); 
            scene.addItem(item); 
            item->setBoundingRegionGranularity(g); // 0 , 0.1 , 0.75 and 1.0 
            QTransform transform; 
            QRegion region = item->boundingRegion(transform); 
            QPainterPath painterPath; 
            painterPath.addRegion(region); 
            QGraphicsPathItem *path = new QGraphicsPathItem(painterPath); 
            scene.addItem(path); 

    在前面的代码中,如果将g替换为0.00.10.751.0,则会得到以下结果。 显然,0的值(默认粒度)导致单个矩形(边界矩形),这不是准确的估计。 随着级别的增加,我们得到了覆盖图形形状和项目的更准确的区域(基本上是矩形集):

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kNTRDeFF-1681869945444)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/18f48d84-95e0-4fd4-9869-ec1c63d52f8c.png)]

    • childItems函数可用于获取填充有QGraphicsItem类的QList,这些类是此项的子级。 将它们视为更复杂项目的子项目。
    • childrenBoundingRectboundingRectsceneBoundingRect函数可用于检索QRectF类,其中包含该项目的子项bounding rect,该项目本身和场景。
    • clearFocussetFocushasFocus函数可用于删除,设置和获取该项目的聚焦状态。 具有焦点的项目接收键盘事件。
    • collidesWithItemcollidesWithPathcollidingItems函数可用于检查此项目是否与任何给定项目发生冲突,以及该项目与之碰撞的项目列表。
    • contains函数获取一个点的位置(准确地说是QPointF类),然后检查此项是否包含该点。
    • cursorsetCursorunsetCursorhasCursor函数对于设置,获取和取消设置此项的特定鼠标光标类型很有用。 您还可以在取消设置之前检查项目是否有任何设置的光标。 设置后,如果鼠标光标悬停在该项目上,则光标形状变为一组。
    • hideshowsetVisibleisVisibleopacitysetOpacityeffectiveOpacity函数均与商品的可见性(和不透明度)有关。 所有这些函数都具有不言自明的名称,唯一值得注意的是effectiveOpacity,它可能与此项的不透明度相同,因为它是基于该项及其父项的不透明度级别计算的。 最终,effectiveOpacity是用于在屏幕上绘制该项目的不透明度级别。
    • flagssetFlagssetFlag函数可用于获取或设置此项的标志。 通过标志,我们基本上是指QGraphicsItem::GraphicsItemFlag枚举中各项的组合。 这是一个示例代码:
    代码语言:javascript
    复制
            item->setFlag(QGraphicsItem::ItemIsFocusable, true); 
            item->setFlag(QGraphicsItem::ItemIsMovable, false);

    重要的是要注意,当我们使用setFlag函数时,所有以前的标志状态都会保留,并且此函数中只有一个标志会受到影响。 但是,当我们使用setFlags时,基本上所有标志都会根据给定的标志组合进行重置。

    • 当我们想要更改从场景中获取鼠标和键盘事件的项目时,grabMousegrabKeyboardungrabMouseungrabKeyboard方法很有用。 显然,使用默认实现时,一次只能抓取一个项目,除非另一个抓取项目或者项目本身不变形或被删除或隐藏,否则抓取器将保持不变。 正如本章前面所看到的,我们总是可以使用QGraphicsScene类中的mouseGrabberItem函数来获取抓取器项目。
    • setGraphicsEffectgraphicsEffect函数可用于设置和获取QGraphicsEffect类。 这是一个非常有趣且易于使用的函数,但功能强大,可用于向场景中的项目添加过滤器或效果。 QGraphicsEffect是 Qt 中所有图形效果的基类。 您可以将其子类化并创建自己的图形效果或过滤器,也可以仅使用提供的 Qt 图形效果之一。 目前,Qt 中有一些图形效果类,您可以自己尝试一下:
      • QGraphicsBlurEffect
      • QGraphicsColorizeEffect
      • QGraphicsDropShadowEffect
      • QGraphicsOpacityEffect

    让我们看一个示例自定义图形效果,并使用 Qt 自己的图形效果使自己更加熟悉这个概念:

    1. 您可以使用我们在本章前面创建的Graphics_Viewer项目。 只需在 Qt Creator 中打开它,然后使用主菜单中的New FileProject,选择 C++ 和 C++ 类,然后单击Choose按钮。
    2. 接下来,确保输入QCustomGraphicsEffect作为类名。 选择QObject作为基类,最后选中Include QObject复选框(如果默认情况下未选中)。 单击下一步,然后单击完成按钮。
    3. 然后,将以下include语句添加到新创建的qcustomgraphicseffect.h文件中:
    代码语言:javascript
    复制
            #include <QGraphicsEffect> 
            #include <QPainter>
    1. 之后,您需要确保我们的QCustomGraphicsEffect类继承了QGraphicsEffect而不是QObject。 确保首先更改qcustomgraphicseffect.h文件中的类定义行,如下所示:
    代码语言:javascript
    复制
            class QCustomGraphicsEffect : public QGraphicsEffect
    1. 我们还需要更新该类的构造器,并确保在我们的类构造器中调用了QGraphicsEffect构造器,否则将出现编译器错误。 因此,更改qcustomgraphics.cpp文件中的类构造器,如下所示:
    代码语言:javascript
    复制
          QCustomGraphicsEffect::QCustomGraphicsEffect(QObject *parent) 
             : QGraphicsEffect(parent) 
    1. 接下来,我们需要实现draw函数。 基本上,这是通过实现draw函数制作所有QGraphicsEffect类的方式。 因此,将以下代码行添加到qcustomgraphicseffect.h文件中的QCustomGraphicsEffect类定义中:
    代码语言:javascript
    复制
            protected: 
              void draw(QPainter *painter); 
    1. 然后,我们需要编写实际的效果代码。 在此示例中,我们将编写一个简单的阈值过滤器,根据像素的灰度值,将其设置为完全黑色或完全白色。 尽管起初代码看起来有些棘手,但它仅使用了我们在前几章中已经学到的经验。 而且,这也是使用QGraphicsEffect类编写新效果和过滤器的简单程度的简单示例。 如您所见,传递给draw函数的QPainter类的指针可用于在效果所需的更改之后简单地对其进行修改和绘制:
    代码语言:javascript
    复制
            void QCustomGraphicsEffect::draw(QPainter *painter) 
            { 
              QImage image; 
              image = sourcePixmap().toImage(); 
              image = image.convertToFormat( 
                    QImage::Format_Grayscale8); 
              for(int i=0; i<image.byteCount(); i++) 
              image.bits()[i] = 
                    image.bits()[i] < 100 ? 
                        0 
                      : 
                        255; 
              painter->drawPixmap(0,0,QPixmap::fromImage(image)); 
            }
    1. 最后,我们可以使用新的效果类。 只要确保它包含在mainwindow.h文件中:
    代码语言:javascript
    复制
            #include "qcustomgraphicseffect.h" 
    1. 然后,通过调用项目的setGraphicsEffect函数来使用它。 在我们的Graphics_Viewer项目中,我们实现了dropEvent。 您可以简单地将以下代码段添加到dropEvent函数中,因此将具有以下内容:
    代码语言:javascript
    复制
            QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); 
            item->setGraphicsEffect(new QCustomGraphicsEffect(this)); 
            scene.addItem(item); 

    如果在运行应用并将其放置在其上的图像时所有操作均正确完成,您将注意到我们的阈值效果的结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wdefyDcB-1681869945444)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/cb0aa492-12bc-40b2-95bf-0bca6581bb29.png)]

    在我们使用自定义图形效果的最后一步中,尝试用任何 Qt 提供的效果的类名替换QCustomGraphicsEffect,然后亲自检查结果。 如您所见,它们在图形效果和类似概念方面提供了极大的灵活性。

    现在,让我们继续进行QGraphicsItem类中的其余函数和属性:

    • 当我们想将一个项目添加到组中或获取包含该项目的组类时,groupsetGroup函数非常有用,只要该项目属于任何组。 QGraphicsItemGroup是负责处理组的类,就像您在本章前面所学的那样。
    • isAncestorOf函数可用于检查该项目是否为任何给定其他项目的父项(或父项的父项,依此类推)。
    • 可以设置setParentItemparentItem并检索当前项目的父项目。 一个项目可能根本没有任何父项,在这种情况下,parentItem函数将返回零。
    • isSelectedsetSelected函数可用于更改项目的所选模式。 这些函数与setSelectionArea和您在QGraphicsScene类中了解的类似函数密切相关。
    • mapFromItemmapToItemmapFromParentmapToParentmapFromScenemapToScenemapRectFromItemmapRectToScenemapRectFromParentmapRectToParentmapRectFromScenemapRectToScene函数 ,所有这些函数甚至都具有更多方便的重载函数,构成了一长串函数,这些函数用于从或向其进行基本映射,或者换句话说,可用于从场景,另一项或父对象到场景的坐标转换。 。 实际上,如果您考虑到每个单独的项目和场景与其他项目无关的事实,那么这很容易掌握。 首先,请看下面的图,然后让我们对其进行更详细的讨论:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QxLI49nF-1681869945445)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/c52b657c-957b-46c3-af3b-b4a0f551c1d9.png)]

    因为场景包含所有项目,所以我们假设主坐标系(或世界坐标系)是场景的坐标系。 实际上,这是一个正确的假设。 因此,项目在场景中的位置值为(A, B)。 同样,父项在场景中的位置为(D, E)。 现在,这有点棘手,子项 1父项中的位置值为(F, G)。 类似地,子项 2父项中的位置值为(H, I)。 显然,如果父项和子项的数量增加,我们将拥有不同坐标系的迷宫,在这里,提到的映射函数会很有用。 这是一些示例情况。 您可以使用以下代码段自己测试它,以创建一个场景,其中包含与前面提到的场景类似的项目:

    代码语言:javascript
    复制
        QGraphicsRectItem *item = 
        new QGraphicsRectItem(0, 
                              0, 
                              100, 
                              100); 
        item->setPos(50,400); 
        scene.addItem(item); 
        QGraphicsRectItem *parentItem = 
            new QGraphicsRectItem(0, 
                                  0, 
                                  320, 
                                  240); 
        parentItem->setPos(300, 50); 
        scene.addItem(parentItem);
    
    QGraphicsRectItem *childItem1 = 
        new QGraphicsRectItem(0, 
                              0, 
                              50, 
                              50, 
                              parentItem); 
    childItem1-&gt;setPos(50,50); 
    QGraphicsRectItem *childItem2 = 
        new QGraphicsRectItem(0, 
                              0, 
                              75, 
                              75, 
                              parentItem); 
    childItem2-&gt;setPos(150,75); 
    
    qDebug() &lt;&lt; item-&gt;mapFromItem(childItem1, 0,0); 
    qDebug() &lt;&lt; item-&gt;mapToItem(childItem1, 0,0); 
    qDebug() &lt;&lt; childItem1-&gt;mapFromScene(0,0); 
    qDebug() &lt;&lt; childItem1-&gt;mapToScene(0,0); 
    qDebug() &lt;&lt; childItem2-&gt;mapFromParent(0,0); 
    qDebug() &lt;&lt; childItem2-&gt;mapToParent(0,0); 
    qDebug() &lt;&lt; item-&gt;mapRectFromItem(childItem1, 
                                  childItem1-&gt;rect()); 
    qDebug() &lt;&lt; item-&gt;mapRectToItem(childItem1, 
                                childItem1-&gt;rect()); 
    qDebug() &lt;&lt; childItem1-&gt;mapRectFromScene(0,0, 25, 25); 
    qDebug() &lt;&lt; childItem1-&gt;mapRectToScene(0,0, 25, 25); 
    qDebug() &lt;&lt; childItem2-&gt;mapRectFromParent(0,0, 30, 30); 
    qDebug() &lt;&lt; childItem2-&gt;mapRectToParent(0,0, 25, 25); </code></pre></div></div><p>尝试在 Qt Creator 和 Qt Widgets 项目中运行前面的代码,您将在 Qt Creator 的应用输出窗格中看到以下内容,这基本上是<code>qDebug()</code>语句的结果:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    QPointF(300,-300) 
    QPointF(-300,300) 
    QPointF(-350,-100) 
    QPointF(350,100) 
    QPointF(-150,-75) 
    QPointF(150,75) 
    QRectF(300,-300 50x50) 
    QRectF(-300,300 50x50) 
    QRectF(-350,-100 25x25) 
    QRectF(350,100 25x25) 
    QRectF(-150,-75 30x30) 
    QRectF(150,75 25x25) </code></pre></div></div><p>让我们尝试看看产生第一个结果的指令:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">    item-&gt;mapFromItem(childItem1, 0,0); </code></pre></div></div><p><code>item</code>在场景中的位置为<code>(50, 400)</code>,<code>childItem1</code>在场景中的<code>(50, 50)</code>位置。 该语句在<code>childItem1</code>坐标系中的位置<code>(0, 0)</code>并将其转换为项目的坐标系。 自己一个个地检查其他说明。 当我们要在场景中的项目周围移动或对场景中的项目进行类似的转换时,这非常简单但非常方便:</p><ul class="ul-level-0"><li> <code>moveBy</code>,<code>pos</code>,<code>setPos</code>,<code>x</code>,<code>setX</code>,<code>y</code>,<code>setY</code>,<code>rotation</code>,<code>setRotation</code>,<code>scale</code>和<code>setScale</code>函数可用于获取或设置项目的不同几何属性。 有趣的是,<code>pos</code>和<code>mapToParent(0,0)</code>返回相同的值。 检查前面的示例,然后通过将其添加到示例代码中来进行尝试。
    
  • transformsetTransformsetTransformOriginPointresetTransform函数可用于对项目应用或检索任何几何变换。 重要的是要注意,所有变换都假设一个原点(通常为(0,0)),可以使用setTransformOriginPoint对其进行更改。
  • scenePos函数可用于获取项目在场景中的位置。 与调用mapToScene(0,0)相同。 您可以自己在前面的示例中进行尝试并比较结果。
  • datasetData函数可用于设置和检索项目中的任何自定义数据。 例如,我们可以使用它来存储设置为QGraphicsPixmapItem的图像的路径,或者存储与特定项目相关的任何其他类型的信息。
  • zValuesetZValue函数可用于修改和检索项目的Z值。 Z值决定应在其他项目之前绘制哪些项目,依此类推。 具有较高Z值的项目将始终绘制在具有较低Z值的项目上。
  • 与我们在QGraphicsScene类中看到的类似,QGraphicsItem类还包含许多受保护的虚函数,这些函数可以重新实现,主要用于处理传递到场景项上的各种事件。 以下是一些重要且非常有用的示例:

    • contextMenuEvent
    • dragEnterEventdragLeaveEventdragMoveEventdropEvent
    • focusInEventfocusOutEvent
    • hoverEnterEventhoverLeaveEventhoverMoveEvent
    • keyPressEventkeyReleaseEvent
    • mouseDoubleClickEventmouseMoveEventmousePressEventmouseReleaseEventwheelEvent

    视图,QGraphicsView

    我们到了 Qt 中的图形视图框架的最后一部分。 QGraphicsView类是 Qt 窗口小部件类,可以将其放置在窗口上以显示QGraphicsScene,该窗口本身包含许多QGraphicsItem子类和/或窗口小部件。 与QGraphicsScene类相似,该类还提供大量函数,方法和属性来处理图形的可视化部分。 我们将审核以下列表中的一些最重要的函数,然后我们将学习如何对QGraphicsView进行子类化并将其扩展为在我们全面的计算机视觉应用中具有若干重要功能,例如放大,缩小, 项目选择等。 因此,这是我们在计算机视觉项目中需要的QGraphicsView类的方法和成员:

    • alignmentsetAlignment函数可用于设置场景在视图中的对齐方式。 重要的是要注意,只有当视图可以完全显示场景并且仍然有足够的空间并且视图不需要滚动条时,这才具有可见效果。
    • dragModesetDragMode函数可用于获取和设置视图的拖动模式。 这是视图的最重要函数之一,它可以决定在视图上单击并拖动鼠标左键时会发生什么。 在下面的示例中,我们将使用它并对其进行全面了解。 我们将使用QGraphicsView::DragMode枚举设置不同的拖动模式。
    • isInteractivesetInteractive函数允许检索和修改视图的交互行为。 交互式视图会响应鼠标和键盘(如果已实现),否则,所有鼠标和键盘事件都将被忽略,并且该视图只能用于查看并且不能与场景中的项目进行交互。
    • optimizationFlagssetOptimizationFlagsrenderHintssetRenderHintsviewportUpdateModesetViewportUpdateMode函数分别用于获取和设置与视图的性能和渲染质量有关的参数。 在下面的示例项目中,我们将在实践中看到这些函数的用例。
    • dragMode设置为RubberBandDrag模式的情况下,可以使用rubberBandSelectionModesetRubberBandSelectionMode函数设置视图的项目选择模式。 可以设置以下内容,它们是Qt::ItemSelectionMode枚举中的条目:
      • Qt::ContainsItemShape
      • Qt::IntersectsItemShape
      • Qt::ContainsItemBoundingRect
      • Qt::IntersectsItemBoundingRect
    • sceneRectsetSceneRect函数可用于获取和设置视图中场景的可视化区域。 显然,该值不必与QGraphicsScene类的sceneRect相同。
    • centerOn函数可用于确保特定点或项目位于视图中心。
    • ensureVisible函数可用于将视图滚动到特定区域(具有给定的边距)以确保它在视图中。 此函数适用于点,矩形和图形项目。
    • fitInView函数与centerOnensureVisible非常相似,但主要区别在于,该函数还使用给定的宽高比处理参数缩放视图的内容以适合视图。 以下:
      • Qt::IgnoreAspectRatio
      • Qt::KeepAspectRatio
      • Qt::KeepAspectRatioByExpanding
    • itemAt函数可用于在视图中的特定位置检索项目。

    我们已经了解到场景中的每个项目和场景中的每个项目都有各自的坐标系,我们需要使用映射函数将位置从一个位置转换到另一个位置,反之亦然。 视图也是如此。 视图还具有自己的坐标系,主要区别在于视图中的位置和矩形等实际上是根据像素进行测量的,因此它们是整数,但是场景和项目的位置使用实数,等等。 这是由于以下事实:场景和项目在视图上被查看之前都是逻辑实体,因此所有实数都将转换为整数,而整个场景(或部分场景)准备在屏幕上显示。 。 下图可以帮助您更好地理解这一点:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FdAfdJek-1681869945445)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/fc1db2f4-4a0c-4eaf-b394-7c389c6fdf71.png)]

    在上图中,视图的中心点实际上是场景右上角的某个位置。 视图提供了类似的映射函数(与我们在项目中看到的函数相同),可以将场景坐标系中的位置转换为视图坐标系,反之亦然。 这里是它们,再加上其他一些函数和方法,在继续之前,我们需要学习以下视图

    • mapFromScenemapToScene函数可用于在场景坐标系之间转换位置。 与前面提到的一致,mapFromScene函数接受实数并返回整数值,而mapToScene函数接受整数并返回实数。 稍后我们将开发视图的缩放功能时,将使用这些函数。
    • items函数可用于获取场景中的项目列表。
    • render函数对于执行整个视图或其一部分的渲染很有用。 该函数的用法与QGraphicsScene中的render完全相同,只是此函数在视图上执行相同的功能。
    • rubberBandRect函数可用于获取橡皮筋选择的矩形。 如前所述,这仅在拖动模式设置为rubberBandSelectionMode时才有意义。
    • setScenescene函数可用于设置和获取视图场景。
    • setMatrixsetTransformtransformrotatescalesheartranslate函数都可以用于修改或检索视图的几何特性。

    QGraphicsSceneQGraphicsItem类相同,QGraphicsView还提供了许多相同的受保护虚拟成员,可用于进一步扩展视图的功能。 现在,我们将扩展Graphics_Viewer示例项目,以支持更多项目,项目选择,项目删除以及放大和缩小功能,并且在此过程中,我们将概述以下项目的一些最重要用例: 我们在本章中学到的视图,场景和项目。 因此,让我们完成它:

    1. 首先在 Qt Creator 中打开Graphics_Viewer项目; 然后,从主菜单中选择“新建文件”或“项目”,然后在“新建文件或项目”窗口中选择“C++ 和 C++ 类”,然后单击“选择”按钮。
    2. 确保输入QEnhancedGraphicsView作为类名,然后选择QWidget作为基类。 另外,如果Include QWidget旁边的复选框尚未选中,请选中它。 然后,单击“下一步”,然后单击“完成”。
    3. 添加以下内容以包含qenhancedgraphicsview.h头文件:
    代码语言:javascript
    复制
            #include <QGraphicsView> 
    1. 确保QEnhancedGraphicsView类继承了qenhancedgraphicsview.h文件中的QGraphicsView而不是QWidget,如下所示:
    代码语言:javascript
    复制
            class QEnhancedGraphicsView : public QGraphicsView 
    1. 您必须更正QEnhancedGraphicsView类的构造器实现,如此处所示。 显然,这是在qenhancedgraphicsview.cpp文件中完成的,如下所示:
    代码语言:javascript
    复制
            QEnhancedGraphicsView::QEnhancedGraphicsView(QWidget
               *parent) 
             : QGraphicsView(parent) 
            { 
            } 
    1. 现在,将以下受保护的成员添加到qenhancedgraphicsview.h文件中的增强型视图类定义中:
    代码语言:javascript
    复制
            protected: 
              void wheelEvent(QWheelEvent *event);
    1. 并将其实现添加到qenhancedgraphicsview.cpp文件,如以下代码块所述:
    代码语言:javascript
    复制
            void QEnhancedGraphicsView::wheelEvent(QWheelEvent *event) 
            { 
              if (event->orientation() == Qt::Vertical) 
              { 
                double angleDeltaY = event->angleDelta().y(); 
                double zoomFactor = qPow(1.0015, angleDeltaY); 
                scale(zoomFactor, zoomFactor); 
                this->viewport()->update(); 
                event->accept(); 
              } 
              else 
              { 
                event->ignore(); 
              } 
            } 

    您需要确保QWheelEventQtMath包含在我们的类源文件中,否则,您将获得qPow函数和QWheelEvent类的编译器错误。 前面的代码大部分是不言自明的-它首先检查鼠标滚轮事件的方向,然后根据滚轮中的移动量在 X 和 Y 轴上都应用一个比例。 然后,它更新视口,以确保根据需要重新绘制所有内容。

    1. 现在,我们需要进入 Qt Creator 中的“设计”模式,以在窗口上提升graphicsView对象(如我们先前所见)。 我们需要右键单击并从上下文菜单中选择“升级为”。 然后,输入QEnhancedGraphicsView作为升级的类名称,然后单击“添加”按钮,最后单击“升级”按钮。 (您已经在前面的示例中学习了关于提升的知识,这也不例外。)由于QGraphicsViewQEnhancedGraphicsView类是兼容的(第一个是后者的父类),因此我们可以将父代提升为子代,和/ 或将其降级(如果我们不需要)。 升级就像将小部件转换为其子小部件以支持和添加更多功能一样。
    2. 您需要在mainwindow.cppdropEvent函数顶部添加一小段代码,以确保在加载新图像时重置缩放级别(准确地说是比例转换):
    代码语言:javascript
    复制
            ui->graphicsView->resetTransform(); 

    现在,您可以启动应用,并尝试使用鼠标滚轮滚动。 向上或向下旋转轮子时,您可以看到比例级别的变化。 这是放大和缩小图像时结果应用的屏幕截图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ly7AWV9w-1681869945445)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/1f9a24f3-3ad9-408a-aa5a-1a6815e21b48.png)]

    如果再尝试一点,很快就会发现一件事,缩放功能总是朝着图像的中心起作用,这很奇怪而且不舒服。 为了能够解决此问题,我们需要利用在本章中学到的更多提示,技巧和功能:

    1. 首先向我们的增强型视图类添加另一个私有受保护的函数。 除了先前使用的wheelEvent外,我们还将使用mouseMoveEvent。 因此,将以下代码行添加到qenhancedgraphicsview.h文件中的受保护成员部分:
    代码语言:javascript
    复制
            void mouseMoveEvent(QMouseEvent *event); 
    1. 另外,添加一个私有成员,如下所示:
    代码语言:javascript
    复制
            private: 
              QPointF sceneMousePos; 
    1. 现在,转到它的实现部分,并将以下代码行添加到qenhancedgraphicsview.cpp文件:
    代码语言:javascript
    复制
            void QEnhancedGraphicsView::mouseMoveEvent(QMouseEvent
               *event) 
           { 
             sceneMousePos = this->mapToScene(event->pos()); 
           }
    1. 您还需要稍微调整wheelEvent函数。 确保其外观如下:
    代码语言:javascript
    复制
            if (event->orientation() == Qt::Vertical) 
            { 
              double angleDeltaY = event->angleDelta().y(); 
              double zoomFactor = qPow(1.0015, angleDeltaY); 
              scale(zoomFactor, zoomFactor); 
              if(angleDeltaY > 0) 
              { 
                this->centerOn(sceneMousePos); 
                sceneMousePos = this->mapToScene(event->pos()); 
              } 
              this->viewport()->update(); 
              event->accept(); 
            } 
            else 
            { 
              event->ignore(); 
            } 

    您只需关注函数名称,就可以很容易地看到这里发生的事情。 我们实现了mouseMoveEvent来拾取鼠标的位置(在场景坐标中,这非常重要); 然后我们确保在放大(而不是缩小)之后,该视图确保所采集的点位于屏幕的中心。 最后,它会更新位置,以获得更舒适的变焦体验。 重要的是要注意,有时诸如此类的小缺陷或功能可能意味着用户可以舒适地使用您的应用,最终这是应用增长(或最坏的情况是下降)的重要参数。

    现在,我们将向Graphics_Viewer应用添加更多功能。 让我们首先确保我们的Graphics_Viewer应用能够处理无限数量的图像:

    1. 首先,我们需要确保在将每个图像拖放到视图中(因此是场景)之后,不会清除场景,因此首先从mainwindow.cppdropEvent中删除以下行:
    代码语言:javascript
    复制
            scene.clear(); 
    1. 另外,从dropEvent中删除以下代码行,我们先前添加了以下代码行以重置缩放比例:
    代码语言:javascript
    复制
            ui->graphicsView->resetTransform();
    1. 现在,将以下两行代码添加到mainwindow.cpp文件中dropEvent的起点:
    代码语言:javascript
    复制
            QPoint viewPos = ui->graphicsView->mapFromParent
              (event->pos()); 
            QPointF sceneDropPos = ui->graphicsView->mapToScene
              (viewPos); 
    1. 然后,确保将项目的位置设置为sceneDropPos,如下所示:
    代码语言:javascript
    复制
            item->setPos(sceneDropPos); 

    就是这样,现在不需要其他任何东西。 启动Graphics_Viewer应用,然后尝试将图像放入其中。 在第一张图像之后,尝试缩小并添加更多图像。 (请不要通过夸大此测试来填充内存,因为如果您尝试添加大量图像,则您的应用将开始消耗过多的内存,从而导致操作系统出现问题。不用说,您的应用可能会崩溃 。)以下是在场景中各个位置拖放的一些图像的屏幕截图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PaY5y7cG-1681869945445)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/376494ed-9341-4a3e-9e44-e3296a513383.png)]

    显然,该应用仍然遗漏了很多东西,但是在剩下的部分让您自己找出并发现之前,我们将在本章中介绍一些非常关键的功能。 一些非常重要的缺失功能是我们无法选择,删除项目或对其施加某些效果。 让我们一次完成一个简单但功能强大的Graphics_Viewer应用。 如您所知,稍后,我们将使用在综合计算机视觉应用(名为Computer_Vision项目)中学到的所有技术。 因此,让我们开始为Graphics_Viewer项目添加以下最终内容:

    1. 首先向增强的图形视图类添加另一个受保护的成员,如下所示:
    代码语言:javascript
    复制
            void mousePressEvent(QMouseEvent *event); 
    1. 然后,将以下专用插槽添加到相同的类定义中:
    代码语言:javascript
    复制
            private slots: 
              void clearAll(bool); 
              void clearSelected(bool); 
              void noEffect(bool); 
              void blurEffect(bool); 
              void dropShadowEffect(bool); 
              void colorizeEffect(bool); 
              void customEffect(bool); 
    1. 现在,将所有必需的实现添加到视图类源文件,即qenhancedgraphicsview.cpp文件。 首先添加mousePressEvent的实现,如下所示:
    代码语言:javascript
    复制
            void QEnhancedGraphicsView::mousePressEvent(QMouseEvent 
              *event) 
            { 
             if(event->button() == Qt::RightButton) 
             { 
              QMenu menu; 
              QAction *clearAllAction = menu.addAction("Clear All"); 
              connect(clearAllAction, 
                    SIGNAL(triggered(bool)), 
                    this, 
                    SLOT(clearAll(bool))); 
              QAction *clearSelectedAction = menu.addAction("Clear Selected"); 
              connect(clearSelectedAction, 
                    SIGNAL(triggered(bool)), 
                    this, 
                    SLOT(clearSelected(bool))); 
              QAction *noEffectAction = menu.addAction("No Effect"); 
              connect(noEffectAction, 
                    SIGNAL(triggered(bool)), 
                    this, 
                    SLOT(noEffect(bool))); 
              QAction *blurEffectAction = menu.addAction("Blur Effect"); 
              connect(blurEffectAction, 
                    SIGNAL(triggered(bool)), 
                    this, 
                    SLOT(blurEffect(bool))); 
              // *** 
              menu.exec(event->globalPos()); 
              event->accept(); 
             } 
             else 
             {  
               QGraphicsView::mousePressEvent(event); 
             } 
            } 

    在前面的代码中,//***对于dropShadowEffectcolorizeEffectcustomEffect函数插槽基本上以相同的模式重复。 在前面的代码中,我们所做的只是简单地创建并打开一个上下文(右键单击)菜单,然后将每个动作连接到将在下一步中添加的插槽。

    1. 现在,添加插槽的实现,如下所示:
    代码语言:javascript
    复制
            void QEnhancedGraphicsView::clearAll(bool) 
            { 
              scene()->clear(); 
            } 
            void QEnhancedGraphicsView::clearSelected(bool) 
            { 
              while(scene()->selectedItems().count() > 0) 
              { 
               delete scene()->selectedItems().at(0); 
               scene()->selectedItems().removeAt(0); 
              } 
            } 
            void QEnhancedGraphicsView::noEffect(bool) 
            { 
              foreach(QGraphicsItem *item, scene()->selectedItems()) 
              { 
               item->setGraphicsEffect(Q_NULLPTR); 
              } 
            }
    
        void QEnhancedGraphicsView::blurEffect(bool) 
        { 
          foreach(QGraphicsItem *item, scene()-&gt;selectedItems()) 
          { 
            item-&gt;setGraphicsEffect(new QGraphicsBlurEffect(this)); 
          } 
        } 
    
       //*** </code></pre></div></div><p>与前面的代码相同,其余插槽遵循相同的模式。</p><ol class="ol-level-0"><li>在我们的应用准备好进行测试运行之前,我们需要处理一些最后的事情。 首先,我们需要确保增强的图形视图类是交互式的,并允许通过单击和拖动来选择项目。 您可以通过将以下代码段添加到<code>mainwindow.cpp</code>文件中来实现。 设置场景后立即在初始化函数(构造器)中执行以下操作:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        ui-&gt;graphicsView-&gt;setInteractive(true); 
        ui-&gt;graphicsView-&gt;setDragMode(QGraphicsView::RubberBandDrag); 
        ui-&gt;graphicsView-&gt;setRubberBandSelectionMode( 
           Qt::ContainsItemShape); </code></pre></div></div><ol class="ol-level-0"><li>最后但并非最不重要的一点是,在<code>mainwindow.cpp</code>的<code>dropEvent</code>函数中添加以下代码行,以确保可以选择项目。 将它们添加到项目创建代码之后以及添加到场景的行之前:</li></ol><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">        item-&gt;setFlag(QGraphicsItem::ItemIsSelectable); 
        item-&gt;setAcceptedMouseButtons(Qt::LeftButton);</code></pre></div></div><p>而已。 我们准备开始并测试我们的<code>Graphics_Viewer</code>应用,该应用现在还可以添加效果并具有更多功能。 这是显示所谓的橡皮筋选择模式行为的屏幕截图:</p><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zEffmXzF-1681869945445)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/221f1f37-dd44-44cb-99ae-1d397a2895f4.png)]</p><p>最后,下面是正在运行的<code>Graphics_Viewer</code>应用的屏幕快照,同时为场景中的图像添加了不同的效果:</p><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RTx9F711-1681869945446)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/8b4e6dc5-a618-4d1b-8c15-1a500a9c672c.png)]</p><p>而已。 现在,我们可以创建功能强大的图形查看器,并将其添加到<code>Computer_Vision</code>项目中,在学习新的以及更多的 OpenCV 和 Qt 技能和技术的同时,还将在接下来的章节中使用。 按照承诺,您可以从以下链接下载<code>Computer_Vision</code>项目的完整版本。</p><p>正如我们在前几章中反复提到的那样,该项目的目标是通过照顾每种所需的 GUI 功能,语言,主题等,帮助我们仅专注于计算机视觉主题。 。 该项目是到目前为止您学到的一切的完整示例。 该应用可以使用样式进行自定义,可以支持新语言,并且可以使用插件进行扩展。 它还将您在本章中学到的所有内容打包到一个漂亮而强大的图形查看器中,我们将在本书的其余部分中使用该图形查看器。 在继续以下各章之前,请确保下载了它。</p><p><code>Computer_Vision</code>项目包含一个 Qt 多项目中的两个项目,或者更确切地说是<code>subdirs</code>项目类型。 第一个是<code>mainapp</code>,第二个是<code>template_plugin</code>项目。 您可以复制(克隆)并替换该项目中的代码和 GUI 文件,以创建与<code>Computer_Vision</code>项目兼容的新插件。 这正是我们在第 6 章,“OpenCV 中的图像处理”)中所做的工作,对于您学习的大多数 OpenCV 技能,我们将为<code>Computer_Vision</code>创建一个插件。 该项目还包含示例附加语言和示例附加主题,可以再次对其进行简单地复制和修改,以为应用创建新的语言和主题。 确保您查看了整个下载的源代码,并确保其中没有奥秘,并且您完全了解<code>Computer_Vision</code>项目源代码中的所有内容。 同样,这是为了总结您所学的所有知识并将其打包到一个单一的,全面的,可重用的示例项目中。</p><h2 id="5rap7" name="%E6%80%BB%E7%BB%93">总结</h2><p>自本书开始以来,我们已经走了很长一段路,到现在,我们已经完全掌握了许多有用的技术来承担计算机视觉应用开发的任务。 在前面的所有章节(包括我们刚刚完成的章节)中,您了解了更多有关创建强大而全面的应用所需的技能(通常,大部分情况),而不仅仅是专注于计算机视觉(准确来说是 OpenCV 技能)方面。 您学习了如何创建支持多种语言,主题和样式,插件的应用; 在本章中,您学习了如何在场景和视图中可视化图像和图形项目。 现在,我们已经拥有了深入研究计算机视觉应用开发世界所需的几乎所有东西。</p><p>在第 6 章,“OpenCV 中的图像处理”中,您将了解有关 OpenCV 以及其中可能的图像处理技术的更多信息。 对于每个学习的主题,我们仅假设我们正在创建与<code>Computer_Vision</code>项目兼容的插件。 这意味着我们将在<code>Computer_Vision</code>项目中使用模板插件,将其复制,然后简单地制作一个能够执行特定计算机视觉任务,转换过滤器或计算的新插件。 当然,这并不意味着您不能创建具有相同功能的独立应用,正如您将在接下来的章节中看到的那样,我们的插件具有 GUI,与创建应用或创建应用本质上没有什么不同。 准确地说,您在上一章中学到了所有的 Qt Widgets 应用。 但是,从现在开始,我们将继续学习更高级的主题,并且我们的重点将主要放在应用的计算机视觉方面。 您将学习如何在 OpenCV 中使用众多的过滤和其他图像处理功能,它支持的色彩空间,许多转换技术等等。</p>