购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.3 使用OpenCV和Python处理数据

数据世界充满了各种各样的数据类型。有时,这会使用户很难区分用于特定值的数据类型。在此,我们将尽量保持简单性,除保留标准数据类型的标量值之外,将所有内容都当成数组处理。因为图像有宽度和高度,所以图像将变成二维数组。一维数组可能是强度随时间变化的一个声音片段。

如果你经常使用OpenCV的C++ 应用程序接口 (Application Programming Interface,API)并打算继续这样做的话,那么你可能会发现用C++处理数据会有点麻烦。你不但必须处理C++语言的语法,而且必须处理各种数据类型以及跨平台的兼容性问题。

如果你使用OpenCV的Python API,就会最大程度上简化这个过程,因为你可以自动访问 科学Python (Scientific Python,SciPy)社区中提供的大量开源包。一个相关示例是 数值Python (Numerical Python,NumPy)包,大多数科学计算工具都是围绕它来构建的。

2.3.1 开始一个新的IPython或Jupyter会话

在我们开始使用NumPy之前,我们需要打开一个IPython shell或者启动一个Jupyter Notebook:

1)如第1章所述,打开一个终端,导航到OpenCV-ML目录:

2)激活我们在第1章中创建的conda环境:

3)启动一个新的IPython或者Jupyter会话:

如果你选择启动一个IPython会话,程序应该会向你发送类似于下面这样的欢迎消息:

在以In [1]开头的代码行中,你可以输入常规Python命令。另外,你还可以在输入变量和函数名称时按下Tab键,让IPython自动完成Python命令。

提示

有限数量的Unix和macOS系统shell命令也可以工作,例如ls和pwd。你可以给shell命令添加前缀“!”(例如,!ping www.github.com),然后运行所有的shell命令。详细信息请查阅官方IPython文献,网址为https://ipython.org/ipython-doc/3/interactive/tutorial.html。

如果你选择启动一个Jupyter会话,在你的Web浏览器中应该会打开一个新的窗口,指向http://localhost:8888。如果你想要创建一个新的notebook,那么点击右上角的 New ,选择 Notebooks(Python 3) ,如图2-2所示。

图2-2 在浏览器中打开一个Jupyter新窗口

这会打开一个新的窗口,如图2-3所示。

图2-3 Jupyter会话窗口

用In [ ]标记的单元格(看起来与之前的文本框很像)与IPython会话中的命令行类似。现在,可以开始输入你的Python代码了!

2.3.2 使用Python的NumPy包处理数据

如果你已经安装了Anaconda,那么就假设你已经在虚拟环境中安装了NumPy。如果你使用过Python的标准发行版或任何其他发行版,你可以访问http://www.numpy.org,并按照所提供的安装说明进行操作。

如前所述,如果你还不是Python专家,也无关紧要。谁知道呢,也许你刚刚从OpenCV的C++API转向Python。一切都正常。我们想让你快速了解一下如何开始使用NumPy。如果你是高级Python用户,那么你可以直接跳过本节内容。

一旦你熟悉了NumPy,就会发现Python世界中的大多数科学计算都是围绕NumPy构建的。这包括OpenCV,因此花在NumPy上的学习时间最终对你是有益的。

1. 导入NumPy

一旦启动了一个新的IPython或者Jupyter会话,就可以导入Numpy模块并按照以下步骤来验证版本:

提示

记得在Jupyter Notebook中,键入命令后,你可以按下Ctrl+Enter,以执行一个单元格。或者,按下Shift+Enter以执行单元格,并自动插入或者选择该单元格下面的单元格。依次单击 Help | Keyboard Shortcut 以检查所有的键盘快捷键,或者依次单击 Help | User Interface Tour 以进行快速浏览。

此处讨论的部分包,建议使用NumPy 1.8版本或后续版本。按照惯例,你会发现在科学Python领域中,大多数人导入NumPy都会使用np作为别名:

本章及本书的其余章节,我们都将遵循同样的惯例。

2. 理解NumPy数组

你可能已经知道Python是一种 弱类型的语言 。这就意味着,你无论何时创建一个新变量,都不必指定数据类型。例如,下面的内容将自动表示为一个整数:

输入下面内容以再次确认:

注意

因为标准Python实现是用C编写的,所以每个Python对象本质上是一个伪C结构。这对于Python中的整数也是如此,实际上它是指向复合C结构的指针,包含的不仅仅是 原始 整数值。因此,用于表示Python整数的默认C数据类型将依赖于你的系统架构(即系统是32位还是64位平台)。

更进一步,我们使用list()命令可以创建一个整数列表,这是Python中的标准多元素容器。range (x)函数将创建从0到x–1的所有整数。要输出变量,你可以使用print函数,也可以直接输入变量名字并按Enter:

类似地,我们通过让Python遍历整数列表int_list中的所有元素,并对每个元素应用str()函数(该函数将一个数转换成一个字符串),来创建一个字符串列表:

可是,用列表进行数学运算并不是很灵活。例如,我们想要将int_list中的每个元素都乘以一个因子2。执行以下操作可能是一种简单的方法——看看输出结果是怎样的:

Python创建了一个列表,其内容是int_list的所有元素生成了两次,这并不是我们想要的!

这就是NumPy的用武之地。NumPy是专为简化Python中的数组运算而设计的。我们可以快速将整数列表转换为一个NumPy数组:

让我们看看试着将数组中的每个元素相乘会怎么样:

这次我们做对了!加法、减法、除法以及很多其他运算也是同样的。

而且,每个NumPy数组都具有以下属性:

让我们来看看整数数组的上述属性:

从这些输出中,我们可以看到我们的数组只包含一维,其包含10个元素且所有元素都是64位的整数。当然,如果你在32位机器上执行这段代码,你可能会得到dtype:int 32。

3. 通过索引访问单个数组元素

如果你之前使用过Python的标准列表索引,那么你就不会发现NumPy中的索引有很多问题。在一维数组中,通过在方括号中指定所需的索引,可以访问第i个值(从0开始计算),与Python列表一样:

要从数组的末尾建立索引,可以使用负索引号:

切割数组 还有一些其他很酷的技巧,如下所示:

建议你自己尝试使用这些数组!

提示

NumPy中切割数组的一般形式与标准Python列表中的相同。使用x [start: stop: step]访问数组x中的一个片段。如果没有指定任何一个值,那么默认值为start=0、stop=size of dimension、step=1。

4. 创建多维数组

数组不必局限于列表。实际上,数组可以有任意维数。在机器学习中,通常我们至少要处理二维数组,列索引表示特定的特征值,行包含实际的特征值。

使用NumPy可以轻松地从头开始创建多维数组。假设我们想要创建一个3行5列的数组,所有的元素都初始化为0。如果我们不指定数据类型,NumPy将默认使用float类型:

使用OpenCV时你可能就知道:这可以解释为所有像素设置为0(黑色)的一个3×5的灰度图像。例如,如果你想要创建具有3个颜色通道(R、G和B)2×4像素的一个小图像,但是所有像素都设置为白色,我们将使用NumPy创建一个3×2×4的三维数组:

这里,第一维定义颜色通道(OpenCV中的蓝色、绿色和红色)。因此,如果这是真实的图像数据,我们可以通过切割数组轻松地获得第一个通道中的颜色信息:

在OpenCV中,图像要么是值在0到1之间的32位浮点数组,要么是值在0到255之间的8位整数数组。因此,使用8位整数,通过指定NumPy的dtype属性并将数组中的所有1乘以255,我们还可以创建一个2×4像素、全为白色的RGB图像:

在后续章节中,我们还将学习更高级的数组操作。

2.3.3 用Python加载外部数据集

感谢SciPy社区,它提供了很多可以帮助我们获得一些数据的资源。

一个特别有用的资源以 scikit-learn 的sklearn.datasets包的形式出现。这个包预装了一些小数据集,我们不需要从外部网站下载任何文件。这些数据集包含以下内容:

此外,scikit_learn允许我们直接从外部存储库下载数据集,例如:

更好的是,可以从机器学习数据库(http://openml.org)中直接下载数据集。例如,要下载Iris数据集,只需输入以下命令:

Iris数据集共有150个样本、4个特征——花萼长度、花萼宽度、花瓣长度和花瓣宽度。数据分为三类——山鸢尾、花斑鸢尾和维吉尼亚鸢尾。数据和标签位于两个独立的容器中,我们可以根据下列操作查看:

此处,我们可以看到iris_data包含150个样本,每个样本包含4个特征(这就是shape中的数字是4的原因)。标签存储在iris_target中,其中每个样本只有一个标签。

我们可以进一步查看所有目标的值,但是我们不希望只输出这些目标值。我们感兴趣的是查看所有不同的目标值,使用NumPy可轻松实现:

注意

你应该听说过另一个用于数据分析的Python库是 pandas (http://pandas.pydata.org)。pandas为数据库和电子表格实现了几个功能强大的数据操作。不管这个库多么强大,此时,pandas对于我们来说有点太高级了。

2.3.4 使用Matplotlib可视化数据

如果我们不知道如何查看数据,那么知道如何加载数据的作用是有限的。谢天谢地,幸好还有 Matplotlib

Matplotlib是建立在NumPy数组上的一个多平台数据可视化库——看吧,我说过NumPy还会再次出现的。在2002年,约翰·亨特(John Hunter)提出Matplotlib,最初的构思是设计为IPython的一个补丁,以便能够从命令行启用交互式MATLAB样式绘图。近几年,更新、更炫酷的工具(例如,R语言中的ggplot和ggvis)层出不穷,最终取代了Matplotlib,可是Matplotlib仍然是一个经过良好测试的、非常重要的跨平台图形引擎。

1. 导入Matplotlib

你可能又走运了:如果你按照第1章中的建议安装了完整的Python Anaconda,那么你已经安装了Matplotlib,可以开始了。否则,你可能要访问http://matplotlib.org以获取安装说明。

就像我们用缩写np来表示NumPy一样,我们也会用一些标准的缩写来表示Matplotlib导入:

plt是我们最常用的一个接口,在本书中我们将常看到plt接口。

2. 生成一个简单的图形

言归正传,让我们创建第一个图形。

假设我们要绘制正弦函数sin(x)的一个简单线图。我们希望函数求x轴(0≤x≤10)上的所有值。我们将使用NumPy的linspace函数在x轴上创建一个线性空间,x值从0到10,共100个样本点:

我们可以使用NumPy的sin函数求sin函数的所有x值,并通过调用plt的plot函数可视化结果:

你亲自试过了吗?发生什么了?有什么发现吗?

问题是,这取决于你在何处运行这个脚本,你可能什么都看不到。以下是可以考虑的可能性:

调用后,图形就会显示出来!

然后,所有图都会自动显示出来,不必每次都调用plt.show()。

在本书中,我们通常会选择内联选项:

现在,让我们再试一次:

上述命令给出的输出如图2-4所示。

图2-4 应用内联选项生成的图

稍后,如果你想保存图表,可以直接从IPython或Jupyter Notebook的选项中保存:

只要保证使用所支持的文件后缀即可,例如.jpg、.png、.tif、.svg、.eps或者.pdf。

提示

在导入matplotlib之后,运行plt.style.use(style_name),你可以更改绘图的样式。在plt.style.available中列出了所有可用的样式。例如,试试plt.style.use('fivethirtyeight')、plt.style.use('ggplot')或者plt.style.use('seaborn-dark')。为了增加乐趣,可以运行plt.xkcd(),再尝试绘制其他内容。

3. 可视化外部数据集的数据

作为本章的最后一个测试,让我们可视化一些来自外部数据集的数据,例如scikit-learn的digits数据集。

具体来说,我们将需要3个可视化工具:

首先,让我们导入所有这些可视化工具:

第一步是实际加载数据:

如果我们没有记错的话,digits应该有2个不同的字段:一个是data字段,包含实际的图像数据;另一个是target字段,包含图像标签。与其相信我们的记忆,不如让我们研究一下digits对象。这通过输入字段名称、添加句点、再按下Tab键——digits.<TAB>来实现。这会显示出digits对象还包含了一些其他字段,例如一个名为images的字段。images和data这2个字段似乎只是形状不同:

在这两个例子中,第一维都对应于数据集中的图像数。但是data将所有像素排列在一个大的向量中,而images则保留了每个图像的8×8空间排列。

因此,如果我们想绘制单张图像,images字段可能更合适。首先,使用NumPy的数组切割,从数据集中抓取一张图像:

这里,我们说想要抓取长为1797项的数组中的第一行,以及所有对应的8×8=64个像素。然后,我们可以使用plt的imshow函数绘制图像:

上述命令给出的输出如图2-5所示。请注意,图像是模糊的,因为我们将该图像调整到了更大的尺寸。原始图像的大小只有8×8。

图2-5 生成单张图像的示例结果

此外,我们还可以使用cmap参数指定一个彩图。在默认情况下,Matplotlib使用MATLAB的默认彩图 jet 。可是,对于灰度图像, gray 彩图更有意义。

最后,我们可以利用plt的subplot函数绘制一组数字样本。subplot函数与在MATLAB中一样,我们指定行数、列数以及当前子图的索引(从1开始)。我们将使用一个for循环遍历数据集中的前10个图像,每个图像都有自己的子图:

生成的输出如图2-6所示。

图2-6 生成包含10个数字的一组子图

提示

对于各种数据集,另一个很好的资源是本书作者迈克尔·贝耶勒的母校加州大学欧文分校的机器学习资源库:http://archive.ics.uci.edu/ml/index.php。

2.3.5 使用C++中的OpenCV TrainData容器处理数据

为了完整起见,也为了那些坚持使用OpenCV的C++ API的人们,让我们快速浏览OpenCV的TrainData容器,该容器允许我们从.csv文件加载数值数据。

除此之外,在C++中,ml模块包含一个名为TrainData的类,该类提供了用C++处理数据的一个容器。它的功能仅限于读取.csv文件中的数值数据(包含逗号分隔的值)。因此,如果我们希望使用的数据是一个组织良好的.csv文件,那么这个类将会为你节省很多时间。如果你的数据来自其他源文件,恐怕你的最佳选择是使用一个合适的程序(例如OpenOffice或者Microsoft Excel)手动创建一个.csv文件。

TrainData类最重要的方法名为loadFromCSV,该方法接受以下参数:

如果在一个逗号分隔的文件中有一些不错的全浮点数据,那么你可以按照下列方式加载:

该类提供了几个便捷的函数,将数据拆分成训练集和测试集,并访问训练集和测试集中的各个数据点。例如,如果你的文件包含100个样本,那么你可以把前90个样本分配给训练集,剩下的10个样本留给测试集。首先,调整训练样本和测试样本可能是个好办法:

接下来,很容易把所有训练样本都存储在一个OpenCV矩阵中:

在https://docs.opencv.org/4.0.0/dc/d32/classcv_1_1m1_1_1TrainData.html中你可以找到本节介绍的所有相关函数。

除此之外,因为本书作者担心TrainData容器及其用例可能有点过时了。因此,在本书的其余章节,我们将重点关注Python。 dk8rmt8/DXq0j4wVlKWxABaeHL1wuoXVvBE1jSKdoHsVf1tCWSn6YDv2t8SlO3pp

点击中间区域
呼出菜单
上一章
目录
下一章
×