OpenCV3 和 Qt5 计算机视觉:11~12

发布时间 2023-04-19 10:16:39作者: ApacheCN

原文:Computer Vision with OpenCV 3 and Qt5

协议:CC BY-NC-SA 4.0

译者:飞龙

本文来自【ApacheCN 计算机视觉 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。

当别人说你没有底线的时候,你最好真的没有;当别人说你做过某些事的时候,你也最好真的做过。

十一、链接与部署

在前几章中了解了使用 Qt Creator 和 Qt Test 框架调试和测试应用之后,我们进入了应用开发的最后阶段之一,即将应用部署到最终用户。 该过程本身具有多种变体,并且可以根据目标平台采取很多不同的形式,但是它们都有一个共同点,就是以一种可以在目标平台中简单地执行它的方式打包应用。 困扰应用的依赖项。 请记住,并非所有目标平台(无论是 Windows,MacOS 还是 Linux)都具有 Qt 和 OpenCV 库。 因此,如果继续进行操作,仅向应用的用户提供应用的可执行文件,它很可能甚至不会开始执行,更不用说正常工作了。

在本章中,我们将通过学习创建应用包(通常是包含所有必需文件的文件夹)的正确方法来解决这些问题,该应用包可以在我们自己的计算机以及开发环境以外的其他计算机上简单执行,而无需用户照顾任何必需的库。 为了能够理解本章中描述的一些概念,我们首先需要了解创建应用可执行文件时幕后发生情况的一些基础知识。 我们将讨论构建过程的三个主要阶段,即预处理,编译和链接应用可执行文件(或库)。 然后,我们将学习可以用两种不同的方式完成链接,即动态链接和静态链接。 我们将讨论它们之间的差异以及它们如何影响部署,以及如何在 Windows,MacOS 和 Linux 操作系统上动态或静态地构建 Qt 和 OpenCV 库。 之后,我们将为所有提到的平台创建并部署一个简单的应用。 我们将借此机会还了解 Qt Installer 框架,以及如何创建网站下载链接,闪存驱动器或任何其他媒体上交付给最终用户的安装程序。 到本章结束时,我们将仅向最终用户提供他们执行我们的应用所需的内容,仅此而已。

本章将讨论的主题包括:

  • Qt 和 OpenCV 框架的动态和静态链接
  • 配置 Qt 项目来使用静态库
  • 部署使用 Qt 和 OpenCV 编写的应用
  • 使用 Qt Installer 框架创建跨平台安装程序

幕后制作过程

当我们通过编辑一些 C++ 头文件或源文件,在项目文件中添加一些模块并最后按下运行按钮来编写应用时,这似乎很自然。 但是,在幕后还有一些流程,这些流程通过按正确的顺序由 IDE(在我们的情况下为 Qt Creator)执行,从而使开发过程具有顺畅自然的感觉。 通常,当我们按 Qt Creator 或任何其他 IDE 的运行或构建按钮时,有三个主要过程可导致创建可执行文件(例如*.exe)。 这是这三个过程:

  • 预处理
  • 编译
  • 链接

这是从源文件创建应用时所经过的过程和阶段的非常高级的分类。 这种分类允许对过程进行更简单的概述,并以更简单的方式大致了解其目的。 但是,这些过程包括许多子过程和阶段,不在本书的讨论范围之内,因为我们对以一种或另一种方式影响部署过程的过程最为感兴趣。 但是,您可以在线阅读它们,也可以阅读有关编译器和链接器的任何书籍。

预处理

此阶段是在将源代码传递到实际编译器之前将其转换为最终状态的过程。 为了进一步解释这一点,请考虑所有包含的文件,各种编译器指令,或更重要的是,对于 Qt 框架,请考虑不属于标准 C++ 语言的 Qt 特定的宏和代码。 在第 3 章,“创建全面的 Qt + OpenCV 项目”中,我们了解了uicmoc,它们可以转换使用 Qt 特定宏和准则编写的 UI 文件和 C++ 代码。 转换为标准 C++ 代码(确切地说,是在最新版本的 Qt 中,转换为 C++ 11 或更高版本)。 即使这些不是对 C++ 源代码执行的标准预处理的一部分,但是当我们使用 Qt 框架或基于自己的规则集生成代码的框架时,它们仍处于大致相同的阶段。

下图描述了预处理阶段,该阶段与使用uicmoc等进行 Qt 特定的代码生成相结合:

该过程的输出在上一个图像中被标记为用于编译器的单个输入文件,显然是一个单个文件,其中包含用于编译源代码的所有必需标记和信息。 然后将该文件传递给编译器和编译阶段。

编译

在构建过程的第二个主要阶段,编译器获取预处理器的输出,或者在我们的示例中为预处理阶段,该输出还包括uicmoc生成的代码,并将其编译为机器代码。 。 可以在构建过程中保存并重复使用该机器代码,因为只要不更改源文件,生成的机器代码也将保持不变。 通过确保重复使用各个单独编译的对象(例如*.obj*.lib文件),而不是在每次构建项目时都生成该对象,此过程有助于节省大量时间。 所有这一切的好处是,IDE 会照顾它,我们通常不需要理会它。 然后,由编译器生成的输出文件将传递到链接器,然后我们进入链接阶段。

链接

链接器是在构建过程链中被调用的最后一个程序,其目标是链接由编译器生成的对象以生成可执行文件或库。 这个过程对我们而言至关重要,因为它会对部署应用的方式,可执行文件的大小等产生巨大影响。 为了更好地理解这一点,首先我们需要讨论两种可能的链接类型之间的区别:

  • 动态链接
  • 静态链接

动态链接是链接编译器生成的对象的过程,方法是将函数的名称放在生成的可执行文件或库中,以使该特定函数的实际代码位于共享库(例如*.dll文件)中 ),并且库的实际链接和加载是在运行时完成的。 动态链接的最明显的优缺点是:

  • 您的应用将在运行时需要共享库,因此您必须将它们与应用的可执行文件一起部署,并确保可以访问它们。 例如,在 Windows 上,可以通过将其复制到与应用可执行文件相同的文件夹中来完成,或者在 Linux 上,可以将它们放在默认库路径(例如/lib/)中来完成。
  • 动态链接通过将应用的各个部分保留在单独的共享库文件中,提供了极大的灵活性。 这样,共享库可以单独更新,而无需重新编译应用的每个部分。

与动态链接相反,可以使用静态链接将所有必需的代码链接到生成的可执行文件中,从而创建静态库或可执行文件。 您可以猜测,使用静态库与使用共享库具有完全相反的优点和缺点,它们是:

  • 您不需要部署用于构建应用的静态库,因为它们的所有代码实际上都已复制到生成的可执行文件中
  • 应用可执行文件的大小将变大,这意味着更长的初始加载时间和更大的文件要部署
  • 对库或应用任何部分的任何更改都需要对其所有组成部分进行完整的重建过程

在整本书中,特别是在为我们全面的计算机视觉应用开发插件时,我们使用了共享库和动态链接。 这是因为当我们使用所有默认的 CMake 设置构建 OpenCV,并使用第 1 章,“OpenCV 和 Qt 简介”中的官方安装程序安装 Qt 框架时, 动态链接和共享的库(Windows 上为*.dll,MacOS 上为*.dylib等)。 不过,在下一节中,我们将学习如何使用它们的源代码静态地构建 Qt 和 OpenCV 库。 通过使用静态链接库,我们可以创建不需要在目标系统上存在任何共享库的应用。 这可以极大地减少部署应用所需的工作量。 在 MacOS 和 Linux 操作系统中的 OpenCV 尤其如此,您的用户除了复制和运行您的应用外完全不需要执行任何操作,而他们将需要采取一些措施或必须执行一些脚本操作以确保执行您的应用时,所有必需的依赖项均已就绪。

构建 OpenCV 静态库

让我们从 OpenCV 开始,它遵循与构建动态库几乎相同的指令集来构建静态库。 您可以参考第 1 章,“OpenCV 和 Qt 简介”以获得更多信息。 只需下载源代码,解压缩并使用 CMake 来配置您的构建,如本章所述。 但是,这次,除了选中BUILD_opencv_world选项旁边的复选框外,还要取消选中每个选项旁边的复选框,以确保关闭了以下所有选项:

  • BUILD_DOCS
  • BUILD_EXAMPLES
  • BUILD_PERF_TESTS
  • BUILD_TESTS
  • BUILD_SHARED_LIBS
  • BUILD_WITH_STATIC_CRT(仅在 Windows 上可用)

关闭前四个参数仅是为了加快构建过程,并且是完全可选的。 禁用BUILD_SHARED_LIBS仅启用 OpenCV 库的静态(非共享)构建模式,而最后一个参数(在 Windows 上)有助于避免库文件不兼容。 现在,如果您使用第 1 章,“OpenCV 和 Qt 简介”中提供的相同说明开始构建过程,这次,而不是共享库(例如,在 Windows 上, *.lib*.dll文件),您将在安装文件夹中得到静态链接的 OpenCV 库(同样,在 Windows 中,仅*.lib文件,没有任何*.dll文件)。 接下来需要做的是将项目配置为使用 OpenCV 静态库。 通过使用*.pri文件,或直接将它们添加到 Qt 项目*.pro文件中,您需要以下几行,以便您的项目可以使用 OpenCV 静态库:

    win32: { 
      INCLUDEPATH += "C:/path_to_opencv_install/include" 
      Debug: { 
        LIBS += -L"C:/path_to_opencv_install/x86/vc14/staticlib"  
            -lopencv_world330d  
            -llibjpegd  
            -llibjasperd  
            -littnotifyd  
            -lIlmImfd  
            -llibwebpd  
            -llibtiffd  
            -llibprotobufd  
            -llibpngd  
            -lzlibd  
            -lipp_iw  
            -lippicvmt 
      } 
      Release: { 
        LIBS += -L"C:/path_to_opencv_install/x86/vc14/staticlib"  
            -lopencv_world330  
            -llibjpeg  
            -llibjasper  
            -littnotify  
            -lIlmImf  
            -llibwebp  
            -llibtiff  
            -llibprotobuf  
            -llibpng  
            -lzlib  
            -lipp_iw  
            -lippicvmt 
      } 
    } 

前面代码中库的顺序不是随机的。 这些库需要以其依赖关系的正确顺序包括在内。 您可以在 Visual Studio 2015 中自己检查一下,方法是从主菜单中选择Project,然后选择Project Build Order…。 对于 MacOS 用户,必须在前面的代码中将win32替换为unix: macx,并且库的路径必须与您的构建文件夹中的路径匹配。 对于 Linux,您可以使用与动态库相同的pkgconfig行,如下所示:

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

请注意,即使在 Windows OS 上以静态方式构建 OpenCV 时,输出文件夹中仍将有一个库作为动态库,即opencv_ffmpeg330.dll。 您无需将其包含在*.pro文件中; 但是,您仍然需要将其与应用可执行文件一起部署,因为 OpenCV 本身依赖于它才能支持某些众所周知的视频格式和编码。

构建 Qt 静态库

默认情况下,官方 Qt 安装程序仅提供动态 Qt 库。 在第 1 章,“OpenCV 和 Qt 简介”中也是如此,当我们使用以下链接提供的安装程序在开发环境中安装 Qt 时

因此,简单来说,如果要使用静态 Qt 库,则必须使用其源代码自行构建它们。 您可以按照此处提供的步骤来配置,构建和使用静态 Qt 库:

  1. 为了能够构建一组静态 Qt 库,您需要首先从 Qt 下载网站下载源代码。 通常将它们作为包含所有必需源代码的单个压缩文件(*.zip*.tar.xz等)提供。 在我们的情况下(Qt 版本 5.9.1),您可以使用以下链接下载 Qt 源代码

下载qt-everywhere-opensource-src-5.9.1.zip(或*.tar.xz),然后继续下一步。

  1. 将源代码提取到您选择的文件夹中。 我们假定提取的文件夹名为Qt_Src,并且位于c:/dev文件夹中(在 Windows 操作系统上)。 因此,假设我们提取的 Qt 源代码的完整路径为c:/dev/Qt_Src

对于 MacOS 和 Linux 用户,该路径可能类似于Users/amin/dev/Qt_Src,因此,如果您使用的是上述操作系统之一而不是 Windows,则需要在提供的所有引用它的说明中将其替换。 现在应该已经很明显了。

  1. 现在,您需要先处理一些依赖关系,然后再继续下一步。 MacOS 和 Linux 用户通常不需要执行任何操作,因为默认情况下,所有必需的依赖项都存在于这些操作系统上。 但是,这不适用于 Windows 用户。 通常,在从源代码构建 Qt 之前,计算机上必须存在以下依赖项:
    • ActivePerl
    • Python,您需要版本 2.7.X,而 X 已被最新的现有版本替换,在撰写本书时为 14。
    • 为了方便 Windows 用户,在 Qt 源代码 ZIP 文件的gnuwin32子文件夹内提供了 Bison。 只需确保将c:/dev/Qt_Src/gnuwin32/bin添加到PATH环境变量即可。
    • Flex 与 Bison 相同,位于gnuwin32子文件夹内,需要添加到PATH中。
    • gnuwin32子文件夹内提供了与 Bison 和 Flex 相同的 GNU gperf,需要将其添加到PATH中。

为确保一切正常,请尝试运行相关命令以执行我们刚刚提到的每个依赖项。 可能是您忘记将其中一个依赖项添加到PATH的情况,或者对于 MacOS 和 Linux 用户,由于任何可能的原因,它们已被删除并且不存在。 仅在命令提示符(或终端)中执行以下每个命令并确保您不会遇到无法识别或找不到的错误类型就足够了:
perl
python bison
flex gperf

  1. 现在,在 Windows 上运行 VS2015 的开发人员命令提示符。 在 MacOS 或 Linux 上,运行终端。 您需要运行一组连续的命令,以根据源代码配置和构建 Qt。 该配置是此步骤中最关键的部分,是通过使用configure命令完成的。 configure命令位于 Qt 源文件夹的根目录中,接受以下参数(请注意,实际的参数集很长,因此我们可以满足使用最广泛的参数的要求):

此处提供的参数列表应足以构建具有更多或更少默认设置的静态版本的 Qt 框架:

  1. 现在是时候配置我们的 Qt 构建了。 首先,我们需要使用以下命令切换到 Qt 源代码文件夹:
 cd c:/dev/Qt_Src"
  1. 然后通过键入以下命令开始配置:
 configure -opensource -confirm-license -static -skip webengine  
          -prefix "c:devQtStatic" -platform win32-msvc 

我们提供-skip webengine的原因是因为(编写本书时)目前尚不支持静态构建 Qt WebEngine 模块。 另请注意,我们提供了-prefix参数,这是我们要获取静态库的文件夹。您需要谨慎使用此参数,因为您不能稍后再复制它,并且由于您的构建配置, 静态库仅在它们保留在磁盘上的该位置时才起作用。 我们已经在参数列表中描述了其余参数。

您还可以将以下内容添加到configure命令中,以跳过可能不需要的部分并加快构建过程,因为这将花费很长时间:

-nomake tests -nomake examples 

在 MacOS 和 Linux 上,必须从configure命令中省略以下部分。 这样做的原因仅仅是该平台将被自动检测的事实。 在 Windows 上当然也是如此,但是由于我们要强制 Qt 库的 32 位版本(以支持更大范围的 Windows 版本),因此我们将坚持使用此参数:

-platform win32-msvc

根据您的计算机规格,配置过程不会花费太长时间。 配置完成后,您应该会看到类似以下的输出,否则,您需要再次仔细地执行上述步骤:

Qt is now configured for building. Just run 'nmake'. 
Once everything is built, you must run 'nmake install'. 
Qt will be installed into 'c:devQtStatic'.

Prior to reconfiguration, make sure you remove any leftovers from 
the previous build. 

请注意,在 MacOS 和 Linux 上,上述输出中的nmake将替换为make

  1. 正如配置输出中提到的那样,您需要输入buildinstall命令。

在 Windows 上,使用以下命令:

 nmake 
      nmake install 

在 MacOS 和 Linux 上,使用以下命令:

 make 
      make install 

请注意,由于 Qt 框架包含许多需要构建的模块和库,因此第一个命令通常需要很长时间才能完成(取决于您的计算机规格),因此在此步骤中需要耐心等待。 无论如何,如果您到目前为止已经完全按照提供的所有步骤进行操作,则应该没有任何问题。

重要的是要注意,如果您使用计算机受限区域中的安装文件夹(-prefix参数),则必须确保使用管理员级别(如果使用 Windows)运行命令提示符实例(如果使用 Windows) 带有sudo前缀的buildinstall命令(如果您使用的是 MacOS 或 Linux)。

  1. 运行install命令后,应该将静态 Qt 库放入配置过程中作为前缀参数提供的文件夹(即安装文件夹)中。 因此,在此步骤中,您需要在 Qt Creator 中将这组新建的 Qt 静态库添加为工具包。 为此,请打开 Qt Creator,然后从主菜单中选择“工具”,然后选择“选项”。 从左侧的列表中,选择Build & Run,然后选择Qt Versions选项卡。 现在,按“添加”按钮,然后浏览至Qt build安装文件夹,选择qmake.exe,在本例中,该文件应位于C:devQtStaticbin文件夹内。 以下屏幕截图显示了正确添加新的 Qt 构建后 Qt 版本标签中的状态:

  1. 现在,切换到“套件”选项卡。 您应该能够看到整本书中用来构建 Qt 应用的工具包。 例如,在 Windows 上,它应该是 Desktop Qt 5.9.1 MSVC2015 32bit。 选择它并按“克隆”按钮,然后选择在上一步的“Qt 版本”选项卡中设置的 Qt 版本(如果您在那里看不到自己的版本,则可能需要按一次“应用”按钮,然后按“将显示在组合框中)。 另外,请确保从其名称中删除Clone of,并在其后附加Static一词,以便于区分。 以下屏幕快照表示“工具”选项卡的状态及其配置方式:

关于构建和配置静态 Qt 套件的问题。 现在,您可以使用与默认 Qt 套件(动态套件)完全相同的方式开始使用它创建 Qt 项目。 您唯一需要注意的就是在创建和配置 Qt 项目时将其选择为目标套件。 让我们用一个简单的例子来做到这一点。 首先创建一个 Qt Widgets 应用,并将其命名为StaticApp。 在“工具包选择”页面上,确保选择了新建的静态 Qt 工具包,然后继续按“下一步”,直到进入 Qt 代码编辑器。 以下屏幕快照描述了“工具包选择”页面及其外观(在 Window OS 上):

无需进行太多更改或添加任何代码,只需按“运行”按钮即可构建并执行该项目。 现在,如果浏览到该项目的build文件夹,您会注意到可执行文件的大小比我们使用默认动态工具包进行构建时的大小要大得多。 为了进行比较,在 Windows 操作系统和调试模式下,动态构建的版本应小于 1 兆字节,而静态构建的版本应约为 30 兆字节,甚至更多。 如前所述,这样做的原因是所有必需的 Qt 代码现在都链接到可执行文件中。 尽管严格说来,从技术上讲它并不正确,但是您可以将其视为将库(*.dll文件等)嵌入可执行文件本身中。

现在,让我们尝试在示例项目中也使用静态 OpenCV 库。 只需将所需的附加内容添加到StaticApp.pro文件中,然后尝试使用几个简单的 OpenCV 函数(例如imreaddilateimshow)来测试一组静态 OpenCV 库。 如果现在检查静态链接的可执行文件的大小,您会发现文件大小现在更大。 这样做的明显原因是所有必需的 OpenCV 代码都链接到可执行文件本身。

部署 Qt + OpenCV 应用

向最终用户提供应用包是非常重要的,该包包含它能够在目标平台上运行所需的一切,并且在照顾所需的依赖方面几乎不需要用户付出任何努力。 为应用实现这种开箱即用的条件主要取决于用于创建应用的链接的类型(动态或静态),以及目标操作系统。

使用静态链接的部署

静态部署应用意味着您的应用将独立运行,并且消除了几乎所有需要的依赖项,因为它们已经在可执行文件内部。 只需确保在构建应用时选择发布模式即可,如以下屏幕截图所示:

在发布模式下构建应用时,您只需选择生成的可执行文件并将其发送给用户。

如果尝试将应用部署到 Windows 用户,则在执行应用时可能会遇到类似于以下错误:

发生此错误的原因是,在 Windows 上,即使以静态方式构建 Qt 应用,您仍然需要确保目标系统上存在 Visual C++ 可再发行组件。 这是使用 Microsoft Visual C++ 生成的 C++ 应用所必需的,并且所需的可再发行版本与计算机上安装的 Microsoft Visual Studio 相对应。 在我们的案例中,这些库的安装程序的正式名称是 Visual Studio 2015 的 Visual C++ 可再发行组件,可以从以下链接下载

通常的做法是在我们的应用的安装程序中包含可再发行文件的安装程序,如果尚未安装,请对其进行静默安装。 大多数情况下,您在 Windows PC 上使用的大多数应用都会执行此过程,而您甚至没有注意到它。

我们已经简要地讨论了静态链接的优点(要部署的文件较少)和缺点(可执行文件的大小较大)。 但是,当在部署环境中使用它时,还需要考虑更多的复杂性。 因此,当使用静态链接部署应用时,这是另一个(更完整的)缺点列表:

  • 构建花费更多的时间,并且可执行文件的大小越来越大。
  • 您不能混合使用静态和共享(动态)Qt 库,这意味着您不能使用插件的功能和扩展应用而无需从头开始构建所有内容。
  • 从某种意义上说,静态链接意味着隐藏用于构建应用的库。 不幸的是,并非所有库都提供此选项,并且不遵守该选项可能导致应用出现许可问题。 之所以会出现这种复杂性,部分原因是 Qt 框架使用了一些第三方库,这些库没有提供与 Qt 本身相同的许可选项。 谈论许可问题不是适合本书的讨论,因此,当您计划使用 Qt 库的静态链接创建商业应用时,您必须一定要小心。 有关 Qt 内第三方库使用的许可证的详细列表,您可以始终通过以下链接引用 Qt 网页中使用的许可证

有关 Qt 模块中使用的各种 LGPL 许可证及其版本(以及可在网上找到的许多其他开源软件)的完整参考,请参考以下链接

您还可以使用以下链接完整讨论有关选择 Qt 开源许可证之前需要了解的知识

静态链接,即使有我们刚刚提到的所有缺点,仍然是一种选择,在某些情况下,如果您可以遵守 Qt 框架的许可选项,那么它还是一个很好的选择。 例如,在 Linux 操作系统中,为我们的应用创建安装程序需要额外的工作和精力,静态链接可以极大地减少部署应用所需的工作量(仅复制和粘贴)。 因此,是否使用静态链接的最终决定主要取决于您以及您打算如何部署应用。 当您对可能的链接和部署方法进行了概述时,到本章末尾,制定此重要决定将变得更加容易。

使用动态链接的部署

使用共享库(或动态链接)部署使用 Qt 和 OpenCV 构建的应用时,需要确保应用的可执行文件能够访问 Qt 和 OpenCV 的运行时库,以便加载和使用它们。 运行时库的这种可到达性或可见性取决于操作系统,可能具有不同的含义。 例如,在 Windows 上,您需要将运行时库复制到应用可执行文件所在的文件夹中,或将它们放在附加到PATH环境值的文件夹中。

Qt 框架提供了命令行工具,以简化 Windows 和 MacOS 上 Qt 应用的部署。 如前所述,您需要做的第一件事是确保您的应用是在“发布”模式而不是“调试”模式下构建的。 然后,如果您使用的是 Windows,请首先将可执行文件(假设我们将其称为app.exe)从构建文件夹复制到一个单独的文件夹(我们将其称为deploy_path),然后使用命令执行以下命令行实例:

cd deploy_path 
QT_PATHbinwindeployqt app.exe

windeployqt工具是一个部署帮助工具,可简化将所需的 Qt 运行时库复制到与应用可执行文件相同的文件夹中的过程。 它只是将可执行文件作为参数,并在确定用于创建可执行文件的模块之后,复制所有必需的运行时库以及所有其他必需的依赖项,例如 Qt 插件,翻译等。 这将处理所有必需的 Qt 运行时库,但是我们仍然需要处理 OpenCV 运行时库。 如果您遵循第 1 章,“OpenCV 和 Qt 简介”中的所有步骤来动态构建 OpenCV 库,则只需手动复制opencv_world330.dllopencv_ffmpeg330.dll 将文件从 OpenCV 安装文件夹(在x86vc14bin文件夹内)复制到应用可执行文件所在的文件夹中。

在本书的早期章节中构建 OpenCV 时,我们并没有真正受益于打开BUILD_opencv_world选项的好处。 但是,现在应该清楚的是,这通过以下方式简化了 OpenCV 库的部署和使用:在*.pro文件中只要求 LIBS 的单个条目,并且在以下情况下仅手动复制单个文件(不计算ffmpeg库): 部署 OpenCV 应用。 还应注意的是,即使您在项目中不需要或不使用所有 OpenCV 代码的所有模块,此方法也存在沿应用复制所有 OpenCV 代码的缺点。

还请注意,在 Windows 上,如在“使用静态链接进行部署”一节中所述,您仍然需要类似地向应用的最终用户提供 Microsoft Visual C++ 重分发版。

在 MacOS 操作系统上,还可以轻松部署使用 Qt 框架编写的应用。 因此,可以使用 Qt 提供的macdeployqt命令行工具。 与windeployqt相似,该文件接受 Windows 可执行文件并用所需的库填充同一文件夹,macdeployqt接受 MacOS 应用捆绑包,并通过将所有必需的 Qt 运行时复制为捆绑包内部的私有框架,使其可部署。 这是一个例子:

cd deploy_path 
QT_PATH/bin/macdeployqt my_app_bundle

(可选)您还可以提供一个附加的-dmg参数,该参数导致创建 macOS *.dmg(磁盘图像)文件。 至于使用动态链接时 OpenCV 库的部署,您可以使用 Qt Installer 框架(我们将在下一节中学习),第三方供应商或确保所需运行时库的脚本来创建安装程序。 复制到其所需的文件夹。 这是因为以下事实:仅将运行时库(无论是 OpenCV 还是其他文件)复制到与应用可执行文件相同的文件夹中,并不能使它们对 MacOS 上的应用可见。 这同样适用于 Linux 操作系统,不幸的是,该操作系统甚至还没有用于部署 Qt 运行时库的工具(至少目前是这样),因此除了 OpenCV 库,我们还需要照顾 Qt 库,方法是受信任的第三方供应商(您可以在线搜索)或通过使用 Qt 本身提供的跨平台安装程序,再结合一些脚本来确保执行我们的应用时所有内容都就位。

Qt 安装程序框架

Qt 安装程序框架允许您为 Windows,MacOS 和 Linux 操作系统创建 Qt 应用的跨平台安装程序。 它允许创建标准的安装程序向导,在该向导中,用户会通过提供所有必要信息的连续对话框进入,并最终显示安装应用时的进度等,这与您可能遇到的大多数安装类似,尤其是安装 Qt 框架本身。 Qt 安装程序框架基于 Qt 框架本身,但以不同的包提供,并且不需要计算机上存在 Qt SDK(Qt 框架,Qt Creator 等)。 也可以使用 Qt Installer 框架来为任何应用(不仅仅是 Qt 应用)创建安装包。

在本节中,我们将学习如何使用 Qt Installer 框架创建基本的安装程序,该程序将在目标计算机上安装应用并复制所有必要的依赖项。 结果将是一个可执行的安装程序文件,您可以将其放在 Web 服务器上进行下载,或以 USB 记忆棒或 CD 或任何其他媒体类型提供。 该示例项目将帮助您自己着手解决 Qt Installer 框架的许多强大功能。

您可以使用以下链接下载并安装 Qt 安装程序框架。 使用此链接或其他任何下载源时,请确保仅下载最新版本。 目前,最新版本是 3.0.2

下载并安装 Qt Installer 框架之后,可以开始创建 Qt Installer 框架创建安装程序所需的必需文件。 您可以通过简单地浏览到 Qt Installer 框架并从examples文件夹复制tutorial文件夹来完成此操作,如果要快速重命名和重新编辑所有文件并创建自己的文件夹,这也是一个快速安装模板。 我们将采用另一种方式手动创建它们; 首先,因为我们想了解 Qt Installer 框架所需文件和文件夹的结构,其次,因为它仍然非常容易和简单。 以下是创建安装程序的必需步骤:

  1. 假设您已经完成了 Qt 和 OpenCV 应用的开发,则可以从创建一个包含安装程序文件的新文件夹开始。 假设此文件夹名为deploy
  2. deploy文件夹中创建一个 XML 文件,并将其命名为config.xml。 此 XML 文件必须包含以下内容:
        <?xml version="1.0" encoding="UTF-8"?> 
        <Installer> 
            <Name>Your application</Name> 
            <Version>1.0.0</Version> 
            <Title>Your application Installer</Title> 
            <Publisher>Your vendor</Publisher> 
            <StartMenuDir>Super App</StartMenuDir> 
            <TargetDir>@HomeDir@/InstallationDirectory</TargetDir> 
        </Installer> 

确保用与您的应用相关的信息替换前面代码中的必需 XML 字段,然后保存并关闭此文件:

  1. 现在,在deploy文件夹内创建一个名为packages的文件夹。 该文件夹将包含您希望用户能够安装的单个包,或者使它们成为必需或可选的包,以便用户可以查看并决定要安装的包。

  2. 对于使用 Qt 和 OpenCV 编写的更简单的 Windows 应用,通常仅包含一个包就可以运行您的应用,甚至可以静默安装 Microsoft Visual C++ 重分发版。 但是对于更复杂的情况,尤其是当您想更好地控制应用的各个可安装元素时,您还可以使用两个或多个包,甚至子包。 通过为每个包使用类似域的文件夹名称来完成此操作。 每个包文件夹都可以具有类似com.vendor.product的名称,其中,供应商和产品将被开发人员名称或公司及应用所代替。 可以通过在父包的名称后添加.subproduct来标识包的子包(或子组件)。 例如,您可以在packages文件夹中包含以下文件夹:

        com.vendor.product 
        com.vendor.product.subproduct1 
        com.vendor.product.subproduct2 
        com.vendor.product.subproduct1.subsubproduct1 
        ... 

我们可以根据需要选择任意数量的产品(包装)和子产品(子包装)。 对于我们的示例案例,让我们创建一个包含可执行文件的文件夹,因为它描述了所有可执行文件,您可以通过将其他包简单地添加到packages文件夹中来创建其他包。 让我们将其命名为com.amin.qtcvapp。 现在,请执行以下必需步骤:

  1. 现在,在我们创建的新包文件夹com.amin.qtcvapp文件夹中创建两个文件夹。 将它们重命名为datameta。 这两个文件夹必须存在于所有包中。
  2. 将您的应用文件复制到data文件夹中。 该文件夹将完全按原样提取到目标文件夹中(我们将在后面的步骤中讨论如何设置包的目标文件夹)。 如果您打算创建多个包,请确保以合理的方式正确分离其数据。 当然,如果不这样做,您将不会遇到任何错误,但是您的应用的用户可能会感到困惑,例如,通过跳过应始终安装的包并最终安装已安装的应用,这行不通。
  3. 现在,切换到meta文件夹并在该文件夹中创建以下两个文件,并为每个文件提供的代码填充它们。

package.xml文件应包含以下内容。 无需提及,您必须使用与包相关的值填充 XML 内的字段:

    <?xml version="1.0" encoding="UTF-8"?> 
    <Package> 
        <DisplayName>The component</DisplayName> 
        <Description>Install this component.</Description> 
        <Version>1.0.0</Version> 
        <ReleaseDate>1984-09-16</ReleaseDate> 
        <Default>script</Default> 
        <Script>installscript.qs</Script>  
    </Package> 

前一个 XML 文件中的脚本(可能是安装程序创建中最重要的部分)是指 Qt 安装程序脚本(*.qs文件),其名称为installerscript.qs,可用于进一步自定义包 ,其目标文件夹等。 因此,让我们在meta文件夹中创建一个具有相同名称(installscript.qs)的文件,并在其中使用以下代码:

    function Component() 
    { 
      // initializations go here 
    } 

    Component.prototype.isDefault = function() 
    { 
      // select (true) or unselect (false) the component by default 
      return true; 
    } 

    Component.prototype.createOperations = function() 
    { 
      try { 
        // call the base create operations function 
        component.createOperations(); 
      } catch (e) { 
        console.log(e); 
        } 
    }

这是最基本的组件脚本,可自定义我们的包(很好,它仅执行默认操作),并且可以选择扩展它以更改目标文件夹,在“开始”菜单或桌面(在 Windows 上)中创建快捷方式,等等。 。 密切注意 Qt Installer 框架文档并了解其脚本,以便能够创建功能更强大的安装程序,这些程序可以自动将应用的所有必需依赖项放置到位,是一个好主意。 您还可以浏览 Qt Installer 框架examples文件夹内的所有示例,并了解如何处理不同的部署案例。 例如,您可以尝试为 Qt 和 OpenCV 依赖关系创建单独的包,并允许用户取消选择它们,前提是他们的计算机上已经具有 Qt 运行时库。

  1. 最后一步是使用binarycreator工具来创建我们的单个和独立安装程序。 只需使用命令提示符(或终端)实例运行以下命令:
 binarycreator -p packages -c config.xml myinstaller 

binarycreator位于 Qt Installer 框架bin文件夹内。 它需要我们已经准备好的两个参数。 -p之后必须是我们的packages文件夹,-c之后必须是配置文件(或config.xml)文件。 执行此命令后,您将获得myinstaller(在 Windows 上,可以在其后附加*.exe),可以执行该命令来安装应用。 该单个文件应包含运行您的应用所需的所有必需文件,其余部分将得到处理。 您只需要提供此文件的下载链接,或通过 CD 将其提供给您的用户。

以下是此默认和最基本的安装程序中将面对的对话框,其中包含安装应用时可能会遇到的大多数常见对话框:

如果转到安装文件夹,您会注意到其中包含的文件比放入包数据文件夹中的文件多。 安装程序需要这些文件来处理修改和卸载应用。 例如,您的应用用户可以通过执行maintenancetool可执行文件轻松卸载您的应用,这将产生另一个简单且用户友好的对话框来处理卸载过程:

总结

无论您是否可以在目标计算机上轻松安装并在目标计算机上轻松使用,这都意味着赢得或失去大量用户。 特别是对于非专业用户而言,必须确保创建和部署包含所有必需依赖项的安装程序,并且可以在目标平台上直接使用。 在本章中,我们对此进行了相当多的讨论。 我们了解了构建过程以及所选择的链接方法如何完全改变部署体验。 我们了解了现有的 Qt 工具,以简化 Windows 和 MacOS 上的部署过程。 请注意,这些工具包含的参数比我们在本章中看到的要多得多,因此值得您自己深入研究,并尝试各种参数以了解它们对自己的影响。 在本章的最后一部分,我们了解了 Qt Installer 框架,并通过使用它创建了一个简单的安装程序。 我们学习了如何创建使用安装程序在目标系统上提取的包。 可以使用此相同技能将所有依赖项放入其所需的文件夹中。 例如,可以将 OpenCV 库添加到包中,并在安装时将它们放在 Linux 操作系统的/usr/lib//usr/local/lib/中,以便您的应用可以毫无问题地访问它们。 有了这最后一组技能,我们现在已经熟悉了开发人员(尤其是计算机视觉开发人员)必须知道的开发周期的大多数现有阶段。

在本书的最后一章中,我们将向您介绍 Qt Quick 和 QML。 我们将学习如何使用 Qt 的功能和 QML 的简单性来创建漂亮的 UI。 我们还将学习如何组合 C++ 和 QML 代码,以编写使用第三方框架(例如 OpenCV)的类,这些类可从我们的 QML 代码中轻松使用。 本书的最后一章旨在帮助您结合使用 OpenCV 和极其易于使用且美观的 Qt Quick Controls,开始开发用于移动设备(Android 和 iOS)的计算机视觉应用。

十二、Qt Quick 应用

使用 Qt 窗口小部件应用项目允许通过使用 Qt Creator 设计模式创建灵活而强大的 GUI,或者在文本编辑器中手动修改 GUI 文件(*.ui)。 到目前为止,在本书的所有章节中,我们都基于 Qt Widgets 应用作为创建的 GUI 的基础,并且我们在第 3 章,“创建一个全面的 Qt + OpenCV 项目”中了解到,我们可以使用样式表来有效地更改 Qt 应用的外观。 但是,除了 Qt Widgets 应用并使用QtWidgetsQtGui模块之外,Qt 框架还提供了另一种创建 GUI 的方法。 这种方法基于QtQuick模块和 QML 语言,并且允许创建更加灵活的 GUI(在外观,感觉,动画,效果等方面),并且更加轻松。 使用这种方法创建的应用称为 Qt Quick 应用。 请注意,在较新的 Qt 版本(5.7 和更高版本)中,您还可以创建 Qt Quick Controls 2 应用,它为创建 Qt Quick 应用提供了更多改进的类型,我们还将重点关注这一点。

QtQuick模块和QtQml模块是包含所有必需类的模块,以便在 C++ 应用中使用 Qt Quick 和 QML 编程。 另一方面,QML 本身是一种高度可读的声明性语言,它使用类似于 JSON 的语法(与脚本结合)来描述用户界面的各种组件以及它们之间的交互方式。 在本章中,我们将向您介绍 QML 语言以及如何使用它简化创建 GUI 应用的过程。 通过创建示例基于 QML 的 GUI 应用(或更确切地说是 Qt Quick Controls 2 应用),我们将了解其简单易读的语法以及如何在实践中使用它。 尽管使用 QML 语言不一定需要对 C++ 语言有深入的了解,但了解 Qt Quick 项目的结构仍然非常有用,因此我们将简要介绍最基本的 Qt Quick 应用的结构。 通过研究一些最重要的 QML 库,我们将了解现有的可视和非可视 QML 类型,这些类型可用于创建用户界面,向其中添加动画,访问硬件等。 我们将学习如何使用集成到 Qt Creator 中的 Qt Quick Designer 通过图形设计器修改 QML 文件。 稍后,通过学习 C++ 和 QML 的集成,我们将填补它们之间的空白,并学习如何在 Qt Quick 应用中使用 OpenCV 框架。 在最后一章中,我们还将学习如何使用与 Qt 和 OpenCV 相同的桌面项目来创建移动计算机视觉应用,并将我们的跨平台范围扩展到桌面平台之外,并扩展到移动世界。

本章涵盖的主题包括:

  • QML 简介
  • Qt Quick 应用项目的结构
  • 创建 Qt Quick Controls 2 应用
  • 使用 Qt Quick Designer
  • 集成 C++ 和 QML
  • 在 Android 和 iOS 上运行 Qt 和 OpenCV 应用

QML 简介

如引言中所述,QML 具有类似于 JSON 的结构,可用于描述用户界面上的元素。 QML 代码导入一个或多个库,并且具有一个包含所有其他可视和非可视元素的根元素。 以下是 QML 代码的示例,该代码导致创建具有指定宽度,高度和标题的空窗口(ApplicationWindow类型):

    import QtQuick 2.7 
    import QtQuick.Controls 2.2 

    ApplicationWindow 
    { 
      visible: true 
      width: 300 
      height: 500 
      title: "Hello QML" 
    } 

每个import语句后都必须带有 QML 库名称和版本。 在前面的代码中,导入了包含大多数默认类型的两个主要 QML 库。 例如,在QtQuick.Controls 2.2库中定义了ApplicationWindow。 现有 QML 库及其正确版本的唯一真实来源是 Qt 文档,因此请确保始终引用它,以防需要使用其他任何类。 如果使用 Qt Creator 帮助模式搜索ApplicationWindow,您将发现所需的import语句就是我们刚刚使用的。 值得一提的另一件事是,先前代码中的ApplicationWindow是单个根元素,并且所有其他 UI 元素都必须在其中创建。 让我们通过添加显示一些文本的Label元素来进一步扩展代码:

    ApplicationWindow 
    { 
      visible: true 
      width: 300 
      height: 500 
      title: "Hello QML" 

      Label 
      { 
        x: 25 
        y: 25 
        text: "This is a label<br>that contains<br>multiple lines!" 
      } 
    } 

由于它们与以前的代码相同,因此我们跳过了前面的代码中的import语句。 请注意,新添加的Label具有text属性,该属性是标签上显示的文本。 xy只是指LabelApplicationWindow内部的位置。 可以使用非常类似的方式添加诸如组框之类的容器项。 让我们添加一个,看看它是如何完成的:

    ApplicationWindow 
    { 
      visible: true 
      width: 300 
      height: 500 
     title: "Hello QML" 
      GroupBox 
      { 
        x: 50 
        y: 50 
        width: 150 
        height: 150 
        Label 
        { 
          x: 25 
          y: 25 
          text: "This is a label<br>that contains<br>multiple lines!" 
        } 
      } 
    } 

此 QML 代码将导致一个类似于以下所示的窗口:

请注意,每个元素的位置都是与其父元素的偏移量。 例如,将GroupBox内提供给Labelxy值添加到GroupBox本身的xy属性中,这就是在根元素(在本例中为ApplicationWindow)中确定 UI 元素的最终位置。

与 Qt 窗口小部件类似,您也可以在 QML 代码中使用布局来控制和组织 UI 元素。 为此,可以使用GridLayoutColumnLayoutRowLayout QML 类型,但首先,需要使用以下语句导入它们:

    import QtQuick.Layouts 1.3 

现在,您可以将 QML 用户界面元素作为子项添加到布局中,并由其自动管理。 让我们在ColumnLayout中添加一些按钮,看看如何完成此操作:

    ApplicationWindow 
    { 
      visible: true 
      width: 300 
      height: 500 
      title: "Hello QML" 

      ColumnLayout 
      { 
        anchors.fill: parent 
        Button 
        { 
          text: "First Button" 
          Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter 
        } 
        Button 
        { 
          text: "Second Button" 
          Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter 
        } 
        Button 
        { 
          text: "Third Button" 
          Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter 
        } 
      } 
    } 

这将导致类似于以下的窗口:

在前面的代码中,ColumnLayout的行为类似于我们在 Qt Widgets 应用中使用的垂直布局。 从上到下,作为子元素添加到ColumnLayout的每个元素都会显示在前一个元素之后,无论ColumnLayout的大小如何,始终调整其大小和位置以保持垂直布局视图。 关于上述内容,还有两点需要注意。 首先,使用以下代码将ColumnLayout本身的大小设置为父大小:

    anchors.fill: parent

anchors是 QML 视觉元素的最重要属性之一,它照顾元素的大小和位置。 在这种情况下,通过将anchorsfill值设置为另一个对象(parent对象),我们将ColumnLayout的大小和位置描述为与ApplicationWindow相同。 通过正确使用锚点,我们可以以更大的功能和灵活性处理对象的大小和位置。 作为另一个示例,将代码中的anchors.fill行替换为以下内容,然后看看会发生什么:

    width: 100 
    height: 100 
    anchors.centerIn: parent 

显然,我们的ColumnLayout现在具有恒定的大小,并且当ApplicationWindow调整大小时它不会改变; 但是,布局始终保持在ApplicationWindow的中心。 关于上述代码的最后一点是:

    Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter 

添加到ColumnLayout的每个项目内的该行使该项目将自身垂直和水平定位在其单元格的中心。 请注意,这种意义上的单元格不包含任何可视边界,并且与布局本身一样,布局内的单元格也是在其中组织项目的非可视方式。

QML 代码的扩展遵循相同的模式,无论添加或需要多少项。 但是,随着 UI 元素的数量越来越大,最好将用户界面分成单独的文件。 可以将同一文件夹中的 QML 文件用作预定义的重要项目。 假设我们有一个名为MyRadios.qml的 QML 文件,其中包含以下代码:

    import QtQuick 2.7 
    import QtQuick.Controls 2.2 
    import QtQuick.Layouts 1.3 

    Item 
    { 
      ColumnLayout 
      { 
        anchors.centerIn: parent 

        RadioButton 
        { 
            text: "Video" 
        } 
        RadioButton 
        { 
            text: "Image" 
        } 
      } 
    } 

您可以在同一文件夹的另一个 QML 文件中使用此 QML 文件及其Item。 假设我们在MyRadios.qml所在的文件夹中有一个main.qml文件。 然后,您可以像这样使用它:

    import QtQuick 2.7 
    import QtQuick.Controls 2.2 
    import QtQuick.Layouts 1.3 

    ApplicationWindow 
    { 
      visible: true 
      width: 300 
      height: 500 
      title: "Hello QML" 

      ColumnLayout 
      { 
        anchors.fill: parent 

        MyRadios 
        { 
            width: 100 
            height: 200 
        } 
      } 
    } 

请注意,只要 QML 文件都在同一文件夹中,就不需要导入语句。 如果要在代码中使用的 QML 文件位于单独的文件夹(同一文件夹中的子文件夹)中,则必须使用以下语句将其导入:

    import "other_qml_path" 

显然,在前面的代码中,other_qml_path是我们的 QML 文件的相对路径。

QML 中的用户交互和脚本编写

对 QML 代码中的用户操作和事件的响应是通过将脚本添加到项目的插槽中来完成的,这与 Qt 窗口小部件非常相似。 此处的主要区别在于,在 QML 类型内部定义的每个信号还具有为其自动生成的对应插槽,并且可以填充脚本以在发出相关信号时执行操作。 好吧,让我们看另一个例子。 QML Button类型具有按下信号。 这自动意味着有一个onPressed插槽,可用于编码特定按钮的所需操作。 这是一个示例代码:

    Button 
    { 
      onPressed:  
      { 
        // code goes here 
      } 
    } 

有关 QML 类型的可用插槽的列表,请参阅 Qt 文档。 如前所述,您可以通过大写信号名称的第一个字母并在其前面加上on来轻松猜测每个信号的插槽名称。 因此,对于pressed信号,您将有一个onPressed插槽,对于released信号,您将有一个onReleased插槽,依此类推。

为了能够从脚本或插槽中访问其他 QML 项目,首先,您必须为其分配唯一的标识符。 请注意,这仅是您要访问和修改或与之交互的项目所必需的。 在本章的所有先前示例中,我们仅创建了项目,而没有为其分配任何标识符。 通过为项目的id属性分配唯一标识符,可以轻松完成此操作。 id属性的值遵循变量命名约定,这意味着它区分大小写,不能以数字开头,依此类推。 这是一个示例代码,演示如何在 QML 代码中分配和使用id

    ApplicationWindow 
    { 
      id: mainWindow 
      visible: true 
      width: 300 
      height: 500 
      title: "Hello QML" 

      ColumnLayout 
      { 
        anchors.fill: parent 
        Button 
        { 
          text: "Close" 
          Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter 

          onPressed: 
          { 
            mainWindow.close() 
          } 
        } 
      } 
    } 

在前面的代码中,ApplicationWindow分配有一个 ID; 也就是mainWindow,它在ButtononPressed插槽内用于访问它。 您可以猜测,按前面代码中的“关闭”按钮将导致mainWindow被关闭。 无论在 QML 文件中的哪个位置定义 ID,都可以在该特定 QML 文件中的任何位置访问它。 这意味着 ID 的范围不限于相同的项目组或项目的子级,依此类推。 简而言之,任何 ID 对 QML 文件中的所有项目都是可见的。 但是,单独的 QML 文件中某项的id呢? 为了能够访问单独的 QML 文件中的项目,我们需要通过将其分配给property alias来导出它,如以下示例所示:

    Item 
    { 
      property alias videoRadio: videoRadio 
      property alias imageRadio: imageRadio 
      ColumnLayout 
      { 
        anchors.centerIn: parent 
        RadioButton 
        { 
          id: videoRadio 
          text: "Video" 
        } 
        RadioButton 
        { 
          id: imageRadio 
          text: "Image" 
        } 
      } 
    }

前面的代码是相同的MyRadios.qml文件,但是这次,我们使用根项的别名属性导出了其中的两个RadioButton项。 这样,我们可以在使用MyRadios的单独 QML 文件中访问这些项目。 除了导出项目中的项目外,属性还可用于包含特定项目所需的任何其他值。 因此,这是在 QML 项中定义附加属性的一般语法:

    property TYPE NAME: VALUE 

TYPE可以包含任何 QML 类型的情况下,NAME是属性的给定名称,VALUE是属性的值,必须与提供的类型兼容。

使用 Qt Quick Designer

由于 QML 文件的语法简单易读,因此可以使用任何代码编辑器轻松对其进行修改和扩展。 但是,您也可以使用 Qt Creator 中集成的快速设计器来简化 QML 文件的设计和修改。 如果您尝试在 Qt Creator 中打开 QML 文件并切换到“设计”模式,则会看到以下“设计”模式,它与标准 Qt Widgets 设计器(用于*.ui文件)有很大不同, 包含使用 QML 文件快速设计用户界面所需的大部分内容:

在“Qt Quick 设计器”屏幕的左侧,您可以在“库”窗格中看到可以添加到用户界面的 QML 类型的库。 它与 Qt Widgets 工具箱类似,但肯定有更多组件可用于设计应用的用户界面。 您只需在用户界面上拖放它们中的每一个,它们就会自动添加到您的 QML 文件中:

“库”窗格的正下方是“导航器”窗格,它在用户界面上显示组件的层次结构视图。 您可以使用“导航器”窗格,只需双击它们即可快速设置 QML 文件中的项目 ID。 此外,您可以将项目导出为别名,以便可以在其他 QML 文件中使用它,也可以在设计时将其隐藏(以便查看重叠的 QML 项目)。 在“导航器”窗格上的以下屏幕快照中,请注意在将button2导出为别名并将button3在设计期间隐藏之后,组件旁边的小图标是如何变化的:

在 Qt Quick 设计器的右侧,您可以找到“属性”窗格。 与标准 Qt 设计模式下的“属性”窗格相似,此窗格可用于详细操作和修改 QML 项的属性。 该窗格的内容根据用户界面上的选定项目而变化。 除了 QML 项目的标准属性外,此窗格还允许修改与单个项目的布局有关的属性。 以下屏幕快照描绘了在用户界面上选择“按钮”项时“属性”窗格的不同视图:

除了用于设计 QML 用户界面的辅助工具外,Qt Quick Designer 可以帮助您了解 QML 语言本身,因为在设计器中完成的所有修改都将转换为 QML 代码并存储在同一 QML 文件中。 通过使用它来设计用户界面,以确保熟悉它的用法。 例如,您可以尝试设计一些与创建 Qt Widgets 应用时相同的用户界面,但是这次使用 Qt Quick Designer 和 QML 文件。

Qt Quick 应用的结构

在本节中,我们将学习 Qt Quick 应用项目的结构。 与 Qt Widgets 应用项目类似,使用 Qt Creator 创建新项目时,会自动创建 Qt Quick 应用项目所需的大多数文件,因此您实际上并不需要记住所有的最低要求,但是仍然重要的是要理解如何处理 Qt Quick 应用的一些基本概念,以便能够进一步扩展它,或者,如我们将在本章后面的部分中了解的那样,在 QML 文件中集成和使用 C++ 代码。

让我们通过创建一个示例应用来解决这个问题。 首先打开 Qt Creator,然后在欢迎屏幕上按“新建项目”按钮,或者从“文件”菜单中选择“新建文件”或“项目”。 选择“Qt Quick Controls 2 应用”作为模板类型,然后按“选择”,如以下屏幕截图所示:

将项目名称设置为CvQml,然后按Next。 在“定义构建系统”页面中,将“构建系统”保留为qmake,默认情况下应将其选中。 在“定义项目详细信息”页面中,可以为 Qt Quick Controls 2 样式选择以下选项之一:

  • 默认
  • 材料
  • 通用

您在此屏幕中选择的选项会影响应用的整体样式。 “默认”选项会导致使用默认样式,从而使 Qt Quick Controls 2 以及我们的 Qt Quick 应用具有最高性能。 Material 样式可用于根据 Google Material Design 准则创建应用。 它提供了更具吸引力的组件,但也需要更多资源。 最后,通用样式可用于基于 Microsoft 通用设计准则创建应用。 与 Material 风格相似,这也需要更多资源,但提供了另一套引人注目的用户界面组件。

您可以参考以下链接,以获得有关用于创建“材质”和“通用”样式的准则的更多信息:

https://goo.gl/TiQEYB

https://dev.windows.com/design

下面的截图描述了一些常见的组件之间的差异,在选择的三种可能的风格每一个选项如何看您的应用:

无论您选择什么,以后都可以在名为qtquickcontrols2.conf的专用设置文件中轻松更改此设置,该文件会自动包含在新项目中。 甚至可以在以后更改颜色以匹配深色或浅色主题或任何其他颜色。 无论如何,请选择所需的一个(或将其保留为默认),然后继续按Next,直到最终进入 Qt 代码编辑器。 现在,您的项目几乎包含 Qt Quick 应用所需的最少文件。

请注意,每当我们在本章中提到 Qt Quick 应用时,我们实际上是指 Qt Quick Controls 2 应用,它是我们刚刚创建并将扩展到的新的增强型 Qt Quick 应用(在 Qt 5.7 和更高版本中可用)。 完整,美观的跨平台计算机视觉应用。

首先,让我们看一下项目(*.pro)文件中的区别。 在与 Qt Widgets 应用相对的 Qt Quick 应用中,默认情况下使用QtQmlQtQuick模块代替QtCoreQtGuiQtWidgets模块。 您可以通过打开CvQml.pro文件来进行检查,该文件的顶部具有以下行:

    QT += qml quick 

您可以在 Qt 项目中期望的两个文件,无论是 Qt Widgets 应用还是 Qt Quick 应用,都是一个项目和一个包含main函数的 C++ 源文件。 因此,除了CvQml.pro文件之外,还有一个main.cpp文件,其中包含以下内容:

    #include <QGuiApplication> 
    #include <QQmlApplicationEngine> 

    int main(int argc, char *argv[]) 
    { 
      QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 
      QGuiApplication app(argc, argv); 

      QQmlApplicationEngine engine; 
      engine.load(QUrl(QLatin1String("qrc:/main.qml"))); 
      if (engine.rootObjects().isEmpty()) 
        return -1; 

      return app.exec(); 
    }

main.cpp与创建 Qt Widgets 应用时所看到的完全不同。 记住,在 Qt Widgets 应用的main.cpp内部和主函数中,创建了QApplication,然后显示主窗口,程序进入事件循环,以便该窗口保持活动状态,并且所有事件已处理,如下所示:

    #include "mainwindow.h" 
    #include <QApplication> 

    int main(int argc, char *argv[]) 
    { 
      QApplication a(argc, argv); 
      MainWindow w; 
      w.show(); 

      return a.exec(); 
    } 

类似地,在 Qt Quick 应用中,创建了QGuiApplication,但是这次没有加载任何窗口,而是使用QQmlApplicationEngine加载了 QML 文件,如下所示:

    QQmlApplicationEngine engine; 
    engine.load(QUrl(QLatin1String("qrc:/main.qml"))); 
    if (engine.rootObjects().isEmpty()) 
      return -1; 

这清楚地表明 QML 文件实际上是在运行时加载的,因此您可以从磁盘加载它们,或者在我们的示例中,可以从作为资源存储在qml.qrc文件中并嵌入到可执行文件中的main.qml文件加载它们。 实际上,这是开发 Qt Quick 应用的常用方法,如果您检查新创建的CvQml项目,则会注意到它包含一个名为qml.qrc的 Qt 资源文件,其中包含该项目的所有 QML 文件 。 qml.qrc文件包含以下文件:

  • main.qml,它是main.cpp文件中加载的 QML 文件,它是我们 QML 代码的入口点。
  • Page1.qml包含Page1Form QML 类型的交互和脚本。
  • Page1Form.ui.qml包含Page1Form类型内的用户界面和 QML 项目。 请注意,成对的Page1.qmlPage1Form.ui.qml是分离用户界面及其底层代码的常用方法,类似于在开发 Qt Widgets 应用时使用mainwindow.uimainwindow.hmainwindow.cpp文件的方法。 。
  • qtquickcontrols2.conf文件是可用于更改 Qt Quick 应用样式的配置文件。 它包含以下内容:
        ; This file can be edited to change the style of the application 
        ; See Styling Qt Quick Controls 2 in the documentation ...  
        ; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html 

        [Controls] 
        Style=Default 

        [Universal] 
        Theme=Light 
        ;Accent=Steel 

        [Material] 
        Theme=Light 
        ;Accent=BlueGrey 
        ;Primary=BlueGray 

行首的分号;表示仅是注释。 您可以将前面代码中的Style变量的值更改为MaterialUniversal,以更改应用的整体样式。 根据所设置的样式,可以在前面的代码中使用ThemeAccentPrimary值来更改应用中使用的主题。

有关主题和颜色的完整列表,以及有关如何在每个主题中使用各种可用的自定义设置的其他信息,您可以参考以下链接:

https://goo.gl/jDZGPm(用于默认样式)

https://goo.gl/Um9qJ4(用于材料样式)

https://goo.gl/U6uxrh(用于通用样式)

关于 Qt Quick 应用的一般结构。 这种结构可立即用于任何平台的任何类型的应用。 请注意,您没有义务使用自动创建的文件,并且可以简单地从一个空项目开始或删除不必要的默认文件并从头开始。 例如,在我们的示例 Qt Quick 应用(标题为CvQml)中,我们不需要Page1.qmlPage1Form.ui.qml文件,因此只需从qml.qrc文件中选择它们并通过右键单击将其删除。 然后选择删除文件。 当然,这将导致main.qml文件中缺少代码。 因此,在继续下一部分之前,请确保将其更新为以下内容:

    import QtQuick 2.7 
    import QtQuick.Controls 2.0 
    import QtQuick.Layouts 1.3 

    ApplicationWindow 
    { 
      visible: true 
      width: 300 
      height: 500 
      title: qsTr("CvQml") 
    } 

集成 C++ 和 QML 代码

即使 QML 库已经成长为可以处理视觉,网络,摄像机等的完整类型集合,但仍然可以使用 C++ 类的功能对其进行扩展仍然很重要。 幸运的是,QML 和 Qt 框架提供了足够的规定以能够轻松地处理此问题。 在本节中,我们将学习如何创建一个非可视的 C++ 类,该类可以在 QML 代码内使用 OpenCV 处理图像。 然后,我们将创建一个 C++ 类,该类可用作 QML 代码中的可视项以显示图像。

请注意,默认情况下,QML 中有一个图像类型,可通过将其 URL 提供给“图像”项来显示保存在磁盘上的图像。 但是,我们将创建一个可用于显示QImage对象的图像查看器 QML 类型,并利用此机会来学习 CML 类(可视化)在 QML 代码中的集成。

首先将 OpenCV 框架添加到上一节中创建的项目中。 这与创建 Qt Widgets 应用时完全相同,并且在*.pro文件中包含必需的行。 然后,通过在项目窗格中右键单击新的 C++ 类并将其添加到项目中,然后选择“添加新的”。 确保类名称为QImageProcessor且其基类为QObject,如以下屏幕截图所示:

将以下#include指令添加到qimageprocessor.h文件中:

    #include <QImage> 
    #include "opencv2/opencv.hpp" 

然后将以下函数添加到QImageProcessor类的公共成员区域:

    Q_INVOKABLE void processImage(const QString &path);

Q_INVOKABLE是 Qt 宏,它允许使用 Qt 元对象系统调用(调用)函数。 由于 QML 使用相同的 Qt 元对象作为对象之间的基础通信机制,因此用Q_INVOKABLE宏标记函数就足够了,以便可以从 QML 代码中调用它。 另外,将以下信号添加到QImageProcessor类:

    signals: 
        void imageProcessed(const QImage &image); 

我们将使用此信号将经过处理的图像传递给稍后将创建的图像查看器类。 最后,为了实现processImage函数,请将以下内容添加到qimageprocessor.cpp文件中:

    void QImageProcessor::processImage(const QString &path) 
    { 
      using namespace cv; 
      Mat imageM = imread(path.toStdString()); 
      if(!imageM.empty()) 
      { 
        bitwise_not(imageM, imageM); // or any OpenCV code 
        QImage imageQ(imageM.data, 
                      imageM.cols, 
                      imageM.rows, 
                      imageM.step, 
                      QImage::Format_RGB888); 
        emit imageProcessed(imageQ.rgbSwapped()); 
      } 
      else 
      { 
        qDebug() << path << "does not exist!"; 
      } 
    } 

这里没有我们没有看到或使用过的新东西。 此函数仅获取图像的路径,从磁盘读取图像,执行图像处理,但为了简单起见,我们可以使用bitwise_not函数将所有通道中的像素值取反,最后使用我们定义的信号的图像产生结果。

我们的图像处理器现已完成。 现在,我们需要创建一个 Visual C++ 类型,该类型可在 QML 中用于显示QImage对象。 因此,创建另一个类并将其命名为QImageViewer,但这一次请确保它是QQuickItem子类,如以下新类向导屏幕截图所示:

修改qimageviewer.h文件,如下所示:

    #include <QQuickItem> 
    #include <QQuickPaintedItem> 
    #include <QImage> 
    #include <QPainter> 

    class QImageViewer : public QQuickPaintedItem 
    { 
      Q_OBJECT 
      public: 
        QImageViewer(QQuickItem *parent = Q_NULLPTR); 
        Q_INVOKABLE void setImage(const QImage &img); 

      private: 
        QImage currentImage; 
        void paint(QPainter *painter); 

    }; 

我们已经将QImageViewer类设为QQuickPaintedItem的子类。 同样,构造器也会进行更新以匹配此修改。 我们在此类中使用Q_INVOKABLE宏定义了另一个函数,该函数将用于设置要在此类实例上显示的QImage,或者确切地说,将设置使用该类型创建的 QML 项。 QQuickPaintedItem提供了一种创建新的可视 QML 类型的简单方法; 也就是说,通过对其进行子类化并重新实现paint函数,如前面的代码所示。 传递给此类中的paint函数的painter指针可用于绘制我们需要的任何内容。 在这种情况下,我们只想在其上绘制图像; 也就是说,我们已经定义了currentImage,它是QImage,它将保存要在QImageViewer类上绘制的图像。

现在,我们需要添加setImage的实现并绘制函数,并根据在头文件中所做的更改来更新构造器。 因此,请确保qimageviewer.cpp文件如下所示:

    #include "qimageviewer.h" 

    QImageViewer::QImageViewer(QQuickItem *parent) 
      : QQuickPaintedItem(parent) 
    { 
    } 

    void QImageViewer::setImage(const QImage &img) 
    { 
      currentImage = img.copy(); // perform a copy 
      update(); 
    } 

    void QImageViewer::paint(QPainter *painter) 
    { 
      QSizeF scaled = QSizeF(currentImage.width(), 
                           currentImage.height()) 
            .scaled(boundingRect().size(), Qt::KeepAspectRatio); 
      QRect centerRect(qAbs(scaled.width() - width()) / 2.0f, 
                     qAbs(scaled.height() - height()) / 2.0f, 
                     scaled.width(), 
                     scaled.height()); 
      painter->drawImage(centerRect, currentImage); 
    } 

在前面的代码中,setImage函数非常简单; 它会复制图像并将其保存,然后调用QImageViwer类的更新函数。 在QQuickPaintedItem(类似于QWidget)内部调用update时,将导致重新绘制,因此将调用我们的绘制函数。 如果我们想在QImageViewer的整个可显示区域上拉伸图像,则此函数仅需要最后一行(centerRect替换为boundingRect); 但是,我们希望结果图像适合屏幕并保留宽高比。 因此,我们进行了比例转换,然后确保图像始终位于可显示区域的中心。

我们快到了,我们的两个新 C++ 类(QImageProcessorQImageViewer)都可以在 QML 代码中使用。 剩下要做的唯一事情就是确保它们对我们的 QML 代码可见。 因此,我们需要确保使用qmlRegisterType函数注册了它们。 必须在我们的main.cpp文件中调用此函数,如下所示:

    qmlRegisterType<QImageProcessor>("com.amin.classes", 
      1, 0, "ImageProcessor"); 
    qmlRegisterType<QImageViewer>("com.amin.classes", 
      1, 0, "ImageViewer"); 

然后将其放在main.cpp文件中定义QQmlApplicationEngine的位置之前。 不用说,您必须通过使用以下#include指令在main.cpp文件中包括我们两个新类:

    #include "qimageprocessor.h" 
    #include "qimageviewer.h" 

请注意,qmlRegisterType函数调用中的com.amin.classes可以用您自己的类似域的标识符替换,这是我们为包含QImageProcessorQImageViewer类的库提供的名称。 以下10引用该库的版本 1.0,最后一个文字字符串是可在我们的 QML 类型内部使用的类型标识符,以访问和使用这些新类。

最后,我们可以开始使用main.qml文件中的 C++ 类。 首先,确保您的import语句符合以下条件:

    import QtQuick 2.7 
    import QtQuick.Controls 2.0 
    import QtQuick.Layouts 1.3 
    import QtMultimedia 5.8 
    import com.amin.classes 1.0 

最后一行包括我们刚刚创建的ImageProcessorImageViewer QML 类型。 我们将使用 QML 摄像机类型访问摄像机并使用它捕获图像。 因此,将以下内容添加为main.qml文件中ApplicationWindow项目的直接子代:

    Camera 
    { 
      id: camera 
      imageCapture 
      { 
        onImageSaved: 
        { 
          imgProcessor.processImage(path) 
        } 
      } 
    } 

在前面的代码中,imgProcessor是我们的ImageProcessor类型的id,还需要将其定义为ApplicationWindow的子项,如下所示:

    ImageProcessor 
    { 
      id: imgProcessor 
      onImageProcessed: 
      { 
        imgViewer.setImage(image); 
        imageDrawer.open() 
      } 
    } 

请注意,由于我们在QImageProcessor类内部创建了imageProcessed信号,因此自动生成了前面代码中的onImageProcessed插槽。 您可以猜测imgViewer是我们之前创建的QImageViewer类,并且将其图像设置在onImageProcessed插槽内。 在此示例中,我们还使用了 QML Drawer,该 QML Drawer在调用其打开函数时在另一个窗口上滑动,并且我们已嵌入imgViewer作为此Drawer的子项。 DrawerImageViewer的定义如下:

    Drawer 
    { 
      id: imageDrawer 
      width: parent.width 
      height: parent.height 
      ImageViewer 
      { 
        id: imgViewer 
        anchors.fill: parent 
        Label 
        { 
          text: "Swipe from right to left<br>to return to capture mode!" 
          color: "red" 
        } 
      } 
    } 

就是这样,剩下要做的唯一一件事情就是添加一个 QML VideoOutput,它可以预览摄像机。 我们将使用此VideoOutput捕获图像,从而调用 QML Camera类型的imageCapture.onImageSaved插槽,如下所示:

    VideoOutput 
    { 
      source: camera 
      anchors.fill: parent 
      MouseArea 
      { 
        anchors.fill: parent 
        onClicked: 
        { 
          camera.imageCapture.capture() 
        } 
      } 
      Label 
      { 
        text: "Touch the screen to take a photo<br>and process it using OpenCV!" 
        color: "red" 
      } 
    } 

如果立即启动该应用,您将立即面对计算机上默认照相机的输出。 如果单击视频输出内部,将捕获并处理图像,然后将其显示在Drawer上,该Drawer在当前页面上从左到右滑动。 以下是该应用执行时的屏幕截图:

Android 和 iOS 上的 Qt 和 OpenCV 应用

理想情况下,您可以在台式机和移动平台上构建并运行通过使用 Qt 和 OpenCV 框架创建的应用,而无需编写任何特定于平台的代码。 但是,实际上,这并不像看起来那样容易,因为 Qt 和 OpenCV 之类的框架充当操作系统本身功能的包装器(在某些情况下),并且由于它们仍在进行广泛的开发,因此可能会有一些尚未在特定操作系统(例如 Android 或 iOS)中完全实现的案例。 好消息是,随着新版本的 Qt 和 OpenCV 框架的发布,这些情况变得越来越罕见,即使现在(Qt 5.9 和 OpenCV 3.3),这两个框架中的大多数类和函数都可以在 Windows 中轻松使用。 ,Linux,macOS,Android 和 iOS 操作系统。

因此,首先,请牢记我们刚才提到的内容,可以说(实际上是相对于理想情况而言),要能够在 Android 和 iOS 上构建和运行使用 Qt 和 OpenCV 编写的应用,我们需要确保以下东西:

  • 必须安装适用于 Android 和 iOS 的相应 Qt 套件。 这可以在 Qt 框架的初始安装过程中完成(有关此信息,请参阅第 1 章,“OpenCV 和 Qt 简介”)。

请注意,Android 套件可在 Windows,Linux 和 MacOS 上使用,而 iOS 套件仅适用于 macOS,因为使用 Qt 的 iOS 应用开发仅限于 macOS(目前)。

  • 必须从 OpenCV 网站上下载适用于 Android 和 iOS 的预构建 OpenCV 库(目前,它们是从 opencv.org 提供)并提取到您的计算机中。 必须按照在 Windows 或任何其他桌面平台中添加的方式将它们添加到 Qt 项目文件中。
  • 对于 iOS,在您的 MacOS 操作系统上拥有最新版本的 Xcode 就足够了。
  • 对于 Android,您必须确保在计算机上安装 JDK,Android SDK,Android NDK 和 Apache Ant。 如果使用 Qt Creator 选项内“设备”页面中的 Android 选项卡,将所需的程序下载并安装到计算机上,则 Qt Creator 可以简化 Android 开发环境的配置(请参见以下屏幕截图):

请注意上图中“浏览”按钮旁边的按钮。 它们提供了下载页面的链接以及在线链接,您可以从中获得所有必需依赖项的副本。

如果要为 Android 和 iOS 操作系统构建应用,这就是您需要照顾的所有事情。 使用 Qt 和 OpenCV 构建的应用也可以在 Windows,macOS,Android 和 iOS 的应用商店中发布。 此过程通常涉及与这些操作系统的提供者注册为开发人员。 您可以在上述应用商店中找到在线和在全球范围内发布应用的准则和要求。

总结

在本章中,我们了解了 Qt Quick 应用开发和 QML 语言。 我们从这种高度可读且易于使用的语言的裸露语法开始,然后转向开发包含可以相互交互以实现一个共同目标的组件的应用。 我们学习了如何填补 QML 和 C++ 代码之间的空白,然后建立了可视类和非可视类来处理和显示使用 OpenCV 处理的图像。 我们还简要介绍了在 Android 和 iOS 平台上构建和运行相同应用所需的工具。 本书的最后一章旨在通过开始使用新的 Qt Quick Controls 2 模块开发快速,美观的应用,并将 C++ 代码和 OpenCV 等第三方框架的功能结合起来,来帮助您站起来。 在开发移动和桌面应用时获得最大的功能和灵活性。

构建跨平台和吸引人的应用从未如此简单。 通过使用 Qt 和 OpenCV 框架,尤其是 QML 的功能,可以快速轻松地构建应用,您可以立即开始实现所有计算机视觉创意。 我们在本章中学到的只是对 Qt Quick 和 QML 语言必须提供的所有可能性的介绍。 但是,您是需要将这些部分放在一起以构建可解决该领域中现有问题的应用的人。