学院网站建设新闻简报,专业搜索引擎seo合作,产品外观设计报价,建设银行的投诉网站原文#xff1a;Computer Vision with OpenCV 3 and Qt5 协议#xff1a;CC BY-NC-SA 4.0 译者#xff1a;飞龙 本文来自【ApacheCN 计算机视觉 译文集】#xff0c;采用译后编辑#xff08;MTPE#xff09;流程来尽可能提升效率。 当别人说你没有底线的时候#xff0c;… 原文Computer Vision with OpenCV 3 and Qt5 协议CC BY-NC-SA 4.0 译者飞龙 本文来自【ApacheCN 计算机视觉 译文集】采用译后编辑MTPE流程来尽可能提升效率。 当别人说你没有底线的时候你最好真的没有当别人说你做过某些事的时候你也最好真的做过。 十一、链接与部署 
在前几章中了解了使用 Qt Creator 和 Qt Test 框架调试和测试应用之后我们进入了应用开发的最后阶段之一即将应用部署到最终用户。 该过程本身具有多种变体并且可以根据目标平台采取很多不同的形式但是它们都有一个共同点就是以一种可以在目标平台中简单地执行它的方式打包应用。 困扰应用的依赖项。 请记住并非所有目标平台无论是 WindowsMacOS 还是 Linux都具有 Qt 和 OpenCV 库。 因此如果继续进行操作仅向应用的用户提供应用的可执行文件它很可能甚至不会开始执行更不用说正常工作了。 
在本章中我们将通过学习创建应用包通常是包含所有必需文件的文件夹的正确方法来解决这些问题该应用包可以在我们自己的计算机以及开发环境以外的其他计算机上简单执行而无需用户照顾任何必需的库。 为了能够理解本章中描述的一些概念我们首先需要了解创建应用可执行文件时幕后发生情况的一些基础知识。 我们将讨论构建过程的三个主要阶段即预处理编译和链接应用可执行文件或库。 然后我们将学习可以用两种不同的方式完成链接即动态链接和静态链接。 我们将讨论它们之间的差异以及它们如何影响部署以及如何在 WindowsMacOS 和 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 项目”中我们了解了uic和moc它们可以转换使用 Qt 特定宏和准则编写的 UI 文件和 C 代码。 转换为标准 C 代码确切地说是在最新版本的 Qt 中转换为 C 11 或更高版本。 即使这些不是对 C 源代码执行的标准预处理的一部分但是当我们使用 Qt 框架或基于自己的规则集生成代码的框架时它们仍处于大致相同的阶段。 
下图描述了预处理阶段该阶段与使用uicmoc等进行 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文件中 并且库的实际链接和加载是在运行时完成的。 动态链接的最明显的优缺点是 
您的应用将在运行时需要共享库因此您必须将它们与应用的可执行文件一起部署并确保可以访问它们。 例如在 Windows 上可以通过将其复制到与应用可执行文件相同的文件夹中来完成或者在 Linux 上可以将它们放在默认库路径例如/lib/中来完成。动态链接通过将应用的各个部分保留在单独的共享库文件中提供了极大的灵活性。 这样共享库可以单独更新而无需重新编译应用的每个部分。 
与动态链接相反可以使用静态链接将所有必需的代码链接到生成的可执行文件中从而创建静态库或可执行文件。 您可以猜测使用静态库与使用共享库具有完全相反的优点和缺点它们是 
您不需要部署用于构建应用的静态库因为它们的所有代码实际上都已复制到生成的可执行文件中应用可执行文件的大小将变大这意味着更长的初始加载时间和更大的文件要部署对库或应用任何部分的任何更改都需要对其所有组成部分进行完整的重建过程 
在整本书中特别是在为我们全面的计算机视觉应用开发插件时我们使用了共享库和动态链接。 这是因为当我们使用所有默认的 CMake 设置构建 OpenCV并使用第 1 章“OpenCV 和 Qt 简介”中的官方安装程序安装 Qt 框架时 动态链接和共享的库Windows 上为*.dllMacOS 上为*.dylib等。 不过在下一节中我们将学习如何使用它们的源代码静态地构建 Qt 和 OpenCV 库。 通过使用静态链接库我们可以创建不需要在目标系统上存在任何共享库的应用。 这可以极大地减少部署应用所需的工作量。 在 MacOS 和 Linux 操作系统中的 OpenCV 尤其如此您的用户除了复制和运行您的应用外完全不需要执行任何操作而他们将需要采取一些措施或必须执行一些脚本操作以确保执行您的应用时所有必需的依赖项均已就绪。 
构建 OpenCV 静态库 
让我们从 OpenCV 开始它遵循与构建动态库几乎相同的指令集来构建静态库。 您可以参考第 1 章“OpenCV 和 Qt 简介”以获得更多信息。 只需下载源代码解压缩并使用 CMake 来配置您的构建如本章所述。 但是这次除了选中BUILD_opencv_world选项旁边的复选框外还要取消选中每个选项旁边的复选框以确保关闭了以下所有选项 
BUILD_DOCSBUILD_EXAMPLESBUILD_PERF_TESTSBUILD_TESTSBUILD_SHARED_LIBSBUILD_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  -LC:/path_to_opencv_install/x86/vc14/staticlib  -lopencv_world330d  -llibjpegd  -llibjasperd  -littnotifyd  -lIlmImfd  -llibwebpd  -llibtiffd  -llibprotobufd  -llibpngd  -lzlibd  -lipp_iw  -lippicvmt } Release: { LIBS  -LC:/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 库 
为了能够构建一组静态 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则需要在提供的所有引用它的说明中将其替换。 现在应该已经很明显了。 
现在您需要先处理一些依赖关系然后再继续下一步。 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 
现在在 Windows 上运行 VS2015 的开发人员命令提示符。 在 MacOS 或 Linux 上运行终端。 您需要运行一组连续的命令以根据源代码配置和构建 Qt。 该配置是此步骤中最关键的部分是通过使用configure命令完成的。 configure命令位于 Qt 源文件夹的根目录中接受以下参数请注意实际的参数集很长因此我们可以满足使用最广泛的参数的要求 
此处提供的参数列表应足以构建具有更多或更少默认设置的静态版本的 Qt 框架 
现在是时候配置我们的 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)] 
现在切换到“套件”选项卡。 您应该能够看到整本书中用来构建 Qt 应用的工具包。 例如在 Windows 上它应该是 Desktop Qt 5.9.1 MSVC2015 32bit。 选择它并按“克隆”按钮然后选择在上一步的“Qt 版本”选项卡中设置的 Qt 版本如果您在那里看不到自己的版本则可能需要按一次“应用”按钮然后按“将显示在组合框中。 另外请确保从其名称中删除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 函数例如imreaddilate和imshow来测试一组静态 OpenCV 库。 如果现在检查静态链接的可执行文件的大小您会发现文件大小现在更大。 这样做的明显原因是所有必需的 OpenCV 代码都链接到可执行文件本身。 
部署 Qt  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 库这意味着您不能使用插件的功能和扩展应用而无需从头开始构建所有内容。从某种意义上说静态链接意味着隐藏用于构建应用的库。 不幸的是并非所有库都提供此选项并且不遵守该选项可能导致应用出现许可问题。 之所以会出现这种复杂性部分原因是 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.exewindeployqt工具是一个部署帮助工具可简化将所需的 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 安装程序框架 
Qt 安装程序框架允许您为 WindowsMacOS 和 Linux 操作系统创建 Qt 应用的跨平台安装程序。 它允许创建标准的安装程序向导在该向导中用户会通过提供所有必要信息的连续对话框进入并最终显示安装应用时的进度等这与您可能遇到的大多数安装类似尤其是安装 Qt 框架本身。 Qt 安装程序框架基于 Qt 框架本身但以不同的包提供并且不需要计算机上存在 Qt SDKQt 框架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 框架所需文件和文件夹的结构其次因为它仍然非常容易和简单。 以下是创建安装程序的必需步骤 
假设您已经完成了 Qt 和 OpenCV 应用的开发则可以从创建一个包含安装程序文件的新文件夹开始。 假设此文件夹名为deploy。在deploy文件夹中创建一个 XML 文件并将其命名为config.xml。 此 XML 文件必须包含以下内容 ?xml version1.0 encodingUTF-8? Installer NameYour application/Name Version1.0.0/Version TitleYour application Installer/Title PublisherYour vendor/Publisher StartMenuDirSuper App/StartMenuDir TargetDirHomeDir/InstallationDirectory/TargetDir /Installer 确保用与您的应用相关的信息替换前面代码中的必需 XML 字段然后保存并关闭此文件 现在在deploy文件夹内创建一个名为packages的文件夹。 该文件夹将包含您希望用户能够安装的单个包或者使它们成为必需或可选的包以便用户可以查看并决定要安装的包。  对于使用 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。 现在请执行以下必需步骤 
现在在我们创建的新包文件夹com.amin.qtcvapp文件夹中创建两个文件夹。 将它们重命名为data和meta。 这两个文件夹必须存在于所有包中。将您的应用文件复制到data文件夹中。 该文件夹将完全按原样提取到目标文件夹中我们将在后面的步骤中讨论如何设置包的目标文件夹。 如果您打算创建多个包请确保以合理的方式正确分离其数据。 当然如果不这样做您将不会遇到任何错误但是您的应用的用户可能会感到困惑例如通过跳过应始终安装的包并最终安装已安装的应用这行不通。现在切换到meta文件夹并在该文件夹中创建以下两个文件并为每个文件提供的代码填充它们。 
package.xml文件应包含以下内容。 无需提及您必须使用与包相关的值填充 XML 内的字段 ?xml version1.0 encodingUTF-8? Package DisplayNameThe component/DisplayName DescriptionInstall this component./Description Version1.0.0/Version ReleaseDate1984-09-16/ReleaseDate Defaultscript/Default Scriptinstallscript.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 Quick 应用 
使用 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 简介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 labelbrthat containsbrmultiple 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 labelbrthat containsbrmultiple 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 元素。 为此可以使用GridLayoutColumnLayout和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: parentanchors是 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 中的用户交互和脚本编写 
对 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是属性的值必须与提供的类型兼容。 
使用 Qt Quick Designer 
由于 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 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://goo.gl/TiQEYB 
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模块代替QtCoreQtGui和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.uimainwindow.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] StyleDefault [Universal] ThemeLight ;AccentSteel [Material] ThemeLight ;AccentBlueGrey ;PrimaryBlueGray 行首的分号;表示仅是注释。 您可以将前面代码中的Style变量的值更改为Material和Universal以更改应用的整体样式。 根据所设置的样式可以在前面的代码中使用ThemeAccent或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) } 集成 C 和 QML 代码 
即使 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文件中调用此函数如下所示 qmlRegisterTypeQImageProcessor(com.amin.classes, 1, 0, ImageProcessor); qmlRegisterTypeQImageViewer(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 leftbrto 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 photobrand 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)] 
Android 和 iOS 上的 Qt 和 OpenCV 应用 
理想情况下您可以在台式机和移动平台上构建并运行通过使用 Qt 和 OpenCV 框架创建的应用而无需编写任何特定于平台的代码。 但是实际上这并不像看起来那样容易因为 Qt 和 OpenCV 之类的框架充当操作系统本身功能的包装器在某些情况下并且由于它们仍在进行广泛的开发因此可能会有一些尚未在特定操作系统例如 Android 或 iOS中完全实现的案例。 好消息是随着新版本的 Qt 和 OpenCV 框架的发布这些情况变得越来越罕见即使现在Qt 5.9 和 OpenCV 3.3这两个框架中的大多数类和函数都可以在 Windows 中轻松使用。 LinuxmacOSAndroid 和 iOS 操作系统。 
因此首先请牢记我们刚才提到的内容可以说实际上是相对于理想情况而言要能够在 Android 和 iOS 上构建和运行使用 Qt 和 OpenCV 编写的应用我们需要确保以下东西 
必须安装适用于 Android 和 iOS 的相应 Qt 套件。 这可以在 Qt 框架的初始安装过程中完成有关此信息请参阅第 1 章“OpenCV 和 Qt 简介”。 
请注意Android 套件可在 WindowsLinux 和 MacOS 上使用而 iOS 套件仅适用于 macOS因为使用 Qt 的 iOS 应用开发仅限于 macOS目前。 
必须从 OpenCV 网站上下载适用于 Android 和 iOS 的预构建 OpenCV 库目前它们是从 opencv.org 提供并提取到您的计算机中。 必须按照在 Windows 或任何其他桌面平台中添加的方式将它们添加到 Qt 项目文件中。对于 iOS在您的 MacOS 操作系统上拥有最新版本的 Xcode 就足够了。对于 Android您必须确保在计算机上安装 JDKAndroid SDKAndroid NDK 和 Apache Ant。 如果使用 Qt Creator 选项内“设备”页面中的 Android 选项卡将所需的程序下载并安装到计算机上则 Qt Creator 可以简化 Android 开发环境的配置请参见以下屏幕截图 
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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 构建的应用也可以在 WindowsmacOSAndroid 和 iOS 的应用商店中发布。 此过程通常涉及与这些操作系统的提供者注册为开发人员。 您可以在上述应用商店中找到在线和在全球范围内发布应用的准则和要求。 
总结 
在本章中我们了解了 Qt Quick 应用开发和 QML 语言。 我们从这种高度可读且易于使用的语言的裸露语法开始然后转向开发包含可以相互交互以实现一个共同目标的组件的应用。 我们学习了如何填补 QML 和 C 代码之间的空白然后建立了可视类和非可视类来处理和显示使用 OpenCV 处理的图像。 我们还简要介绍了在 Android 和 iOS 平台上构建和运行相同应用所需的工具。 本书的最后一章旨在通过开始使用新的 Qt Quick Controls 2 模块开发快速美观的应用并将 C 代码和 OpenCV 等第三方框架的功能结合起来来帮助您站起来。 在开发移动和桌面应用时获得最大的功能和灵活性。 
构建跨平台和吸引人的应用从未如此简单。 通过使用 Qt 和 OpenCV 框架尤其是 QML 的功能可以快速轻松地构建应用您可以立即开始实现所有计算机视觉创意。 我们在本章中学到的只是对 Qt Quick 和 QML 语言必须提供的所有可能性的介绍。 但是您是需要将这些部分放在一起以构建可解决该领域中现有问题的应用的人。