![机器学习:使用OpenCV、Python和scikit-learn进行智能图像处理(原书第2版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/629/34667629/b_34667629.jpg)
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目录:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/030-01.jpg?sign=1739165096-5bGXZINxcqMbOwHoP58LTGHvVma2J0El-0-9dbf1998bae6014973e699b363e21350)
2)激活我们在第1章中创建的conda环境:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/030-02.jpg?sign=1739165096-W0K5pazG6IC2yKKa4tNXYIvhxLGEu5km-0-f026e8d2b45c38496f80033f57e617c8)
3)启动一个新的IPython或者Jupyter会话:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/031-01.jpg?sign=1739165096-Dciuv9YDvpPs5vx2O8jry0VmtcezZATd-0-3a3d2f369a964f403646583594366945)
如果你选择启动一个IPython会话,程序应该会向你发送类似于下面这样的欢迎消息:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/031-02.jpg?sign=1739165096-BS2OqC1db179YbckWlFoa7tIXQ6cCPxG-0-64308ca0987a6799208994efb1275eb4)
在以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所示。
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/031-03.jpg?sign=1739165096-cra12lF58NbnH2HRROyvIPB4fsbpqP81-0-08e6afedf10183eb9090abe909e9a90e)
图2-2 在浏览器中打开一个Jupyter新窗口
这会打开一个新的窗口,如图2-3所示。
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/032-01.jpg?sign=1739165096-5W5yEdJogRCN2HQPnTTPkMqVfXXRRylS-0-a622ed3589fdeb397e3169e3672e88a0)
图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模块并按照以下步骤来验证版本:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/032-02.jpg?sign=1739165096-4zXAZgzwziceaUmoPvOM0kgOsSUp3oad-0-0e4420381886a876d244d933a1c1e744)
提示
记得在Jupyter Notebook中,键入命令后,你可以按下Ctrl+Enter,以执行一个单元格。或者,按下Shift+Enter以执行单元格,并自动插入或者选择该单元格下面的单元格。依次单击Help | Keyboard Shortcut以检查所有的键盘快捷键,或者依次单击Help | User Interface Tour以进行快速浏览。
此处讨论的部分包,建议使用NumPy 1.8版本或后续版本。按照惯例,你会发现在科学Python领域中,大多数人导入NumPy都会使用np作为别名:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/032-03.jpg?sign=1739165096-ASgdKKgFk2LqNCTWCuSr3wyPPNAWh7Dg-0-3cd10ee64394518c4aea46da48f1a9c4)
本章及本书的其余章节,我们都将遵循同样的惯例。
2. 理解NumPy数组
你可能已经知道Python是一种弱类型的语言。这就意味着,你无论何时创建一个新变量,都不必指定数据类型。例如,下面的内容将自动表示为一个整数:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/033-01.jpg?sign=1739165096-lQ8dKCBgwItrLb6JSQyn8Y5tVHe1Hr9p-0-9846a5d8f9f8f0e50e4cbabe7d7a0311)
输入下面内容以再次确认:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/033-02.jpg?sign=1739165096-n55OfhKjLL4ismyLSNSKxjqcfNDYSZfM-0-9c46f57424ec1870f44dff2019f94171)
注意
因为标准Python实现是用C编写的,所以每个Python对象本质上是一个伪C结构。这对于Python中的整数也是如此,实际上它是指向复合C结构的指针,包含的不仅仅是原始整数值。因此,用于表示Python整数的默认C数据类型将依赖于你的系统架构(即系统是32位还是64位平台)。
更进一步,我们使用list()命令可以创建一个整数列表,这是Python中的标准多元素容器。range (x)函数将创建从0到x–1的所有整数。要输出变量,你可以使用print函数,也可以直接输入变量名字并按Enter:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/033-03.jpg?sign=1739165096-c5YVOcp0o8e2SuiIsiWE8eH46zJGQcJt-0-6667fcdc7477853bcb0d4742c62b360a)
类似地,我们通过让Python遍历整数列表int_list中的所有元素,并对每个元素应用str()函数(该函数将一个数转换成一个字符串),来创建一个字符串列表:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/033-04.jpg?sign=1739165096-oWDSPp1Vgbotjk7yp5UyLWQuuyMmePKo-0-faf1ca8aacdd73830f1e828df621261b)
可是,用列表进行数学运算并不是很灵活。例如,我们想要将int_list中的每个元素都乘以一个因子2。执行以下操作可能是一种简单的方法——看看输出结果是怎样的:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/033-05.jpg?sign=1739165096-pLA7NDpLAPuajvUsVOV8nS72qUE2JRMB-0-4f5f1488d8a36673cb898687ddd6e9d7)
Python创建了一个列表,其内容是int_list的所有元素生成了两次,这并不是我们想要的!
这就是NumPy的用武之地。NumPy是专为简化Python中的数组运算而设计的。我们可以快速将整数列表转换为一个NumPy数组:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/033-06.jpg?sign=1739165096-RYlqSivulRbzZyhh6IDzPdWTrwIWxRmD-0-4dbfffd49367c8e2ced56f8081955dc6)
让我们看看试着将数组中的每个元素相乘会怎么样:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/033-07.jpg?sign=1739165096-VfYp8hM1rsG3LkjbfevTsAPNhFo04L7M-0-a823b729250af48a2d1a33c34e21036e)
这次我们做对了!加法、减法、除法以及很多其他运算也是同样的。
而且,每个NumPy数组都具有以下属性:
- ndim:维数。
- shape:每一维的大小。
- size:数组中元素的总数。
- dtype:数组的数据类型(例如int、float、string等)。
让我们来看看整数数组的上述属性:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/034-01.jpg?sign=1739165096-3746KNoeByVxhyCmwMlJaUBgGsMK7gAA-0-4719c0c7ef42c9e9a1845dada4bdf178)
从这些输出中,我们可以看到我们的数组只包含一维,其包含10个元素且所有元素都是64位的整数。当然,如果你在32位机器上执行这段代码,你可能会得到dtype:int 32。
3. 通过索引访问单个数组元素
如果你之前使用过Python的标准列表索引,那么你就不会发现NumPy中的索引有很多问题。在一维数组中,通过在方括号中指定所需的索引,可以访问第i个值(从0开始计算),与Python列表一样:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/034-02.jpg?sign=1739165096-lLXj2VwYykKocnWgCQZVuCP0mEme1Vjj-0-6019595574bb559737555b04eb9e3677)
要从数组的末尾建立索引,可以使用负索引号:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/034-03.jpg?sign=1739165096-atjezsDwLo2NBQWBeXbHmgJVfoYfsiWW-0-42502f7eae53f4cdabbc986f99839373)
切割数组还有一些其他很酷的技巧,如下所示:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/034-04.jpg?sign=1739165096-Sou0dNSJhKAvzuwSsWzn4IPAraMNMUAo-0-8feb6760d9a58f41ab6534029b8491c8)
建议你自己尝试使用这些数组!
提示
NumPy中切割数组的一般形式与标准Python列表中的相同。使用x [start: stop: step]访问数组x中的一个片段。如果没有指定任何一个值,那么默认值为start=0、stop=size of dimension、step=1。
4. 创建多维数组
数组不必局限于列表。实际上,数组可以有任意维数。在机器学习中,通常我们至少要处理二维数组,列索引表示特定的特征值,行包含实际的特征值。
使用NumPy可以轻松地从头开始创建多维数组。假设我们想要创建一个3行5列的数组,所有的元素都初始化为0。如果我们不指定数据类型,NumPy将默认使用float类型:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/035-01.jpg?sign=1739165096-fZ6e8MJpMGbKLazbJIue20XR5pjYRAnj-0-8c941c74e951179deb812f683f552153)
使用OpenCV时你可能就知道:这可以解释为所有像素设置为0(黑色)的一个3×5的灰度图像。例如,如果你想要创建具有3个颜色通道(R、G和B)2×4像素的一个小图像,但是所有像素都设置为白色,我们将使用NumPy创建一个3×2×4的三维数组:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/035-02.jpg?sign=1739165096-PMeluf5w6J5H9J6PVyn5bbwpNiNeSh1W-0-eecb2ce09a9878dde5a4426b7c154831)
这里,第一维定义颜色通道(OpenCV中的蓝色、绿色和红色)。因此,如果这是真实的图像数据,我们可以通过切割数组轻松地获得第一个通道中的颜色信息:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/035-03.jpg?sign=1739165096-vB4a8iVo0j9m0QyuDGfBIXMybxXcybAS-0-cd7dc304c22be3aa5714a508c80ad9e2)
在OpenCV中,图像要么是值在0到1之间的32位浮点数组,要么是值在0到255之间的8位整数数组。因此,使用8位整数,通过指定NumPy的dtype属性并将数组中的所有1乘以255,我们还可以创建一个2×4像素、全为白色的RGB图像:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/035-04.jpg?sign=1739165096-Ljs88OT5I6Z3lerX9el8KU35sPyoYvNL-0-363df60ff6a60f1788100e5e1585694e)
在后续章节中,我们还将学习更高级的数组操作。
2.3.3 用Python加载外部数据集
感谢SciPy社区,它提供了很多可以帮助我们获得一些数据的资源。
一个特别有用的资源以scikit-learn的sklearn.datasets包的形式出现。这个包预装了一些小数据集,我们不需要从外部网站下载任何文件。这些数据集包含以下内容:
- load_boston:Boston数据集包含不同城区的房价以及一些有趣的特征,如城镇的人均犯罪率、居住用地比例,以及非零售业务的数量。
- load_iris:Iris数据集包含三种不同类型的鸢尾花(山鸢尾、花斑鸢尾和维吉尼亚鸢尾)以及描述花萼和花瓣的宽度和长度的4个特征。
- load_diabetes:diabetes数据集依据患者的年龄、性别、体重指数、平均血压以及6次血清测量值等特征,使我们可以将患者分类为糖尿病患者和非糖尿病患者。
- load_digits:digits数据集包含数字0~9的8×8像素图像。
- load_linnerud:Linnerud数据集包含3个生理变量以及3个运动变量,测量了20名健身俱乐部的中年男性。
此外,scikit_learn允许我们直接从外部存储库下载数据集,例如:
- fetch_olivetti_faces:Olivetti faces数据集包含40个不同主题,每个主题包含10个不同的图像。
- fetch_20newsgroups:20 newsgroup数据集包含20个主题,大约18 000篇新闻组帖子。
更好的是,可以从机器学习数据库(http://openml.org)中直接下载数据集。例如,要下载Iris数据集,只需输入以下命令:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/036-01.jpg?sign=1739165096-rEnEFUrHnVPuIxhErK829CGPasE5EzCt-0-e04720fb726a18ba1d85bac8f2741208)
Iris数据集共有150个样本、4个特征——花萼长度、花萼宽度、花瓣长度和花瓣宽度。数据分为三类——山鸢尾、花斑鸢尾和维吉尼亚鸢尾。数据和标签位于两个独立的容器中,我们可以根据下列操作查看:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/036-02.jpg?sign=1739165096-cQgbGkWbtbvoKUe5p1jAUFjXpW0EZxGI-0-48cb6352954f0c76eb6468dd35f15a6b)
此处,我们可以看到iris_data包含150个样本,每个样本包含4个特征(这就是shape中的数字是4的原因)。标签存储在iris_target中,其中每个样本只有一个标签。
我们可以进一步查看所有目标的值,但是我们不希望只输出这些目标值。我们感兴趣的是查看所有不同的目标值,使用NumPy可轻松实现:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/037-01.jpg?sign=1739165096-y2rXc0Ag5dTVhaxEqpeJcDGzjJHJUaR2-0-177f010ee99afc08ad4d359946105153)
注意
你应该听说过另一个用于数据分析的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导入:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/037-02.jpg?sign=1739165096-afPx4678bQvWgEq5uxgCJYRw55OshUl3-0-71a32f53ce2e306825d19e8fdd2b436e)
plt是我们最常用的一个接口,在本书中我们将常看到plt接口。
2. 生成一个简单的图形
言归正传,让我们创建第一个图形。
假设我们要绘制正弦函数sin(x)的一个简单线图。我们希望函数求x轴(0≤x≤10)上的所有值。我们将使用NumPy的linspace函数在x轴上创建一个线性空间,x值从0到10,共100个样本点:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/037-03.jpg?sign=1739165096-IBsJ2bLO87GKCHnxOWRuAc8IIaKH7lZX-0-f066eb19f21e594307c8b0c7941ae3d8)
我们可以使用NumPy的sin函数求sin函数的所有x值,并通过调用plt的plot函数可视化结果:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/038-01.jpg?sign=1739165096-q6TwgDKlByLnEumWe30QOWwBrIoIbaIs-0-2de02fec957810cbe1b7d1f82e865ffe)
你亲自试过了吗?发生什么了?有什么发现吗?
问题是,这取决于你在何处运行这个脚本,你可能什么都看不到。以下是可以考虑的可能性:
- 从.py脚本绘图:如果你正从一个脚本运行matplotlib,那么你只需要调用plt,如下所示:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/038-02.jpg?sign=1739165096-0VncVhVQeX7ZOEtHnt4zE11nEBMiVCvg-0-ed8e2ad01e0a5f7aad556b19f1ca9f40)
调用后,图形就会显示出来!
- 从IPython shell绘图:这实际上是以交互方式运行matplotlib的最便捷的方式之一。要显示绘图,你需要在启动IPython之后,调用%matplotlib魔术命令:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/038-03.jpg?sign=1739165096-kNGBS1vYKxeVhdtLiIx4j1xqBCwtzokt-0-78e19b619fb978a2e369330d0f11c301)
然后,所有图都会自动显示出来,不必每次都调用plt.show()。
- 从Jupyter Notebook绘图:如果你从基于浏览器的Jupyter Notebook上查看这段代码,你需要使用同样的%matplotlib魔术命令。可是,你还可以选择将图形直接嵌入notebook中,这有两种可能的结果:
◆ %matplotlib notebook将生成的交互式图嵌入notebook中。
◆ %matplotlib inline将生成的静态图嵌入notebook中。
在本书中,我们通常会选择内联选项:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/038-04.jpg?sign=1739165096-ASR3gpxQudrf7n4H335sDmofP6guu3sB-0-ee23b1197b01f174b6df7d5ddf8f5cb4)
现在,让我们再试一次:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/038-05.jpg?sign=1739165096-29UQRKao6QO0RX3m77xmdc1upaBFsvaA-0-f92524825a165dc3084af7cd50cbc1b3)
上述命令给出的输出如图2-4所示。
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/038-06.jpg?sign=1739165096-E0LeHPXcA5cqo8xPNHv8DiCVO5RFkPZR-0-987f753e827ec6d14c94fffe443ceaaa)
图2-4 应用内联选项生成的图
稍后,如果你想保存图表,可以直接从IPython或Jupyter Notebook的选项中保存:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/039-01.jpg?sign=1739165096-HjtkNTGYqi7xSBTbBEi3poOTD3GpJ8Tp-0-e52ac954196946679e7281df7a7b729b)
只要保证使用所支持的文件后缀即可,例如.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个可视化工具:
- 用于实际数据的scikit-learn
- 用于数据处理的NumPy
- Matplotlib
首先,让我们导入所有这些可视化工具:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/039-02.jpg?sign=1739165096-f7HDUk5flX1JbO6XVZuCddKeqkX2S3az-0-8e82e195caff3ea23d7b6d90ba02b64c)
第一步是实际加载数据:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/039-03.jpg?sign=1739165096-dBEGYZUvI2s8Sh3Jg9njVj9ZWNMLwtak-0-f16191d92be7048681f7a4b934478893)
如果我们没有记错的话,digits应该有2个不同的字段:一个是data字段,包含实际的图像数据;另一个是target字段,包含图像标签。与其相信我们的记忆,不如让我们研究一下digits对象。这通过输入字段名称、添加句点、再按下Tab键——digits.<TAB>来实现。这会显示出digits对象还包含了一些其他字段,例如一个名为images的字段。images和data这2个字段似乎只是形状不同:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/039-04.jpg?sign=1739165096-lQC9hyRQuv1HYL6GcC4rYlwW4LL3Iv6h-0-6e226a35e9463231103675304710f45c)
在这两个例子中,第一维都对应于数据集中的图像数。但是data将所有像素排列在一个大的向量中,而images则保留了每个图像的8×8空间排列。
因此,如果我们想绘制单张图像,images字段可能更合适。首先,使用NumPy的数组切割,从数据集中抓取一张图像:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/039-05.jpg?sign=1739165096-nJ5PcEvxloZxyXQ2JRpQf0essqOf3MSs-0-88a9efd1b75356cc63c781424572a044)
这里,我们说想要抓取长为1797项的数组中的第一行,以及所有对应的8×8=64个像素。然后,我们可以使用plt的imshow函数绘制图像:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/040-01.jpg?sign=1739165096-GlMLvtMPF1awMwYDWHnzVoI8QFjeesK6-0-e91aaf34b6815a06442516028880b1b5)
上述命令给出的输出如图2-5所示。请注意,图像是模糊的,因为我们将该图像调整到了更大的尺寸。原始图像的大小只有8×8。
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/040-02.jpg?sign=1739165096-mSUx83nmVEFHFPzOjSTNksuYfLfCGPHl-0-3209f3a7879a2647101786a7dc2143ce)
图2-5 生成单张图像的示例结果
此外,我们还可以使用cmap参数指定一个彩图。在默认情况下,Matplotlib使用MATLAB的默认彩图jet。可是,对于灰度图像,gray彩图更有意义。
最后,我们可以利用plt的subplot函数绘制一组数字样本。subplot函数与在MATLAB中一样,我们指定行数、列数以及当前子图的索引(从1开始)。我们将使用一个for循环遍历数据集中的前10个图像,每个图像都有自己的子图:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/040-03.jpg?sign=1739165096-urjWEUclj54IDraz6DDs2vm6a9qS5z5H-0-cf2a61e8ae1f3bf578025e5719eccf03)
生成的输出如图2-6所示。
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/040-04.jpg?sign=1739165096-zzO2Iq8OnZD8VNAxcPuYViSBwPoCzfjz-0-c918437935816dd4231c014e17c16a42)
图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,该方法接受以下参数:
- const String& filename:输入文件名。
- int headerLineCount:开始时跳过的行数。
- int responseStartIdx:第一个输出变量的索引。
- int responseEndIdx:最后一个输出变量的索引。
- const String& varTypeSpec:描述所有输出变量数据类型的一个文本字符串。
- char delimiter:用于分隔每行值的字符。
- char missch:用于指定缺失测量值的字符。
如果在一个逗号分隔的文件中有一些不错的全浮点数据,那么你可以按照下列方式加载:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/041-01.jpg?sign=1739165096-RQom5jFSfeS0pad10qrIicIYxUvS2Sj0-0-18e7ca6f5e0bd9e0252376c9205c95eb)
该类提供了几个便捷的函数,将数据拆分成训练集和测试集,并访问训练集和测试集中的各个数据点。例如,如果你的文件包含100个样本,那么你可以把前90个样本分配给训练集,剩下的10个样本留给测试集。首先,调整训练样本和测试样本可能是个好办法:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/041-02.jpg?sign=1739165096-wb4vDuCVihQjvPY7ZZEqMbmj0aV31tBm-0-649af5c18b950b4baa7933f0a12b4632)
接下来,很容易把所有训练样本都存储在一个OpenCV矩阵中:
![](https://epubservercos.yuewen.com/91C9F3/18519310108432306/epubprivate/OEBPS/Images/041-03.jpg?sign=1739165096-N5wXdyLNEHqJZnGIBympoiQnUB8aOQaE-0-868e10f3d945b72586a9db90930b00ff)
在https://docs.opencv.org/4.0.0/dc/d32/classcv_1_1m1_1_1TrainData.html中你可以找到本节介绍的所有相关函数。
除此之外,因为本书作者担心TrainData容器及其用例可能有点过时了。因此,在本书的其余章节,我们将重点关注Python。