Python OpenCV 计算机视觉项目(一)

部署运行你感兴趣的模型镜像

原文:annas-archive.org/md5/61756cde4c66978a12599ccffeb53dae

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

在这本书中,您将学习如何利用 Python、OpenCV 和 TensorFlow 的强大功能来解决计算机视觉中的问题。Python 是快速原型设计和开发图像处理和计算机视觉生产级代码的理想编程语言,它具有稳健的语法和丰富的强大库。

本书将是您设计和开发针对现实世界问题的生产级计算机视觉项目的实用指南。您将学习如何为主要的操作系统设置 Anaconda Python,并使用计算机视觉的尖端第三方库,您还将学习分类图像和视频中的检测和识别人类的最先进技术。通过本书的结尾,您将获得使用 Python 及其相关库构建自己的计算机视觉项目所需的技能。

本书面向对象

希望使用机器学习和 OpenCV 的强大功能构建令人兴奋的计算机视觉项目的 Python 程序员和机器学习开发者会发现这本书很有用。本书的唯一先决条件是您应该具备扎实的 Python 编程知识。

要充分利用这本书

在 Python 及其包(如 TensorFlow、OpenCV 和 dlib)中具备一些编程经验,将帮助您充分利用这本书。

需要一个支持 CUDA 的强大 GPU 来重新训练模型。

下载示例代码文件

您可以从 www.packt.com 的账户下载本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packt.com/support 并注册,以便将文件直接通过电子邮件发送给您。

您可以通过以下步骤下载代码文件:

  1. www.packt.com 登录或注册。

  2. 选择支持选项卡。

  3. 点击代码下载和勘误表。

  4. 在搜索框中输入书籍名称,并遵循屏幕上的说明。

下载文件后,请确保使用最新版本解压或提取文件夹:

  • WinRAR/7-Zip for Windows

  • Zipeg/iZip/UnRarX for Mac

  • 7-Zip/PeaZip for Linux

本书代码包也托管在 GitHub 上,地址为 github.com/PacktPublishing/Computer-Vision-Projects-with-OpenCV-and-Python-3。如果代码有更新,它将在现有的 GitHub 仓库中更新。

我们还有其他来自我们丰富的图书和视频目录的代码包可供选择,请访问 github.com/PacktPublishing/。查看它们!

下载彩色图像

我们还提供了一个包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:www.packtpub.com/sites/default/files/downloads/9781789954555_ColorImages.pdf

使用的约定

本书使用了多种文本约定。

CodeInText: 表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“word_counts.txt文件包含一个词汇表,其中包含我们从训练模型中得到的计数,这是我们的图像标题生成器所需要的。”

代码块按照以下方式设置:

testfile = 'test_images/dog.jpeg'

figure()
imshow(imread(testfile))

任何命令行输入或输出都按照以下方式编写:

conda install -c menpo dlib

粗体: 表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“点击 下载 按钮。”

警告或重要注意事项看起来像这样。

小贴士和技巧看起来像这样。

联系我们

我们读者的反馈总是受欢迎的。

一般反馈: 如果你对此书的任何方面有疑问,请在邮件主题中提及书名,并通过customercare@packtpub.com发送邮件给我们。

勘误: 尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果你在这本书中发现了错误,我们将不胜感激,如果你能向我们报告这个错误。请访问www.packt.com/submit-errata,选择你的书籍,点击勘误提交表单链接,并输入详细信息。

盗版: 如果你在互联网上以任何形式遇到我们作品的非法副本,如果你能提供位置地址或网站名称,我们将不胜感激。请通过发送链接至copyright@packt.com与我们联系。

如果你有兴趣成为作者: 如果你有一个你擅长的主题,并且你对撰写或为书籍做出贡献感兴趣,请访问authors.packtpub.com

评论

请留下评论。一旦你阅读并使用了这本书,为什么不在你购买它的网站上留下评论呢?潜在读者可以看到并使用你的客观意见来做出购买决定,我们 Packt 可以了解你对我们的产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!

关于 Packt 的更多信息,请访问packt.com

第一章:设置 Anaconda 环境

欢迎来到使用 OpenCV 和 Python 3 的计算机视觉项目。如果你是 OpenCV 和计算机视觉的新手,这本书你可能想看看。

在本章中,我们将安装本书中将要使用到的所有必需工具。我们将处理 Python 3、OpenCV 和 TensorFlow。

你可能想知道:为什么我应该使用 Python 3,而不是 Python 2?你问题的答案可以在 Python 自己的网站上找到:

“Python 2 是遗留的,Python 3 是语言的现在和未来。”

我们在这里展望未来,如果我们想要使我们的代码具有前瞻性,最好使用 Python 3。如果你使用的是 Python 2,这里的一些代码示例可能无法运行,因此我们将安装 Python 3 并使用它来完成本书的所有项目。

在本章中,我们将涵盖以下主题:

  • 介绍和安装 Python 和 Anaconda

  • 安装额外的库

  • 探索 Jupyter Notebook

介绍和安装 Python 和 Anaconda

我们首先需要 Python 3。安装它的最佳方式是下载 Continuum Analytics 和 Anaconda 发行版。

Anaconda 是一个功能齐全的 Python 发行版,附带大量包,包括数值分析、数据科学和计算机视觉。它将使我们的生活变得更加容易,因为它为我们提供了基础 Python 发行版中不存在的库。

Anaconda 最好的部分是它为我们提供了conda包管理器,以及pip,这使得为我们的 Python 发行版安装外部包变得非常容易。

让我们开始吧。

安装 Anaconda

我们将首先设置 Anaconda 和 Python 发行版,按照以下步骤进行:

  1. 访问 Anaconda 网站,使用以下链接www.anaconda.com/download。你应该会看到一个类似于以下截图的着陆页:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/37e77f75-2fc4-49e4-9ee1-91daaa5dd445.png

  1. 接下来,选择你的操作系统,下载包含 Python 3.7 的最新版本的 Anaconda 发行版。点击下载按钮,如图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/9d792ef5-1ed6-45eb-9326-490aa90d9d02.png

Windows 的安装程序是图形化的;然而,你可能需要为 macOS 或 Linux 使用命令行安装程序。

安装设置文件非常简单,所以我们不会在这里逐个步骤说明。

  1. 当你正确安装了所有软件并定义了路径变量后,前往命令提示符,通过输入where python命令来确保一切正常。这会显示 Python 安装的所有目录。你应该会看到类似于以下截图的内容:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/19a5c556-2731-4d8b-a77c-fc0099616d79.png

如前一个截图所示,我们看到 Python 的第一个实例在我们的 Anaconda 发行版中。这意味着我们可以继续我们的 Python 程序。

在 macOS 或 Linux 中,命令将是which python而不是where python

  1. 现在,让我们确保我们拥有我们的其他工具。我们的第一个工具将是 IPython,它本质上是一种用于多种编程语言的交互式计算命令壳。我们将使用where ipython命令来检查它,如图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/77be8db4-88dd-49c0-aaf6-dce30274319e.png

  1. 下一个我们将检查的包是pip工具,它是 Python 安装程序包。我们使用where pip命令来完成此操作,如图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/1a46c810-55db-4b0e-868b-f470c48ac57f.png

  1. 下一个要检查的工具是conda包,它是 Anaconda 内置的包管理器。这是通过where conda命令完成的,如图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/7fae52b9-a14a-47a0-9cee-85dc045622ad.png

我们现在应该可以使用 Python 了。

如果你运行which python在 macOS 或 Linux 上,并且它显示类似user/bin/Python的内容,这意味着 Python 可能未安装或不是我们路径中的第一项,因此我们应该根据我们的系统进行修改。

在下一节中,我们将介绍安装额外的库,如 OpenCV、TensorFlow、dlib 和 Tesseract,这些库将用于本书中的项目。

安装额外的库

我们将在本节中安装的所有包对我们即将到来的项目至关重要。所以,让我们开始吧。

安装 OpenCV

要获取 OpenCV,请访问以下链接:anaconda.org/conda-forge/opencv。技术上,我们不需要访问网站来安装此包。该网站仅显示 OpenCV 的各种版本以及我们可以在其上安装的所有不同系统。

将网站上的安装命令复制并粘贴到命令提示符中,然后运行,如图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/0439ec88-b7e8-43e9-9e37-a1a341bf0d3f.png

上述命令是一个简单、平台无关的方法来获取 OpenCV。还有其他获取它的方法;然而,使用此命令可以确保我们安装的是最新版本。

安装 dlib

我们需要从 Anaconda 发行版安装 dlib,类似于 OpenCV。正如安装 OpenCV 一样,安装 dlib 是一个简单的过程。

运行以下命令:

conda install -c menpo dlib

你将得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/b6d0f804-9c85-4645-9270-90fbd0fdf2f7.png

这将花费大约 10 到 20 秒的时间运行。如果一切顺利,我们应该可以使用 dlib 了。

安装 Tesseract

Tesseract 是 Google 的光学字符识别库,并且不是原生的 Python 包。因此,有一个 Python 绑定,它调用可执行文件,然后可以手动安装。

访问 Tesseract 的 GitHub 仓库,该仓库位于以下链接:github.com/tesseract-ocr/tesseract

滚动到 GitHub 自述文件中的安装 Tesseract部分。在这里,我们有两个选项:

  • 通过预构建的二进制包安装

  • 从源代码构建

我们想要通过预构建的二进制包来安装它,因此点击该链接。我们也可以从源代码构建,如果我们想的话,但这并不真正提供任何优势。Tesseract Wiki 解释了在各个不同的操作系统上安装它的步骤。

由于我们使用的是 Windows,并且我们想要安装一个预构建的版本,请点击 UB Mannheim 的 Tesseract 链接,在那里您可以找到所有最新的设置文件。从网站上下载最新的设置文件。

下载完成后,运行安装程序或执行命令。然而,这并不会将 Tesseract 添加到您的路径中。我们需要确保它在您的路径中;否则,当您在 Python 中调用 Tesseract 时,您将收到一个错误消息。

因此,我们需要找出 Tesseract 的位置并修改我们的路径变量。为此,在命令提示符中输入where tesseract命令,如下截图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/bb972736-865d-4eb0-b45e-4b1857b8e179.png

一旦您有了二进制包,使用pip命令将 Python 绑定应用到这些包上。使用以下命令:

$ pip install tesseract
$ pip install pytesseract

现在应该可以使用 Tesseract 了。

安装 TensorFlow

最后但同样重要的是,我们将安装 TensorFlow,这是一个用于跨各种任务的数据流编程的软件库。它通常用于机器学习应用,如神经网络。

要安装它,请访问以下链接的 TensorFlow 网站:tensorflow.org/install/。网站包含所有主要操作系统的说明。

由于我们使用的是 Windows,安装过程非常简单。我们只需在命令提示符中运行pip install tensorflow命令,如下截图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/502bc85a-c5e2-4df6-ba74-c12da592da19.png

如前一个截图所示,TensorFlow 已经安装在系统上,因此它表示要求已满足。现在我们应该可以使用 TensorFlow 了。

使用以下命令安装tensorflow-hub

pip install tensorflow-hub

接下来,使用以下命令安装tflearn

pip install tflearn

最后,Keras 是一个高级接口,可以使用以下命令安装:

pip install keras

我们已经安装了 OpenCV、TensorFlow、dlib 和 Tesseract,因此我们应该可以使用我们书籍的工具了。我们的下一步将是探索 Jupyter Notebook,这应该很有趣!

探索 Jupyter Notebook

现在我们已经安装了库,我们准备好开始使用 Jupyter Notebook 了。Jupyter Notebook 是一种创建交互式代码和小部件的好方法。它允许我们创建带有实时代码和实验的交互式演示,就像我们现在所做的那样。

如果使用 Anaconda 正确设置了一切,我们可能已经安装了 Jupyter。让我们现在看看 Jupyter Notebook。

在你的代码文件所在的目录中打开命令提示符,然后运行 jupyter notebook 命令。这将打开一个在命令执行目录中的网络浏览器。这应该会得到一个类似于以下截图的界面:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/e1ea5303-cf0c-46e2-ac9b-82be97f372c8.png

接下来,打开 .ipynb 文件,以便你可以探索 Jupyter Notebook 的基本功能。一旦打开,我们应该看到一个类似于以下截图的页面:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/7cc692ae-dc2c-4bf9-a517-acd7a7e61291.png

如所示,有一些块(称为 cells),我们可以在这里输入 Python 命令和 Python 代码。我们还可以输入其他命令,也称为 magic commands,这些命令本身不是 Python 的一部分,但允许我们在 Jupyter 或 IPython(Python 的交互式外壳)中做一些很酷的事情。开头 % 的意思是该命令是一个 magic command。

这里最大的优点是我们可以执行单个代码行,而不是一次输入整个代码块。

如果你刚开始使用 Jupyter Notebook,请访问以下链接:www.cheatography.com/weidadeyue/cheat-sheets/jupyter-notebook/。在这里,他们列出了 Jupyter Notebook 的键盘快捷键,这对于快速代码测试非常有用。

让我们回顾一下前面截图中的某些命令,并看看它们的作用,如下所示:

  1. 如第一个单元格所示,%pylab notebook 命令导入了许多非常有用且常见的库,特别是 NumPy 和 PyPlot,而无需我们显式调用导入命令。它还简化了 Notebook 的设置。

  2. 同样在第一个单元格中,我们指定我们将要工作的目录,如下所示:

%cd C:\Users\<user_name>\Documents\<Folder_name>\Section1-Getting_started

这导致了以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/eafeb6bd-eeae-405c-83ef-941a31bbdce0.png

到目前为止,一切顺利!

  1. 下一个单元格显示了如何导入我们的库。我们将导入 OpenCV、TensorFlow、dlib 和 Tesseract,只是为了确保一切正常,没有出现任何令人不快的惊喜。这是通过以下代码块完成的:
import cv2
import tensorflow as tf
import dlib
import pytesseract as ptess

如果在这里收到错误信息,请按照说明仔细重新安装库。有时事情确实会出错,这取决于我们的系统。

  1. 图表中的第三个单元格包含导入 TensorFlow 图模块的命令。这可以在 Notebook 内部获取函数帮助时派上用场,如下所示:
tf.Graph?

我们将在 第七章 中讨论这个函数,使用 TensorFlow 进行深度学习图像分类

  1. Jupyter Notebooks 的另一个优点是,我们可以在单元格中直接运行 shell 命令。如图表中的第四个单元格所示(此处重复),ls 命令显示了我们从该目录中工作的所有文件:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/f64d7093-c15d-49f3-b3a6-023ccd69b4ab.png

  1. 在这本书中,我们将处理很多图像,所以我们会希望在笔记本中直接查看图像。使用 imread() 函数从你的目录中读取图像文件。之后,你的下一步是创建一个 figure() 小部件来显示图像。最后,使用 imshow() 函数实际显示图像。

整个过程总结在下面的截图里:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/f4406eac-4105-40c7-b757-3c72011cc3e7.png

这很棒,因为我们有灵活的部件。

  1. 通过抓住右下角,我们可以将其缩小到一个合理的尺寸,以便查看带有像素轴的颜色图像。我们还有平移选项可用。点击它,我们可以平移图像并框选放大。按主页按钮将重置原始视图。

我们将想要查看我们的图像、处理后的图像等等——如前所述,这是一个非常方便且简单的方法。我们还可以使用 PyLab 内联,这在某些情况下很有用,我们将会看到。

  1. 正如我们所知,计算机视觉的一部分是处理视频。要在笔记本中播放视频,我们需要导入一些库并使用 IPython 的 HTML 功能,如下面的截图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/bcbddf0c-ed6a-47a9-9d89-787ff46bf800.png

实际上,我们是在使用我们的网络浏览器的播放功能。所以,这并不是真正的 Python 在做这件事,而是我们的网络浏览器,它使得 Jupyter Notebook 和我们的浏览器之间能够实现交互性。

这里,我们定义了 playvideo() 函数,它接受视频文件名作为输入,并返回一个包含我们视频的 HTML 对象。

  1. 在 Jupyter 中执行以下命令来播放 Megamind 视频。这只是电影 Megamind 的一段剪辑,如果我们下载所有源代码,它就会随 OpenCV 一起提供:
playvideo(' ./Megamind.mp4')
  1. 你会看到一个黑色盒子,如果你向下滚动,你会找到一个播放按钮。点击这个按钮,电影就会播放,如下面的截图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/a132f34e-5b85-4160-af2f-559ee77d11fb.png

这可以用来播放我们自己的视频。你只需要将命令指向你想播放的视频。

一旦所有这些运行起来,你应该能够良好地运行我们在接下来的章节中将要查看的项目。

摘要

在本章中,我们学习了 Anaconda 发行版和安装 Python 的不同方法。我们学习了如何使用 Anaconda 发行版设置 Python。

接下来,我们看了如何在 Anaconda 中安装各种库,以便我们更容易运行各种程序。最后,我们学习了 Jupyter Notebook 的基础知识及其工作原理。

在下一章(第二章),我们将探讨如何使用 TensorFlow 进行图像字幕生成。

第二章:使用 TensorFlow 进行图像标题生成

首先,本章将简要概述创建详细英语图像描述的过程。使用基于 TensorFlow 的图像标题生成模型,我们将能够用详细且完美描述图像的标题替换单个单词或复合词/短语。我们首先将使用预训练的图像标题生成模型,然后从头开始重新训练模型以在一系列图像上运行。

在本章中,我们将涵盖以下内容:

  • 图像标题生成简介

  • Google Brain im2txt 标题生成模型

  • 在 Jupyter 中运行我们的标题生成代码

  • 重新训练模型

技术要求

除了 Python 知识、图像处理和计算机视觉的基础知识外,我们还需要以下 Python 库:

  • NumPy

  • Matplotlib

本章中使用的代码已添加到以下 GitHub 仓库中:

github.com/PacktPublishing/Computer-Vision-Projects-with-OpenCV-and-Python-3

图像标题生成简介

图像标题生成是一个基于图像生成文本描述的过程。为了更好地理解图像标题生成,我们首先需要将它与图像分类区分开来。

图像分类与图像标题生成的区别

图像分类是一个相对简单的过程,它只能告诉我们图像中有什么。例如,如果有一个骑自行车的男孩,图像分类不会给我们一个描述;它只会提供结果作为男孩自行车。图像分类可以告诉我们图像中是否有女人或狗,或者是否有动作,如滑雪。这不是一个理想的结果,因为图像中并没有描述具体发生了什么。

以下是使用图像分类得到的结果:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/3f7f3a53-c971-43ed-9e6b-52e3f449daee.png

相比之下,图像标题生成将提供一个带有描述的结果。对于前面的例子,图像标题生成的结果将是一个男孩骑在自行车上一个男人在滑雪。这可能有助于为书籍生成内容,或者可能有助于帮助听力或视觉障碍者。

以下是使用图像标题生成得到的结果:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/f449b9bd-d21a-4c25-9193-9507bade3c26.png

然而,这要困难得多,因为传统的神经网络虽然强大,但它们与序列数据不太兼容。序列数据是指按顺序到来的数据,而这个顺序实际上很重要。在音频或视频中,我们有按顺序到来的单词;打乱单词可能会改变句子的含义,或者只是使其成为完全的胡言乱语。

带有长短期记忆的循环神经网络

尽管卷积神经网络(CNNs)非常强大,但它们并不擅长处理序列数据;然而,它们非常适合非序列任务,如图像分类。

CNN 是如何工作的,以下图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/a8176ff9-a902-4bdf-b868-6fae3abe7c5e.png

循环神经网络RNNs),实际上确实是当前最先进的技术,可以处理序列任务。一个 RNN 由一系列接收数据的 CNN 组成。

RNNs 是如何工作的,以下图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/be749fa7-8b9b-4378-a461-f83bed83e489.png

按序列进入的数据(x[i])通过神经网络,我们得到输出(y[i])。然后输出被送入另一个迭代,形成一个循环。这有助于我们记住之前来的数据,对于音频和语音识别、语言翻译、视频识别和文本生成等序列数据任务非常有帮助。

另一个存在已久且非常有用的概念是与 RNNs 结合的长短期记忆LSTM)。这是一种处理长期记忆并避免仅仅将数据从一次迭代传递到下一次迭代的方法。它以稳健的方式处理迭代中的数据,并使我们能够有效地训练 RNNs。

Google Brain im2txt 字幕模型

Google Brain im2txt 被 Google 用于论文《2015 MSCOCO 图像字幕挑战》,并将成为我们将在项目中实现的图像字幕代码的基础。

Google 的 GitHub TensorFlow 页面可以在github.com/tensorflow/models/tree/master/research/im2txt找到.

在研究目录中,我们将找到 Google 在论文《2015 MSCOCO 图像字幕挑战》中使用的im2txt文件,该文件可在arxiv.org/abs/1609.06647免费获取。它详细介绍了 RNNs、LSTM 和基本算法。

我们可以检查 CNN 是如何用于图像分类的,也可以学习如何使用 LSTM RNNs 来实际生成序列字幕输出。

我们可以从 GitHub 链接下载代码;然而,它尚未设置得易于运行,因为它不包含预训练模型,所以我们可能会遇到一些挑战。我们已经为您提供了预训练模型,以避免从头开始训练图像分类器,因为这是一个耗时的过程。我们对代码进行了一些修改,使得代码在 Jupyter Notebook 上运行或集成到您自己的项目中变得容易。使用 CPU,预训练模型学习非常快。没有预训练模型的相同代码实际上可能需要几周时间才能学习,即使在好的 GPU 上也是如此。

在 Jupyter 上运行字幕代码

现在我们将在 Jupyter Notebook 上运行我们自己的代码版本。我们可以启动自己的 Jupyter Notebook,并从 GitHub 仓库加载Section_1-Tensorflow_Image_Captioning.ipynb文件(github.com/PacktPublishing/Computer-Vision-Projects-with-OpenCV-and-Python-3/blob/master/Chapter01/Section_1-Tensorflow_Image_Captioning.ipynb)。

一旦我们在 Jupyter Notebook 中加载了文件,它看起来会像这样:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/7cf6b337-2a16-48f8-9c27-327a28fb278f.png

在第一部分,我们将加载一些基本库,包括mathostensorflow。我们还将使用我们方便的实用函数%pylab inline,以便在 Notebook 中轻松读取和显示图像。

选择第一个代码块:

# load essential libraries
import math
import os

import tensorflow as tf

%pylab inline

当我们按下Ctrl + Enter来执行单元格中的代码时,我们将得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/5279fc38-3cc4-4768-9c74-05fc8cbbb101.png

我们现在需要加载 TensorFlow/Google Brain 的基础代码,我们可以从github.com/PacktPublishing/Computer-Vision-Projects-with-OpenCV-and-Python-3获取。

有多个实用函数,但在我们的示例中,我们只会使用和执行其中的一些:

# load Tensorflow/Google Brain base code
# https://github.com/tensorflow/models/tree/master/research/im2txt

from im2txt import configuration
from im2txt import inference_wrapper
from im2txt.inference_utils import caption_generator
from im2txt.inference_utils import vocabulary

我们需要告诉我们的函数在哪里可以找到训练模型和词汇表:

# tell our function where to find the trained model and vocabulary
checkpoint_path = './model'
vocab_file = './model/word_counts.txt'

训练模型和词汇表的代码已添加到 GitHub 仓库中,您可以通过此链接访问:github.com/PacktPublishing/Computer-Vision-Projects-with-OpenCV-and-Python-3

github.com/PacktPublishing/Computer-Vision-Projects-with-OpenCV-and-Python-3

文件夹包含checkpointword_counts.txt和预训练模型。我们需要确保我们使用这些文件,并避免使用可能不与 TensorFlow 最新版本兼容的其他过时文件。word_counts.txt文件包含一个词汇表,其中包含我们从训练模型中得到的计数,这是我们的图像标题生成器所需要的。

一旦这些步骤完成,我们就可以查看我们的main函数,它将为我们生成标题。该函数可以接受一个字符串形式的输入文件(以逗号分隔)或仅一个我们想要处理的文件。

将冗余度设置为tf.logging.FATAL,这是可用的不同日志级别之一,因为它会告诉我们是否真的出了问题:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/30533d4c-b33a-4aa2-b3fb-062758390569.png

在主代码的初始部分,我们执行以下步骤:

  1. 将冗余度设置为tf.logging.FATAL

  2. 加载我们的预训练模型。

  3. 从 Google 提供的实用文件中加载推理包装器。

  4. 从之前单元格中建立的checkpoint路径加载我们的预训练模型。

  5. 运行finalize函数:

# this is the function we'll call to produce our captions 
# given input file name(s) -- separate file names by a,
# if more than one

def gen_caption(input_files):
    # only print serious log messages
    tf.logging.set_verbosity(tf.logging.FATAL)
    # load our pretrained model
    g = tf.Graph()
    with g.as_default():
        model = inference_wrapper.InferenceWrapper()
        restore_fn = model.build_graph_from_config(configuration.ModelConfig(),
                                                 checkpoint_path)
    g.finalize()
  1. 再次从之前运行的单元格中加载词汇表文件:
    # Create the vocabulary.
    vocab = vocabulary.Vocabulary(vocab_file)
  1. 预处理文件名:
    filenames = []
    for file_pattern in input_files.split(","):
  1. 执行Glob操作:
        filenames.extend(tf.gfile.Glob(file_pattern))
  1. 创建一个文件名列表,这样你可以知道图像标题生成器正在哪个文件上运行:
    tf.logging.info("Running caption generation on %d files matching %s",
                    len(filenames), input_files)
  1. 创建一个会话。由于我们正在使用预训练的模型,我们需要使用restore函数:
    with tf.Session(graph=g) as sess:
        # Load the model from checkpoint.
        restore_fn(sess)

这些步骤的代码包含在此处:

# this is the function we'll call to produce our captions 
# given input file name(s) -- separate file names by a,
# if more than one

def gen_caption(input_files):
    # only print serious log messages
    tf.logging.set_verbosity(tf.logging.FATAL)
    # load our pretrained model
    g = tf.Graph()
    with g.as_default():
        model = inference_wrapper.InferenceWrapper()
        restore_fn = model.build_graph_from_config(configuration.ModelConfig(),
                                                 checkpoint_path)
    g.finalize()

    # Create the vocabulary.
    vocab = vocabulary.Vocabulary(vocab_file)

    filenames = []
    for file_pattern in input_files.split(","):
        filenames.extend(tf.gfile.Glob(file_pattern))
    tf.logging.info("Running caption generation on %d files matching %s",
                    len(filenames), input_files)

    with tf.Session(graph=g) as sess:
        # Load the model from checkpoint.
        restore_fn(sess)

我们现在转向主代码的第二部分。一旦会话已恢复,我们执行以下步骤:

  1. 从我们的模型和存储在名为generator的对象中的词汇中加载caption_generator
        generator = caption_generator.CaptionGenerator(model, vocab)
  1. 制作标题列表:
        captionlist = []
  1. 遍历文件并将它们加载到名为beam_search的生成器中,以分析图像:
        for filename in filenames:
            with tf.gfile.GFile(filename, "rb") as f:
                image = f.read()
            captions = generator.beam_search(sess, image)
  1. 打印标题:
            print("Captions for image %s:" % os.path.basename(filename))
  1. 迭代以创建多个标题,迭代已为模型设置:
            for i, caption in enumerate(captions):
                # Ignore begin and end words.
                sentence = [vocab.id_to_word(w) for w in caption.sentence[1:-1]]
                sentence = " ".join(sentence)
                print(" %d) %s (p=%f)" % (i, sentence, math.exp(caption.logprob)))
                captionlist.append(sentence)
  1. 返回captionlist
    return captionlist

运行代码以生成函数。

请看以下代码块中的完整代码:

    # Prepare the caption generator. Here we are implicitly using the default
    # beam search parameters. See caption_generator.py for a description of the
    # available beam search parameters.
        generator = caption_generator.CaptionGenerator(model, vocab)

        captionlist = []

        for filename in filenames:
            with tf.gfile.GFile(filename, "rb") as f:
                image = f.read()
            captions = generator.beam_search(sess, image)
            print("Captions for image %s:" % os.path.basename(filename))
            for i, caption in enumerate(captions):
                # Ignore begin and end words.
                sentence = [vocab.id_to_word(w) for w in caption.sentence[1:-1]]
                sentence = " ".join(sentence)
                print(" %d) %s (p=%f)" % (i, sentence, math.exp(caption.logprob)))
                captionlist.append(sentence)
    return captionlist

在下一个代码块中,我们将对来自test文件夹的样本股票照片执行代码。代码将创建一个图形,显示它,然后运行标题生成器。然后我们可以使用print语句显示输出。

以下是我们用来选择用于计算的图像的代码:

testfile = 'test_images/dog.jpeg'

figure()
imshow(imread(testfile))

capts = gen_caption(testfile)

当我们运行我们的第一个测试图像,dog.jpeg,我们得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/b760c014-a429-4f3d-8387-50867828e243.png

结果,“一位女士和一只狗站在草地上”,是对图像的一个很好的描述。由于所有三个结果都很相似,我们可以说我们的模型工作得相当好。

分析结果标题

让我们拿几个例子来检查我们的模型。当我们执行football.jpeg时,我们得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/d9aaa6e6-3abc-4202-936d-33952a1612a8.png

在这里,我们清楚地看到图像中正在进行美式足球比赛,而“一对男子在踢足球”是一个非常好的结果。然而,第一个结果,“一对男子在玩飞盘”,并不是我们想要的结果,也不是“一对男子在踢足球”。因此,在这种情况下,第二个标题通常会是最好的,但并不总是完美的,这取决于对数概率。

让我们再试一个例子,giraffes.jpeg

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/edfbad17-b3e0-4342-9c32-190f28865bec.png

很明显,我们有一张长颈鹿的图片,第一个标题,“一排长颈鹿并排站立”,看起来似乎是正确的,除了语法问题。其他两个结果是“一排长颈鹿站在田野中”和“一排长颈鹿在田野中并排站立”。

让我们再看一个例子,headphones.jpeg

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/589ac694-b233-4dac-b54c-c5d32e45a5a5.png

在这里,我们选择了headphones.jpeg,但结果中没有包含耳机。结果是“一位女士手里拿着一部手机”,这是一个很好的结果。第二个结果,“一位女士把手机举到耳边”,技术上是不正确的,但总体上是一些好的标题。

让我们再举一个例子,ballons.jpeg。当我们运行图像时,我们得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/c60032d5-a1ea-45a8-9d20-fd35908a06b6.png

对于这张图像,我们得到的结果是“一个站在海滩上放风筝的妇女”、“一个在海滩上放风筝的妇女”,以及“一个在海滩上放风筝的年轻女孩”。所以,模型得到了“妇女”或“年轻女孩”,但它得到了“风筝”而不是气球,尽管“气球”在词汇表中。因此,我们可以推断出模型并不完美,但它很令人印象深刻,可以包含在你的应用程序中。

在 Jupyter 上运行多图像的标题代码

也可以通过使用逗号分隔不同图像的图像路径,将多个图像作为输入字符串添加。字符串图像的执行时间将大于我们之前看到的任何时间。

以下是一个多个输入文件的示例:

input_files = 'test_images/ballons.jpeg,test_images/bike.jpeg,test_images/dog.jpeg,test_images/fireworks.jpeg,test_images/football.jpeg,test_images/giraffes.jpeg,test_images/headphones.jpeg,test_images/laughing.jpeg,test_images/objects.jpeg,test_images/snowboard.jpeg,test_images/surfing.jpeg'

capts = gen_caption(input_files)

我们将不会显示图像,所以输出将只包括结果。我们可以看到,一些结果比其他结果要好:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/c759ff9d-66fb-4db3-b062-de2575419d27.png

这就完成了预训练图像标题模型的运行。我们现在将介绍从头开始训练模型并在标题图像上运行它。

重新训练标题模型

因此,既然我们已经看到了图像标题代码的实际应用,我们接下来将在我们自己的数据上重新训练图像标题器。然而,我们需要知道,如果想要在合理的时间内处理,这将非常耗时,并且需要超过 100 GB 的硬盘空间。即使有好的 GPU,完成计算可能也需要几天或一周的时间。既然我们有意愿实施并且有资源,让我们开始重新训练模型。

在笔记本中,第一步是下载预训练的 Inception 模型。webbrowser 模块将使打开 URL 并下载文件变得容易:

# First download pretrained Inception (v3) model

import webbrowser 
webbrowser.open("http://download.tensorflow.org/models/inception_v3_2016_08_28.tar.gz")

# Completely unzip tar.gz file to get inception_v3.ckpt,
# --recommend storing in im2txt/data directory

以下将是输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/726b3308-e90e-4581-ac71-9b977bd35bef.png

当我们选择代码块并执行它时,我们可能无法在网页上查看内容,但我们可以点击对话框中的“保存”来下载文件。解压文件以获取 Inception v3 检查点文件。我们可以使用任何可用的解压工具,但最好是使用 7-zip 来获取 Inception v3 检查点文件,并将其存储在项目目录的 im2txt/data 中。

cd 命令用于导航到 im2txt/data 目录,那里存放着所有我们的文件。run_build_mscoco_data.py Python 脚本将抓取并处理所有图像数据和预制的标题数据。这个过程可能需要超过 100 GB 的空间,并且需要超过一个小时来完成执行。

一旦计算完成,我们将在项目目录中看到三个 ZIP 文件。我们可以解压这些文件以获取以下目录:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/ec271f43-3ccd-485b-83a0-1d7bf5354776.png

训练和验证的 JSON 文件位于annotations文件夹中。其他目录包含图像训练和验证数据。在train2014目录下,我们将找到与训练数据对应的 JPEG 图像。同样,与验证数据对应的资源将存在于val2014文件夹中。我们也可以替换自己的图像,并编辑annotations文件夹中相应的 JSON 文件。我们需要很多示例,因为少量的示例不会提供有效的结果。train2014目录中有超过 80,000 张图像,处理它们将需要大量的资源。

执行run_build_mscoco_data.py命令后,我们需要加载所需的模块:

# Now gather and prepare the MSCOCO data

# Comment out cd magic command if already in data directory
%cd im2txt/data
# This command will take an hour or more to run typically.
# Note, you will need a lot of HD space (>100 GB)!
%run build_mscoco_data.py

# At this point you have files in im2txt/data/mscoco/raw-data that you can train
# on, or you can substitute your own data

%cd ..

# load needed modules

import tensorflow as tf

from im2txt import configuration
from im2txt import show_and_tell_model

我们需要在im2txt文件夹中加载configurationshow_and_tell_model,以及 TensorFlow。我们可以运行cd ..命令以进入正确的目录。

现在,我们将定义以下变量:

  • input_file_pattern:定义指向预训练 Inception 检查点的文件,这些文件将来自我们的模型

  • train_dir:包含下载并解压后存储训练数据的路径

  • train_inception:设置为false,因为我们不会在初始运行时训练 Inception 模型

  • number_of_steps:我们的函数为一百万步

  • log_every_n_steps:将我们的函数设置为1

下面是代码:

# Initial training
input_file_pattern = 'im2txt/data/mscoco/train-?????-of-00256'

# change these if you put your stuff somewhere else
inception_checkpoint_file = 'im2txt/data/inception_v3.ckpt'
train_dir = 'im2txt/model'

# Don't train inception for initial run
train_inception = False
number_of_steps = 1000000
log_every_n_steps = 1

现在,让我们定义我们的train函数。train函数中执行的步骤如下:

  1. 创建train目录

  2. 创建图文件

  3. 加载必要的文件

  4. 添加 TensorFlow 开始训练模型所需的变量,以获得每批次的延迟步数的学习率

  5. 设置层

  6. 设置用于保存和恢复模型检查点的保存器

  7. 调用 TensorFlow 进行训练

以下是我们train函数的内容:

  1. 定义(但尚未运行)我们的字幕训练函数:
def train():
    model_config = configuration.ModelConfig()
    model_config.input_file_pattern = input_file_pattern
    model_config.inception_checkpoint_file = inception_checkpoint_file
    training_config = configuration.TrainingConfig()  
  1. 创建训练目录:
    train_dir = train_dir
    if not tf.gfile.IsDirectory(train_dir):
        tf.logging.info("Creating training directory: %s", train_dir)
        tf.gfile.MakeDirs(train_dir)

  1. 构建 TensorFlow 图:
    g = tf.Graph()
    with g.as_default():

  1. 构建模型:
        model = show_and_tell_model.ShowAndTellModel(
                model_config, mode="train", train_inception=train_inception)
        model.build()

  1. 设置学习率:
        learning_rate_decay_fn = None
        if train_inception:
            learning_rate = tf.constant(training_config.train_inception_learning_rate)
        else:
            learning_rate = tf.constant(training_config.initial_learning_rate)
            if training_config.learning_rate_decay_factor > 0:
                num_batches_per_epoch = (training_config.num_examples_per_epoch /
                                 model_config.batch_size)
                decay_steps = int(num_batches_per_epoch *
                          training_config.num_epochs_per_decay)

                def _learning_rate_decay_fn(learning_rate, global_step):
                    return tf.train.exponential_decay(
                                      learning_rate,
                                      global_step,
                                      decay_steps=decay_steps,
                                      decay_rate=training_config.learning_rate_decay_factor,
                                      staircase=True)

                learning_rate_decay_fn = _learning_rate_decay_fn

  1. 设置训练操作:
        train_op = tf.contrib.layers.optimize_loss(
                                        loss=model.total_loss,
                                        global_step=model.global_step,
                                        learning_rate=learning_rate,
                                        optimizer=training_config.optimizer,
                                        clip_gradients=training_config.clip_gradients,
                                        learning_rate_decay_fn=learning_rate_decay_fn)

  1. 设置用于保存和恢复模型检查点的Saver
        saver = tf.train.Saver(max_to_keep=training_config.max_checkpoints_to_keep)

    # Run training.
    tf.contrib.slim.learning.train(
                                train_op,
                                train_dir,
                                log_every_n_steps=log_every_n_steps,
                                graph=g,
                                global_step=model.global_step,
                                number_of_steps=number_of_steps,
                                init_fn=model.init_fn,
                                saver=saver)

Ctrl + Enter执行此代码单元格,因为我们现在可以执行它。之后,我们需要调用train函数:

train()

这将需要很长时间来处理,即使在好的 GPU 上也是如此,但如果我们有资源并且仍然想改进模型,请运行以下代码以微调我们的inception模型:

# Fine tuning
input_file_pattern = 'im2txt/data/mscoco/train-?????-of-00256'

# change these if you put your stuff somewhere else
inception_checkpoint_file = 'im2txt/data/inception_v3.ckpt'
train_dir = 'im2txt/model'

# This will refine our results
train_inception = True
number_of_steps = 3000000
log_every_n_steps = 1

# Now run the training (warning: takes even longer than initial training!!!)
train()

该模型将运行三百万步。它实际上是从初始训练完成的地方继续,生成新的检查点和改进的模型,然后再运行train函数。这将需要更多的时间来处理并提供良好的结果。我们可以在 Jupyter Notebook 中通过正确指定我们的checkpoint路径和词汇文件路径来完成这项工作:

# tell our function where to find the trained model and vocabulary
checkpoint_path = './model'
vocab_file = './model/word_counts.txt'

之后,我们可以重新运行 Jupyter Notebook 文件中的代码块 4,该文件位于github.com/PacktPublishing/Computer-Vision-Projects-with-OpenCV-and-Python-3/blob/master/Chapter01/Section_1-Tensorflow_Image_Captioning.ipynb,以找到gen_caption

最后一步是运行以下代码,就像我们在“在 Jupyter 上运行标题代码”部分所做的那样:

testfile = 'test_images/ballons.jpeg'

figure()
imshow(imread(testfile))

capts = gen_caption(testfile)

一旦计算完成,我们应该得到一些不错的结果。这标志着使用 TensorFlow 的图像标题的结束。

摘要

在本章中,我们介绍了不同的图像标题方法。我们学习了谷歌大脑的 im2txt 标题模型。在项目工作中,我们能够在 Jupyter Notebook 上运行我们的预训练模型,并根据结果分析模型。在章节的最后部分,我们从零开始重新训练了我们的图像标题模型。

在下一章中,我们将介绍使用 OpenCV 读取车牌。

第三章:使用 OpenCV 读取车牌

本章概述了如何从任何包含车牌的样本照片中提取和显示车牌字符。OpenCV 及其车牌实用函数帮助我们找到车牌上的字符,并让我们对计算机视觉和图像处理的工作有良好的了解。

在本章中,我们将学习以下内容:

  • 读取车牌所需的步骤

  • 车牌实用函数

  • 寻找车牌字符

  • 寻找和读取车牌

识别车牌

在这个项目中,我们将检测和读取汽车照片中的车牌。我们将执行多个步骤,从定位车牌到显示定位车牌中的字符。

让我们参考 Jupyter Notebook 中分析我们的样本图像所需的代码:

%pylab notebook
figure()
imshow(imread('tests/p1.jpg'))

运行代码后,我们得到以下照片:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/fcf031dc-bfee-453c-8480-c869cd11721f.png

我们有一张汽车的照片,车牌清晰可见且可读。挑战在于定位车牌,将其从照片的其余部分中分离出来,并从中提取字符。

我们现在可以更仔细地查看车牌,使用可用的实用函数:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/f8b01357-ab91-49a4-898d-bdaab043cde7.png

有许多算法可以帮助我们完成这两项任务。例如,YOLO:实时目标检测等对象检测器可以使用相关的机器学习方法执行此类任务,并做得非常好。

然而,我们将采用一种简单的方法,使用传统的图像处理和计算机视觉技术,而不是复杂的机器学习技术,如深度学习和 TensorFlow。

我们将使用的算法将帮助我们学习计算机视觉和图像处理技术,从而更好地理解项目。让我们从我们的代码开始,检查我们将使用的车牌实用函数。

车牌实用函数

让我们跳转到 Jupyter Notebook 中的代码,以便了解车牌实用函数。我们首先将导入我们的实用工具。

我们将导入以下库:

  • OpenCV(版本 3.4)

  • NumPy

  • Pickle,它允许我们保存 Python 数据和案例函数

按照以下方式导入库:

import cv2
import numpy as np
import pickle
def gray_thresh_img(input_image):
     h, w, _ = input_image.shape
     grayimg = cv2.cvtColor(input_image, cv2.COLOR_BGR2HSV)[:,:,2]

     kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))

     tophat = cv2.morphologyEx(grayimg, cv2.MORPH_TOPHAT, kernel)
     blackhat = cv2.morphologyEx(grayimg, cv2.MORPH_BLACKHAT, kernel)
     graytop = cv2.add(grayimg, tophat)
     contrastgray = cv2.subtract(graytop, blackhat)
     blurred = cv2.GaussianBlur(contrastgray, (5,5), 0)
     thesholded = cv2.adaptiveThreshold(blurred, 255.0, 
     cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 
     cv2.THRESH_BINARY_INV, 19, 9)

我们将使用这些库来加载用于读取字符的 k 最近邻分类器,这隐式地依赖于 scikit-learn。

我们现在将讨论我们代码中将使用的实用工具。

灰度阈值图像函数和形态学函数

gray_thresh_img 函数接受一个输入图像并将其转换为灰度。我们需要灰度图像,因为彩色图像可能会引起歧义,因为车牌的颜色会根据地区而有所不同。gray_thres_img 函数为我们提供了一个二值化图像。

我们可以使用形态学操作进行预处理,因为这有助于我们减少噪声和间隙。这将去除图像中的噪声并移除多余的特征。

内核

内核是一个三乘三的正方形,我们将在这个正方形上使用tophatblackhatgraytop操作来创建灰度图像。这也有助于我们去除图像噪声——噪声通常存在于自然图像中,对于计算机视觉来说并不理想。图像也可以使用高斯模糊进行去噪。

我们将使用自适应阈值,它检查图像中的局部统计和平均值,以确定它相对于其邻域是亮还是暗。与硬阈值相比,这是更好的选择,因为它将以更好的方式二值化我们的图像。

我们使用return函数来获取灰度图像和二值化图像,如下所示:

return grayimg, thesholded

匹配字符函数

让我们看看下一个函数来获取匹配的字符:

def getmatchingchars(char_cands):
    char_list = [] 

    for char_cand in char_cands:
        ch_matches = [] \n",
        for matching_candidate in char_cands: 
           if matching_candidate == char_cand:
              continue 
          chardistance = np.sqrt((abs(char_cand.x_cent  - matching_candidate.x_cent) ** 2) +  
          (abs(char_cand.y_cent - matching_candidate.y_cent)**2))
          x = float(abs(char_cand.x_cent - matching_candidate.x_cent))
          y = float(abs(char_cand.y_cent - matching_candidate.y_cent))
          angle = np.rad2deg(np.arctan(y/x) if x != 0.0 else np.pi/2)

          deltaarea = float(abs(matching_candidate.rect_area - char_cand.rect_area))\
          / float(char_cand.rect_area)
         deltawidth = float(abs(matching_candidate.rect_w-char_cand.rect_w))\
         / float(char_cand.rect_w)
         deltaheight = float(abs(matching_candidate.rect_h-char_cand.rect_h))
         / float(char_cand.rect_h)

         if (chardistance < (char_cand.hypotenuse * 5.0) and
             angle < 12.0 and deltaarea < 0.5 and deltawidth < 0.8 
             and deltaheight < 0.2):
             ch_matches.append(matching_candidate) 

     ch_matches.append(char_cand) 
     if len(ch_matches) < 3: 
         continue 

     char_list.append(ch_matches) 

getmatchingchars函数帮助我们根据以下标准找到我们的字符候选:

  • 大小

  • 相对距离

  • 角度

  • 面积

如果潜在的字符与其邻居的距离合理,角度与 JSON 字符相比不是太大,面积也不是太大,那么我们可以说可能的字符是一个字符候选

以下代码将返回一个包含车牌字符的列表,然后创建一个容器类,该类将包含对象,如字符子图像的宽度、高度、中心、对角距离或斜边,以及宽高比:

    for charlist in getmatchingchars(list(set(char_cands)-set(ch_matches))):
        char_list.append(charlist) 
    break 

 return char_list

# information container for possible characters in images
class charclass:
     def __init__(self, _contour):
         self.contour = _contour
         self.boundingRect = cv2.boundingRect(self.contour)
         self.rect_x, self.rect_y, self.rect_w, self.rect_h = self.boundingRect
         self.rect_area = self.rect_w * self.rect_h
         self.x_cent = (self.rect_x + self.rect_x + self.rect_w) / 2
         self.y_cent = (self.rect_y + self.rect_y + self.rect_h) / 2
         self.hypotenuse = np.sqrt((self.rect_w ** 2) + (self.rect_h ** 2))
         self.aspect_ratio = float(self.rect_w) / float(self.rect_h)

k-最近邻数字分类器

预训练的 scikit-learn k-最近邻k-nn)数字分类器也需要加载,如下所示:

# load pre-trained scikit-learn knn digit classifier
    with open('knn.p', 'rb') as f:
     knn = pickle.load(f) "

k-nn 分类器将一个小图像与它已知的一系列图像进行比较,以找到最接近的匹配。

我们在这个例子中并没有使用复杂的算法,因为车牌上的字符是相似的。这就是为什么我们可以使用 k-nn 方法,它将进行像素级的比较以找到最接近的匹配。车牌上的字符不是手写的数字,字体可能不同,这需要更多的计算。

在分类器中,p代表 Pickle,这是 Python 存储数据的方式。

寻找车牌字符

接下来,我们执行初始搜索以找到车牌字符。首先,我们找到大致的字符,然后根据特定标准找到候选者。

让我们从 Notebook 中的以下行开始:

%pylab notebook

现在我们可以执行我们的函数单元,用于导入、工具和加载我们的库:

import cv2
import numpy as np
import pickle
def getmatchingchars(char_cands):
    char_list = [] 

    for char_cand in char_cands:
        ch_matches = [] \n",
        for matching_candidate in char_cands: 
           if matching_candidate == char_cand:
              continue 
          chardistance = np.sqrt((abs(char_cand.x_cent  - matching_candidate.x_cent) ** 2) +  
          (abs(char_cand.y_cent - matching_candidate.y_cent)**2))
          x = float(abs(char_cand.x_cent - matching_candidate.x_cent))
          y = float(abs(char_cand.y_cent - matching_candidate.y_cent))
          angle = np.rad2deg(np.arctan(y/x) if x != 0.0 else np.pi/2)

          deltaarea = float(abs(matching_candidate.rect_area - char_cand.rect_area))\
          / float(char_cand.rect_area)
         deltawidth = float(abs(matching_candidate.rect_w-char_cand.rect_w))\
         / float(char_cand.rect_w)
         deltaheight = float(abs(matching_candidate.rect_h-char_cand.rect_h))
         / float(char_cand.rect_h)

         if (chardistance < (char_cand.hypotenuse * 5.0) and
             angle < 12.0 and deltaarea < 0.5 and deltawidth < 0.8 
             and deltaheight < 0.2):
             ch_matches.append(matching_candidate) 

     ch_matches.append(char_cand) 
     if len(ch_matches) < 3: 
         continue 

     char_list.append(ch_matches) 

    for charlist in getmatchingchars(list(set(char_cands)-set(ch_matches))):
        char_list.append(charlist) 
    break 

 return char_list

# information container for possible characters in images
class charclass:
     def __init__(self, _contour):
         self.contour = _contour
         self.boundingRect = cv2.boundingRect(self.contour)
         self.rect_x, self.rect_y, self.rect_w, self.rect_h = self.boundingRect
         self.rect_area = self.rect_w * self.rect_h
         self.x_cent = (self.rect_x + self.rect_x + self.rect_w) / 2
         self.y_cent = (self.rect_y + self.rect_y + self.rect_h) / 2
         self.hypotenuse = np.sqrt((self.rect_w ** 2) + (self.rect_h ** 2))
         self.aspect_ratio = float(self.rect_w) / float(self.rect_h)

现在我们可以加载我们的输入图像,它将被用于分析。在这里我们使用plt函数而不是 OpenCV,因为 OpenCV 默认以蓝绿红BGR)格式而不是红绿蓝RGB)格式加载图像。这对于你的自定义项目很重要,但对我们项目来说并不重要,因为我们将会将图像转换为灰度。

让我们加载我们的图像:

input_image = plt.imread('tests/p5.jpg') #use cv2.imread or 
 #import matplotlib.pyplot as plt
 #if running outside notebook
figure()
imshow(input_image)

这是输出照片:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/644c259c-32e2-4af2-bb38-54331f147e32.png

让我们仔细看看这辆车的车牌:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/baf74a28-f1b2-4e53-af3b-cad48e8a11ce.png

我们将从这张图像中找到字符。然而,我们首先需要移除背景,这对我们来说并不重要。在这里,我们需要对图像进行初始预处理,使用 gray_thresh_imgblurredmorphology 函数,这将帮助我们去除背景。

这里是初始预处理的代码:

def gray_thresh_img(input_image):
     h, w, _ = input_image.shape
     grayimg = cv2.cvtColor(input_image, cv2.COLOR_BGR2HSV)[:,:,2]

     kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))

     tophat = cv2.morphologyEx(grayimg, cv2.MORPH_TOPHAT, kernel)
     blackhat = cv2.morphologyEx(grayimg, cv2.MORPH_BLACKHAT, kernel)
     graytop = cv2.add(grayimg, tophat)
     contrastgray = cv2.subtract(graytop, blackhat)
     blurred = cv2.GaussianBlur(contrastgray, (5,5), 0)
     thesholded = cv2.adaptiveThreshold(blurred, 255.0, 
     cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 
     cv2.THRESH_BINARY_INV, 19, 9)

让我们看看我们的主要代码:

h, w = input_image.shape[:2] 

# We don't use color information
# + we need to binarize (theshold) image to find characters
grayimg, thesholded = gray_thresh_img(input_image)
contours = cv2.findContours(thesholded, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[1]

# initialize variables for possible characters/plates in image
char_cands = [] 
plate_candidates = [] 

我们将给出图像形状,这将返回照片的高度、宽度和 RGB 深度。我们现在不需要 RGB 深度,所以我们只提取 2 个元素;高度和宽度。由于我们将处理灰度图像而不是彩色图像,我们将调用我们方便的 gray_thresh_img 函数,该函数将返回灰度和二值化的阈值图像。

为了找到轮廓,我们需要图像中的子图像,这些子图像对应于字符,然后对应于轮廓。我们将使用 OpenCV 的内置算法 findContours 来找到可能作为字符和作为我们的 k-nn 使用的复杂形状的轮廓细节。然后我们将初始化我们的 char_candsplate_candidates 变量。

让我们尝试第一次寻找字符:

for index in range(0, len(contours)): 
    char_cand = charclass(contours[index])
    if (char_cand.rect_area > 80 and char_cand.rect_w > 2 
        and char_cand.rect_h > 8 and 0.25 < char_cand.aspect_ratio 
        and char_cand.aspect_ratio < 1.0):

        char_cands.append(char_cand) 

我们将使用字符来寻找车牌,这是一种与其他机器学习算法不同的方法。这种方法将帮助我们更好地理解寻找字符的过程。

我们将遍历所有轮廓,并使用我们已定义的 charclass 类(charclass 类)。这个类会自动提取中心、对角线长度和宽高比,以确定图像是否过大或过小,或者宽高比是否过于倾斜。从这个推断中,我们可以得出结论,该字符不是车牌上的字母或数字。这有助于我们仅考虑符合几何标准的轮廓。

寻找匹配的字符和字符组

一旦第一次遍历完成,我们将细化我们的匹配,以找到可能属于车牌的一组字符。参考以下代码:

for ch_matches in getmatchingchars(char_cands): 
     class blank: pass
     plate_candidate = blank() 

     ch_matches.sort(key = lambda ch: ch.x_cent) 

     plate_w = int((ch_matches[len(ch_matches) - 1].rect_x + \
                    ch_matches[len(ch_matches) - 1].rect_w - ch_matches[0].rect_x) * 1.3)

     sum_char_h = 0
     for ch in ch_matches:
         sum_char_h += ch.rect_h

     avg_char_h = sum_char_h / len(ch_matches)
     plate_h = int(avg_char_h * 1.5)

     y = ch_matches[len(ch_matches) - 1].y_cent - ch_matches[0].y_cen
     r = np.sqrt((abs(ch_matches[0].x_cent 
                   - ch_matches[len(ch_matches) - 1].x_cent) ** 2) 
                + (abs(ch_matches[0].y_cent 
                   - ch_matches[len(ch_matches) - 1].y_cent) ** 2))
     rotate_angle = np.rad2deg(np.arcsin(y / r))

我们将通过调用之前使用的 getmatchingchars 函数遍历所有潜在的字符,该函数根据标准提供额外的过滤。它取决于与相邻字符的角、三角学、宽度和高度的比较,以及邻居的类型。这些标准帮助我们实现一致性。

一旦我们有了车牌候选者,我们可以创建一个 blank 对象。因此,我们有一个没有任何属性的 blank 对象,并创建了一个列表。我们首先按字符的中心排序,这将帮助我们按从左到右的顺序通过匹配进行排序。

sum_char_h 的求和将帮助我们找到字符的平均高度和宽度。

让我们看看以下代码:

     platex = (ch_matches[0].x_cent + ch_matches[len(ch_matches) - 1].x_cent) / 2
     platey = (ch_matches[0].y_cent + ch_matches[len(ch_matches) - 1].y_cent) / 2
     plate_cent = platex, platey

车牌的理想位置是垂直于摄像头的。如果车牌的角度大于特定的可接受角度,或者颠倒,那么我们可能无法读取车牌。

我们从代码中找到xy,并校正车牌的角度,如果它在合理的角度范围内。

然后,我们根据这里找到的角度确定车牌位置,并使用rotationMatrix将其存储起来,以便稍后进行计算。我们可以一步完成,因为我们已经找到了这个角度。我们希望围绕车牌的中心旋转,如下所示:

     plate_candidate.plateloc = (tuple(plate_cent), (plate_w, plate_h), rotate_angle)
     rotationMatrix = cv2.getRotationMatrix2D(tuple(plate_cent), rotate_angle, 1.0)

我们在这里创建旋转后的图像,cv2.wrapAffine函数将帮助我们进行拉伸、倾斜、旋转和平移,以及更高阶的变换,如缩放、拉伸和旋转:

     rotated = cv2.warpAffine(input_image, rotationMatrix, tuple(np.flipud(input_image.shape[:2])))

    plate_candidate.plate_im = cv2.getRectSubPix(rotated, (plate_w, plate_h), tuple(plate_cent))
    if plate_candidate.plate_im is not None:
        plate_candidates.append(plate_candidate)

一旦我们有了旋转并围绕车牌候选中心对齐的子图像,我们就将其保存到我们之前初始化的车牌候选列表中。现在我们有了字符和车牌候选的初始猜测,利用这些我们可以找到并读取车牌候选。

使用 OpenCV 查找和读取车牌

我们已经找到了字符,它们是车牌候选。现在我们需要确定哪些字符匹配,以便我们可以提取文本数据并将字符映射到车牌中。

首先,我们运行每个车牌候选通过我们的gray_thresh_img函数,该函数执行我们的去噪和二值化。在这种情况下,我们得到更干净的输出,因为我们使用的是子图像而不是完整图像。

这是我们将要使用的提取代码:

for plate_candidate in plate_candidates: 

    plate_candidate.grayimg, plate_candidate.thesholded = \
                              gray_thresh_img(plate_candidate.plate_im) 
    plate_candidate.thesholded = cv2.resize(plate_candidate.thesholded, 
                                             (0, 0), fx = 1.6, fy = 1.6)
    thresholdValue, plate_candidate.thesholded = \
                               cv2.threshold(plate_candidate.thesholded, 
                                            0.0, 255.0, 
                                            cv2.THRESH_BINARY | cv2.THRESH_OTSU)

我们需要字符具有相同的大小,因为我们将会使用 k-nn 方法,它是区分大小写的。如果大小不同,我们将收到垃圾值。在将图像调整到大小后,我们需要进行阈值处理,我们将使用OTSU方法。

然后,我们需要在子图像中找到轮廓,并进行合理性检查,以确保我们找到的子图像中的轮廓满足某些标准,其中大小和宽高比是合理的,如下所示:

contours = cv2.findContours(plate_candidate.thesholded, cv2.RETR_LIST, 
 cv2.CHAIN_APPROX_SIMPLE)[1]
plate_chars = [] 
 for contour in contours: 
 char_cand = charclass(contour)
 if (char_cand.rect_area > 80 and char_cand.rect_w > 2 
 and char_cand.rect_h > 8 and 0.25 < char_cand.aspect_ratio 
 and char_cand.aspect_ratio < 1.0):
 plate_chars.append(char_cand) 

如果轮廓不符合标准,这意味着我们可能没有看到车牌,或者没有得到好的字符。

一旦完成合理性检查,我们就运行getmatchingchars函数,这将确保我们得到一组大小大致相同的良好字符:

plate_chars = getmatchingchars(plate_chars)

     if (len(plate_chars) == 0):
     plate_candidate.chars = \"
     continue
for index in range(0, len(plate_chars)): 
    plate_chars[index].sort(key = lambda ch: ch.x_cent) 
    filt_matching_chars = list(plate_chars[index])

这是一个冗余检查,但对于获得干净和可靠的结果是必要的。我们按顺序从左到右迭代所有字符,以检查字符是否足够远。我们这样做是因为,理论上,重叠的轮廓可能是重叠的字符,这在现实中的车牌中是不会发生的。

我们需要确保字符之间距离足够远,因为我们不是在重复检测相同的东西;在这里我们执行多个for循环,并将字符相互比较,如下所示:

    for thischar in plate_chars[index]:
 for alt_char in plate_chars[index]:
 if thischar != alt_char: 
 chardistance = np.sqrt((abs(thischar.x_cent-alt_char.x_cent)**2) 
 + (abs(thischar.y_cent-alt_char.y_cent) ** 2))
 if chardistance < (thischar.hypotenuse * 0.3):
 if thischar.rect_area < alt_char.rect_area: 
 if thischar in filt_matching_chars: 
 filt_matching_chars.remove(thischar) 
 else: 
 if alt_char in filt_matching_chars: 
 filt_matching_chars.remove(alt_char) 

我们需要确保所有内容都在感兴趣的区域中心,这样在执行缩放、旋转和平移等操作时,字符就不会丢失,因为我们正在寻找 k-nn。

在此代码中,我们遍历我们的字符列表中的每个字符和每个阈值区域,以确保我们将区域调整到20乘以30,这与我们的 k-nn 预测相匹配:

     charlistlen = 0
 char_index = 0

 for index in range(0, len(plate_chars)):
 if len(plate_chars[index]) > charlistlen:
 charlistlen = len(plate_chars[index])
 char_index = index

 full_char_list = plate_chars[char_index]
 full_char_list.sort(key = lambda ch: ch.x_cent) 

 plate_candidate.chars = \
 for thischar in full_char_list: 
 roi = plate_candidate.thesholded[thischar.rect_y : 
 thischar.rect_y + thischar.rect_h,
 thischar.rect_x : 
 thischar.rect_x + thischar.rect_w]

 resized_roi = np.float32(cv2.resize(roi, (20, 30)).reshape((1, -1)))
 plate_candidate.chars += str(chr(int(knn.predict(resized_roi)[0])))

现在,所有这些区域长度都是 600。NumPy 的reshape函数将二维输入的区域通过某些维度映射,以得到 1/600。

thischar函数最初实际上是一个空字符串,但随着我们找到 k-nn,它将不断被填充。

此外,当我们寻找最佳候选者时,我们需要确保我们的plate_candidates不是空的:

if len(plate_candidates) > 0:
    plate_candidates.sort(key = lambda plate_candidate: 
                           len(plate_candidate.chars), reverse = True)
    best_plate = plate_candidates[0]
    print("License plate read: " + best_plate.chars + "\n") 

对于给定的图像,您可能会找到多个车牌候选者,但通常它们是同一件事。您可能只是找到了四个字符,而实际上有六个,或者类似的情况。具有最多字符的那个可能是正确的,但您也可以查看其他候选者。

我们将再次按字符串长度提取并排序,找到best_plate,并打印出结果。

结果分析

当我们使用最佳候选代码块运行我们的代码时,我们得到以下结果:

if len(plate_candidates) > 0: 
    plate_candidates.sort(key = lambda plate_candidate: 
                          len(plate_candidate.chars), reverse = True)
    best_plate = plate_candidates[0]
    print("License plate read: " + best_plate.chars + "\n")

License plate read: LTLDBENZ

一旦我们得到输出,我们可以使用以下代码显示我们的结果:

figure()
imshow(best_plate.thesholded)

显示的图像如下:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/29224956-b4cf-4893-8da4-65719bf221b2.png

虽然有一个多余的字符,但我们可以看到我们的显示图像与车牌字符非常接近。我们可以用我们的其他可能的车牌字符来检查,以获得最接近的结果。

让我们再试一个车牌,以检查我们的代码如何工作:

input_image = plt.imread('tests/p2.jpg') #use cv2.imread or 
                                         #import matplotlib.pyplot as plt
                                         #if running outside notebook
figure()
imshow(input_image)

这里是输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/57f183fe-8a9b-4ad1-8ba9-ffb6f0a7792e.png

显示的照片如下:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/7b28522d-8233-4afb-878e-0093dadb36d1.png

如果您只想获取车牌的子图像,可以使用以下代码:

imshow(best_plate.plate_im)

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/11243ab0-18fc-481a-bf19-2fa2ebb7ed64.png

我们还可以找到结果的位置:

figure()
# best_plate.plate_im
imshow(best_plate.plate_im)
best_plate.plateloc

输出中您得到以下位置:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/cb45ca54-91ea-4b91-9409-287cfeeaa58e.png

因此,这里我们有xy坐标,宽度,高度和一些偏移信息。

我们可以尝试其他可用的函数,如下所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/f1a092ba-ec2b-41ae-ba6e-b1bb4da83729.png

让我们看看另一个例子,其中车牌号码不够清晰:

input_image = plt.imread('tests/p3.jpg') #use cv2.imread or 
                                         #import matplotlib.pyplot as plt
                                         #if running outside notebook
figure()
imshow(input_image)

这给我们以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/065f0342-9089-4974-baa9-8e318f920fbd.png

让我们更仔细地看看车牌:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/450aade3-a977-4687-8bdc-380bf9ff769f.png

我们的display函数给出了相当不错的结果,如下所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/3ae5a073-5b57-4c78-9103-5542367fa5d2.png

让我们看看我们的最后一个例子:

input_image = plt.imread('tests/p1.jpg') #use cv2.imread or 
                                         #import matplotlib.pyplot as plt
                                         #if running outside notebook
figure()
imshow(input_image)

这里是视图:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/2cfe4a47-732f-4e28-afa4-3846c3e99449.png

以下截图显示了输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/1ee92e1c-609b-4dbb-ab44-7b1d24bcb9e3.png

结果照片显示如下:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/cf337289-29d2-427e-9641-7c8d1098083c.png

摘要

在本章中,我们学习了如何使用 OpenCV 进行车牌识别,这让我们对计算机视觉和图像处理的工作原理有了很好的了解。

我们首先学习了不同的车牌效用函数,这帮助我们找到了车牌特征。然后,我们使用 OpenCV 找到了车牌字符的可能候选者。最后,我们分析了我们的结果,以检查我们算法的效率。

在下一章,第四章,使用 TensorFlow 进行人体姿态估计,我们将使用 DeeperCut 算法和 ArtTrack 模型进行人体姿态估计。

第四章:使用 TensorFlow 进行人体姿态估计

在本章中,我们将介绍如何使用 DeeperCut 算法通过 TensorFlow 进行人体姿态估计。我们将学习使用 DeeperCut 和 ArtTrack 模型进行单人和多人姿态检测。稍后,我们还将学习如何使用该模型与视频一起使用,并重新训练它以用于我们项目中的定制图像。

在本章中,我们将涵盖以下主题:

  • 使用 DeeperCut 和 ArtTrack 进行姿态估计

  • 单人姿态检测

  • 多人姿态检测

  • 视频和重新训练

使用 DeeperCut 和 ArtTrack 进行姿态估计

人体姿态估计是从图像或视频中估计身体(姿态)配置的过程。它包括地标(点),这些点类似于脚、脚踝、下巴、肩膀、肘部、手、头部等关节。我们将使用深度学习自动完成这项工作。如果您考虑面部,地标相对刚性,或者说相对恒定,例如眼睛相对于鼻子的相对位置,嘴巴相对于下巴,等等。

以下照片提供了一个示例:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/a2c43966-d8da-481e-aff1-ff313b244730.png

虽然身体结构保持不变,但我们的身体不是刚性的。因此,我们需要检测身体的不同部位相对于其他部位的位置。例如,相对于膝盖检测脚部是非常具有挑战性的,与面部检测相比。此外,我们可以移动我们的手和脚,这可能导致各种各样的姿势。以下图片提供了一个示例:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/85561526-7b48-492b-b381-eb42bfddb855.png

这在我们从世界各地不同研究小组在计算机视觉方面的突破之前是非常困难的。已经开发了不同的代码来执行姿态估计,但我们将介绍一个名为DeeperCut的算法。

您可以参考 MPII 人体姿态模型(pose.mpi-inf.mpg.de)以获取详细信息。

DeeperCut 是由德国马克斯·普朗克学会的一个研究小组开发的,与斯坦福大学合作,他们发布了他们的算法并发表了论文。建议查看他们的论文《DeepCut:用于多人姿态估计的联合子集划分和标记》,该论文概述了 DeeperCut 之前的早期算法,其中他们讨论了如何检测身体部位以及他们如何运行优化算法以获得良好的结果。您还可以参考他们后续的论文《DeeperCuts》:一个更深、更强、更快的多人姿态估计模型,该论文由同一组作者发表,这将涵盖许多技术细节。我们肯定不会得到精确的结果,但您可以用合理的概率确定一些事情。

在 GitHub 页面github.com/eldar/pose-tensorflow,有他们代码的公开实现,包括 DeeperCut 和一个新版本 ArtTrack。这是在野外进行的人体姿态跟踪,你可以在下面的照片中看到输出结果:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/aaf57d4e-3c54-4ea2-8635-9385de2c2ab3.png

我们将运行一个修改后的代码版本,它被设计在 Jupyter Notebook 环境中运行,并且适用于所有学习目的,因此它应该比直接从 GitHub 获取要简单一些。我们将学习如何运行代码并在我们的项目中使用它。所有预训练的模型都包含在这里:github.com/eldar/pose-tensorflow.

单人姿态检测

现在我们已经了解了人体姿态估计和新的 DeeperCut 算法的概述,我们可以运行单人姿态检测的代码,并在 Jupyter Notebook 中检查它。

我们将从单人检测开始。在开始之前,我们需要确保我们使用的是一个干净的内核。你可以重启你的内核,或者你可以使用快捷键来完成同样的操作。当你处于命令模式时,你可以按下0键两次,这与实际编辑单元格时的编辑模式相反。

让我们从以下示例中的单人检测代码开始:

!pip install pyyaml easydict munkres

感叹号表示执行一个 shell 命令。这将安装一些你可能没有的库,如果你在你的系统中安装了 Python 3,你可能需要将命令更改为pip 3

在下一个单元格中,我们将调用%pylab notebook函数,这将允许我们在笔记本中使用一些有用的控件查看图像,以及加载一些数值库,例如numpy等。我们将进行一些通用导入,例如ossyscv2。为了进行注释,我们将使用imageioimread函数并从randint获取一切。你不需要导入numpy,因为我们已经使用了%pylab notebook,但如果你想在笔记本外复制粘贴此代码,你需要它。然后,我们需要导入tensorflow,它已经包含了一些来自pose-tensorflow仓库的粘合工具。代码,仅供参考,如下所示:

%pylab notebook
import os
import sys
import cv2
from imageio import imread
from random import randint
import numpy as np
import tensorflow as tf
from config import load_config
from nnet.net factory import pose_net

然后我们执行前面的单元格。

我们现在将设置姿态预测,如下面的代码所示:

def setup_pose_prediction(cfg):
    inputs = tf.placeholder(tf.float32, shape=[cfg.batch_size, None, None, 3])

    outputs = pose_net(cfg).test(inputs)

    restorer = tf.train.Saver()

    sess = tf.Session()

    sess.run(tf.global_variables_initializer())
    sess.run(tf.local_variables_initializer())

    # Restore variables from disk.
    restorer.restore(sess, cfg.init_weights)

    return sess, inputs, outputs

它将启动会话并加载我们的模型。我们将使用一个预训练模型,您可以从 GitHub 仓库快速访问它。tf.Session()将启动 TensorFlow 会话并将其保存到sess变量中,我们将返回它。请注意,当您运行此函数时,它将保持 TensorFlow 会话开启,所以如果您想继续做其他事情,比如加载新模型,那么您将不得不关闭会话或重新启动。在这里这很有用,因为我们将要查看多张图片,如果每次都加载会话,将会更慢。然后,我们将获取配置,它加载相应的模型和变量,并将返回运行模型所需的必要值。

然后,我们使用extract_cnn_outputs函数提取 CNN 输出。在输出中,我们将获得联合位置,以了解一切相对于其他事物的确切位置。我们希望得到一个有序的二维数组,其中我们知道脚踝、手或肩膀的位置的 X 和 Y 坐标。以下是一个示例:

def extract_cnn_output(outputs_np, cfg, pairwise_stats = None):
    scmap = outputs_np['part_prob']
    scmap = np.squeeze(scmap)
    locref = None
    pairwise_diff = None
    if cfg.location_refinement:
        locref = np.squeeze(outputs_np['locref'])
        shape = locref.shape
        locref = np.reshape(locref, (shape[0], shape[1], -1, 2))
        locref *= cfg.locref_stdev
    if cfg.pairwise_predict:
        pairwise_diff = np.squeeze(outputs_np['pairwise_pred'])
        shape = pairwise_diff.shape
        pairwise_diff = np.reshape(pairwise_diff, (shape[0], shape[1], -1, 2))
        num_joints = cfg.num_joints
        for pair in pairwise_stats:
            pair_id = (num_joints - 1) * pair[0] + pair[1] - int(pair[0] < pair[1])
            pairwise_diff[:, :, pair_id, 0] *= pairwise_stats[pair]["std"][0]
            pairwise_diff[:, :, pair_id, 0] += pairwise_stats[pair]["mean"][0]
            pairwise_diff[:, :, pair_id, 1] *= pairwise_stats[pair]["std"][1]
            pairwise_diff[:, :, pair_id, 1] += pairwise_stats[pair]["mean"][1]
    return scmap, locref, pairwise_diff

这将把神经网络输出(有点难以理解)转换成我们可以实际使用的格式。然后,我们将输出传递给其他东西,或者在这种情况下可视化它。argmax_pose_predict与我们之前所做的是互补的。它是一个辅助函数,将帮助我们理解输出,以下是一个示例:

def argmax_pose_predict(scmap, offmat, stride):
    """Combine scoremat and offsets to the final pose."""
    num_joints = scmap.shape[2]
    pose = []
    for joint_idx in range(num_joints):
        maxloc = np.unravel_index(np.argmax(scmap[:, :, joint_idx]),
                                  scmap[:, :, joint_idx].shape)
        offset = np.array(offmat[maxloc][joint_idx])[::-1] if offmat is not None else 0
        pos_f8 = (np.array(maxloc).astype('float') * stride + 0.5 * stride +
                  offset)
        pose.append(np.hstack((pos_f8[::-1],
                               [scmap[maxloc][joint_idx]])))
    return np.array(pose)

现在,让我们执行定义函数的那个单元格。它将立即运行。

以下代码将加载配置文件,即demo/pose_cfg.yamlsetup_pose_prediction(cfg)将返回sessinputsoutputs。以下是一个示例:

cfg = load_config("demo/pose_cfg.yaml")
sess, inputs, outputs = setup_pose_prediction(cfg)

当我们运行前面的代码时,它将保持 TensorFlow 会话开启,建议只运行一次以避免错误,或者您可能需要重新启动内核。因此,如果命令被执行,我们理解模型已经被加载,正如您在以下输出中看到的那样:

INFO:tensorflow:restoring parameters from models/mpii/mpii-single-resnet-101

现在,我们将看到如何实际应用该模型:

file_name = "testcases/standing-lef-lift.jpg"
image = np.array(imread(file_name))
image_batch = np.expand_dims(image, axis=0).astype(float)
outputs_np = sess.run(outputs, feed_dict={inputs: image_batch})
scmap, locref, pairwise_diff = extract_cnn_output(outputs_np, cfg)
pose = argmax_pose_predict(scmap, locref, cfg.stride)

对于我们的模型,我们必须给我们的文件命名。因此,我们有一个名为testcases的目录,里面有一系列不同姿势的人的股票照片,我们将使用这些照片进行测试。然后,我们需要以合适的格式加载standing-leg-lift.jpg图像。我们将把图像转换成 TensorFlow 实际需要的格式。输入类似于image_batch,它将在0轴上扩展维度。所以,只需创建 TensorFlow 可以使用的数组。然后,outputs_np将运行会话,在下一行提取 CNN 输出,然后进行实际的姿态预测。pose变量在这里使用最好。然后我们应该执行单元格并按Esc按钮进入命令模式。然后,我们需要创建一个新的单元格;输入pose并按Ctrl + Enter*。然后我们将得到以下 2D 数组输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/e66b51af-effe-45c2-8b51-20e4e815f1fc.png

输出给出了与手腕、脚踝、膝盖、头部、下巴、肩膀等关节对应的xy坐标。从这些坐标中,我们得到x坐标、y坐标和匹配分数。我们不需要亚像素级别的精度,所以我们可以将其四舍五入到最接近的整数。在下面的示例中,你可以看到我们已经用数字标记了对应的关节,并在它们之间画了线:

pose2D = pose[:, :2]
image_annot = image.copy()

for index in range(5):
    randcolor = tuple([randint(0, 255) for i in range(3)])
    thickness = int(min(image_annot[:,:,0].shape)/250) + 1
    start_pt = tuple(pose2D[index].astype('int'))
    end_pt = tuple(pose2D[index+1].astype('int'))
    image_annot = cv2.line(image_annot, start_pt, end_pt, randcolor, thickness)
for index in range(6,11): #next bunch are arms/shoulders (from one hand to other)
    randcolor = tuple([randint(0,255) for i in range(3)])
    thickness = int(min(image_annot[:,:,0].shape)/250) + 1
    start_pt = tuple(pose2D[index].astype('int'))
    end_pt = tuple(pose2D[index+1].astype('int'))
    image_annot = cv2.line(image_annot, start_pt, end_pt, randcolor, thickness)
#connect Line from chin to top of head
image_annot = cv2.line(image_annot,
                       tuple(pose2D[12].astype('int')), tuple(pose2D[13].astype('int'))
                       tuple([randint(0,255) for i in range(3)]), thickness)

我们需要在这里创建一个pose2D标签,然后我们将从前两列中提取 x 和 y 坐标。我们将使用image.copy()来制作一个副本,因为我们希望我们的注释图像与原始图像分开。

我们将运行以下代码来显示原始图像:

figure()
imshow(image)

现在,我们将学习如何注释原始图像。我们将创建图像的一个副本,然后我们将遍历前六个关节并在它们之间画线。它从脚踝开始,标记为1,然后穿过臀部,最后下降到另一只脚踝。数字611将是手臂和肩膀,最后两个点是下巴和头顶。我们现在将使用pose2D中的所有这些点用线连接起来。实际上,我们没有腰部衣领的点,但我们可以很容易地从臀部和肩膀的中点估计它们,这对于完成骨骼很有用。

让我们看看以下代码,它帮助我们估计中点:

# There no actual joints on waist or coLLar,
# but we can estimate them from hip/shoulder midpoints
waist = tuple(((pose2D[2]+pose2D[3])/2).astype('int'))
collar = tuple(((pose2D[8]+pose2D[9])/2).astype('int'))
# draw the "spine"
image_annot = cv2.line(image_annot, waist, collar,
                       tuple([randint(0,255) for i in range(3)]), thickness)
image_annot = cv2.line(image_annot, tuple(pose2D[12].astype('int')), collar,
                       tuple([randint(0,255) for i in range(3)]), thickness)
# now Label the joints with numbers
font = cv2.FONT_HERSHEY_SIMPLEX
fontsize = min(image_annot[:,:,0].shape)/750 #scale the font size to the image size
for idx, pt in enumerate(pose2D):
    randcolor = tuple([randint(0,255) for i in range(3)])
image_annot = cv2.putText(image_annot, str(idx+1),
                          tup1e(pt.astype('int')),font, fontsize,
                          randcolor,2,cv2.LINE_AA)
figure()
imshow(image_annot)

现在,我们可以通过从腰部到衣领,再从衣领到下巴画点来绘制脊柱。我们还可以标记这些关节,以显示我们连接的确切位置,这有助于你的定制应用。我们将标记关节,创建图形,显示注释图像,并处理随机颜色。以下截图显示了输出看起来像什么:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/cbe433a8-4db9-4dcc-b044-b7a2d51bad78.png

在这里,1 是右脚踝,但它可能是左脚踝,取决于人的面向方向。所以,除了 13(在这里有点遮挡)和 14(稍微超出图像)之外,所有的链接都已经连接。这个的好处是,即使其他关节被遮挡(例如,如果它们在屏幕外或被某物覆盖),它也可能工作。你会注意到图像很简单,有一个平坦的背景,平坦的地板,简单的姿态和衣服。代码也可以处理更复杂的图像,如果你在阅读细节时遇到任何困难,可以使用这里的工具并放大查看。

让我们尝试使用不同的图像并分析我们的结果,如下所示:

file_name = "testcases/mountain_pose.jpg"
image = np.array(imread(file_name))
image_batch = np.expand_dims(image, axis=0).astype(float)
outputs_np = sess.run(outputs, feed_dict={inputs: image_batch})
scmap, locref, pairwise_diff = extract_cnn_output(outputs_np, cfg)
pose = argmax_pose_predict(scmap, locref, cfg.stride)

下面是我们将要测试的图片:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/a2eb4e31-b79b-46d9-b13a-301bab289c87.png

当我们再次运行我们的模型,使用不同的图像时,我们得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/19445ba0-e0f2-4b91-be29-1b3ab509e062.png

如果我们拍一个交叉双臂的人的图像,我们会得到以下截图:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/68002f06-88dc-43a5-8da1-c0a1019254c7.png

即使双臂交叉,结果仍然非常好。

现在,让我们看看一些比较困难的图像。这可能不会给我们一个完整的运动捕捉姿态估计解决方案的准确结果,但仍然非常令人印象深刻。

选择acrobatic.jpeg,如下所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/e6d30678-98a9-45f0-bbed-2b148f6e243e.png

当我们运行这张照片时,得到的输出如下所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/6a998f24-5ca7-4468-a184-b9e35e12a17c.png

看起来它找到了关节,或多或少,但没有正确连接它们。它显示这个人的头在他的手上,手触地。我们可以看到结果并不那么好。但我们不能期望所有图像都能得到准确的结果,即使这是最先进的技术。

多人姿态检测

现在,让我们从单人姿态检测转到多人姿态检测。在单人姿态检测中,我们看到代码会取一个单个人的图像,并生成带有所有关节标记的姿态估计。我们现在将学习一个更高级的模型,称为 ArtTrack,它将允许我们计数人数,找到人,并估计他们的姿态。

让我们看看多人姿态检测的代码,以下是一个示例:

import os
import sys
import numpy as np
import cv2 I
from imageio import imread, imsave
from config import load_config
from dataset.factory import create as create_dataset
from nnet import predict
from dataset.pose_dataset import data_to_input
from multiperson.detections import extract_detections
from multiperson.predict import SpatialModel, eval_graph, get_person_conf_mu1ticut
# from muLtiperson.visuaLize import PersonDraw, visuaLize_detections

这有点复杂。我们将首先使用当前目录下的!ls命令列出我们的目录,在那里你会找到一个名为compile.sh的文件。

我们需要运行这个文件,因为这个模块中包含一些二进制依赖项。但是这是一个 shell 脚本文件,你可能在 macOS 或 Linux 上遇到一些问题。因此,为了生成那些特定于操作系统的文件/命令,你需要运行这个脚本。对于 Windows,那些二进制文件已经生成好了。所以,如果你使用的是 Python 和 TensorFlow 的最新版本,那么文件将是兼容的,二进制文件应该可以正常工作。

如果它不起作用,您将需要下载并安装 Visual Studio Community。您可以在github.com/eldar/pose-tensorflowdemo代码部分找到一些关于多人姿态的安装说明。

一旦一切运行正常,我们就可以开始我们的示例。此外,正如我们之前已经讨论过的,我们需要确保重启内核。这是因为如果您已经打开了会话来运行不同的项目,TensorFlow 可能无法计算代码,因为已经加载了之前的模型。始终从一个全新的内核开始是一个好的做法。

我们将运行我们的 %pylab notebook 来进行可视化和数值计算。代码的工作方式与我们之前已经覆盖的类似,其中我们有一些模板代码并加载了一个预训练模型。预训练模型已经包含在内,所以我们不需要下载它。由于 TensorFlow 的原因,代码将在一秒内执行,我们将导入模块并加载存储库。此外,我们还需要分别加载模型并进行预测。如果我们按下 C*trl + Shift *+ -,我们可以将预测分别放入不同的单元格中,使其看起来更整洁。

当我们运行第一个单元格时,我们得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/a2797da1-e46c-40d2-8f76-5cc565d2b363.png

这不是一个大的错误消息,这是因为在这里定义了 imread;笔记本覆盖了它,只给你一个警告消息。我们可以重新运行那段代码来忽略警告并得到整洁的输出。

在这个单元格中,我们将加载 ArtTrack/DeeperCut 作者提供的多人配置文件。

以下行加载了数据集:

cf = load_config("demo/pose_cfg_multi.yaml) 

然后,以下行创建模型并加载它:

dataset = create_dataset(cfg)
sm = SpatialModel(cfg)
sm.load()
sess, inputs, outputs = predict.setup_pose_prediction(cfg)

当我们执行这个操作时,我们得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/75cf5251-4345-430b-8250-fac3e0b66805.png

我们将在这里保持会话开启,这样我们就可以继续运行不同的事情,并快速浏览不同的帧。

我们现在将运行一些测试案例,这些案例实际上有多个人,如下所示:

file_name = "testcases/bus_people.jpg"
image = np.array(imread(file_name))
image_batch = data_to_input(image)
# Compute prediction with the CNN
outputs_np = sess.run(outputs, feed_dict={inputs: image_batch})
scmap, locref, pairwise_diff = predict.extract_cnn_output(outputs_np, cfg, dataset
detections = extract_detections(cfg, scmap, locref, pairwise_diff)
unLab, pos_array, unary_array, pwidx_array, pw_array = eval_graph(sm, detections)
person_conf_multi = get_person_conf_multicut(sm, unLab, unary_array, pos_array)
image_annot = image.copy()
for pose2D in person_conf_mu1ti:
    font = cv2.FONT_HERSHEY_SIMPLEX
    fontsize = min(image_annot[:,:,0].shape)/1000

我们需要将 np.array 转换为平面数组网络,以便使用 sess.run 进行预测,然后使用模型工具提取 CNN 输出和 detections。我们在这里不会标记骨骼,而是用数字标记关节。

当我们运行代码时,我们得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/19c5a46a-bb1f-4893-b003-02abe003965d.png

这是一张多人的简单图像,穿着朴素的衣服,背景平坦。这实际上起作用了。然而,数字与之前不同。之前,数字 1 对应右脚踝,向上通过 2、3、4、5 和 6,然后 7 是右腕,以此类推。所以,数字不同,而且更多,这实际上检测到了更多的关节,因为面部有多个数字,所以这里有多点。让我们放大查看细节,如下面的图片所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/53647dd1-9068-4d16-b90c-91ae7364ca89.png

在这里,我们有了面部特征点 1、2、3、4 和 5,因此这可以与第六章中提到的 dlib 检测器结合使用,即dlib 的面部特征追踪和分类。如果我们想了解某人的面部表情,除了全身特征检测器和它们的姿态,这里也可以做到。我们还可以得到一个非常详细的描述,说明人们面向哪个方向,以及他们在图像中确切在做什么。

让我们尝试另一个exercise_class.jpeg图像,它给出了以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/5770e208-4320-4fd7-8d02-c2567cc740b4.png

在这里,我们可以看到最右侧的女士膝盖上有多个点。这仍然是一个不错的结果。

让我们再试一张图片,这是我们之前在 GitHub 页面上看到的,gym.png

你可以看到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/8ab0b140-7c79-4dda-952f-4f795d32db80.png

这个模型确实检测到了这里的身体部位。所以,让我们尝试使用这个模型来检测单个人的姿态。你认为它会起作用吗?答案是是的,它确实起作用了。你可能想知道为什么我们有这个模型,还要使用之前的模型。这个模型在计算上稍微高效一些,所以如果你知道只有一个人,实际上你不需要它,因为这个算法提供了人数。

你可以从可用的照片中选择单个人的照片。例如,我们将选择mountain_pose.jpg,它给出了以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/a40eaebf-7ee6-440f-9b1a-491c13fd042f.png

这也将显示人数,如下面的代码所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/c96952fb-1a85-4c57-9c85-272547f8ff93.png

但是,如果你为单个人使用多人检测器,它可能会过度拟合,并检测到图像中实际不存在的人数。所以,如果你已经知道只有一个人,那么仍然使用原始模型而不是 ArtTrack 模型可能仍然是一个好主意。但如果它确实起作用,尝试两者,或者使用最适合你应用的方法。然而,这可能在复杂图像和复杂多样的姿态上可能不会完美工作。

让我们尝试最后一个island_dance.jpeg图像。以下截图显示了结果:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/ca3d7ae4-b8d8-4b8f-a451-b77bd2361b8f.png

重新训练人体姿态估计模型

我们现在将讨论如何处理视频以及重新训练我们的人类姿态估计网络。我们已经涵盖了人脸检测以及如何将模型应用于视频。打开视频相当直接,OpenCV 提供了相应的机制。它基本上是逐帧执行相同的事情。以下示例显示了相应的代码:

predictor_path = "./shape_predictor_68_face_landmarks.dat"
detector = dlib.get_fronta1_face_detector()
predictor = dlib.shape_predictor(predictor_path)

#Uncomment Line below if you want to use your webcam
#cap = cv2.VideoCapture(0) #0 is the first camera on your computer, change if you
#more than one camera

#Comment out the Line below if using webcam
cap = cv2.VideoCapture('./rollerc.mp4')
figure(100)
font = cv2.FONT_HERSHEY_SIMPLEX

首先,我们需要创建一个 cv2 捕获设备,然后打开文件,在读取文件的同时,我们应该加载图像并在图像上运行网络。请参考以下代码:

font = cv2.FONT_HERSHEY_SIMPLEX
while(True):
    #Capture frame-by-frame
    ret, img = cap.read()
    img.flags['WRITEABLE']=True #just in case

    try:
        dets = detector(img, 1)
        shape = predictor(img, dets[0])
    except:
        print('no face detected', end='\r')
        cap.release()
        break
#similar to previous example, except frame-by-frame here
    annotated=img.copy()
    head_width = shape.part(16).x-shape.part(6).x
    fontsize = head_width/650
    for pt in range(68):
        x,y = shape.part(pt).x, shape.part(pt).y
        annotated=cv2.putText(annotated, str(pt), (x,y), font, fontsize, (255,255,255), 2, cv2.LINE_AA)

#Let's see our results
    fig=imshow(cv2.cvtColor(annotated,cv2.COLOR_BGR2RGB)) #OpenCV uses BGR format

    display.c1ear_output(wait=True)
    display.display(gcf())

#When everything is done, release the capture
cap.release()

使用一个好的 GPU,我们应该能够以每秒几帧的速度进行计算,如果不是 30 到 60 FPS,这取决于你的硬件。你应该几乎能够实时完成。

对于训练你的模型,你首先需要确保你有良好的硬件和大量的时间。首先,你需要下载 ImageNet 和 ResNet 模型。然后,你需要查看github.com/eldar/pose-tensorflow/blob/master/models/README.md页面上的步骤和说明。你需要大量的数据,因此你可以使用他们提供的数据。使用你自己的数据可能既耗时又难以获得,但这是可能的。你可以参考提供的上一个链接以获取完整的说明。

这里使用的说明在某些地方使用了 MATLAB 来转换数据,尽管在 Python 中也有方法可以做到这一点,并用 MS COCO 数据集训练模型。这与我们在第二章中做的类似,即使用 TensorFlow 进行图像标题生成,同时也提供了如何使用自己的数据集训练模型的说明。这需要大量的工作和计算能力。你可以尝试这样做,或者使用预训练模型中已经提供的内容,这些内容可以做很多事情。

摘要

在本章中,我们学习了人类姿态估计的基础知识,然后在我们的项目中使用了 DeeperCut 和 ArtTrack 模型进行人类姿态估计。使用这些模型,我们进行了单人和多人的姿态检测。在章节的末尾,我们学习了如何使用模型处理视频,并对定制图像重新训练了模型。

在下一章第五章,《使用 scikit-learn 和 TensorFlow 进行手写数字识别》中,我们将学习如何使用 scikit-learn 和 TensorFlow 进行手写数字识别。

第五章:使用 scikit-learn 和 TensorFlow 进行手写数字识别

在本章中,我们将学习如何将机器学习应用于计算机视觉项目,使用几个不同的 Python 模块。我们还将创建并训练一个支持向量机,它将实际执行我们的数字分类。

在本章中,我们将涵盖以下主题:

  • 获取和处理 MNIST 数字数据

  • 创建和训练支持向量机

  • 将支持向量机应用于新数据

  • 介绍 TensorFlow 与数字分类

  • 评估结果

获取和处理 MNIST 数字数据

如前所述,我们将使用 scikit-learn 和 TensorFlow 来处理手写数字识别。在这里,我们将学习如何将机器学习应用于计算机视觉项目,我们将学习几种不同的方法和模型,使用几个不同的 Python 模块。让我们开始吧。

你可能已经听说过机器学习。在这里,我们将特别讨论监督机器学习,其中我们有一系列想要完成的例子。所以,我们不是明确告诉计算机我们想要什么,而是给出一个例子。

让我们以 0 到 9 的手写数字为例,这些数字由人类创建的标签指示它们应该是什么。因此,我们不是手动编码特征并明确告诉计算机算法,我们将构建一个模型,其中我们接受这些输入,优化一些函数,如一组变量,然后训练计算机将输出设置为我们所希望的。

因此,我们将从手写数字开始,从 0、1、2、3 等等开始。这是机器学习的一般范式,我们将在这里介绍三种不同的算法。

那么,让我们开始运行一些代码。

打开你的 Jupyter Notebook,就像我们在上一章中所做的那样,让我们在本章中从头开始。如您在以下代码中所观察到的,我们将导入我们的基本模块,例如numpy,它是 Python 中数值计算的基础:

#import necessary modules here
#--the final notebook will have complete codes that can be
#--copied out into self-contained .py scripts

import numpy as np
import matplotlib.pyplot as plt
import cv2 
import sys
import tempfile

from sklearn import svm, metrics
import tensorflow as tf

from tensorflow.examples.tutorials.mnist import input_data

如您在前面的代码中所见,我们正在导入pyplot,这样我们就可以可视化我们所做的事情。我们还将使用一点 OpenCV 来转换一些图像。我们还将使用 scikit-learn,它在实际模块中缩写为sklearn,同时导入支持向量机,以及一些将给我们提供度量指标的工具。这将告诉我们事情实际上工作得有多好。我们还将导入 TensorFlow,缩写为tf,因为我们将从其中获取我们的数据。

scikit-learn 和 TensorFlow 的一个主要优势是它们内置了获取数字识别的功能,这在计算机视觉和机器学习包中是如此常见。因此,你不必去网站下载,然后自己编写这些行。它会为你处理。因此,scikit-learn 实际上有相当多的内置数据集,一些用于计算机视觉,一些用于其他任务。它有一个数字示例,然后我们可以通过编写datasets并按Tab键来选择可用的内置数据集,如下面的截图所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/e26c33b4-1d2f-4905-827a-a886bd59b62c.png

现在,我们有一个内置数据集的列表。例如,你想知道california_housing的价格;也就是说,你想根据房屋的平方英尺和卧室数量等因素估计房价——有一个数据集是针对这个的。其中一些是图像数据,一些不是。所以,如果你想尝试不同的机器学习技术,这可能是一个你想检查的项目,但对于dataset.load_digits(),我们有以下代码来展示它是如何工作的:

#what kind of data do we already have?
from sklearn import datasets
digits=datasets.load_digits()

example_image=digits.images[0]
print(type(example_image))
plt.imshow(example_image); plt.show()
example_image.reshape((8*8,1))

让我们分解并理解这段代码。首先,我们加载一个示例图像,即集合中的第一个图像,如下所示:

example_image=digits.images[0]
print(type(example_image))

数据实际上存储在图像中,它是一个示例数组,其中每个示例都是一个 8 x 8 的手写数字图像。

然后,我们按照以下方式绘制图像:

plt.imshow(example_image); plt.show()
example_image.reshape((8*8,1))

我们应该看到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/c6d7d2c1-ee8a-456f-adcc-ef9f4da83ae8.png

但是我喜欢使用一个稍微高分辨率的示例,我们稍后会从 MNIST 中看到。低分辨率图像在计算上稍微快一些,因为它们是更小的图像。如果我们想要预处理这些图像,它们存储为 8 x 8,我们需要将每个图像转换为 1D 数组。我们可以通过使用reshape函数轻松地做到这一点,就像我们在之前的代码中做的那样:

example_image.reshape((8*8,1))

这将为我们提供一个输出,其中,而不是 8 x 8 的数组,我们得到一个 1 x 64 的数组,如下所示:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/1f1f2016-6359-475b-8c87-cc232bc53330.png

现在,我们将使用以下网站可用的 MNIST 数据:

yann.lecun.com/exdb/mnist/

这是一个相当标准的数据集。TensorFlow 足够好,提供了获取这些数据的功能,因此你不必去网站手动下载。我们需要定义data_dir并指定一个保存数据的位置。所以,只需创建这个/tmp/tensorflow/mnist/input_data目录,这将是好的,无论你运行的是哪种操作系统,然后我们有一些从tensorflowread_data_sets导入的input_data。现在,让我们运行以下代码:

#acquire standard MNIST handwritten digit data
#http://yann.lecun.com/exdb/mnist/

data_dir = '/tmp/tensorflow/mnist/input_data'
mnist = input_data.read_data_sets(data_dir, one_hot=True)

我们应该得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/3c828acb-7b21-4136-b8fe-34bce96b048e.png

如果你没有文件,代码将下载 gzip 文件,如果你已经有了,它将只读取现有的 gzip 文件并将它们存储在 mnist 变量中。one_hot=True 确保你得到标签,以向量的形式表示,这意味着它不会用像零、一、二、三、四这样的美国数字来标记,而是一个主要由零组成的数组。它将是一个长度为 10 的数组,其中除了一个值为 1 的元素外,其余都是 0。所以,如果我们有,例如,0, 1, 0, 0, 0, 0,等等,那将代表一个 1,如果它是 9,那么除了最后一个值为 1 的元素外,其余都是 0。所以,这是机器学习标记输出的一个有用方法。这是我们获取数据的方式,我们将会使用它;这对于我们实际使用 TensorFlow 时更有帮助,但对于 scikit-learn 实际上确实需要数值。

在我们深入实际机器学习之前,让我们先了解数据。我们有 mnist 变量,它已经按照训练和测试数据分开。在机器学习中,你不想用所有数据来训练;你不想用所有数据来构建你的模型,因为那样你就不知道它将如何处理之前未见过的新的数据例子。你想要做的是将其分成训练数据和测试数据。所以,训练数据将用来构建模型,而测试数据将用来验证它。所以,数据的分割已经为我们完成,只需要以下变量:

#now we load and examine the data
train_data=mnist.train.images
print(train_data.shape)
n_samples = train_data.shape[0]

train_labels=np.array(np.where(mnist.train.labels==1))[1]

plt.imshow(train_data[1000].reshape((28,28))); plt.show()

让我们分解代码以更好地理解。

首先,我们按照以下方式从 train.images 加载 train_data

#now we load and examine the data
train_data=mnist.train.images

我们将使用 .shape 来查看形状,以便理解它,如下所示:

print(train_data.shape)

如果你需要知道样本数量,我们可以从 shape 输出中提取,如下所示:

n_samples = train_data.shape[0]

再次强调,它是一个 NumPy 数组,所以所有 NumPy 函数和特性都在那里。

然后,执行以下代码以获取 train_labels

train_labels=np.array(np.where(mnist.train.labels==1))[1]
plt.imshow(train_data[1000].reshape((28,28))); plt.show()

在这里,我们只查看 train.label 等于 1 的位置,并提取这些值以创建一个包含这些值的数组,这将给我们 train_labels。所以,一个一维数组对应于包含每个实际输出的例子数量。我们将只看一个例子;让我们从 55000 个训练例子中取出 1000 个。

运行此代码将给出以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/3e3c2879-f179-4de7-94b3-99c325736b94.png

784 是图像中的像素数,因为它们是 28 x 28 的方块,28 x 28 = 784。所以,我们有 55000 个例子,每个例子有 784 个像素,或者我们称之为特征,然后 train_labels 的长度将是 55000,另一个维度只是 1。这里有一个例子。这些数据已经以 1D 数组的形式提供,这就是为什么我们使用了 reshape 函数并传递了 28 x 28 的值,以便将其转换为我们可以看到的实际图像。

太好了,我们的数据已加载并处理完毕,准备使用,因此我们可以开始实际的机器学习。现在我们的数据已经设置好并准备就绪,我们可以继续到下一节,在那里我们将创建和训练我们的支持向量机,并执行我们的数字分类。

创建和训练支持向量机

在本节中,我们将创建并训练一个支持向量机,该机器将实际执行我们的数字分类。

在第一个例子中,我们将使用 scikit-learn,我们将使用所谓的支持向量机,这是一种非常强大、非常通用的经典机器学习技术,可以学习各种函数和从输入到输出的各种映射。我们将进行分类,即映射输入为一个像素数组,在我们的情况下,我们将把每个输入分类到十个类别之一,对应于十个数字。但我们可以将不同类型的事物分类为连续有序函数,这被称为回归,这可能很有用,例如,如果你想提取位置或体积区域,它不仅仅适合于一个整洁的分类。

对于本节,我们将主要进行分类。因此,scikit-learn 使得创建此类模型变得非常简单。可以使用svm.SVC调用支持向量分类器,其中支持向量机来自sklearn包,我们为模型有一个名为gamma的元参数,它类似于半径的倒数,是支持向量的影响区域,如下面的代码所示:

# Create a classifier: a support vector classifier
classifier = svm.SVC(gamma=0.001)
# Learn about gamma and other SVM parameters here:
# http://scikit-learn.org/stable/auto_examples/svm/plot_rbf_parameters.html
# Exercise: Experiment with the parameters to see how they affect execution 
# time and accuracy

# Train the model -- we're only going to use the training data (and not
# the test data) to ensure that our model generalizes to unseen cases.
# This (training) is typically what takes the most computational time
# when doing machine learning.
classifier.fit(train_data, train_labels)

支持向量机的工作原理在此处没有涉及,因为关于这个主题有大量的文献,并且为了学习它,并不绝对有必要完全理解它。现在,我们只是看看我们如何将其应用于某些情况。

gamma参数是我建议你作为练习进行实验的东西。我们将从一个已知的工作良好的gamma参数开始,即.001,但你应该了解其他可用的参数。我建议你访问scikit-learn.org/stable/auto_examples/svm/plot_rbf_parameters.html,并且我再次建议你尝试使用它来查看它如何影响执行时间和准确性。但是,这里重要的是我们要知道我们可以用一行代码创建我们的模型。它定义了模型,但我们实际上并没有训练它。我们实际上没有提供任何数据,也没有调整其参数,以便它能够产生期望的输出。现在,如果我们给它一个五的图像,它会说,没问题,这是一个 5。所以,为了做到这一点,我们必须对其进行拟合。

在前面的代码中,我们创建了我们的分类器,它非常简单:classifier.fit。我们给它我们从前一个代码执行中获得的train_datatrain_labels。提前提醒一下,这个执行将需要几分钟;通常是这样的。通常,训练过程是机器学习中最慢的部分。这通常是情况,但这不应该太糟糕。这只需要几分钟,而且,我们再次只是使用您的训练数据,这样我们就可以验证这将对未见案例进行泛化。

现在我们已经看到了我们的支持向量机,并且它实际上已经被训练了,我们可以继续到下一个部分,在那里我们将支持向量机应用于它未训练过的新的数据。

将支持向量机应用于新数据

现在我们有了我们的训练支持向量机,我们可以实际上将支持向量机应用于未见过的新的数据,并看到我们的数字分类器实际上是否在起作用。

细胞执行成功并且如果一切正常工作,我们应该看到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/b80dd4a3-bab1-4049-b30c-1781e67d40e6.png

这只是创建支持向量分类器的输出。这仅仅提供了我们使用的元数据参数的信息;我们使用了所谓的径向基函数核,拟合数据没有产生任何错误信息。所以,这意味着代码已经工作。所以,现在我们有了我们的训练模型,我们想看看它在它未见过的数据上表现如何。

现在,我们将获取我们的测试数据,如下所示:

# Now predict the value of the digit on the test data:
test_data=mnist.test.images
test_labels=np.array(np.where(mnist.test.labels==1))[1]

expected = test_labels
predicted = classifier.predict(test_data)

我们获取mnist.test.images,它等于mnist.train.images,并以相同的方式提取标签,通过调用expected变量,然后我们将从classifier模型计算predicted,使用classifier.predict(test_data)。所以,这需要一点时间来执行。执行后,应该没有错误信息,这表明我们的预测运行成功。

所以,现在我们可以看到我们做得怎么样。我们将使用 scikit-learn 的内置度量函数。我们将记录一些度量,例如精确度召回率,如果您想了解这些含义,我推荐以下维基百科文章:

zh.wikipedia.org/wiki/精确度与召回率

简而言之,它们是评估你的机器学习算法表现如何的不同指标。准确率可能是最常用的。它是简单的:正确数据点除以总数。但也有精确率和召回率,它权衡了真实阳性、真实阴性、假阳性和假阴性,哪个是最好的取决于你的应用。它取决于假阳性和假阴性哪个更糟糕,以及如此等等。此外,我们还将输出所谓的混淆矩阵,它告诉你哪些是成功的,哪些被错误分类了。让我们运行以下代码:

# And display the results
print("See https://en.wikipedia.org/wiki/Precision_and_recall to understand metric definitions")
print("Classification report for classifier %s:\n%s\n"
      % (classifier, metrics.classification_report(expected, predicted)))
print("Confusion matrix:\n%s" % metrics.confusion_matrix(expected, predicted))

images_and_predictions = list(zip(test_data, predicted))
for index, (image, prediction) in enumerate(images_and_predictions[:4]):
    plt.subplot(2, 4, index + 5)
    plt.axis('off')
    plt.imshow(image.reshape((28,28)), cmap=plt.cm.gray_r, interpolation='nearest')
    plt.title('Prediction: %i' % prediction)

plt.show()

它应该给出以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/634bd8ef-8432-49da-b72a-381338a357ba.png

好吧,所以我们得到了分类报告,我们可以看到precision(精确率)、recall(召回率)以及另一个称为f1-score的指标,你可以在同一篇维基百科文章中了解到这些。简而言之,零是最坏的情况,一是最理想的情况。在先前的屏幕截图中,我们可以看到不同数字的precisionrecallf1-score,我们可以看到我们处于 90%的范围内;它有所变化,这是可以接受的。它取决于你的应用,但这可能已经足够好了,或者可能非常糟糕。这取决于。我们实际上稍后会看到如何使用更强大的模型做得更好。我们可以看到它总体上是有效的。我们来看看混淆矩阵,其中列告诉你实际值是什么,行告诉你预测值是什么。理想情况下,我们会看到对角线上的所有大值,其他地方都是零。总是会有一些错误,因为我们都是人类,所以这种情况会发生,但就像我说的,我们将看看是否可以做得更好,在大多数情况下,它确实有效。现在,我们可以看到一些随机输出的示例,其中有一些数字如下:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/1677df97-8bed-4199-9ac3-bb9ac2f3ae4b.png

如我们所见,所有的预测都根据它们的图像是正确的。好吧,一切都很好,但我感觉我现在有点是在相信计算机的话了。我想用我自己的数据来测试它。我想看看这实际上工作得怎么样,这在机器学习中通常是一个推荐的步骤。你想要用你自己的数据来测试它,以便真正知道它是否在正常工作,而且,如果不是其他原因,这会让人感到更加满意。所以,这里有一小段代码,它将使用 Jupyter 的小部件功能,它的交互功能:

#Let's test our model on images we draw ourselves!

from matplotlib.lines import Line2D
%pylab notebook 
#This is needed for plot widgets

class Annotator(object):
    def __init__(self, axes):
        self.axes = axes

        self.xdata = []
        self.ydata = []
        self.xy = []
        self.drawon = False

    def mouse_move(self, event):
        if not event.inaxes:
            return

        x, y = event.xdata, event.ydata
        if self.drawon:
            self.xdata.append(x)
            self.ydata.append(y)
            self.xy.append((int(x),int(y)))
            line = Line2D(self.xdata,self.ydata)
            line.set_color('r')
            self.axes.add_line(line)

            plt.draw()

    def mouse_release(self, event):
        # Erase x and y data for new line
        self.xdata = []
        self.ydata = []
        self.drawon = False

    def mouse_press(self, event):
        self.drawon = True

img = np.zeros((28,28,3),dtype='uint8')

fig, axes = plt.subplots(figsize=(3,3))
axes.imshow(img)
plt.axis("off")
plt.gray()
annotator = Annotator(axes)
plt.connect('motion_notify_event', annotator.mouse_move)
plt.connect('button_release_event', annotator.mouse_release)
plt.connect('button_press_event', annotator.mouse_press)

axes.plot()

plt.show()

所以,现在我们实际上要创建一个小绘图小部件。它将允许我们生成自己的数字。让我们看看代码。

让我们从matplotlib.line导入Line2D,这将允许我们绘制单独的线条,就像根据我们的鼠标移动创建一种矢量图像一样:

#Let's test our model on images we draw ourselves!

from matplotlib.lines import Line2D

我们执行%pylab notebook;百分号表示以下魔法命令:

%pylab notebook

这是一种 Jupyter 和 Pylab 笔记本中的元命令,它将大量内容加载到你的命名空间中用于绘图和数值计算。这不是必需的,因为我们已经用 NumPy 和 Matplotlib 做了这件事,但为了启用小部件,我们使用这个命令。

然后,创建这个Annotator类,它包含当我们在显示的图像上移动鼠标时发生回调的代码,如下所示:

class Annotator(object):
    def __init__(self, axes):
        self.axes = axes

        self.xdata = []
        self.ydata = []
        self.xy = []
        self.drawon = False

    def mouse_move(self, event):
        if not event.inaxes:
            return

        x, y = event.xdata, event.ydata
        if self.drawon:
            self.xdata.append(x)
            self.ydata.append(y)
            self.xy.append((int(x),int(y)))
            line = Line2D(self.xdata,self.ydata)
            line.set_color('r')
            self.axes.add_line(line)

            plt.draw()

    def mouse_release(self, event):
        # Erase x and y data for new line
        self.xdata = []
        self.ydata = []
        self.drawon = False

    def mouse_press(self, event):
        self.drawon = True

我们不需要理解Annotator类,但如果你将来想要进行标注或绘制某些内容,以及获取完整的代码片段,这可能会很有用。

然后,我们将创建一个空白图像,大小与我们的图像相同。目前它只是三个 RGB 值。即使我们最终会将其变为黑白,因为它就是我们的数据。创建图像如下:

img = np.zeros((28,28,3),dtype='uint8')

现在,创建一个图表,显示它,并将我们的annotator函数连接到它,如下所示:

fig, axes = plt.subplots(figsize=(3,3))
axes.imshow(img)
plt.axis("off")
plt.gray()
annotator = Annotator(axes)
plt.connect('motion_notify_event', annotator.mouse_move)
plt.connect('button_release_event', annotator.mouse_release)
plt.connect('button_press_event', annotator.mouse_press)

axes.plot()

plt.show()

运行代码后,我们应该得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/7e873b37-046c-4f77-aaaf-caad496ca0d9.png

那么,让我们画一下数字三:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/3e1d18ae-a7b8-44d6-a4d5-27d8be21a696.png

现在,这有点慢,而且并不完全能替代 Photoshop,但这种方法仍然比进入一个单独的程序创建图像文件、确保其格式正确、保存它,然后编写代码加载它并使其正确要快。因此,这将使我们能够快速地玩和实验我们的模型。我们刚刚创建了一种线数组,因此我们需要将其光栅化并处理,使其看起来更像实际的手写数字,这可能是来自扫描的铅笔草图或压力感应平板。以下是如何做到这一点:

# Now we see how our model "sees" (predicts the digit from)
# our hand drawn image...
# First, we rasterize (convert to pixels) our vector data
# and process the image to more closely resemble something
# drawn with a pencil or pressure-sensitive tablet.

digimg = np.zeros((28,28,3),dtype='uint8')
for ind, points in enumerate(annotator.xy[:-1]):
    digimg=cv2.line(digimg, annotator.xy[ind], annotator.xy[ind+1],(255,0,0),1)
digimg = cv2.GaussianBlur(digimg,(5,5),1.0)
digimg = (digimg.astype('float') *1.0/np.amax(digimg)).astype('float')[:,:,0]
digimg **= 0.5; digimg[digimg>0.9]=1.0

#The model is expecting the input in a particular format
testim = digimg.reshape((-1,28*28))

print("Support vector machine prediction:",classifier.predict( testim ))

outimg = testim.reshape((28,28))
figure(figsize=(3,3)); imshow(outimg);

让我们看看代码。首先,我们创建一个空白图像,如下所示:

digimg = np.zeros((28,28,3),dtype='uint8')

我们遍历来自annotatorxy对,然后我们将在光栅化图像上绘制线条,如下所示:

for ind, points in enumerate(annotator.xy[:-1]):
    digimg=cv2.line(digimg, annotator.xy[ind], annotator.xy[ind+1],(255,0,0),1)
digimg = cv2.GaussianBlur(digimg,(5,5),1.0)

然后,我们将图像转换为float类型,范围从01,就像我们的输入数据一样,如下所示:

digimg = (digimg.astype('float') *1.0/np.amax(digimg)).astype('float')[:,:,0]

然后,我们将它稍微调整得更接近1,因为这就是我们的输入图像看起来像的,以及我们的模型所期望的:

digimg **= 0.5; digimg[digimg>0.9]=1.0

然后,我们有了二维图像,但当然,为了运行它通过我们的模型,我们需要将其展平为1 x 784,这就是reshape函数的作用:

#The model is expecting the input in a particular format
testim = digimg.reshape((-1,28*28))

然后,我们将运行它通过我们的classifier,并打印输出。我们将创建一个图表,我们可以看到我们的光栅化图像如下所示:

print("Support vector machine prediction:",classifier.predict( testim ))

outimg = testim.reshape((28,28))
figure(figsize=(3,3)); imshow(outimg);

我们应该得到以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/64e0f2af-31ab-4e3d-8457-e65f326b3aaa.png

我们画了一个三,预测结果是 3。太棒了。让我们尝试其他的东西。通过按 Ctrl + Enter 清除之前的输出,我们得到一个警告信息;它只是告诉我们它覆盖了一些创建的变量。这不是什么大问题。你可以安全地忽略它。只是提前提醒,你的使用效果可能会有所不同,这取决于你的书写风格和训练数据中的内容。如果你希望每次都能完美工作,或者尽可能接近完美,你可能需要在自己的书写上训练它。

让我们尝试一个零:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/54ef84b4-a1ba-4453-b443-57ad38f50a2d.png

以下是输出结果:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/980d11b9-8013-4631-8cba-0c53e53a8f3a.png

因此,你可以看到它不起作用的例子。预测应该是零,但模型不知何故预测了三。有可能如果你重新绘制它,它可能会工作。所以,再次提醒,你的使用效果可能会有所不同。你可以尝试实验,你也可以玩玩预处理,尽管据我所知,这工作得相当好。但无论如何,我们可以看到我们的模型至少在大部分情况下是正常工作的。所以,关于 scikit-learn 支持向量机的内容就到这里了。现在,在我们下一节中,我们将介绍 TensorFlow 并使用它进行数字分类。

使用数字分类介绍 TensorFlow

我们将看到 TensorFlow 的实际应用,并了解如何用可管理的代码量进行数字分类。TensorFlow 是 Google 的机器学习库,用于一般的数值分析。它被称为 TensorFlow,因为它据说可以流动张量,其中张量被定义为 n 维的数组。张量具有多维数组所不具备的真正几何意义,但我们只是使用这个术语。张量只是一个多维数组。

在这里,我们将进行一个简单的 softmax 示例。这是一个非常简单的模型;你可以访问 TensorFlow 的官方网站 (www.tensorflow.org/get_started/mnist/beginners) 获取更多信息。让我们看一下以下代码:

data_dir = '/tmp/tensorflow/mnist/input_data'
mnist = input_data.read_data_sets(data_dir, one_hot=True)

# Create the model
x = tf.placeholder(tf.float32, [None, 784])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.matmul(x, W) + b

# Define loss and optimizer
y_ = tf.placeholder(tf.float32, [None, 10])

cross_entropy = tf.reduce_mean(
  tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
# Train
for _ in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

# Test trained model
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(\"Model accuracy:\",sess.run(accuracy, feed_dict={x: mnist.test.images,
                                    y_: mnist.test.labels}))

简而言之,你将取你的输入数据,并将其乘以一个矩阵。数据有784个点。每个点都有一个矩阵值,对于10个类别中的每一个,你将通过乘以 784 × 784 并求和来计算一个内积。将会有10个输出。它将是一个 1 乘以 10 的数组,你将向数组的输出添加一个偏置变量,并通过softmax函数运行它,这将将其转换为某种东西。矩阵的输出加上偏置将计算一个在01范围内的值,这大致对应于该数据属于该类别的概率。例如,可能有一个 0.4%的概率或 40%的概率是1,2%的概率是2,90%的概率是9,输出将是那个最大输出。

TensorFlow 非常复杂。这里的设置比 scikit-learn 示例中要复杂一些。你可以在他们的网站上了解更多信息。现在,让我们详细地通过以下代码:

data_dir = '/tmp/tensorflow/mnist/input_data'
mnist = input_data.read_data_sets(data_dir, one_hot=True)

我们已经在前面的例子中这样做过了。现在,我们将从data_dir获取数据;确保它在我们的mnist变量中。

然后,我们创建模型,其中x对应于我们的输入数据,尽管我们还没有加载数据,但我们只需要创建一个占位符,这样 TensorFlow 就知道东西在哪里了。我们不需要知道有多少个例子,这就是None维度的含义,但我们确实需要知道每个例子有多大,在这个例子中是784W是乘以x类别的矩阵,对图像进行内积,784 784,你这样做10次。所以,这对应于一个 784/10 的矩阵,10是类别的数量;然后,你向那个添加b偏置变量。Wb的值是 TensorFlow 将根据我们的输入为我们产生的,y定义了对我们的数据进行矩阵乘法时实际要执行的操作。我们按照以下方式向它添加b偏置变量:

x = tf.placeholder(tf.float32, [None, 784])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.matmul(x, W) + b

我们需要为我们的标记数据创建一个占位符,如下所示:

y_ = tf.placeholder(tf.float32, [None, 10])

为了进行机器学习,你需要一个损失函数或适应度函数,它告诉你根据像Wb这样的学习参数,你的模型做得有多好。因此,我们将使用所谓的交叉熵;我们不会深入讨论交叉熵,但那将给我们一些标准,让我们知道我们正在接近一个工作的模型,如下面的代码行所示:

cross_entropy = tf.reduce_mean(
  tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

随着我们添加越来越多的数据,我们将使用所谓的GradientDescentOptimizer来最小化误差,最小化交叉熵,并尽可能使我们的模型拟合得更好。

在下面的代码中,我们实际上将首先创建一个交互式会话,如下所示:

sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
for _ in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

我们希望使其成为一个交互式会话,这样我们就可以在之后使用我们的模型并给它添加新数据。我们将初始化run(),然后我们将分批计算数据。TensorFlow 是一个非常强大的程序,它允许你分割你的数据。我们在这里不会这样做,但你可以用它轻松地运行并行化代码。在这里,我们将迭代1000次,并在分批中输入我们的训练数据。

在运行之后,我们将看看我们做得如何,并查看我们的预测数据与给定的标签相等的部分。我们可以通过查看平均有多少预测是正确的来计算accuracy。然后,按照以下方式打印数据:

correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(\"Model accuracy:\",sess.run(accuracy, feed_dict={x: mnist.test.images,
                                    y_: mnist.test.labels}))

以下是对应的输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/4d8cf5fb-5bb4-4041-8402-1629d6cee9ee.png

由于这是一个非常简单的模型,它运行得很快,我们可以看到我们得到了不到 92%的准确率。代码执行得更快,但准确率略低于我们的支持向量机SVM),但这没关系。这段代码只是提供了一个 TensorFlow 如何工作的非常简单的例子。

你会很快变得稍微高级一些,但让我们像之前一样测试以下代码:

img = np.zeros((28,28,3),dtype='uint8')
fig, axes = plt.subplots(figsize=(3,3))
axes.imshow(img)
plt.axis("off")
plt.gray()
annotator = Annotator(axes)
plt.connect('motion_notify_event', annotator.mouse_move)
plt.connect('button_release_event', annotator.mouse_release)
plt.connect('button_press_event', annotator.mouse_press)
axes.plot()
plt.show()

我们得到了以下输出:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/1af2c010-209e-41b9-913d-923e77aec493.png

我们初始化了注释器,并输入一个数字。试一个3

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/15a25209-0eed-4937-9a5a-7256ffe39d97.png

现在,我们将对绘制的数字进行预处理,这几乎与之前的代码相同,它将遍历我们的数据及其可能的类别,看看 TensorFlow 的softmax模型认为哪个是最好的:

for tindex in range(10):
    testlab = np.zeros((1,10))
    testlab[0,tindex] = 1
    if sess.run(accuracy, feed_dict={x: testim, y_ : testlab}) == 1:
        break

因此,我们将运行前面的代码块,如图所示,它从3预测出3

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/a71f2e2c-549b-42f8-9450-31496778a708.png

有时它可能无法正确预测,这很遗憾。所以,有两种方法可以改进:在自己的手写数据上训练或使用更好的模型。

我们将进入本节中最强大的模型。在这里,我们将简要介绍使用卷积神经网络CNNs)的深度学习。这里我们不涉及理论。关于深度学习和多层神经网络有很多东西要了解。深度学习是一个深奥的主题,但在这个章节中,我们将看看我们如何实际上使用相对简单的代码块将最先进的机器学习技术应用于数字识别。

因此,我们这里有一个deepnn(x)函数,它创建我们的深度神经网络,找到我们的隐藏层或卷积层,池化层等等,并定义了我们从输入所需的所有内容:

def deepnn(x):
     with tf.name_scope('reshape'):
     x_image = tf.reshape(x, [-1, 28, 28, 1])

deepnn构建用于对数字进行分类的深度网络图,reshape函数是在卷积神经网络中使用。这里使用的参数是:

  • x:一个具有维度(N_examples, 784)的输入张量,其中784是标准 MNIST 图像中的像素数。

  • y:一个形状为(N_examples, 10)的张量,其值等于将数字分类到 10 个类别(数字 0-9)的逻辑。keep_prob是一个表示 dropout 概率的标量占位符。

这返回一个元组(y, keep_prob)。最后一个维度是用于特征的——这里只有一个,因为图像是灰度的——对于 RGB 图像将是 3,对于 RGBA 将是 4,依此类推。

第一个卷积层将一个灰度图像映射到32个特征图:

     with tf.name_scope('conv1'):
     W_conv1 = weight_variable([5, 5, 1, 32])
     b_conv1 = bias_variable([32])
     h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)

     # Pooling layer - downsamples by 2X.
     with tf.name_scope('pool1'):
     h_pool1 = max_pool_2x2(h_conv1)

     # Second convolutional layer -- maps 32 feature maps to 64.
     with tf.name_scope('conv2'):
     W_conv2 = weight_variable([5, 5, 32, 64])
     b_conv2 = bias_variable([64])
     h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)

     # Second pooling layer.
     with tf.name_scope('pool2'):
     h_pool2 = max_pool_2x2(h_conv2)

     # Fully connected layer 1 -- after 2 round of downsampling, our 28x28 image
     # is down to 7x7x64 feature maps -- maps this to 1024 features.
     with tf.name_scope('fc1'):
     W_fc1 = weight_variable([7 * 7 * 64, 1024])
     b_fc1 = bias_variable([1024])

     h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
     h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

     # Dropout - controls the complexity of the model, prevents co-adaptation of
     # features.
     with tf.name_scope('dropout'):
     keep_prob = tf.placeholder(tf.float32)
     h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

     # Map the 1024 features to 10 classes, one for each digit
     with tf.name_scope('fc2'):
     W_fc2 = weight_variable([1024, 10])
     b_fc2 = bias_variable([10])

     y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
     return y_conv, keep_prob

    def conv2d(x, W):
 """conv2d returns a 2d convolution layer with full stride."""
     return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

    def max_pool_2x2(x):
     """max_pool_2x2 downsamples a feature map by 2X."""
     return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
     strides=[1, 2, 2, 1], padding='SAME')

    def weight_variable(shape):
    """weight_variable generates a weight variable of a given shape."""
     initial = tf.truncated_normal(shape, stddev=0.1)
     return tf.Variable(initial)

    def bias_variable(shape):
    """bias_variable generates a bias variable of a given shape."""
     initial = tf.constant(0.1, shape=shape)
     return tf.Variable(initial)

我们有执行卷积、权重变量、偏置变量等函数。然后,我们有这里的主要代码:

  ###begin main code

    data_dir= '/tmp/tensorflow/mnist/input_data'
    # Import data
    mnist = input_data.read_data_sets(data_dir, one_hot=True)

    # Create the model
    x = tf.placeholder(tf.float32, [None, 784])

    # Define loss and optimizer
    y_ = tf.placeholder(tf.float32, [None, 10])

    # Build the graph for the deep net
    y_conv, keep_prob = deepnn(x)

    with tf.name_scope('loss'):
     cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_,
     logits=y_conv)
    cross_entropy = tf.reduce_mean(cross_entropy)

    with tf.name_scope('adam_optimizer'):
     train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

    with tf.name_scope('accuracy'):
     correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
     correct_prediction = tf.cast(correct_prediction, tf.float32)
     accuracy = tf.reduce_mean(correct_prediction)

    graph_location = tempfile.mkdtemp()
    print('Saving graph to: %s' % graph_location)
    train_writer = tf.summary.FileWriter(graph_location)
    train_writer.add_graph(tf.get_default_graph())

    # Let's run the model
    sess = tf.InteractiveSession()
    sess.run(tf.global_variables_initializer())
    for i in range(20000):
     batch = mnist.train.next_batch(50)
     if i % 100 == 0:
     train_accuracy = accuracy.eval(feed_dict={
     x: batch[0], y_: batch[1], keep_prob: 1.0})
     print('step %d, training accuracy %g' % (i, train_accuracy))
     train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

    # How did we do?
    print('test accuracy %g' % accuracy.eval(feed_dict={
     x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

mnist变量获取数据,以防我们还没有它。我们定义了输入的占位符,输出构建了图。然后我们定义了fitness函数和cross-entropy并创建了我们的图。在创建会话时我们必须小心;在他们网站的示例中,他们只是创建了一个正常的会话。我们希望有一个交互式会话,这样我们就可以将我们的模型应用于我们自己生成数据,并且我们将将其分成批次。我们将运行它,每100次迭代它将告诉我们它正在做什么,然后,在最后,它将告诉我们我们的准确率。

让我们运行代码并提取数据,你可以看到以下统计数据:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/820ee86a-10a9-4c9d-bac7-d4e93158c64e.png

它开始时的训练准确率非常差,但很快就上升到超过 90%,然后跃升至1。它并不完全是 100%,但通常这意味着它大约是 99%,所以非常接近1。这通常需要几分钟。好的,现在我们已经创建了我们的 TensorFlow 分类器。

评估结果

我们完成训练后,如以下截图所示,我们得到了超过 99%的结果,这比我们用softmax或我们的 SVM 得到的结果要好得多:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/4eabb374-6994-4a4b-a598-fa2b2b9b38bd.png

深度学习可能是有史以来最强大的机器学习技术,因为它能够学习非常复杂的模式识别。它几乎统治了所有其他技术,包括高级计算机视觉、语音处理等——这些是传统机器学习技术不太成功的领域。然而,这并不意味着你想要在所有事情上都使用深度学习。深度学习通常需要大量示例——有时是数千甚至数百万个示例——并且它也可能非常计算密集。因此,它并不总是最佳解决方案,尽管它非常强大,正如我们在这里所看到的。所以,99%几乎就是你能得到的最佳结果。

下面的代码用于绘制数字:

# Test on handwritten digits again
img = np.zeros((28,28,3),dtype='uint8')
fig, axes = plt.subplots(figsize=(3,3))
axes.imshow(img)
plt.axis("off")
plt.gray()
annotator = Annotator(axes)
plt.connect('motion_notify_event', annotator.mouse_move)
plt.connect('button_release_event', annotator.mouse_release)
plt.connect('button_press_event', annotator.mouse_press)
axes.plot()
plt.show()

以下代码将手写数字图像进行光栅化和预处理:

# Rasterize and preprocess the above
digimg = np.zeros((28,28,3),dtype='uint8')
for ind, points in enumerate(annotator.xy[:-1]):
    digimg=cv2.line(digimg, annotator.xy[ind], annotator.xy[ind+1],(255,0,0),1)
digimg = cv2.GaussianBlur(digimg,(5,5),1.0)
digimg = (digimg.astype('float') *1.0/np.amax(digimg)).astype('float')[:,:,0]
digimg **= 0.5; digimg[digimg>0.9]=1.0
testim = digimg.reshape((-1,28*28))

# And run through our model
for tindex in range(10):
    testlab = np.zeros((1,10))
    testlab[0,tindex] = 1
    if accuracy.eval(feed_dict={x: testim, y_: testlab, 
                                keep_prob: 1.0}) == 1:
        break

print("Predicted #:",tindex) #tindex = TF model prediction

# Display our rasterized digit
outimg = testim.reshape((28,28))
figure(figsize=(3,3)); imshow(outimg)

因此,让我们再次在我们的手写数字0上测试它:

https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/cv-proj-ocv-py/img/1880122f-8b2d-4199-b02f-e02ef3c0a43b.png

再次强调,我们处理矢量化图像的代码是相似的,我们得到了输出和光栅化形式,然后将其通过我们的模型,在这里进行 accuracy.eval。正如我们可以在前面的屏幕截图中所见,我们得到了预期的零,这是完美的。因此,在下一章中,我们将更多地讨论使用 CNN 的深度学习,但我们已经看到,它只需要相对较少的代码就非常强大,并且我们能够将其应用于我们特定的数字识别问题。好的,那么,有了这个,我们将继续进入下一章,即第六章,使用 dlib 进行面部特征跟踪和分类

摘要

在本章中,我们学习了如何使用 TensorFlow 的 softmax 进行数字分类。我们学习了如何获取和处理 MNIST 数字数据。然后我们学习了如何创建和训练支持向量机,并将其应用于新数据。

在下一章中,我们将学习使用 dlib 进行面部特征跟踪和分类。

您可能感兴趣的与本文相关的镜像

Python3.10

Python3.10

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值