带你读《OpenCV 4计算机视觉项目实战 (原书第2版)》之三:学习图形用户界面

简介: 本书首先介绍OpenCV的入门知识及安装,然后介绍OpenCV的基础知识,包括用户界面、矩阵运算、滤波器和直方图等,之后介绍复杂的计算机视觉算法,包括对象分割和分类、视频监控、对象跟踪等,后探讨对象跟踪、文本识别、机器学习和人脸检测等高级技术。阅读本书之后,你将掌握常用和新的计算机视觉技术,并学会从零开始构建各类OpenCV项目。

点击查看第一章
点击查看第二章

第3章 学习图形用户界面

在第2章中,我们学习了OpenCV的基本类和结构,以及最重要的类Mat,还学习了如何读取和保存图像及视频,以及图像在内存中的内部结构。我们现在已准备好使用OpenCV,但是,在大多数情况下,我们需要使用许多用户界面来显示图像结果,并检索用户与图像的交互。OpenCV为我们提供了一些基本的用户界面,以便创建应用程序和原型。为了更好地理解用户界面的工作原理,我们将在本章最后创建一个名为PhotoTool的小应用程序,在这个应用程序中,我们将学习如何使用滤镜和颜色转换。
本章介绍以下主题:

  • OpenCV基本用户界面
  • OpenCV Qt界面
  • 滑块和按钮
  • 高级用户界面:OpenGL
  • 颜色转换
  • 基本滤波器

3.1 技术要求

本章需要熟悉基本的C++编程语言,所使用的所有代码都可以从以下的GitHub链接下载:https://github.com/PacktPublishing/Learn-OpenCV-4-By-Building-Projects-Second-Edition/tree/master/Chapter_03 。代码可以在任何操作系统上执行,尽管它只在Ubuntu上测试过。

3.2 OpenCV用户界面介绍

OpenCV拥有自己的跨操作系统用户界面,它使开发人员能够创建自己的应用程序,而无须学习复杂的用户界面库。OpenCV用户界面是基础性的,但是它为计算机视觉开发人员提供了创建和管理软件开发的基本功能。所有这些功能都是原生的,并针对实时应用进行了优化。
OpenCV提供两种用户界面选项:

  • 基于原生用户界面的基本界面,适用于Mac OS X的cocoa或carbon,以及适用于Linux或Windows用户界面的GTK,这些界面在编译OpenCV时被默认选择。
  • 基于Qt库的略微更高级的界面,这是跨平台的界面。必须在编译OpenCV之前,在CMake中手动启用Qt选项。

在图3-1中,可以看到左侧的基本用户界面窗口,而右侧则是Qt用户界面。

image.png

3.3 OpenCV的基本图形用户界面

我们将使用OpenCV创建一个基本用户界面。OpenCV用户界面使我们能够创建窗口,然后在里面添加图像,并移动、调整大小和销毁所添加的图像。用户界面位于OpenCV的highui模块中。在下面的代码中,我们将学习如何创建和显示两个图像,具体来说,可以在桌面上通过按键来显示多个窗口,并使图像移入这些窗口中。
不要担心阅读完整的代码,我们会用小代码块来逐个解释它:

image.png
image.png
image.png

我们来理解这段代码:

  1. 为了便于使用图形用户界面,第一项必须完成的任务是导入OpenCV的highui模块:

image.png

  1. 完成创建新窗口的准备工作之后,我们必须加载一些图像:

image.png

  1. 要创建窗口,我们使用namedWindow函数。该函数有两个参数。第一个参数是带有窗口名称的常量字符串,第二个参数是我们需要的标志,第二个参数是可选的:

image.png

  1. 在这个例子中,我们创建了两个窗口:第一个叫作Lena,第二个叫作Photo。

对于Qt和原生界面,默认有三个标志:

  • WINDOW_NORMAL:此标志允许用户调整窗口的大小
  • WINDOW_AUTOSIZE:如果设置了此标志,则窗口大小为自动调整以适应显示图像,但不能调整窗口的大小
  • WINDOW_OPENGL:此标志启用OpenGL支持

Qt有许多额外的标志:

  • WINDOW_FREERATIO或WINDOW_KEEPRATIO:如果设置了WINDOW_FREERATIO,则调整图像时不考虑其比例。如果设置了WINDOW_KEEPRATIO,则根据其比例调整图像。
  • WINDOW_GUI_NORMAL或WINDOW_GUI_EXPANDED:第一个标志提供没有状态栏和工具栏的基本界面。第二个标志使用状态栏和工具栏来支持最高级的图形用户界面。

image.png

  1. 当创建多个窗口时,它们是叠加的,但我们可以使用moveWindow函数将窗口移动到桌面的任何区域,如下所示:

    image.png

  1. 在这段代码中,我们将Lena窗口向左移动了10个像素,向上移动了10个像素,将Photo窗口向左移动了520个像素,向上移动了10个像素。
    在使用imshow函数显示此前加载的图像之后,我们通过调用resizeWindow函数将Lena窗口的大小调整为512像素,该函数有三个参数:window name、width和height。

image.png

  1. 在利用waitKey函数等待按键按下之后,我们用destroyWindow函数删除这个窗口,其中,窗口的名称是唯一需要的参数:

    image.png

  1. OpenCV可以通过一次调用删除所创建的所有窗口,该函数称为destroyAllWindows。为了演示它是如何工作的,我们在样本中创建10个窗口,然后等待按键按下。当用户按下任意键时,它就会销毁所有得窗口:

    image.png

在任何情况下,OpenCV都会在应用程序终止时自动销毁所有窗口,因此在我们的应用程序结束时不必调用此函数。
所有这些代码的结果可以在以下图像中看到。首先,它显示两个窗口,如图3-2所示。
按下任意键之后,应用程序继续运行,并在不同的位置绘制出几个窗口,如图3-3所示。
只需几行代码,我们就可以创建和操作窗口并显示图像。我们现在已经准备好进行用户与图像的交互,并添加用户界面控件。

image.png
image.png

将滑块和鼠标事件添加到界面

鼠标事件和滑块控件在计算机视觉和OpenCV中非常有用。使用这些控件,可以直接与界面交互,并改变输入图像或变量的属性。在本节中,我们将介绍用于基本交互的鼠标事件和滑块控件。为了便于正确理解,我们创建了以下代码,通过这些代码,我们将使用鼠标事件在图像中绘制绿色圆圈,并使用滑块对图像进行模糊处理:

image.png

我们来理解这段代码。
首先,创建一个变量来保存滑块位置。我们需要保存滑块位置,以便从其他函数访问它:

image.png

现在,为滑块和鼠标事件定义回调函数,这是OpenCV的setMouseCallback函数和createTrackbar函数必需的:

image.png

在main函数中,加载一个图像并创建一个名为Lena的新窗口:

image.png
image.png

现在创建滑块。OpenCV的createTrackbar函数用于生成滑块,其参数按顺序如下所示:

  1. 跟踪条名称。
  2. 窗口名称。
  3. 将作为值使用的整数指针。该参数是可选的,如果被设置,则滑块会在创建时获得该位置。
  4. 滑块上的最大位置。
  5. 滑块位置变化时的回调函数。
  6. 要发送到回调函数的用户数据。它可用于在不使用全局变量的情况下将数据发送到回调函数。

对于这段代码,我们为Lena窗口添加了trackbar,然后调用Lena跟踪条对图像进行模糊处理。跟踪条的值存储在将会作为指针传递的blurAmount整数中,并将跟踪条的最大值设置为30。把onChange设置为回调函数,并将lena mat图像作为用户数据发送:

image.png

滑块创建好以后,当用户单击鼠标左键时,我们添加鼠标事件来绘制圆形。这需要使用OpenCV的setMouseCallback函数,该函数有三个参数:

  • 获取鼠标事件的窗口名称。
  • 当有任何鼠标交互时调用的回调函数。
  • 用户数据:这是在触发时将要发送给回调函数的任意数据。在这个例子中,我们将会发送整个Lena图像。

使用以下代码,可以向Lena窗口添加鼠标回调,并将onMouse设置为回调函数,从而将lena mat图像作为用户数据进行传递:

image.png

为了完成主函数,需要使用与滑块相同的参数来初始化图像。要执行初始化,只需调用onChange回调函数,并在使用destroyWindow关闭窗口之前等待事件,如下面的代码所示:

image.png

滑块回调函数使用滑块值作为模糊量,将基本的模糊滤镜应用于图像:

image.png

该函数使用变量pos来检查滑块值是否为0。在这种情况下,我们不使用过滤器,因为它会生成执行错误,也不能用0像素模糊。检查滑块值后,我们创建一个名为imgBlur的空矩阵来存储模糊结果。要检索通过回调函数中的用户数据发送的图像,必须把void userData转换为正确的图像类型指针Mat
现在我们有了正确的变量来应用模糊滤镜。模糊函数将基本的中值滤波器应用于输入图像,在这个例子中是* img。对于输出图像,最后需要的参数是想要应用的模糊内核的大小(内核是用于计算内核和图像之间卷积平均值的小矩阵)。在这个例子中使用的是pos大小的平方内核。最后,只需用imshow函数更新图像界面。
鼠标事件的回调函数有五个输入参数:第一个参数定义事件类型,第二个和第三个定义鼠标位置,第四个参数定义滚轮动作,第五个参数定义用户输入数据。
鼠标事件类型如下:

image.png

在这个例子中,我们只处理单击鼠标左键所产生的事件,并且丢弃除EVENT_LBUTTONDOWN之外的任何事件。丢弃其他事件后,用滑块回调获取输入图像,并用OpenCV的circle函数获取图像中的圆:

image.png

3.4 Qt图形用户界面

Qt用户界面为我们提供了更多控制和选项来处理图像。
其界面分为以下三个主要区域:

  • 工具栏
  • 图像区域
  • 状态栏

我们可以在图3-4中看到这三个区域。图像上面是工具栏,中间是图像区域,底部是状态栏。

image.png

工具栏从左到右具有以下按钮:

  • 用于平移的四个按钮
  • 缩放x1
  • 缩放x30,显示标签
  • 放大
  • 缩小
  • 保存当前图像
  • 显示属性

可以在图3-5中清楚地看到这些选项。

image.png

图像区域显示图像,并且当我们在图像上按下鼠标右键时显示上下文菜单。这个区域可以使用displayOverlay函数在该区域顶部显示叠加消息,该函数接受三个参数:窗口名称、要显示的文本以及显示叠加文本的时间(以毫秒为单位)。如果这个时间设置为0,则文本永远不会消失:

image.png

我们可以在图3-6中看到前面代码的运行结果。你可以在图像顶部看到一个小黑框,其中包含字符串“Overse 5secs”:

image.png

最后,状态栏在窗口的底部显示图像中的像素值和坐标位置,如图3-7所示。

image.png

我们可以在状态栏中像叠加层一样显示消息,displayStatusBar函数可以更改状态栏的消息。该函数具有与叠加函数相同的参数:窗口名称、要显示的文本和文本的显示时间,如图3-8所示。

image.png

将按钮添加到用户界面

在前面的部分中,我们学习了如何创建一般界面或者Qt界面,并使用鼠标和滑块与它们进行交互,但我们也可以创建不同类型的按钮。

image.png

OpenCV Qt支持的按钮类型如下:

  • 按钮
  • 复选框
  • 单选框

这些按钮仅出现在控制面板中。控制面板是每个程序的独立窗口,我们可以在其中附加按钮和跟踪条。要显示控制面板,我们可以按下最后一个工具栏按钮,右键单击Qt窗口的任何部分,并选择“显示属性”窗口,或者用Ctrl + P快捷键。让我们用按钮创建一个基本的例子。代码较长,我们首先解释主函数,然后分别解释每个回调函数,以便更好地理解所有内容。以下代码向我们展示生成用户界面的主函数代码:

image.png

我们应用三种类型的滤镜:模糊、Sobel过滤器以及将颜色转换为灰色。所有这些都是可选的,用户可以使用要创建的按钮选择每一种滤镜。然后,为了获得每个过滤器的状态,我们创建了三个全局布尔变量:

image.png

在main函数中,在加载图像并创建窗口之后,必须使用createButton函数来创建每个按钮。
OpenCV中定义了三种按钮类型:

  • QT_CHECKBOX
  • QT_RADIOBOX
  • QT_PUSH_BUTTON

每个按钮有五个参数,按顺序如下所示:

  1. 按钮名称
  2. 回调函数
  3. 传递给回调函数的用户变量数据的指针
  4. 按钮类型
  5. 用于复选框和单选框按钮类型的默认初始化状态

然后,创建一个模糊复选框按钮,两个用于颜色转换的单选框按钮,以及一个用于sobel过滤器的按钮,如下面的代码所示:

image.png

这些是main函数中最重要的部分。我们将探讨回调(Callback)函数。每个回调函数都会更改其状态变量以调用另一个名为applyFilters的函数,以便将激活的过滤器添加到输入图像:

image.png

applyFilters函数检查每个过滤器的状态变量:

image.png
image.png

要将颜色更改为灰色,我们使用cvtColor函数,该函数接受三个参数:输入图像、输出图像和颜色转换类型。
最有用的颜色空间转换如下:

  • RGB或BGR到灰度(COLOR_RGB2GRAY, COLOR_BGR2GRAY)
  • RGB或BGR到YcrCb (或YCC) (COLOR_RGB2YCrCb, COLOR_BGR2YCrCb)
  • RGB或BGR到HSV (COLOR_RGB2HSV, COLOR_BGR2HSV)
  • RGB或BGR到Luv (COLOR_RGB2Luv, COLOR_BGR2Luv)
  • 灰度到RGB或BGR (COLOR_GRAY2RGB, COLOR_GRAY2BGR)

可以看到代码很容易记忆。

image.png

模糊滤波器已经在前一节中做过描述,最后,如果applySobel变量为真,就应用sobel滤波器。sobel滤波器是使用sobel算子获得的图像导数,通常用于检测图像边缘。OpenCV能够生成具有内核大小的不同导数,但最常见的是用于计算x导数或y导数的3x3内核。
最重要的sobel参数如下:

  • 输入图像
  • 输出图像
  • 输出图像深度(CV_8U,CV_16U,CV_32F,CV_64F)
  • 导数x的阶
  • 导数y的阶
  • 内核大小(默认值为3)

要生成3×3内核和第一个x阶导数,必须使用以下参数:

image.png

以下参数用于y阶导数:

image.png

在这个例子中,我们同时使用x和y导数来重写输入。以下代码段显示如何通过在第四个和第五个参数中添加1来同时生成x和y导数:

image.png

同时应用x和y导数的结果看起来像应用于Lena图片的图3-9。

image.png

3.5 OpenGL支持

OpenCV包括对OpenGL的支持。OpenGL是一个作为标准而集成在几乎所有图形卡中的图形库。OpenGL能够把2D图像绘制成复杂的3D场景。由于在许多任务中表现3D空间的重要性,OpenCV包括了对OpenGL的支持。要在OpenGL中允许支持窗口,必须在调用namedWindow创建窗口时设置WINDOW_OPENGL标志。
下面的代码创建一个支持OpenGL的窗口,并绘制一个旋转平面,我们将在其中显示网络摄像头框架:

image.png
image.png
image.png
image.png

我们一起来理解这段代码!
第一个任务是创建所需的全局变量,用来存储捕获的视频帧并保存帧,然后控制动画角度平面和OpenGL纹理:

image.png

在主函数中,必须打开摄像机以检索拍摄的帧:

image.png

如果摄相机正确打开,则使用WINDOW_OPENGL标志创建支持OpenGL的窗口:

image.png

在这个例子中,我们想在平面中绘制来自网络摄像头的图像,因此,需要启用OpenGL纹理:

image.png

现在,我们已准备好在窗口中用OpenGL进行绘制,但是需要像典型的OpenGL应用程序一样设置绘制OpenGL回调。OpenCV提供了带有两个参数的setOpenGLDrawCallback函数,其参数是窗口名称和回调函数:

image.png

在定义OpenCV窗口和回调函数之后,需要创建一个循环来加载纹理,并更新调用OpenGL绘图回调的窗口内容,最后更新角度位置。要更新窗口内容,我们用OpenCV函数更新窗口,并用窗口名称作为参数:

image.png

当用户按下Q键时进入循环。在编译示例应用程序之前,我们需要定义loadTexture函数和on_opengl回调绘制函数。loadTexture函数将Mat帧转换为OpenGL纹理图像,这样就可以在每个回调绘图中加载和使用。在将图像作为纹理加载之前,必须确保在帧矩阵中有数据,即检查数据变量对象是否为空:

image.png

如果帧矩阵中有数据,那么可以创建OpenGL纹理绑定,并将OpenGL纹理参数设置为线性插值:

image.png

现在,必须定义像素如何存储在矩阵中,以及如何使用OpenGL glTexImage2D函数生成像素。非常重要的是,要注意OpenGL默认使用RGB格式,而OpenCV默认使用BGR格式,因此必须在此函数中设置正确的格式:

image.png

现在,当我们在主循环中调用updateWindow时,只需在每个回调上完成平面绘制。我们使用常见的OpenGL函数,然后加载标识OpenGL矩阵以重置之前的所有更改:

image.png

我们还必须加载帧纹理:

image.png

在绘制平面之前,将所有变换应用到场景中。在这个例子中,我们将在1,1,1轴上旋转平面:

image.png

现在,场景已被正确设置,可以绘制平面了,我们将绘制四边形面(具有四个顶点的面),并用glBegin(GL_QUADS)来实现:

image.png

接下来,我们将绘制一个以0,0位置为中心的平面,其大小为2个单位。然后用glTextCoord2D和glVertex2D函数定义要使用的纹理坐标和顶点位置:

image.png
image.png

我们可以在图3-10中看到结果。

image.png

3.6 总结

在这一章中,我们学习了如何使用OpenGL创建不同类型的用户界面来显示图像或3D界面,学习了如何创建滑块和按钮或绘制3D。还学习了原生OpenCV的一些基本图像处理过滤器,但也有新的开源替代品,它们能够添加更多功能,比如cvui(https://dovyski.github.io/cvui/ )或OpenCVGUI(https://damiles.github.io/OpenCVGUI/ )。
在下一章中,我们将构建一个完整的照片工具应用程序,并将应用到目前为止学过的所有知识。我们将学习如何通过图形用户界面将多个过滤器应用于输入图像。

相关文章
|
3月前
|
存储 机器学习/深度学习 算法
使用 OpenCV4 和 C++ 构建计算机视觉项目:1~5
使用 OpenCV4 和 C++ 构建计算机视觉项目:1~5
70 0
|
3月前
|
存储 编解码 算法
OpenCV2 计算机视觉应用编程秘籍:6~10
OpenCV2 计算机视觉应用编程秘籍:6~10
24 0
|
3月前
|
设计模式 算法 开发工具
OpenCV2 计算机视觉应用编程秘籍:1~5
OpenCV2 计算机视觉应用编程秘籍:1~5
22 0
|
3月前
|
存储 API 计算机视觉
Python OpenCV 计算机视觉:1~5
Python OpenCV 计算机视觉:1~5
150 0
|
1月前
|
存储 资源调度 算法
Opencv(C++)系列学习---SIFT、SURF、ORB算子特征检测
Opencv(C++)系列学习---SIFT、SURF、ORB算子特征检测
|
2月前
|
机器学习/深度学习 算法 数据可视化
计算机视觉+深度学习+机器学习+opencv+目标检测跟踪+一站式学习(代码+视频+PPT)-2
计算机视觉+深度学习+机器学习+opencv+目标检测跟踪+一站式学习(代码+视频+PPT)
97 0
|
2月前
|
机器学习/深度学习 Ubuntu Linux
计算机视觉+深度学习+机器学习+opencv+目标检测跟踪+一站式学习(代码+视频+PPT)-1
计算机视觉+深度学习+机器学习+opencv+目标检测跟踪+一站式学习(代码+视频+PPT)
55 1
|
1月前
|
算法 C++ 计算机视觉
Opencv(C++)学习系列---Laplacian拉普拉斯边缘检测算法
Opencv(C++)学习系列---Laplacian拉普拉斯边缘检测算法
|
1月前
|
算法 计算机视觉 C++
Opencv(C++)学习系列---Sobel索贝尔算子边缘检测
Opencv(C++)学习系列---Sobel索贝尔算子边缘检测
|
1月前
|
算法 C++ 计算机视觉
Opencv(C++)学习系列---Canny边缘检测算法
Opencv(C++)学习系列---Canny边缘检测算法

热门文章

最新文章