十一、链接与部署
在前几章中了解了使用 Qt Creator 和 Qt Test 框架调试和测试应用之后,我们进入了应用开发的最后阶段之一,即将应用部署到最终用户。 该过程本身具有多种变体,并且可以根据目标平台采取很多不同的形式,但是它们都有一个共同点,就是以一种可以在目标平台中简单地执行它的方式打包应用。 困扰应用的依赖项。 请记住,并非所有目标平台(无论是 Windows,MacOS 还是 Linux)都具有 Qt 和 OpenCV 库。 因此,如果继续进行操作,仅向应用的用户提供应用的可执行文件,它很可能甚至不会开始执行,更不用说正常工作了。
在本章中,我们将通过学习创建应用包(通常是包含所有必需文件的文件夹)的正确方法来解决这些问题,该应用包可以在我们自己的计算机以及开发环境以外的其他计算机上简单执行,而无需用户照顾任何必需的库。 为了能够理解本章中描述的一些概念,我们首先需要了解创建应用可执行文件时幕后发生情况的一些基础知识。 我们将讨论构建过程的三个主要阶段,即预处理,编译和链接应用可执行文件(或库)。 然后,我们将学习可以用两种不同的方式完成链接,即动态链接和静态链接。 我们将讨论它们之间的差异以及它们如何影响部署,以及如何在 Windows,MacOS 和 Linux 操作系统上动态或静态地构建 Qt 和 OpenCV 库。 之后,我们将为所有提到的平台创建并部署一个简单的应用。 我们将借此机会还了解 Qt Installer 框架,以及如何创建网站下载链接,闪存驱动器或任何其他媒体上交付给最终用户的安装程序。 到本章结束时,我们将仅向最终用户提供他们执行我们的应用所需的内容,仅此而已。
本章将讨论的主题包括:
当我们通过编辑一些 C++ 头文件或源文件,在项目文件中添加一些模块并最后按下运行按钮来编写应用时,这似乎很自然。 但是,在幕后还有一些流程,这些流程通过按正确的顺序由 IDE(在我们的情况下为 Qt Creator)执行,从而使开发过程具有顺畅自然的感觉。 通常,当我们按 Qt Creator 或任何其他 IDE 的运行或构建按钮时,有三个主要过程可导致创建可执行文件(例如*.exe
)。 这是这三个过程:
这是从源文件创建应用时所经过的过程和阶段的非常高级的分类。 这种分类允许对过程进行更简单的概述,并以更简单的方式大致了解其目的。 但是,这些过程包括许多子过程和阶段,不在本书的讨论范围之内,因为我们对以一种或另一种方式影响部署过程的过程最为感兴趣。 但是,您可以在线阅读它们,也可以阅读有关编译器和链接器的任何书籍。
此阶段是在将源代码传递到实际编译器之前将其转换为最终状态的过程。 为了进一步解释这一点,请考虑所有包含的文件,各种编译器指令,或更重要的是,对于 Qt 框架,请考虑不属于标准 C++ 语言的 Qt 特定的宏和代码。 在第 3 章,“创建全面的 Qt + OpenCV 项目”中,我们了解了uic
和moc
,它们可以转换使用 Qt 特定宏和准则编写的 UI 文件和 C++ 代码。 转换为标准 C++ 代码(确切地说,是在最新版本的 Qt 中,转换为 C++ 11 或更高版本)。 即使这些不是对 C++ 源代码执行的标准预处理的一部分,但是当我们使用 Qt 框架或基于自己的规则集生成代码的框架时,它们仍处于大致相同的阶段。
下图描述了预处理阶段,该阶段与使用uic
,moc
等进行 Qt 特定的代码生成相结合:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9rsc6wJn-1681870159296)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/e1a601ba-1e3f-482b-a4eb-79caa46f13e2.png)]
该过程的输出在上一个图像中被标记为用于编译器的单个输入文件,显然是一个单个文件,其中包含用于编译源代码的所有必需标记和信息。 然后将该文件传递给编译器和编译阶段。
在构建过程的第二个主要阶段,编译器获取预处理器的输出,或者在我们的示例中为预处理阶段,该输出还包括uic
和moc
生成的代码,并将其编译为机器代码。 。 可以在构建过程中保存并重复使用该机器代码,因为只要不更改源文件,生成的机器代码也将保持不变。 通过确保重复使用各个单独编译的对象(例如*.obj
或*.lib
文件),而不是在每次构建项目时都生成该对象,此过程有助于节省大量时间。 所有这一切的好处是,IDE 会照顾它,我们通常不需要理会它。 然后,由编译器生成的输出文件将传递到链接器,然后我们进入链接阶段。
链接器是在构建过程链中被调用的最后一个程序,其目标是链接由编译器生成的对象以生成可执行文件或库。 这个过程对我们而言至关重要,因为它会对部署应用的方式,可执行文件的大小等产生巨大影响。 为了更好地理解这一点,首先我们需要讨论两种可能的链接类型之间的区别:
动态链接是链接编译器生成的对象的过程,方法是将函数的名称放在生成的可执行文件或库中,以使该特定函数的实际代码位于共享库(例如*.dll
文件)中 ),并且库的实际链接和加载是在运行时完成的。 动态链接的最明显的优缺点是:
/lib/
)中来完成。与动态链接相反,可以使用静态链接将所有必需的代码链接到生成的可执行文件中,从而创建静态库或可执行文件。 您可以猜测,使用静态库与使用共享库具有完全相反的优点和缺点,它们是:
在整本书中,特别是在为我们全面的计算机视觉应用开发插件时,我们使用了共享库和动态链接。 这是因为当我们使用所有默认的 CMake 设置构建 OpenCV,并使用第 1 章,“OpenCV 和 Qt 简介”中的官方安装程序安装 Qt 框架时, 动态链接和共享的库(Windows 上为*.dll
,MacOS 上为*.dylib
等)。 不过,在下一节中,我们将学习如何使用它们的源代码静态地构建 Qt 和 OpenCV 库。 通过使用静态链接库,我们可以创建不需要在目标系统上存在任何共享库的应用。 这可以极大地减少部署应用所需的工作量。 在 MacOS 和 Linux 操作系统中的 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 库。 在第 1 章,“OpenCV 和 Qt 简介”中也是如此,当我们使用以下链接提供的安装程序在开发环境中安装 Qt 时。
因此,简单来说,如果要使用静态 Qt 库,则必须使用其源代码自行构建它们。 您可以按照此处提供的步骤来配置,构建和使用静态 Qt 库:
*.zip
,*.tar.xz
等)提供。 在我们的情况下(Qt 版本 5.9.1),您可以使用以下链接下载 Qt 源代码。下载qt-everywhere-opensource-src-5.9.1.zip
(或*.tar.xz
),然后继续下一步。
Qt_Src
,并且位于c:/dev
文件夹中(在 Windows 操作系统上)。 因此,假设我们提取的 Qt 源代码的完整路径为c:/dev/Qt_Src
。对于 MacOS 和 Linux 用户,该路径可能类似于Users/amin/dev/Qt_Src
,因此,如果您使用的是上述操作系统之一而不是 Windows,则需要在提供的所有引用它的说明中将其替换。 现在应该已经很明显了。
gnuwin32
子文件夹内提供了 Bison。 只需确保将c:/dev/Qt_Src/gnuwin32/bin
添加到PATH
环境变量即可。gnuwin32
子文件夹内,需要添加到PATH
中。gnuwin32
子文件夹内提供了与 Bison 和 Flex 相同的 GNU gperf
,需要将其添加到PATH
中。为确保一切正常,请尝试运行相关命令以执行我们刚刚提到的每个依赖项。 可能是您忘记将其中一个依赖项添加到PATH
的情况,或者对于 MacOS 和 Linux 用户,由于任何可能的原因,它们已被删除并且不存在。 仅在命令提示符(或终端)中执行以下每个命令并确保您不会遇到无法识别或找不到的错误类型就足够了:
perl
python bison
flex gperf
configure
命令完成的。 configure
命令位于 Qt 源文件夹的根目录中,接受以下参数(请注意,实际的参数集很长,因此我们可以满足使用最广泛的参数的要求):此处提供的参数列表应足以构建具有更多或更少默认设置的静态版本的 Qt 框架:
cd c:/dev/Qt_Src"
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
。
build
和install
命令。在 Windows 上,使用以下命令:
nmake
nmake install
在 MacOS 和 Linux 上,使用以下命令:
make
make install
请注意,由于 Qt 框架包含许多需要构建的模块和库,因此第一个命令通常需要很长时间才能完成(取决于您的计算机规格),因此在此步骤中需要耐心等待。 无论如何,如果您到目前为止已经完全按照提供的所有步骤进行操作,则应该没有任何问题。
重要的是要注意,如果您使用计算机受限区域中的安装文件夹(-prefix
参数),则必须确保使用管理员级别(如果使用 Windows)运行命令提示符实例(如果使用 Windows) 带有sudo
前缀的build
和install
命令(如果您使用的是 MacOS 或 Linux)。
install
命令后,应该将静态 Qt 库放入配置过程中作为前缀参数提供的文件夹(即安装文件夹)中。 因此,在此步骤中,您需要在 Qt Creator 中将这组新建的 Qt 静态库添加为工具包。 为此,请打开 Qt Creator,然后从主菜单中选择“工具”,然后选择“选项”。 从左侧的列表中,选择Build & Run
,然后选择Qt Versions
选项卡。 现在,按“添加”按钮,然后浏览至Qt build
安装文件夹,选择qmake.exe
,在本例中,该文件应位于C:devQtStaticbin
文件夹内。 以下屏幕截图显示了正确添加新的 Qt 构建后 Qt 版本标签中的状态:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rm7lPZuX-1681870159297)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/30983d93-121c-425d-aa71-03ce6d0581c9.png)]
Clone of
,并在其后附加Static
一词,以便于区分。 以下屏幕快照表示“工具”选项卡的状态及其配置方式:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mLvGKKn-1681870159297)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/e91f8bc1-dfc0-491e-96d9-3c90f24df13f.png)]
关于构建和配置静态 Qt 套件的问题。 现在,您可以使用与默认 Qt 套件(动态套件)完全相同的方式开始使用它创建 Qt 项目。 您唯一需要注意的就是在创建和配置 Qt 项目时将其选择为目标套件。 让我们用一个简单的例子来做到这一点。 首先创建一个 Qt Widgets 应用,并将其命名为StaticApp
。 在“工具包选择”页面上,确保选择了新建的静态 Qt 工具包,然后继续按“下一步”,直到进入 Qt 代码编辑器。 以下屏幕快照描述了“工具包选择”页面及其外观(在 Window OS 上):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JG7Nsl9d-1681870159297)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/8b32a5d8-ace0-48e5-a687-9cd984a5eaef.png)]
无需进行太多更改或添加任何代码,只需按“运行”按钮即可构建并执行该项目。 现在,如果浏览到该项目的build
文件夹,您会注意到可执行文件的大小比我们使用默认动态工具包进行构建时的大小要大得多。 为了进行比较,在 Windows 操作系统和调试模式下,动态构建的版本应小于 1 兆字节,而静态构建的版本应约为 30 兆字节,甚至更多。 如前所述,这样做的原因是所有必需的 Qt 代码现在都链接到可执行文件中。 尽管严格说来,从技术上讲它并不正确,但是您可以将其视为将库(*.dll
文件等)嵌入可执行文件本身中。
现在,让我们尝试在示例项目中也使用静态 OpenCV 库。 只需将所需的附加内容添加到StaticApp.pro
文件中,然后尝试使用几个简单的 OpenCV 函数(例如imread
,dilate
和imshow
)来测试一组静态 OpenCV 库。 如果现在检查静态链接的可执行文件的大小,您会发现文件大小现在更大。 这样做的明显原因是所有必需的 OpenCV 代码都链接到可执行文件本身。
向最终用户提供应用包是非常重要的,该包包含它能够在目标平台上运行所需的一切,并且在照顾所需的依赖方面几乎不需要用户付出任何努力。 为应用实现这种开箱即用的条件主要取决于用于创建应用的链接的类型(动态或静态),以及目标操作系统。
静态部署应用意味着您的应用将独立运行,并且消除了几乎所有需要的依赖项,因为它们已经在可执行文件内部。 只需确保在构建应用时选择发布模式即可,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ouGlYacz-1681870159298)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/7b9ff795-b56a-47d6-8846-a0f4bd470f0f.png)]
在发布模式下构建应用时,您只需选择生成的可执行文件并将其发送给用户。
如果尝试将应用部署到 Windows 用户,则在执行应用时可能会遇到类似于以下错误:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4gsk4gl-1681870159298)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/872182d4-bbf0-42e3-a9c0-10b1a2fb2870.png)]
发生此错误的原因是,在 Windows 上,即使以静态方式构建 Qt 应用,您仍然需要确保目标系统上存在 Visual C++ 可再发行组件。 这是使用 Microsoft Visual C++ 生成的 C++ 应用所必需的,并且所需的可再发行版本与计算机上安装的 Microsoft Visual Studio 相对应。 在我们的案例中,这些库的安装程序的正式名称是 Visual Studio 2015 的 Visual C++ 可再发行组件,可以从以下链接下载。
通常的做法是在我们的应用的安装程序中包含可再发行文件的安装程序,如果尚未安装,请对其进行静默安装。 大多数情况下,您在 Windows PC 上使用的大多数应用都会执行此过程,而您甚至没有注意到它。
我们已经简要地讨论了静态链接的优点(要部署的文件较少)和缺点(可执行文件的大小较大)。 但是,当在部署环境中使用它时,还需要考虑更多的复杂性。 因此,当使用静态链接部署应用时,这是另一个(更完整的)缺点列表:
有关 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.dll
和opencv_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 安装程序框架允许您为 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 框架所需文件和文件夹的结构,其次,因为它仍然非常容易和简单。 以下是创建安装程序的必需步骤:
deploy
。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 字段,然后保存并关闭此文件:
deploy
文件夹内创建一个名为packages
的文件夹。 该文件夹将包含您希望用户能够安装的单个包,或者使它们成为必需或可选的包,以便用户可以查看并决定要安装的包。
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
。 现在,请执行以下必需步骤:
com.amin.qtcvapp
文件夹中创建两个文件夹。 将它们重命名为data
和meta
。 这两个文件夹必须存在于所有包中。data
文件夹中。 该文件夹将完全按原样提取到目标文件夹中(我们将在后面的步骤中讨论如何设置包的目标文件夹)。 如果您打算创建多个包,请确保以合理的方式正确分离其数据。 当然,如果不这样做,您将不会遇到任何错误,但是您的应用的用户可能会感到困惑,例如,通过跳过应始终安装的包并最终安装已安装的应用,这行不通。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 运行时库。
binarycreator
工具来创建我们的单个和独立安装程序。 只需使用命令提示符(或终端)实例运行以下命令: binarycreator -p packages -c config.xml myinstaller
binarycreator
位于 Qt Installer 框架bin
文件夹内。 它需要我们已经准备好的两个参数。 -p
之后必须是我们的packages
文件夹,-c
之后必须是配置文件(或config.xml
)文件。 执行此命令后,您将获得myinstaller
(在 Windows 上,可以在其后附加*.exe
),可以执行该命令来安装应用。 该单个文件应包含运行您的应用所需的所有必需文件,其余部分将得到处理。 您只需要提供此文件的下载链接,或通过 CD 将其提供给您的用户。
以下是此默认和最基本的安装程序中将面对的对话框,其中包含安装应用时可能会遇到的大多数常见对话框:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3SuEHvi6-1681870159298)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/882949a8-d029-484d-9166-fc2256709c74.png)]
如果转到安装文件夹,您会注意到其中包含的文件比放入包数据文件夹中的文件多。 安装程序需要这些文件来处理修改和卸载应用。 例如,您的应用用户可以通过执行maintenancetool
可执行文件轻松卸载您的应用,这将产生另一个简单且用户友好的对话框来处理卸载过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EDeVsQVz-1681870159298)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/86dd4add-94ff-4e2c-b24e-133444f0b878.png)]
无论您是否可以在目标计算机上轻松安装并在目标计算机上轻松使用,这都意味着赢得或失去大量用户。 特别是对于非专业用户而言,必须确保创建和部署包含所有必需依赖项的安装程序,并且可以在目标平台上直接使用。 在本章中,我们对此进行了相当多的讨论。 我们了解了构建过程以及所选择的链接方法如何完全改变部署体验。 我们了解了现有的 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 窗口小部件应用项目允许通过使用 Qt Creator 设计模式创建灵活而强大的 GUI,或者在文本编辑器中手动修改 GUI 文件(*.ui
)。 到目前为止,在本书的所有章节中,我们都基于 Qt Widgets 应用作为创建的 GUI 的基础,并且我们在第 3 章,“创建一个全面的 Qt + OpenCV 项目”中了解到,我们可以使用样式表来有效地更改 Qt 应用的外观。 但是,除了 Qt Widgets 应用并使用QtWidgets
和QtGui
模块之外,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 具有类似于 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
属性,该属性是标签上显示的文本。 x
和y
只是指Label
在ApplicationWindow
内部的位置。 可以使用非常类似的方式添加诸如组框之类的容器项。 让我们添加一个,看看它是如何完成的:
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 代码将导致一个类似于以下所示的窗口:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aNgrOaed-1681870159299)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/ed9f90e3-0c61-496f-b0b7-2049511b28b9.png)]
请注意,每个元素的位置都是与其父元素的偏移量。 例如,将GroupBox
内提供给Label
的x
和y
值添加到GroupBox
本身的x
和y
属性中,这就是在根元素(在本例中为ApplicationWindow
)中确定 UI 元素的最终位置。
与 Qt 窗口小部件类似,您也可以在 QML 代码中使用布局来控制和组织 UI 元素。 为此,可以使用GridLayout
,ColumnLayout
和RowLayout
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
}
}
}
这将导致类似于以下的窗口:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QdC8fKpT-1681870159299)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/4dcc5ebe-d937-44e6-8281-fea8d5c4f6c4.png)]
在前面的代码中,ColumnLayout
的行为类似于我们在 Qt Widgets 应用中使用的垂直布局。 从上到下,作为子元素添加到ColumnLayout
的每个元素都会显示在前一个元素之后,无论ColumnLayout
的大小如何,始终调整其大小和位置以保持垂直布局视图。 关于上述内容,还有两点需要注意。 首先,使用以下代码将ColumnLayout
本身的大小设置为父大小:
anchors.fill: parent
anchors
是 QML 视觉元素的最重要属性之一,它照顾元素的大小和位置。 在这种情况下,通过将anchors
的fill
值设置为另一个对象(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 代码中的用户操作和事件的响应是通过将脚本添加到项目的插槽中来完成的,这与 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
,它在Button
的onPressed
插槽内用于访问它。 您可以猜测,按前面代码中的“关闭”按钮将导致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
是属性的值,必须与提供的类型兼容。
由于 QML 文件的语法简单易读,因此可以使用任何代码编辑器轻松对其进行修改和扩展。 但是,您也可以使用 Qt Creator 中集成的快速设计器来简化 QML 文件的设计和修改。 如果您尝试在 Qt Creator 中打开 QML 文件并切换到“设计”模式,则会看到以下“设计”模式,它与标准 Qt Widgets 设计器(用于*.ui
文件)有很大不同, 包含使用 QML 文件快速设计用户界面所需的大部分内容:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vdSQEgFb-1681870159299)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/cbb0de94-3c29-438a-89a5-f7e5599a3380.png)]
在“Qt Quick 设计器”屏幕的左侧,您可以在“库”窗格中看到可以添加到用户界面的 QML 类型的库。 它与 Qt Widgets 工具箱类似,但肯定有更多组件可用于设计应用的用户界面。 您只需在用户界面上拖放它们中的每一个,它们就会自动添加到您的 QML 文件中:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9AyrxHdZ-1681870159299)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/ae6fad19-2681-43ac-8882-1f3de10f782e.png)]
“库”窗格的正下方是“导航器”窗格,它在用户界面上显示组件的层次结构视图。 您可以使用“导航器”窗格,只需双击它们即可快速设置 QML 文件中的项目 ID。 此外,您可以将项目导出为别名,以便可以在其他 QML 文件中使用它,也可以在设计时将其隐藏(以便查看重叠的 QML 项目)。 在“导航器”窗格上的以下屏幕快照中,请注意在将button2
导出为别名并将button3
在设计期间隐藏之后,组件旁边的小图标是如何变化的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q9DZeDTI-1681870159300)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/21a36553-7783-44ce-a941-bcfd8eddf124.png)]
在 Qt Quick 设计器的右侧,您可以找到“属性”窗格。 与标准 Qt 设计模式下的“属性”窗格相似,此窗格可用于详细操作和修改 QML 项的属性。 该窗格的内容根据用户界面上的选定项目而变化。 除了 QML 项目的标准属性外,此窗格还允许修改与单个项目的布局有关的属性。 以下屏幕快照描绘了在用户界面上选择“按钮”项时“属性”窗格的不同视图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tmGKnTPo-1681870159300)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/a51edb7e-7de1-44a3-b3b6-e4062aa179d2.png)]
除了用于设计 QML 用户界面的辅助工具外,Qt Quick Designer 可以帮助您了解 QML 语言本身,因为在设计器中完成的所有修改都将转换为 QML 代码并存储在同一 QML 文件中。 通过使用它来设计用户界面,以确保熟悉它的用法。 例如,您可以尝试设计一些与创建 Qt Widgets 应用时相同的用户界面,但是这次使用 Qt Quick Designer 和 QML 文件。
在本节中,我们将学习 Qt Quick 应用项目的结构。 与 Qt Widgets 应用项目类似,使用 Qt Creator 创建新项目时,会自动创建 Qt Quick 应用项目所需的大多数文件,因此您实际上并不需要记住所有的最低要求,但是仍然重要的是要理解如何处理 Qt Quick 应用的一些基本概念,以便能够进一步扩展它,或者,如我们将在本章后面的部分中了解的那样,在 QML 文件中集成和使用 C++ 代码。
让我们通过创建一个示例应用来解决这个问题。 首先打开 Qt Creator,然后在欢迎屏幕上按“新建项目”按钮,或者从“文件”菜单中选择“新建文件”或“项目”。 选择“Qt Quick Controls 2 应用”作为模板类型,然后按“选择”,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FAmSzV1L-1681870159300)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/01d23b6d-7f89-4722-a2ba-47b1f7e1d0b8.png)]
将项目名称设置为CvQml
,然后按Next
。 在“定义构建系统”页面中,将“构建系统”保留为qmake
,默认情况下应将其选中。 在“定义项目详细信息”页面中,可以为 Qt Quick Controls 2 样式选择以下选项之一:
您在此屏幕中选择的选项会影响应用的整体样式。 “默认”选项会导致使用默认样式,从而使 Qt Quick Controls 2 以及我们的 Qt Quick 应用具有最高性能。 Material 样式可用于根据 Google Material Design 准则创建应用。 它提供了更具吸引力的组件,但也需要更多资源。 最后,通用样式可用于基于 Microsoft 通用设计准则创建应用。 与 Material 风格相似,这也需要更多资源,但提供了另一套引人注目的用户界面组件。
您可以参考以下链接,以获得有关用于创建“材质”和“通用”样式的准则的更多信息:
https://dev.windows.com/design
下面的截图描述了一些常见的组件之间的差异,在选择的三种可能的风格每一个选项如何看您的应用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MjDSQiHp-1681870159300)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/67b09157-8f31-4393-a7b3-07f2e1cf7484.png)]
无论您选择什么,以后都可以在名为qtquickcontrols2.conf
的专用设置文件中轻松更改此设置,该文件会自动包含在新项目中。 甚至可以在以后更改颜色以匹配深色或浅色主题或任何其他颜色。 无论如何,请选择所需的一个(或将其保留为默认),然后继续按Next
,直到最终进入 Qt 代码编辑器。 现在,您的项目几乎包含 Qt Quick 应用所需的最少文件。
请注意,每当我们在本章中提到 Qt Quick 应用时,我们实际上是指 Qt Quick Controls 2 应用,它是我们刚刚创建并将扩展到的新的增强型 Qt Quick 应用(在 Qt 5.7 和更高版本中可用)。 完整,美观的跨平台计算机视觉应用。
首先,让我们看一下项目(*.pro
)文件中的区别。 在与 Qt Widgets 应用相对的 Qt Quick 应用中,默认情况下使用QtQml
和QtQuick
模块代替QtCore
,QtGui
和QtWidgets
模块。 您可以通过打开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.qml
和Page1Form.ui.qml
是分离用户界面及其底层代码的常用方法,类似于在开发 Qt Widgets 应用时使用mainwindow.ui
,mainwindow.h
和mainwindow.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
变量的值更改为Material
和Universal
,以更改应用的整体样式。 根据所设置的样式,可以在前面的代码中使用Theme
,Accent
或Primary
值来更改应用中使用的主题。
有关主题和颜色的完整列表,以及有关如何在每个主题中使用各种可用的自定义设置的其他信息,您可以参考以下链接:
https://goo.gl/jDZGPm(用于默认样式)
https://goo.gl/Um9qJ4(用于材料样式)
https://goo.gl/U6uxrh(用于通用样式)
关于 Qt Quick 应用的一般结构。 这种结构可立即用于任何平台的任何类型的应用。 请注意,您没有义务使用自动创建的文件,并且可以简单地从一个空项目开始或删除不必要的默认文件并从头开始。 例如,在我们的示例 Qt Quick 应用(标题为CvQml
)中,我们不需要Page1.qml
和Page1Form.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")
}
即使 QML 库已经成长为可以处理视觉,网络,摄像机等的完整类型集合,但仍然可以使用 C++ 类的功能对其进行扩展仍然很重要。 幸运的是,QML 和 Qt 框架提供了足够的规定以能够轻松地处理此问题。 在本节中,我们将学习如何创建一个非可视的 C++ 类,该类可以在 QML 代码内使用 OpenCV 处理图像。 然后,我们将创建一个 C++ 类,该类可用作 QML 代码中的可视项以显示图像。
请注意,默认情况下,QML 中有一个图像类型,可通过将其 URL 提供给“图像”项来显示保存在磁盘上的图像。 但是,我们将创建一个可用于显示QImage
对象的图像查看器 QML 类型,并利用此机会来学习 CML 类(可视化)在 QML 代码中的集成。
首先将 OpenCV 框架添加到上一节中创建的项目中。 这与创建 Qt Widgets 应用时完全相同,并且在*.pro
文件中包含必需的行。 然后,通过在项目窗格中右键单击新的 C++ 类并将其添加到项目中,然后选择“添加新的”。 确保类名称为QImageProcessor
且其基类为QObject
,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LgZdlJnx-1681870159301)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/47b41113-98d2-48c0-9af6-02a92d9f05cf.png)]
将以下#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
子类,如以下新类向导屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l8ehmup8-1681870159301)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/dfc97761-19e5-447e-bba0-51fa998e943d.png)]
修改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++ 类(QImageProcessor
和QImageViewer
)都可以在 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
可以用您自己的类似域的标识符替换,这是我们为包含QImageProcessor
和QImageViewer
类的库提供的名称。 以下1
和0
引用该库的版本 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
最后一行包括我们刚刚创建的ImageProcessor
和ImageViewer
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
的子项。 Drawer
和ImageViewer
的定义如下:
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
在当前页面上从左到右滑动。 以下是该应用执行时的屏幕截图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LnXaX8Jl-1681870159301)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/2e4bd7a2-5365-4ecd-9ffe-91ba1c8baf8f.jpg)]
理想情况下,您可以在台式机和移动平台上构建并运行通过使用 Qt 和 OpenCV 框架创建的应用,而无需编写任何特定于平台的代码。 但是,实际上,这并不像看起来那样容易,因为 Qt 和 OpenCV 之类的框架充当操作系统本身功能的包装器(在某些情况下),并且由于它们仍在进行广泛的开发,因此可能会有一些尚未在特定操作系统(例如 Android 或 iOS)中完全实现的案例。 好消息是,随着新版本的 Qt 和 OpenCV 框架的发布,这些情况变得越来越罕见,即使现在(Qt 5.9 和 OpenCV 3.3),这两个框架中的大多数类和函数都可以在 Windows 中轻松使用。 ,Linux,macOS,Android 和 iOS 操作系统。
因此,首先,请牢记我们刚才提到的内容,可以说(实际上是相对于理想情况而言),要能够在 Android 和 iOS 上构建和运行使用 Qt 和 OpenCV 编写的应用,我们需要确保以下东西:
请注意,Android 套件可在 Windows,Linux 和 MacOS 上使用,而 iOS 套件仅适用于 macOS,因为使用 Qt 的 iOS 应用开发仅限于 macOS(目前)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Wjry7Jw-1681870159301)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/cv-opencv3-qt5/img/e01d444e-586e-4a6c-88cb-3bf54ac760fe.png)]
请注意上图中“浏览”按钮旁边的按钮。 它们提供了下载页面的链接以及在线链接,您可以从中获得所有必需依赖项的副本。
如果要为 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 语言必须提供的所有可能性的介绍。 但是,您是需要将这些部分放在一起以构建可解决该领域中现有问题的应用的人。