带你读《OpenCV 4计算机视觉项目实战 (原书第2版)》之二:OpenCV基础知识导论

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

点击查看第一章
点击查看第三章

第2章 OpenCV基础知识导论

在第1章介绍了在不同操作系统上安装OpenCV之后,我们将在本章介绍OpenCV开发的基础知识。首先介绍如何使用CMake创建项目。我们将介绍基本的图像数据结构和矩阵,以及在项目中工作所需的其他结构。我们还会介绍如何通过OpenCV的XML/YAML存储函数将变量和数据保存到文件中。
本章介绍以下主题:

  • 使用CMake配置项目
  • 从/向磁盘读取/写入图像
  • 读取视频和访问相机设备
  • 主要图像结构(例如,矩阵)
  • 其他重要和基本的结构(例如,向量和标量)
  • 基本矩阵运算简介
  • 使用XML/YAML存储OpenCV API进行文件存储操作

2.1 技术要求

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

2.2 基本CMake配置文件

为配置和检查项目的所有必要依赖项,我们会用到CMake,但这不是唯一可以完成此操作的方法。我们可以在任何其他工具或IDE中配置我们的项目,例如Makefiles或Visual Studio,但CMake是一种用于配置多平台C++项目的更便携的方式。
CMake使用名为CMakeLists.txt的配置文件,可以在其中定义编译和依赖关系过程。对于从单个源代码文件构建可执行文件的基本项目,只需要一个包含三行代码的CMakeLists.txt文件。
该文件的内容类似于:

image.png

第一行定义所需的CMake最低版本,该行在CMakeLists.txt文件中是必需的,它使我们能够使用在特定版本中定义的CMake功能。在我们的例子中,要求最低版本为CMake 3.0。第二行定义项目的名称。这个名称保存在名为PROJECT_NAME的变量中。
最后一行从main.cpp文件创建一个可执行命令(add_executable()),并将其命名为与项目(${PROJECT_NAME})相同的名称,然后将源代码编译成一个名为CMakeTest的可执行文件,这是我们设置的项目名称。${}表达式能够访问环境中定义的任何变量。之后,我们就可以用${PROJECT_NAME}变量作为输出的可执行文件的名称。

2.3 创建一个库

CMake可用于创建由OpenCV构建系统使用的库。在多个应用程序之间分解共享代码是软件开发中常见且有用的做法。在大型应用程序中,或者在多个应用程序共享的公共代码中,这种做法非常有用。在这种情况下,我们不创建二进制可执行文件,而是创建一个包含所有函数、类等的编译文件。这样就可以和其他应用程序分享此库文件,而无须共享我们的源代码。
CMake为此提供了add_library函数:

image.png

以#开头的行是添加的注释,会被CMake忽略。add_library(Hello hello.cpp hello.h)命令定义库的源文件及其名称,其中Hello是库名,hello.cpp和hello.h是源文件。我们还添加了头文件,使得诸如Visual Studio这样的IDE能够链接到头文件。该行将会生成一个共享(.so适用于Mac OS X和Unix,.dll适用于Windows)或静态库(.a适用于Mac OS X和Unix,.lib适用于Windows)文件,具体取决于我们是否在库名和源文件之间添加SHARED或STATIC字。target_link_libraries(executable Hello)是将可执行文件链接到所需库的函数,在我们的例子中,需要的库是Hello库。

2.4 管理依赖项

CMake具备搜索依赖项和外部库的能力,这使我们能够根据项目中的外部组件构建复杂的项目,并添加一些要求。
在本书中,最重要的依赖项自然是OpenCV,我们将把它添加到我们的所有项目中:

image.png

现在,我们通过以下代码了解脚本的工作原理:

image.png

第一行定义CMake的最低版本,第二行告诉CMake使用CMake的新行为,以便识别正确的数字和布尔常量,而无须使用这些名称间接引用变量。该策略是在CMake 2.8.0中引入的,当3.0.2版本中未设置此策略时,CMake会发出警告。最后一行定义项目的标题。定义项目名称后,我们必须定义需求、库和依赖项:

image.png

这段代码搜索OpenCV依赖项。FIND_PACKAGE能够查找依赖项、所需的最低版本以及该依赖是必需的还是可选的。在这个示例脚本中,我们查找4.0.0或更高版本的OpenCV,并声明它是必需包。

image.png

如果CMake没有找到它,就会返回错误,并且不会阻止我们编译应用程序。MESSAGE函数在终端或CMake GUI中显示一条消息。在这个例子中,我们将这样显示OpenCV版本:

image.png

${OpenCV_VERSION}是CMake用来存储OpenCV包版本的变量。include_directories()和link_directories()向环境中添加指定库的头文件和路径。OpenCV CMake的模块将这些数据保存在${OpenCV_INCLUDE_DIRS}和${OpenCV_LIB_DIR}变量中。并非所有平台(例如Linux)都需要这些命令行,因为这些路径通常位于环境中,但是建议使用多个OpenCV版本来选择正确的链接并包含路径。现在包含我们开发的源文件:

image.png

最后一行创建可执行文件,并将可执行文件与OpenCV库链接,如上一节中所述。这段代码中有一个新的函数SET,该函数创建一个新变量,并向其添加我们需要的任何值。在这个例子中,我们将main.cpp值合并到SRC变量中。我们还可以在同一个变量中添加更多的值,如下面的脚本所示:

image.png

2.5 让脚本更复杂

在本节中,我们将要展示一个更复杂的脚本,它包括子文件夹、库和可执行文件。但实际上,该脚本只有两个文件和几行代码,如下例所示。没有必要创建多个CMakeLists.txt文件,因为我们可以在主CMakeLists.txt文件中指定所有内容。但是,为每个项目子文件夹使用不同的CMakeLists.txt文件更为常见,可以使其更加灵活和便携。
这个例子有一个代码结构文件夹,其中包含一个utils库文件夹和一个根文件夹,后者包含主可执行文件:

image.png

然后,我们必须定义两个CMakeLists.txt文件,一个在根文件夹中,另一个在utils文件夹中。CMakeLists.txt根文件夹文件具有以下内容:

image.png

除了我们将要解释的一些函数之外,几乎所有的代码行都在前面中有过描述。add_subdirectory()告诉CMake分析所需子文件夹的CMakeLists.txt。在继续说明主CMakeLists.txt文件之前,我们先解释utils中的CMakeLists.txt文件。
在utils文件夹的CMakeLists.txt文件中,我们将编写一个将包含在主项目文件夹中的新库:

image.png

此CMake脚本文件定义一个变量UTILS_LIB_SRC,我们在其中添加库中包含的所有源文件,并使用add_library函数生成库,并且使用target_include_directories函数以便允许主项目检测所有头文件。离开utils子文件夹,继续准备根CMake脚本,其中,Option函数创建一个新的变量,在这个例子中为WITH_LOG,并附带一小段描述。可以通过ccmake命令行或显示描述内容的CMake GUI界面更改这个变量,用户还可以通过一个复选框启用或禁用此选项。这个函数非常有用,它使用户能够决定编译时功能,例如,我们是否要启用或禁用日志,是否像OpenCV一样使用Java或Python进行编译,等等。
在这个例子中,我们使用此选项在应用程序中启用记录器。为启用记录器,我们在代码中使用了一个预编译器定义,如下所示:

image.png

可以通过调用add_definitions函数(-DLOG)在CMakeLists.txt中定义这个LOG宏,该函数本身可以使用简单条件根据CMake变量WITH_LOG运行或隐藏:

image.png

至此,我们就完成了创建CMake脚本文件的准备工作,可以在任何操作系统中编译我们的计算机视觉项目。然后,在开始示例工程之前,我们会继续介绍OpenCV的基础知识。

2.6 图像和矩阵

毫无疑问,计算机视觉中最重要的结构是图像。计算机视觉中的图像是用数字设备捕获的物理世界的表示。这种图片只是以矩阵格式存储的一系列数字(参见图2-1)。每个数字是所考虑的波长(例如,彩色图像中的红色、绿色或蓝色)或波长范围(对于全色设备)的光强度的测量结果。图像中的每个点都称为像素(对于图像元素),并且每个像素可以存储一个或多个值,这取决于它是否是仅存储一个值的黑白图像(也称为二进制图像,比如只存储0或1),还是存储两个值的灰度图像,或者是存储三个值的彩色图像。这些值通常在整数0~255,但也可以使用其他范围,比如在高动态范围成像(high dynamic range imaging,简称HDRI)或热图像领域中的浮点数0~1。
图像是以矩阵格式存储的,其中的每个像素都有一个位置,并且可以通过列和行的编号来引用。OpenCV用Mat类来达到这个目的。在灰度图像中,使用单个矩阵,如图2-2所示。
在如图2-3所示的彩色图像中,使用了一个宽度×高度×颜色通道数的矩阵。

image.png
image.png

但Mat类不仅仅用于存储图像,它还能存储任何类型和不同大小的矩阵。你可以将其用作代数矩阵并用它执行运算。在接下来的内容中,我们将描述最重要的矩阵运算,例如加法、乘法、对角化。但是,在此之前,了解矩阵如何存储在计算机内存中是非常重要的,因为直接访问内存,总比用OpenCV函数访问每个像素更加高效。
在内存中,矩阵被保存为按列和行排序的数组或值序列。表2-1显示BGR图像格式的像素序列。

image.png

按照这个顺序,我们可以通过以下公式来访问任何像素:

image.png

2.7 读/写图像

在介绍矩阵之后,我们将首先讨论OpenCV代码的基础知识。我们要学习的第一件事是如何读/写图像:

image.png
image.png

现在我们来理解代码。

image.png

首先,必须包括例子中需要的函数的声明。这些函数来自core(基本图像数据处理)和highgui(OpenCV提供的跨平台I/O函数是core和highgui;第一个包括基本类,比如矩阵,而第二个包括读函数、写函数,以及用图形界面显示图像的函数)。现在读取图像:

image.png

imread是读取图像的主函数。该函数打开图像,并以矩阵格式存储它。imread接受两个参数,第一个参数是图像路径字符串,第二个参数是可选的,用于指定要加载的图像类型,默认情况下为彩色图像。第二个参数可以使用以下选项:

  • cv::IMREAD_UNCHANGED:如果设置,当输入具有相应的深度时,返回16位/ 32位图像,否则将其转换为8位
  • cv::IMREAD_COLOR:如果设置,它总是将图像转换为彩色图像(BGR,8位无符号)
  • cv::IMREAD_GRAYSCALE:如果设置,它总是将图像转换为灰度图像(8位无符号)

要保存图像,可以使用imwrite函数,它将矩阵图像存储在计算机中:

image.png

第一个参数是保存图像的路径,以及想要的扩展名格式,第二个参数是要保存的矩阵图像。在这个代码例子中,我们创建并存储图像的灰度版本,然后将其另存为.jpg文件。加载的灰度图像将存储在gray变量中:

image.png

通过使用矩阵的.cols和.rows属性,可以访问图像的列数和行数,换句话说,可以访问其宽度和高度:

image.png

要访问图像的一个像素,可以用Mat OpenCV类中的模板函数cv::Mat::at (row,col),模板参数是所需的返回类型。8位彩色图像中的类型名称是Vec3b类,它存储三个无符号字符数据(Vec =向量,3 =组件数,b = 一个字节)。在灰度图像中,可以直接使用无符号字符,或图像中使用的任何其他数字格式,例如uchar pixel = color.at (myRow,myCol)。最后,为了展示图像,可以使用imshow函数,它创建一个窗口,其标题作为第一个参数,图像矩阵作为第二个参数:

image.png

前面代码的结果如图2-4所示,左边的图像是彩色图像,右边的图像是灰度图像。

image.png

最后,我们按以下示例创建CMakeLists.txt文件,并使用该文件编译代码。
以下代码描述了CMakeLists.txt文件:

image.png

要使用此CMakeLists.txt文件编译代码,必须执行以下步骤:

  1. 创建一个build文件夹。
  2. 在build文件夹内,(在Windows中)执行CMake或打开CMake GUI应用程序,选择source文件夹和build文件夹,然后按下“Configure”(配置)和“Generate”(生成)按钮。
  3. 如果正在使用Linux或MacOSX,请照常生成Makefile,然后用make命令编译项目。如果正在使用Windows,请用在步骤2中选择的编辑器打开项目,然后进行编译。

在编译应用程序之后,将会在build文件夹中生成一个名为app的可执行文件。

2.8 读取视频和摄像头

本节将用这个简单示例向你介绍视频和摄像头的读取。在解释如何读取视频或摄像头的输入之前,我们想介绍一个非常有用的新类,它可以帮助我们管理输入命令行参数。这个新类是在OpenCV 3.0版中引入的,它就是CommandLineParser类:

image.png

我们必须为CommandLineParser做的第一件事是在常量char向量中定义我们需要或允许的参数,每一行都采用以下模式:

image.png

name_param可以以@开头,这会将此参数定义为默认输入。我们可以使用多个name_param:

image.png

构造函数将获取main函数的输入和先前定义的key常量:

image.png

.has类方法检查参数是否存在。在示例中,我们检查用户是否添加参数help或?,然后使用类函数printMessage显示所有描述参数:

image.png

使用.get(parameterName)函数可以访问和读取任何输入参数:

image.png

获取所有必需的参数以后,即可检查这些参数是否被正确解析,并在其中一个参数未被解析时显示错误消息,例如,添加的是一个字符串而不是一个数字:

image.png

用于视频读取和摄像头读取的类是相同的VideoCapture类,与之前版本的OpenCV中一样,它属于videoio子模块而不是highgui子模块。创建对象后,我们检查输入命令行参数videoFile是否有路径文件名。如果它是空的,那么尝试打开网络摄像头;如果它有文件名,则打开视频文件。为此,可以使用open函数,将视频文件名或我们要打开的索引摄像头作为参数。如果我们有一个摄像头,可以用0作为参数。
要检查是否可以读取视频文件名或摄像头,可以使用isOpened函数:

image.png

最后,创建一个窗口,使用namedWindow函数和无限循环来显示帧,用>>操作抓取每个帧,如果正确地检索到帧,则使用imshow函数显示该帧。在这种情况下,我们不想让应用程序停止,但是会调用waitKey(30)等待30毫秒,以此检查用户是否使用任何键停止应用程序的执行。

image.png

当用户想结束应用程序时,他们所要做的就是按下任意键,然后我们必须使用释放函数释放所有的视频资源。

image.png

前面代码的结果是用一个新窗口显示BGR格式的视频或网络摄像头。

2.9 其他基本对象类型

我们已经了解了Mat和Vec3b类,但还有很多类需要学习。
在本节中,我们将学习大多数项目中所需的最基本的对象类型:

  • Vec
  • Scalar
  • Point
  • Size
  • Rect
  • RotatedRect

2.9.1 Vec对象类型

Vec是一个主要用于数值向量的模板类。我们可以定义向量的类型和组件的数量:

image.png

我们还可以使用任何的预定义类型:

image.png
image.png
image.png

2.9.2 Scalar对象类型

Scalar对象类型是从Vec派生的模板类,有四个元素。Scalar类型在OpenCV中广泛用于传递和读取像素值。
要访问Vec和Scalar值,可以使用[]运算符,其初始化可以用传值的方式通过设置另一个标量、向量或值来完成,如下例所示:

image.png

2.9.3 Point对象类型

另一个非常常见的类模板是Point。该类定义一个由其坐标x和y指定的2D点。

image.png

与Vec类一样,OpenCV为方便起见定义了以下Point别名:

image.png

OpenCV为Point定义了以下运算符:

image.png

2.9.4 Size对象类型

Size是另一个非常重要并且在OpenCV中广泛使用的模板类,用于指定图像或矩形大小。这个类添加了两个成员width和height,以及有用的area()函数。在下面的示例中,我们可以看到许多使用Size的方法:

image.png

2.9.5 Rect对象类型

Rect是另一个重要的模板类,用于定义由以下参数定义的2D矩形:

  • 左上角的坐标
  • 矩形的宽度和高度

Rect模板类可用于定义图像的感兴趣区域(Region of Interest,简称ROI),如下所示:

image.png

2.9.6 RotatedRect对象类型

最后一个有用的类是名为RotatedRect的特定矩形。该类表示一个旋转矩形,该矩形由中心点、矩形的宽度和高度以及单位为度的旋转角度指定:

image.png

这个类的一个有趣的函数是boundingBox,该函数返回一个包含旋转矩形的Rect,如图2-5所示。

image.png

2.10 基本矩阵运算

在本节中,我们将学习一些基本和重要的矩阵运算,这些运算可以应用于图像或任何矩阵数据。我们已经知道如何加载图像并将其存储在变量Mat中,此外还可以手动创建Mat。最常见的构造函数是为矩阵提供大小和类型,如下所示:

image.png

受支持的类型取决于要存储的数字类型和通道数,最常见的类型如下:

image.png

初始化不会设置数据的值,因此可能获得不需要的值。为了避免不需要的值,可以使用0或1值及其各自的函数来初始化矩阵:

image.png

前面矩阵的结果如图2-6所示。

image.png

一个特殊矩阵初始化是eye函数,它可以创建具有指定类型和大小的单位矩阵:

image.png

其输出如图2-7所示。

image.png

OpenCV的Mat类能够执行所有的矩阵运算。我们可以用+和-运算符来加上或减去两个相同大小的矩阵,如以下代码块所示:

image.png

上述操作的结果如图2-8所示。

image.png

我们可以用运算符乘以一个标量,或者用mul函数乘以矩阵的每个元素,也可以用运算符执行矩阵乘法:

image.png

上述操作的结果如图2-9所示。

image.png

其他常见的数学矩阵运算是转置(transposition)和矩阵求逆(matrix inversion),分别由t()和inv()函数定义。OpenCV提供的其他有趣的函数是矩阵中的数组运算,例如,计算非零元素。这对于计算对象的像素或区域很有用:

image.png

OpenCV提供了一些统计功能,可以使用meanStdDev函数计算通道的平均值和标准差:

image.png

另一个有用的统计函数是minMaxLoc,该函数可以查找矩阵或数组的最小值和最大值,并返回位置和值:

image.png

这里的src是输入矩阵,minVal和maxVal是检测到的最小值和最大值,minLoc和maxLoc是检测到的Point值。

image.png

2.11 基本数据存储

在结束本章之前,我们将探讨OpenCV用来存储和读取数据的函数。在许多应用程序中(例如校准或机器学习),当我们完成大量计算时,需要保存这些结果,以便在后续操作中检索它们。OpenCV为此提供了XML / YAML持久层。
写入FileStorage
要把一些OpenCV或其他数值数据写入文件,可以用FileStorage类,同时要使用流运算符<<操作STL流:

image.png
image.png

要创建保存数据的文件,只需调用构造函数,并提供包含所需扩展名格式的路径文件名(XML或YAML),以及第二个要写入的参数集:

image.png

如果要保存数据,只需在第一步给出一个标识符,然后提供想要保存的矩阵或值,通过这种方式来使用流操作符。例如,要保存int变量,只需要编写以下代码行:

image.png

否则,可以按如下所示写入/保存mat:

image.png

上述代码的结果是YAML格式:

image.png

从文件中读取先前保存的文件与save函数非常相似:

image.png

第一个阶段是通过调用FileStorage构造函数并使用适当的参数、路径和FileStorage::READ来打开一个保存的文件:

image.png

要读取任何存储的变量,只需使用公共的流运算符>>并使用FileStorage对象和带[]运算符的标识符:

image.png

2.12 总结

在本章中,我们学习了OpenCV的基础知识和最重要的类型和操作(访问图像和视频),以及它们如何存储在矩阵中。我们还学习了基本的矩阵运算和用于存储像素的其他基本OpenCV类、向量等。最后,我们学习了如何将数据保存在文件中,以便在其他应用程序或其他操作中读取它们。
在下一章中,我们将学习如何创建第一个应用程序,从而学习OpenCV提供的图形用户界面的基础知识。我们将创建按钮和滑块,并介绍一些图像处理的基础知识。

相关文章
|
2月前
|
机器学习/深度学习 监控 算法
基于计算机视觉(opencv)的运动计数(运动辅助)系统-源码+注释+报告
基于计算机视觉(opencv)的运动计数(运动辅助)系统-源码+注释+报告
58 3
|
5月前
|
机器学习/深度学习 人工智能 算法
Python在计算机视觉(CV)中扮演重要角色,得益于其丰富的库如OpenCV、Pillow和Scikit-image。
【7月更文挑战第5天】Python在计算机视觉(CV)中扮演重要角色,得益于其丰富的库如OpenCV、Pillow和Scikit-image。CV涉及图像处理、模式识别和机器学习,用于图像理解和生成。Python的跨平台特性和活跃社区使其成为CV的理想工具。基本流程包括图像获取、预处理、特征提取、分类识别及图像生成。例如,面部识别通过预处理图像,使用如`cv2.CascadeClassifier`进行检测;物体检测类似,但需适应不同目标;图像生成则利用GAN创造新图像。
71 4
|
5月前
|
机器学习/深度学习 数据采集 算法
Python基于OpenCV和卷积神经网络CNN进行车牌号码识别项目实战
Python基于OpenCV和卷积神经网络CNN进行车牌号码识别项目实战
375 19
|
6月前
|
机器学习/深度学习 算法 Java
计算机视觉——opencv快速入门(一) opencv的介绍与安装
OpenCV是开源计算机视觉库,支持C++, Python, Java等,用于图像处理、视频分析等。建议使用较早版本如3.4.3,因高版本部分算法需付费。通过Anaconda创建Python虚拟环境来安装,选择合适的Python和OpenCV版本。激活环境后,用`pip`安装`opencv-python`。基本环境配置完成后,通过显示图像的Python代码测试安装是否成功。
计算机视觉——opencv快速入门(一) opencv的介绍与安装
|
5月前
|
机器学习/深度学习 XML 计算机视觉
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习库,它提供了大量的函数和工具,用于处理图像和视频数据。
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习库,它提供了大量的函数和工具,用于处理图像和视频数据。
|
6月前
|
算法 计算机视觉 Python
openCV 3计算机视觉 Python语言实现 笔记 第4章 深度估计与分割
openCV 3计算机视觉 Python语言实现 笔记 第4章 深度估计与分割
|
6月前
|
算法 计算机视觉 Python
openCV 3计算机视觉 Python语言实现 笔记 第三章 使用OpenCV 3处理图像
openCV 3计算机视觉 Python语言实现 笔记 第三章 使用OpenCV 3处理图像
|
2月前
|
计算机视觉
Opencv学习笔记(三):图像二值化函数cv2.threshold函数详解
这篇文章详细介绍了OpenCV库中的图像二值化函数`cv2.threshold`,包括二值化的概念、常见的阈值类型、函数的参数说明以及通过代码实例展示了如何应用该函数进行图像二值化处理,并展示了运行结果。
473 0
Opencv学习笔记(三):图像二值化函数cv2.threshold函数详解
|
3月前
|
算法 计算机视觉
opencv图像形态学
图像形态学是一种基于数学形态学的图像处理技术,它主要用于分析和修改图像的形状和结构。
52 4
|
3月前
|
存储 计算机视觉
Opencv的基本操作(一)图像的读取显示存储及几何图形的绘制
本文介绍了使用OpenCV进行图像读取、显示和存储的基本操作,以及如何绘制直线、圆形、矩形和文本等几何图形的方法。
Opencv的基本操作(一)图像的读取显示存储及几何图形的绘制