树莓派计算机视觉编程:1~5

发布时间 2023-04-19 11:01:54作者: ApacheCN

原文:Raspberry Pi Computer Vision Programming

协议:CC BY-NC-SA 4.0

译者:飞龙

本文来自【ApacheCN 计算机视觉 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。

当别人说你没有底线的时候,你最好真的没有;当别人说你做过某些事的时候,你也最好真的做过。

一、计算机视觉和 Raspberry Pi 简介

OpenCV 是用于计算机视觉的简单而强大的编程框架。 计算机视觉领域的新手和专家都喜欢它。 通过使用 Python 3 作为编程语言编写 OpenCV 程序,我们可以轻松地学习计算机视觉。 Raspberry Pi 单板计算机家族使用 Python 作为其首选开发语言。 使用 Raspberry Pi 开发板和 Python 3 学习 OpenCV 编程是我们可以遵循的最佳方法之一,可以开始我们的奇妙旅程,进入计算机视觉编程的惊人领域。 在本章中,您将熟悉开始使用 Raspberry Pi 和计算机视觉所需的所有重要概念。 在本章结束时,您将能够在各种 Raspberry Pi 主板型号上设置 Raspbian 操作系统OS)。 您还将学习如何将这些板连接到互联网。

在本章中,我们将介绍以下主题:

  • 了解计算机视觉
  • 单板计算机
  • Raspberry Pi 系列单板计算机
  • 在 Raspberry Pi 上设置 Raspbian OS
  • 通过 LAN 或 Wi-Fi 将各种 Pi 板型号连接到互联网

在本章结束时,您将能够设置自己的 Raspberry Pi 板。

了解计算机视觉

计算机视觉领域是不同领域的结合,包括(但不限于)计算机科学,数学和电气工程。 它包括捕获,处理和分析来自现实世界的图像和视频以帮助决策的方法。 计算机视觉意味着模仿生物(即人类和非人类)视觉。 大多数计算机视觉系统的最终目标是从静止图像和视频(包括预先录制的视频和实时提要)中提取有用的信息,以用于决策。 生物视觉系统的工作方式与此类似。 另外,与生物视觉不同,计算机视觉还可以从生物实体不可见的可见光谱中获取图像并进行处理,例如红外图像和深度图像。

计算机视觉还涉及领域,该领域从捕获的图像和视频中提取信息。 计算机视觉系统可以接受各种类型的数据(例如图像,视频和实时视频流)作为输入,以进一步处理,分析和提取有意义的信息,从而做出重要决策。

人工智能,机器视觉和计算机视觉的领域重叠并且共享许多主题,例如图像处理,模式识别和机器学习,如下图所示:

Figure 1.1 – The relationships between different scientific domains

图 1.1 –不同科学领域之间的关系

为了成为计算机视觉领域的研究人员,您需要具有扎实的背景和对数学的理解。 但是,要使用 OpenCV 和 Python 3 编写用于计算机视觉的程序,您不需要了解很多数学。 请注意,在本书中,您将学习入门图像处理和计算机视觉所需的所有数学和理论概念。

计算机视觉系统的典型目标可能是以下一项或多项:

  • 对象的识别,视觉检测的分类以及运动分析
  • 使用图像重建场景
  • 图像降噪和还原

如果您不熟悉这些关键术语,请不要感到压力。 在整个旅程中,我们将探索并实现许多这样的概念。

OpenCV

OpenCV(也称为开源计算机视觉)是用于计算机视觉和机器学习的开源库。 它具有用于图像处理和计算机视觉的许多功能。 它是一个跨平台的库,可与许多编程语言和 OS 一起使用。 它具有大量的计算机视觉和与机器学习相关的功能。 它还具有几个图形用户界面GUI)和事件处理功能。

OpenCV 已获得伯克利软件发行BSD)许可,可免费用于学术和商业用途。 它是用 C++ 编程语言编写的。 它具有适用于大多数流行编程语言的接口,包括(但不限于)C/C++,Python 和 Java。 它可以在各种操作系统上运行,包括 Windows,Android,Linux,macOS 和其他类似 Unix 的操作系统。 在本书中,我们将使用 OpenCV 和 Python 3 编写与计算机视觉相关的程序。

该库具有 2500 多种针对机器学习和计算机视觉任务的优化算法。 它拥有超过 47,000 名计算机视觉专业人员的社区,并且已被下载超过 1800 万次。 OpenCV 广泛用于教学,研究组织,政府组织和各个行业领域的学术界。 诸如 Google,Yahoo,Microsoft,Intel,IBM,Sony,Honda 和 Toyota 之类的知名组织都使用 OpenCV。

让我们看一下 OpenCV 的历史。 OpenCV 最初是 Intel Research 的一项内部计划,被用来开发一个处理图像和视频的框架。 最初由 Willow Garage 和,然后是 Itseez 支持。

注意

您可以在这个页面上访问 Willow Garage 的网站。

2012 年 8 月,一个独立的非营利组织 OpenCV.org 承担了进一步开发和支持 OpenCV 的责任。 它维护 OpenCV 的网站。 2016 年 5 月,英特尔收购了 Itseez。 以下 URL 具有 Intel 和 OpenCV.org 的新闻发布:

这是与 OpenCV 相关的发展的简要时间表:

Figure 2: Timeline of OpenCV

图 1.2 – OpenCV 的时间表

您可以在这个页面上找到所有详细信息,包括不同的版本和 OpenCV 库的新闻发布。

由于我们将以 Raspberry Pi 为平台编写计算机视觉程序,因此我们将详细研究单板计算机和 Raspberry Pi。 我们将学习如何在 Raspberry Pi 单板计算机的各种型号上设置 Raspbian OS。

单板计算机

单板计算机(缩写为 SBC)是在单个印刷电路板(缩写为 PCB)上的完整计算机系统。 。 该板通常具有处理器,RAM,输入/输出I/O),用于联网的以太网端口和用于接口的 USB 端口 USB 设备。 一些单板计算机也具有 Wi-Fi 和蓝牙。 SBC 运行 OS 发行版,例如 Ubuntu,Windows,Debian 等。 这些 OS 发行版具有专门用于这些 SBC 的定制版本。

与传统计算机不同,单板计算机不是模块化的,其硬件也无法升级,因为所有组件(例如 CPU,RAM,GPU 和接口端口)都集成在单个 PCB 本身上。 SBC 在学术界,研究和其他各种行业中用作低成本计算机。 SBC 在嵌入式系统中的使用非常广泛,许多个人,研究组织和公司已经开发并发布了基于 SBC 的功能齐全且可用的产品。 这些产品很多都是众筹的。 SBC 的主要优势是板载通用输入/输出GPIO)引脚。 这些引脚提供的功能包括各种总线(串行外围设备接口SPI),I2C 和 SMBus),数字 I/O,模拟输入和脉宽调制PWM)输出。 尽量不要让淹没所有这些技术词汇。 在实验的帮助下,我们将更详细地学习其中的大多数概念。 几乎所有流行的 SBC 都具有某种形式的 GPIO。 由于它们的外形小巧和板载 GPIO,它们在学校,大学,训练中心,新兵训练营和制造商场所都很流行。 它们经常用于传感器网络的领域和物联网IoT)。

总而言之,SBC 的优点如下:

  • 低成本
  • 小尺寸
  • 低功耗
  • 提供板载网络和 I/O

但是,SBC 具有其自身的缺点。 由于 SBC 的所有组件都在同一 PCB 上,如果由于机械或电气原因损坏了某个组件,可能很难修理。 出于同样的原因,我们甚至无法升级 SBC 上的任何内容。 这些是 SBC 的唯一主要缺点。

约翰 Titus 在 1976 年设计的微型计算机训练器 MMD-1 是第一台基于英特尔微处理器 C8080A 的真正的单板微型计算机。 在原型开发阶段将其称为动态微型,而生产单元称为 MMD-1Mini-Micro Designer 1 的缩写)。

现在,我们将详细介绍 Raspberry Pi 系列。 但是,在此之前,我们将结识其他受欢迎的 SBC 家庭。

Beagleboard 家族

BeagleBoard.org 基金会是位于美国的组织。 它是一个非营利性实体,其目标是围绕嵌入式系统领域的设计,开发,测试和使用开源硬件和软件提供教育和协作。 他们开发了各种以小猎犬(家犬的流行犬种)命名的单板单元。 您可以在这个页面上找到他们开发的当前 SBC 的列表,它们是正在生产的。 您也可以在同一 URL 上找到 Beagle 板的相关产品和配件。

在写作时,他们的最新产品是 PacketBeagle

华硕 Tinkerboard

华硕 Tinkerboard 是由台湾华硕(ASUS)设计制造的。 其尺寸,布局和引脚与第二代和第三代 Raspberry Pi 板兼容。 您可以在这个页面上找到有关 ASUS Tinkerboard 所有版本的更多详细信息。 下图显示了华硕 Tinkerboard 的俯视图:

Figure 3: ASUS Tinkerboard

图 1.3 –华硕 Tinkerboard

NVIDIA Jetson

NVIDIA Jetson 是系列模块,用于计算机视觉,AI 和语音处理任务。 对于初学者来说,最好的成员是 Jetson Nano。 最好的起点是 Jetson Nano Developer Kit 的网页。 这是开发者套件的侧视图:

Figure 4: Nvidia Jetson Nano

图 1.4 – Nvidia Jetson Nano

英特尔主板

英特尔公司还生产许多可以称为 SBC 的板。 您可以在这个页面上找到有关当前生产的模块的详细信息。 我们拥有特权,可以使用几个出色的 Intel SBC 和模块。 其中许多产品已停产,您可以在这个页面中找到它们的完整列表和支持文档。 请注意,您也许能够从英特尔获得大量二手板和停产板。 它们也非常适合学习。 对于计算机视觉的初学者,我喜欢推荐 Intel Up Squared Kit。 您可以在这个页面中找到更多信息。

Raspberry Pi

Raspberry Pi 是由英国 Raspberry Pi 基金会开发的一系列低成本和信用卡大小的 SBC 。 开发 Raspberry Pi 的目的是在学校中推广基本的计算机技能和编程教学,在此方面,它发挥了很好的作用。 Raspberry Pi 通过在嵌入式系统市场以及学术界和工业应用中的计算机科学研究方面赢得关注,已大大超出了其预期的用途。

Raspberry Pi Foundation 为许多流行的 OS 发行版提供下载。 我们可以在 Raspberry Pi 中使用多种编程语言,例如 Python,C,C++ 和 Java。 您可以在 Raspberry Pi Foundation 网站上找到更多信息。

Raspberry Pi 型号

Raspberry Pi 板有多种型号。 此外,这些型号还有很多相关的附件。 您可以在 Raspberry Pi Foundation 的产品页面上找到生产中的当前型号列表。 不幸的是,页面上没有 Raspberry Pi 系列停产产品板上的任何信息。

此外,Raspberry Pi 还以更灵活的形式提供,适用于工业和嵌入式应用。 这被称为计算模块。 计算模块也有许多迭代。 基金会还提供了计算模块原型套件。 您可以在我们之前讨论的同一 Raspberry Pi 产品页面上找到有关计算模块和原型套件的更多信息。

正如我们已经讨论过的,有许多型号的 Raspberry Pi 开发板可用。 尽管很想详细讨论所有这些板的技术规格,但很难简单地实现。 在本书的第一版中,我详细讨论了所有可用的 Raspberry Pi 电路板型号的规格,因为型号数量少得多,我们可以指望它们。 自编写本书第二版以来,已有十几种 Raspberry Pi 模型。 因此,我们将只讨论 Raspberry Pi 的几种板型号的技术规格。

对于我们的计算机视觉示例,我们将使用带有标头模型的 Raspberry Pi 4B 4 GB 和 Raspberry Pi ZeroW。 但是,这些示例也可以在 Raspberry Pi 的其他主板型号上运行。 这是因为我们使用的所有软件(操作系统,编程语言和 OpenCV 库)都完全向后兼容。

树莓派型号 4B

您可以在这个页面上找到 Raspberry Pi 4B 的产品规格。

下表详细说明产品规格:

Figure 5: Product specification list of the Raspberry Pi model 4B

图 1.5 – Raspberry Pi 4B 型的产品规格列表

下图显示了 Raspberry Pi 板上的所有重要连接器和组件:

Figure 6: Raspberry Pi 4B top view

图 1.6 – Raspberry Pi 4B 顶视图

下图显示了 Raspberry Pi 4B 型的俯视图:

Figure 7: The top view of Raspberry Pi 4B

图 1.7 – Raspberry Pi 4B 的俯视图

这是模型的一个角度照片:

Figure 8: Raspberry Pi 4B at an angle

图 1.8 – Raspberry Pi 4B 倾斜

我们将使用此模型的 4GB 变体。

Raspberry Pi Zero W

您可以在这个页面找到 Raspberry Pi Zero W 的规格。

下表更详细地说明了该型号的规格:

Figure 9: Product specification list of the Raspberry Pi Zero W

图 1.9 – Raspberry Pi Zero W 的产品规格列表

在哪里可以买到这些型号?

您可以在 RPi 网站的产品页面上找到在哪里购买 Raspberry Pi 板及其配件。 这是屏幕截图:

Figure 10: Buying a Raspberry Pi

图 1.10 –购买 Raspberry Pi

您还可以在 Amazon 上找到 Raspberry Pi 板及其配件。 如果您生活在大城市,那么您会发现很多业余爱好电子产品商店都在销售 Raspberry Pi 板和相关产品。

Raspberry Pi 的操作系统

许多操作系统都为 Raspberry Pi 板量身定制了操作系统版本。 但是,早期的主板型号不支持所有操作系统。 最新的模型板 Raspberry Pi 4B 支持提到的的所有操作系统。

Raspbian 操作系统支持 Raspberry Pi 板的所有型号,它是初学者最推荐的操作系统。 我们将在下一节中演示如何安装它。

Raspbian 是基于 Debian 的免费操作系统,它是 Linux 的流行发行版。 Raspbian 已针对 Raspberry Pi 硬件进行了优化。 您可以在其主页上找到有关 Raspbian 项目的更多信息。

注意

Raspbian 的主页提到它不隶属于 Raspberry Pi 基金会,由 Raspberry Pi 和 Debian 项目的粉丝管理。

Raspbian 网页在这个页面上提供了推荐的 Raspbian 图像列表。 OS 映像是可以将写入 SD 卡的文件,然后可以使用该 SD 卡来启动 Raspberry Pi 板。 这是 RPi 入门的最简单方法。 从现在开始,我们将尝试使用它。 RPi Foundation 的下载页面上提供的图像是 Raspbian 最推荐的图像。 在下一部分中,我们将学习如何使用该图像来开始使用 RPi。

在 Raspberry Pi 上设置 Raspbian

设置是通常阻止许多新手爱好者开始使用 SBC 的一件事。 很多时候,这些指令是非常通用的,并未涵盖各种类型的硬件组件的所有情况。 这就是为什么我将整个部分专门介绍 RPi 上 Raspbian 的设置的原因。 在本节中,我们将详细介绍安装过的所有电路板模型的设置,计算模块除外。

我们需要以下组件进行设置:

  • 任何型号的 Raspberry Pi 板。
  • 如果您有 Raspberry Pi 4B 板,则需要具有 USB Type-C 引脚的 5V 3A 电源。 这是 USB Type-C 引脚的照片:

Figure 11: USB Type-C pin

图 1.11 – USB Type-C 引脚

  • 为了安全起见,您可能需要由 Raspberry Pi Foundation 购买官方的 Raspberry Pi 15.3W USB-C 电源。 该产品的 URL 为这个页面
  • 对于其他所有 Raspberry Pi 型号,应兼容具有 Micro-USB 型引脚的 5V 2.5A 电源。 这是 Micro-USB 引脚的照片:

Figure 12: A Micro-USB plug

图 1.12 –微型 USB 引脚

  • 您可能想为这个目的购买 Raspberry Pi 通用电源
  • USB 键盘和鼠标:购买带有集成鼠标垫的 USB 键盘是的一个好主意,如下所示:

Figure 13: A keyboard with an integrated mousepad

图 1.13 –带有集成鼠标垫的键盘

  • 对于 RPi Zero 和 RPi Zero W,必须使用带鼠标垫的键盘,因为这些板型号仅具有到外围设备接口的一种 Micro-USB 类型的连接器。 此外,对于 RPi Zero 和 RPi Zero W,我们需要一个 USB 到 Micro-USB OTG 转换器,如下所示:

Figure 14: USB OTG cable

图 1.14 – USB OTG 电缆

  • 任何型号的 Raspberry Pi 板均可与任何 microSD 卡配合使用。 该准则指出,我们应使用至少 16 GB 的 10 级 microSD 卡。 您可能需要访问这个页面获取指导,并访问这个页面获得兼容性列表。 RPi 1 Model A 和 RPi 1 Model B 使用 SD 卡。 因此,最好使用 microSD 到 SD 卡的适配器,如下所示:

Figure 15: MicroSD to SD card adapter/converter

图 1.15 – MicroSD 到 SD 卡的适配器/转换器

  • 用于视觉显示的 HDMI 监视器或 VGA 监视器。
  • 除 RPi 4B,RPi Zero 和 RPi Zero W 外,所有 RPi 板型号均具有 HDMI 输出,并可使用 HDMI 公对公电缆直接连接到 HDMI 监视器:

Figure 16: HDMI cable

图 1.16 – HDMI 电缆

RPi 4B 具有微型 HDMI 输出。 因此,我们需要一个微型 HDMI 到 HDMI 转换器。 RPi Zero 和 RPi Zero W 都具有 mini-HDMI 输出。 因此,对于他们来说,我们需要一个 mini-HDMI 至 HDMI 转换器。 下图分别显示了 HDMI,mini-HDMI 和 micro-HDMI 端口:

Figure 17: HDMI, mini-HDMI, and micro-HDMI ports

图 1.17 – HDMI,mini-HDMI 和 micro-HDMI 端口

我们还需要将 mini-和 micro-HDMI 端插入 RPi 板,并将 HDMI 插入显示器。 如果您打算使用 VGA 显示器,那么根据板子型号的不同,我们将需要 HDMI / mini-HDMI / micro-HDMI 到 VGA 转换器。

这是 HDMI 到 VGA 转换器的照片:

Figure 18: HDMI to VGA converter

图 1.18 – HDMI 转 VGA 转换器

以下是 mini-HDMI 到 VGA 转换器的照片:

Figure 19: Mini-HDMI to VGA converter

图 1.19 – Mini-HDMI 到 VGA 转换器

以下是微型 HDMI 到 VGA 转换器的照片:

Figure 20: Micro-HDMI to VGA converter

图 1.20 – Micro-HDMI 转 VGA 转换器

我们需要 Windows 计算机和有线或无线互联网连接。

最后,我们需要一个 SD 卡读卡器,如下所示:

Figure 21: SD card reader

图 1.21 – SD 卡读卡器

许多笔记本电脑都内置了此功能(SD 卡读卡器)。 因此,在那种情况下,不需要单独的阅读器,因为我们可以使用内置阅读器。

在本章结束时,我们将需要更多的硬件组件。 如有需要,我们将进行讨论。 目前,我们可以继续进行。

下载必要的软件

首先,我们需要下载所有免费软件。 请按照以下说明下载所有必需的软件:

  1. 我们需要 Raspbian OS 的最新映像文件。 可以从 Raspberry Pi Foundation 网站的下载页面下载该文件。以下屏幕快照显示了可供下载的各种选项:

    Figure 22: Raspbian image download page

    图 1.22 – Raspbian 图像下载页面

  2. 到您访问 URL 时,页面可能已经更新,但是下载选项通常将保持不变。 第一个选项是带有桌面和推荐的软件的 Raspbian Buster,最适合初学者。 第二个选项是带有桌面的 Raspbian Buster。 第三个选项是 Raspbian Buster Lite,它带有最低限度的软件。 在所有下载选项中,它的大小最小。

  3. 我们可以直接下载 ZIP 文件,也可以下载图像的种子文件。 我建议下载种子文件。 一旦下载了带有桌面和推荐软件的 Raspbian Buster 的种子文件,我们可以从这个页面下载种子文件。下载免费的经典版本并将其安装在 PC 上。 然后,使用 BitTorrent 打开种子文件,然后开始下载。 以下是完成下载的屏幕截图:

    Figure 23: BitTorrent application window

    图 1.23 – BitTorrent 应用窗口

  4. 在屏幕底部,我们可以看到下载位置。 此外,我们可以右键单击完成的安装,然后单击“打开包含文件夹”选项,如下所示:

    Figure 24: Opening the location of the downloaded image

    图 1.24 –打开下载图像的位置

    这将打开包含 Raspbian OS 映像的 ZIP 文件的文件夹。

  5. 我们需要用于解压缩文件的软件。 7-Zip 是免费的开源软件。 我们可以下载适当的可安装文件(32 位 x86 或 64 位 x64)并进行安装。 安装完成后,使用该软件打开 ZIP 文件。 以下是 7-Zip 的屏幕截图:

    Figure 25: 7-Zip application window

    图 1.25 – 7-Zip 应用窗口

    双击 ZIP 文件,然后在菜单中单击提取按钮。 这将提取文件。 提取的文件具有img扩展名。

  6. 我们需要软件将此图像写入 microSD 卡,Win32DiskImager 是完成此任务的理想软件。 从这个页面下载。 运行安装文件并安装它。

手动准备 microSD 卡

在 microSD 卡的上安装 OS 的最好方法是手动进行操作。 这使我们能够手动准备 SD 卡,以便我们可以更轻松地访问/boot/config.txt配置文件,在某些情况下,在启动 RPi 之前必须对其进行修改。 我们将在后面详细讨论。 默认的 Raspbian 映像只有两个分区:引导系统。 我建议至少选择一个 16 GB 的 Class 10 microSD 卡。 然后,请按照下列步骤操作:

  1. 打开新的 microSD 卡的包装,然后将其插入读卡器中。 将读卡器插入 Windows 便携式计算机或计算机。 许多笔记本电脑和计算机都配有 SD 卡读卡器。 为此,请将 microSD 卡插入 microSD 至 SD 卡适配器,然后将适配器插入计算机或笔记本电脑的 SD 卡读取器的插槽。

  2. 然后,新驱动器将出现在Windows File Explorer的左侧面板中。 右键单击驱动器,然后选择“格式”。 这是“格式”窗口的屏幕截图:

    Figure 26: Formatting the microSD card

    图 1.26 –格式化 microSD 卡

  3. 确保选中“快速格式化”复选框。 然后,点击“开始”按钮。 它将显示警告消息,如下所示:

    Figure 27: Dialogue box for confirmation

    图 1.27 –确认对话框

  4. 单击OK按钮以完成格式化。

  5. 格式化完成后,我们需要将 Raspbian OS 映像文件写入 microSD 卡。 打开Win32DiskImager,然后选择 Raspbian OS 映像文件,如以下屏幕截图所示:

    Figure 28: Win32 Disk Imager application window

    图 1.28 – Win32 Disk Imager 应用窗口

  6. 然后,单击“写入”按钮。 它将显示以下警告框。 只需单击“确定”按钮:

    Figure 29: Dialogue box to confirm writing the image to the microSD card

    图 1.29 –对话框确认将图像写入 microSD 卡

  7. 将操作系统成功写入 SD 卡后,将显示以下消息框:

    Figure 30: Confirmation message box

    图 1.30 –确认消息框

    这意味着图像已成功写入 microSD 卡。 现在我们可以用它来启动 RPi。

  8. 现在,仅当您使用 VGA 监视器而不是 HDMI 监视器时,才需要执行此步骤。 使用 HDMI 监视器的读者可以放心地忽略此步骤。 可以使用 Windows 文件浏览器访问 microSD 卡的 BOOT 分区。 它具有config.txt文件。 双击并打开文件。 我们必须如下编辑/boot/config.txt文件中的设置,以在 VGA 监视器上正确显示:

    a)将#disable_overscan = 1更改为disable_overscan = 1

    b)将#hdmi_force_hotplug = 1更改为hdmi_force_hotplug = 1

    c)将#hdmi_group = 1更改为hdmi_group = 2

    d)将#hdmi_mode = 1更改为hdmi_mode = 16

    e)将#hdmi_drive = 2更改为hdmi_drive = 2

    f)将#config_hdmi_boost = 4更改为config_hdmi_boost = 4

    g)保存文件。

注释行(开头为#)被禁用。 我们必须通过取消注释来启用这些行。 这可以通过在这些注释的行的开头删除#来完成。

注意

如果您使用的是 Linux 或 macOS,则可以在这个页面中找到有关在这些操作系统上的 microSD 卡上安装 Raspbian 操作系统的说明。 ]。

首次启动 Raspberry Pi

让我们使用以下步骤通过 microSD 卡首次启动的 Pi:

  1. 将 microSD 卡插入 Pi 的 microSD 卡插槽中。 RPi 1 Model A 和 RPi 1 Model B 没有 SD 卡插槽。 因此,对于这些主板型号,我们必须使用 microSD 到 SD 卡的转换器。
  2. 将 Pi 连接到 HDMI 监视器。 如前所述,如果您有 VGA 监视器,请使用 HDMI/mini-HDMI/micro-HDMI 至 VGA 转换器连接它。
  3. 连接 USB 鼠标和 USB 键盘。 建议您使用带鼠标垫的单个键盘。 对于 RPi Zero 和 RPi Zero W,您需要先将其连接到 USB OTG 电缆,然后再将 USB OTG 电缆连接到板上。
  4. 将 RPi 板连接到适当的电源。 将显示器连接到电源。 我们需要确保此时关闭电源。
  5. 确保一次验证所有连接。 然后,打开显示器的电源。 最后,打开 RPi 的电源。

现在,我们的 RPi 板将开始启动。 板上的绿色 LED 将开始闪烁。 恭喜你! RPi 板是第一次启动。

注意

如果您的 HDMI 监视器没有显示信号,请关闭 RPi 的电源,然后将 microSD 卡/boot/config.txt中的#hdmi_force_hotplug = 1更改为hdmi_force_hotplug = 1。使用此更改的设置启动 RPi,HDMI 监视器将显示该信号。

RPi 启动后,将显示 Raspbian 桌面和引导式设置窗口,如下所示:

Figure 31: Welcome window on Raspbian

图 1.31 – Raspbian 上的欢迎窗口

单击下一个按钮,然后将显示以下窗口:

Figure 32: Window for setting the country

图 1.32 –设置国家/地区的窗口

在上一个窗口中,设置国家/地区语言。 它将根据您选择的国家自动选择时区。 如果愿意,您也可以更改它。 单击下一个按钮,将出现以下窗口:

图 1.33 –设置新密码的窗口

您可以选择为默认的pi用户设置新密码。 如果将其保留为空白,则它将保留默认密码。 以下是出现的下一个窗口:

图 1.34 –设置屏幕的窗口

如果桌面视图的边缘上有黑色边框,请选中该复选框。 Raspbian 操作系统将在下次启动时对其进行纠正。 单击下一个按钮后,将出现以下窗口,但仅在板型号具有 Wi-Fi 的情况下:

Figure 35: Wi-Fi connections

图 1.35 – Wi-Fi 连接

选择您知道凭据的网络,然后单击 Next 按钮。 将出现以下窗口:

Figure 36: Connecting to the Wi-Fi at my home

图 1.36 –在家里连接到 Wi-Fi

在此处输入您的 Wi-Fi 密码,然后在上单击下一个按钮。 将出现以下窗口:

Figure 37: Update Software

图 1.37 –更新软件

我们可以在此处更新 Raspbian 操作系统和已安装的软件。 我们将在本章的后半部分学习如何手动进行操作。 单击跳过下一个按钮,将出现以下窗口:

Figure 38: Confirmation of completing the initial setup

图 1.38 –完成初始设置的确认

我们已经完成了大部分设置。 现在,在我们重新启动 RPi 之前,还有一些事情要做,因此请单击下一个按钮。

现在,在桌面的左上角,您应该看到一个 Raspberry 图标。 它是 Raspbian 的菜单,其功能类似于 Microsoft Windows 上的 Windows 徽标。 单击徽标,然后导航到首选项 | Raspberry Pi 配置

Figure 39: Raspberry Pi Configuration in the Raspbian menu

图 1.39 – Raspbian 菜单中的 Raspberry Pi 配置

这是 Raspberry Pi 配置工具。 它将打开一个窗口,如下所示,我们可以更改 Raspberry Pi 板的设置:

Figure 40: Configuring the system

图 1.40 –配置系统

前面的屏幕截图是系统选项卡。 截至目前,这里无需更改任何内容。 以下是接口选项卡:

Figure 41: Configuring Interfaces

图 1.41 –配置接口

启用摄像机,SSH 和 VNC。 以下是性能标签:

Figure 42: Memory and Overclock options

图 1.42 –内存和超频选项

此菜单有用于超频和 GPU 内存的选项。 对于 RPi 4B,禁用超频。 在下一章中,我们将学习如何对 RPi 4B 板进行超频。 本地化标签如下:

Figure 43: Localisation options

图 1.43-位置选项

您可能要根据居住地区更改这些设置。

一旦根据我们的选择更改了所有这些设置,我们就可以通过单击Raspbian菜单中的关闭按钮来重新启动 RPi 板:

Figure 44: Rebooting the Pi

图 1.44 –重新启动 Pi

在这里,我们找到选项来重新启动 RPi。 重新启动后,如果我们选择保留默认用户pi的原始密码,则在启动时将出现以下警告消息窗口:

Figure 45: Message after rebooting if the default password has not been changed

图 1.45 –如果未更改默认密码,则重启后的消息

只要我们选择保留默认密码,该密码就会在每次启动后继续出现。

将各种 RPi 板型号连接到互联网

我们可以将以太网电缆直接插入 RJ45 以太网端口 Pi 板。 这将自动检测连接并且连接到互联网。

注意

确保在 Wi-Fi 路由器,管理型交换机或互联网网关上启用了 DHCP动态主机配置协议)。

PRi 1 A,PRi 1 A +,RPi Zero,RPi Zero W 和 RPi 3 A +没有以太网端口。 但是,RPi Zero W 和 RPi 3 A +具有内置的 Wi-Fi。 对于其余型号,我们可以使用 USB Wi-Fi 加密狗:

Figure 46: USB Wi-Fi adapter

图 1.46 – USB Wi-Fi 适配器

将此 Wi-Fi 适配器插入 USB 端口。 如果 USB 端口不够,请使用有源 USB 集线器。 对于 Raspberry Pi Zero,我们需要使用其他 USB OTG 电缆,如前所述。

插入 USB Wi-Fi 适配器后,我们需要打开lxterminal。 这是命令行工具。 我们可以在 Raspbian 的任务栏中以及 Raspbian 菜单中的Accessories下找到一个黑色的小图标。 单击后,将出现以下窗口:

Figure 47: Raspberry Pi LXterminal window

图 1.47 – Raspberry Pi LXterminal 窗口

我们可以在此处输入 Linux 命令。 输入它们后,按Enter执行命令。 我们已经打开了它,以便我们可以手动配置 Raspbian 的网络接口。 这很容易。 所有与网络有关的信息都存储在/etc/network/interfaces文件中。 要在插入 USB Wi-Fi 加密狗之后连接到 Wi-Fi,我们需要向该文件添加一些条目。 首先,通过执行以下命令来备份原始文件:

mv /etc/network/interfaces /etc/network/interfaces.bkp

然后,我们可以通过运行以下命令从头创建接口文件:

sudo nano /etc/network/interfaces

前面的命令将使用称为nano的纯文本编辑器打开网络接口的文件。 这是一个简单的所见即所得编辑器。 在此处输入以下行:

source-directory /etc/network/interfaces.d
auto lo
iface lo inet loopback
auto wlan0
allow-hotplug wlan0
iface wlan0 inet dhcp
wpa-ssid "AshwinIon"
wpa-psk "internet1"

输入行后,按Ctrl+X,然后按Y。 在上述设置中,用您自己的 SSID 替换AshwinIon,并用密码替换internet1。 然后,在命令提示符下运行以下命令:

sudo service networking restart

这将重新启动网络服务并连接到 Wi-Fi。 在任何情况下(以太网或 Wi-Fi),RPi 均分配有唯一的 IP 地址。 我们可以通过在lxterminal上运行ifconfig命令找到它。 命令的输出将在inet下列出 Ipv4 地址。

知道 RPi 的 IP 地址的另一种方法是检查 RPi 板连接到的路由器或受管交换机中的活动客户端表。 以下是路由器的活动客户端表的屏幕截图,我们可以在其中看到 RPi 的条目:

Figure 48: Active client table of a home Wi-Fi router

图 1.48 –家庭 Wi-Fi 路由器的活动客户端表

更新 RPi

高级包工具APT)是 Debian,Ubuntu,Raspbian 及其衍生版本中的包管理工具。 APT 用于安装,升级和删除软件。 我们将学习如何使用它来更新 RPi 板上的操作系统和软件。

运行以下命令:

sudo apt-get update

此命令从在线软件源存储库中同步包列表。 所有包的索引都会刷新。 这会将应用的所有存储库更新为所有最新更新列表。 在执行升级命令之前,必须先执行此命令。

然后,运行以下命令:

sudo apt-get dist-upgrade –fix-missing -y

这将下载并安装所有包。 它还会删除过时的包。 根据互联网的速度,它需要一些时间。 最后,通过运行以下命令来更新固件:

sudo rpi-update

这将更新固件。 此后,RPi 板将在所有方面保持最新。

最后,我们可以运行以下命令关闭 RPi:

sudo shutdown -h now

然后以下命令重新启动它:

sudo reboot

这将更新固件。 此后,RPi 板将在所有方面保持最新。

总结

在本章中,我们学习了重要术语,例如计算机视觉,OpenCV,SBC 和 Raspberry Pi。 我们学习了如何在 Raspberry Pi 上设置 Raspbian 操作系统以及如何配置 Pi 来访问互联网。 我们还学习了如何更新 Pi。

完成本章后,您可以继续在 Raspberry Pi 上设置 Raspbian OS。 此外,您可以使用 Wi-Fi 或以太网将 RPi 板连接到互联网。 这将使您为即将到来的计算机视觉冒险做好准备。

在下一章中,您将学习如何远程访问 RPi,如何对其进行超频以及如何在 RPi 上安装适用于 Python 3 的 OpenCV 4。

二、为计算机视觉准备 Raspberry Pi

在上一章中,我们学习了单板计算机,计算机视觉和 OpenCV 的基础知识。 我们了解了 Raspberry PiRPi)4B 和 RPi Zero W 的详细规格。我们还详细了解了如何在所有 RPi 主板型号上设置 Raspbian OS。

在本章中,我们将学习如何为计算机视觉准备 RPi 板。 从上一章的结尾处开始,我们将开始安装用于计算机视觉的 OpenCV 库和用于远程访问桌面以及命令提示符的其他必要软件。 我们将学习如何在 RPi 和 Windows PC 之间传输文件。 我们还将学习如何通过超频 RPi 并在其上安装散热器以降低处理器温度来利用 RPi 的计算能力。

我们将在本章中介绍的主题如下:

  • 使用 SSH 远程登录 RPi
  • 远程桌面访问
  • 在 RPi 板上安装 OpenCV
  • 散热器和 RPi 4B 超频

使用 SSH 远程登录 RPi

我们可以使用 Windows 上的各种软件来远程访问 RPi 板的命令提示符。 我们可以从 Windows 远程运行所有不涉及的 Linux 命令。 您可能还记得,我们在的第 1 章,“计算机视觉和 Raspberry Pi 简介”中讨论了如何使用 Raspberry Pi 配置工具启用 SSH。 它启用通过 SSH 的远程登录。

为了开始,请按照下列步骤操作:

  1. 首先,我们需要免费安装任何可用的 SSH 软件。 最受欢迎的是 PUTTY。 我更喜欢使用 SFTP 附带的另一个流行的 SSH 客户端,称为 Bitvise SSH 客户端。 您可以从以下位置下载 Windows 的安装文件并安装它。 之后,打开 Bitvise SSH 客户端。 将出现以下窗口:

    Figure 2.1 – Bitwise Connection window

    图 2.1 –按位连接窗口

    输入主机名,用户名和密码。 主机名不过是我们 RPi 板的 IPv4 地址,我们在第 1 章,“计算机视觉和 Raspberry Pi”中了解了如何找到它。

  2. 输入所有必要信息后,单击“登录”按钮。 这将启动 RSA 密钥交换并显示以下消息框:

    Figure 2.2 – Message window for the first-time connection

    图 2.2 –首次连接的消息窗口

  3. 点击“接受并保存”按钮。 这将保存交换的 RSA 密钥。 请注意,如果我们尝试使用同一台 Windows 计算机再次连接到 Raspberry Pi,则不会显示此消息框。 之后,将出现两个单独的窗口。 第一个是 Raspberry Pi 的命令提示符。 就像lxterminal一样,我们也可以从这里运行 Linux 命令:

    图 2.3 –按位 SSH 窗口

  4. 我们可以通过更改属性来更改此处显示的字体和文本的大小,这些属性可以通过右键单击标题栏来找到。 以下是文件传输窗口:

Figure 2.4 – Bitwise FTP file transfer window

图 2.4 –按位 FTP 文件传输窗口

在左侧窗格中,我们具有 Windows 桌面,在右侧窗格中,我们具有/home/pi,即pi用户的主目录。 我们只需在 Windows 和 RPi 之间在这些窗格之间拖放文件即可。

注意

我们可以使用*sudo raspi-config命令从命令提示符访问 Raspberry Pi 配置工具。 此是该工具的命令行版本。

这是我们可以远程连接到 Raspbian OS 的命令提示符并传输文件的方式。 接下来,我们将学习如何远程访问 Raspbian OS 桌面。

远程桌面访问

Bitvise SSH 客户端非常适合进行文件传输和访问 RPi 的命令提示符终端。 但是,我们需要使用另一软件来远程访问 RPi 的桌面。 我们可以采用两种方法。 第一个是 VNC(我们在第 1 章,“计算机视觉和 Raspberry Pi”中介绍了如何启用它) Raspberry Pi 配置工具),而另一个则使用 Windows 内置的远程桌面连接工具。 我们可以在 Windows 搜索栏中找到它,如下所示:

Figure 2.5 – Remote Desktop Connection option in the Windows search bar

图 2.5 – Windows 搜索栏中的“远程桌面连接”选项

但是在使用它之前,我们需要在 RPi 上安装xrdp。 安装非常简单。 我们只需要在 RPi 的lxterminal上运行以下命令:

sudo apt-get install xrdp -y

信息

您可能想在这个页面上阅读有关xrdp的更多信息。

在 RPi 上安装xrdp后,您需要执行以下步骤:

  1. 在 Windows PC 上打开“远程桌面连接”应用:

    Figure 2.6 – Remote Desktop Connection

    图 2.6 –远程桌面连接

  2. 在标有“计算机”和“用户名”的文本框中输入 IP 地址和pi。 您可能要选中“允许我保存凭据”复选框并保存连接设置。 单击“连接”按钮后,将出现以下窗口:

    Figure 2.7 – Credentials for the Raspbian OS for Remote Desktop Connection

    图 2.7 – Raspbian OS 的远程桌面连接凭据

  3. 输入密码,如果要保存此连接的密码,请选中该复选框。 点击OK按钮; 稍后,将显示 RPi 远程桌面窗口。 如果 LAN 上的流量较少,则远程桌面的工作将很顺利。 以下是“远程桌面”窗口的屏幕截图:

Figure 2.8 – Raspbian OS Remote Desktop

图 2.8 – Raspbian OS 远程桌面

我们可以从这里执行与 GUI 相关的所有任务。 表示如果使用远程桌面,则不需要为 RPi 板单独显示。

在 RPi 板上安装 OpenCV

请按照这些步骤在 RPi 上安装 OpenCV:

  1. 首先,我们需要安装一些依赖项。 运行以下命令以安装所有这些依赖项:

    sudo apt-get install -y libhdf5-dev libhdf5-serial-dev libatlas-base-dev libjasper-dev libqtgui4 libqt4-test
    
  2. 安装成功后,我们可以在 RPi 上安装 OpenCV:

    pip3 install opencv-python==4.0.1.24
    
  3. 一旦成功安装了 OpenCV,我们可以通过运行以下命令进行验证:

    python3 -c "import cv2; print(cv2.__version__)"
    

    以下应该是输出:

    4.0.1
    

这意味着安装已完成,并且我们可以在 Python 3 程序中导入 OpenCV。

接下来,我们将学习如何对 RPi 4B 超频以及如何在其上安装散热器。

散热器和超频 RPi 4B

超频意味着以比预期更高的速度运行处理器。 当我们对处理器进行超频时,它们的温度趋于升高,并且会散发更多的热量。 Raspberry Pi 主板型号不附带任何内置散热器。 您可以从许多在线商店(例如 Amazon)购买无源散热器。 以下是带有风扇的散热器的示例:

Figure 2.9 – Small heatsink for RPi

图 2.9 – RPi 的小散热器

散热器风扇可以通过将其连接到 5V 或 3.3V 电源来供电。 风扇的速度取决于电压,我们可以将其连接到 RPi 电源引脚。 在下一章中,我们将详细了解 RPi 的 GPIO 和电源引脚。 我发现最好,最有效的散热器是 RPi 4B 的 ICE Tower 风扇

以下是我自己的安装有 ICE Tower 的 Pi:

Figure 2.10 – ICE Tower installed on Raspberry Pi

图 2.10 – Raspberry Pi 上安装的 ICE Tower

它附带一本小册子,其中包含易于安装的说明。

注意:

有必要在 RPi 的处理器上安装主动冷却的散热器和风扇以使其超频。 对任何处理器进行超频而没有足够的冷却可能会损坏它。

我们可以对 RPi 板的 CPU,GPU 和 RAM 超频。 在本节中,我们将讨论如何对 RPi 4B 板进行超频。

确保使用以下命令更新固件:

sudo rpi-update

对 Pi 超频之前,必须先更新固件。 完成此操作后,运行以下命令:

sudo nano /boot/config.txt

这将使用nano文本编辑器打开/boot/config.txt。 在文件末尾,添加以下行:

over_voltage=6
arm_freq=2147 

在第一行中,我们设置过电压,因为超频需要额外的功率。 在下一行中,我们将覆盖 CPU 的默认时钟频率。 保存更改并重新启动 RPi。

通常,RPi 可能无法启动备份。 在这种情况下,您可能想要将超频的/boot/config.txt设置(使用 Windows PC)更改为over_voltage = 2arm_freq = 1750, 分别,

如果这些设置也无法启动 RPi,请在这两行中都加上注释,然后 RPi 将启动。 超频并非在每个处理器上都稳定运行。

当我们在 RPi 板上运行计算繁重的过程时,所有这些额外的兆​​赫兹都会显现出来。 我们可以使用以下命令实时监视时钟:

watch -n1 vcgencmd measure_clock arm

一旦我们在 RPi 上启动任何繁重的程序,输出的速度将超过 20 亿(2 GHz)。

我们通过对 RPi 板进行超频获得的所有这些额外处理能力将有助于我们进行计算机视觉实验。

总结

在本章中,我们学习了如何远程登录 RPi 以及如何使用 RDP 远程访问 RPi 桌面。 我们还学习了如何安装 OpenCV 以及如何对其进行验证。 另外,我们学习了如何对 RPi 板进行超频。

在整本书中,我们将使用在本章中学到的所有技能,在编写用于计算机视觉的程序时远程访问 Raspbian OS 的命令提示符和桌面。 我们还将多次使用文件传输,以及大多数程序中的 OpenCV 库。

在下一章中,我们将学习 Python,NumPy,Matplotlib 和 RPi GPIO 库的基础知识。 我们还将了解 SciPy 生态系统。

三、Python 编程简介

在上一章中,我们学习了如何远程访问 Raspberry Pi(RPi)板的命令提示符和桌面。 我们还安装了适用于 Python 3 的 OpenCV。最后,我们学习了如何对 RPi 超频并检查了 RPi 的各种散热器。

从本章上一章的结尾处继续,在本章中,我们将从研究 RPi 上的 Python 3 编程开始。 我们将简要介绍科学 PythonSciPy)生态系统及其中的所有库。 然后,我们将编写使用 NumPy N 维数组ndarray)进行数值计算的基本程序。 我们还将学习如何使用 Matplotlib 可视化数据。 最后,我们将使用针对 RPi 的 Python 通用输入输出GPIO)库来探索 RPi 的硬件​​方面。

简而言之,我们将涵盖以下主题:

  • 了解 Python 3
  • SciPy 生态系统
  • 用 NumPy 和 Matplotlib 编程
  • RPi GPIO 编程

技术要求

可以在 GitHub 上找到本章的代码文件。

观看以下视频,以查看这个页面上的“正在执行的代码”。

了解 Python 3

Python 是一种高级的解释型通用编程语言。 它是由 Guido van Rossum 创建的,最初是一个个人爱好项目,但此后逐渐发展为今天的样子。 以下是 Python 编程语言开发中主要里程碑的时间表:

Figure 3.1 – Timeline of Python development milestones

图 3.1 – Python 开发里程碑时间表

Guido van Rossum 在 Python 项目的整个生命周期中都获得了仁慈的独裁者头衔。 他于 2018 年 7 月卸任,自此成为 Python 指导委员会的成员。

您可以在其主页 www.python.org 上阅读有关 Python 的更多信息。

Python 编程语言有两个主要版本-Python 2 和 Python3。它们彼此之间大多不兼容。 如前面的时间线所示,Python 2 的日落发生在 2019 年 12 月 31 日。这意味着 Python 2 不再进一步开发。官方支持也不再存在。 正在进行积极开发并得到持续支持的唯一 Python 版本是 Python3。许多组织正在生产的许多代码(实际上是数十亿行代码)仍在 Python 2 中。因此,从 Python 2 移植到 Python 3 需要付出很大的努力。

RPi 和 Raspberry Pi OS 上的 Python

在我们下载的 Raspberry Pi OS 映像上预装了 Python。 Raspberry Pi OS 映像随附 Python 的版本(Python 2 和 Python 3)。 我们将以的形式详细介绍 Python 3,并使用 Python 3 编写所有程序。

打开lxterminal或远程登录 RPi 并运行以下命令:

python -V

这将产生以下输出:

Python 2.7.16

-V选项返回 Python 解释器的版本。 因此,python命令引用 Python 2 解释器。 但是,我们需要 Python3。因此,请在命令提示符中运行以下命令:

Python3 -V

这将产生以下输出:

Python 3.7.3

这是 Python 3 解释器,在本书中的所有编程练习中都将使用它。 要在光盘上找到解释器的位置(在我们的情况下为我们的 microSD 卡),请运行以下命令:

which python3

这将产生以下输出:

/usr/bin/python3

这是 Python 3 解释器的可执行文件所在的位置。

Raspberry Pi OS 上的 Python 3 IDE

在开始使用 Python 3 编程之前,我们将学习哪些集成开发环境IDE)可用于用 Python 编写程序。 到目前为止,Raspberry Pi OS 带有两个 IDE。 可以通过 Raspbian 菜单中的编程选项访问,如下所示:

Figure 3.2 – The Thonny and Geany Python IDEs in the Raspbian menu

图 3.2 – Raspbian 菜单中的 Thonny 和 Geany Python IDE

第一个选项是 Geany IDE,它可以与许多编程和标记语言一起使用,包括 Python 2 和 Python 3。 。 的第二个选项是 Thonny Python IDE,它支持 Python 3 和 MicroPython 变体。

我个人更喜欢使用集成开发和学习环境IDLE),它是由 Python 基金会开发和维护的。 您可以在这个页面上阅读有关它的更多信息。 较早版本的 Raspberry Pi OS 曾经带有 IDLE。 但是,它在最新版本的 Raspberry Pi OS 中不再存在。 相反,我们有 Geany 和 Thonny。 但是,我们可以使用以下命令下载 IDLE:

sudo apt-get install idle3 -y

安装完成后,我们可以在 Raspbian 菜单下的编程菜单选项中找到它,如以下屏幕截图所示:

Figure 3.3 – The option for IDLE for Python 3

图 3.3 – Python 3 的 IDLE 选项

单击它以将其打开。 或者,我们可以使用以下命令从命令提示符启动它:

idle

请注意,如果在命令调用 GUI 时我们已远程连接到 RPi 的命令提示符(使用 SSH 客户端,例如 PuTTY 或 Bitvise),则此命令将不起作用,并且会引发错误。 如果将视觉显示器直接连接到 RPi 或我们远程访问 RPi 桌面,则它将起作用。 这将调用一个新窗口,如下所示:

Figure 3.4 – Python 3 interactive mode in IDLE

图 3.4 – IDLE 中的 Python 3 交互模式

这是 Python 3 解释器提示符或 Python 3 shell。 我们将在本章后面详细讨论这个概念。

现在,转到顶部菜单中的文件 | 新文件。 这将打开一个新的代码编辑器窗口,如下所示:

图 3.5 –一个新的空白 Python 程序

发生这种情况时,解释器窗口也将保持打开状态。 您可以关闭或将其最小化。 如果由于字体大小而导致难以在 IDLE 的解释器或代码编辑器中阅读文本,则可以转到选项 | 从菜单配置 IDLE 以设置文本的字体和大小。 配置窗口如下所示:

图 3.6 – IDLE 配置

让我们写一个习惯的 HelloWorld 程序。 在窗口中输入以下文本:

print('Hello World!')

然后,从菜单中单击运行模块。 它将要求您保存它。 单击OK按钮,它将带您到保存对话框。 我更喜欢按章节将本书的代码保存在目录中,每个章节都有子目录。 您可以通过在pi用户的主目录中运行以下命令,通过创建目录结构:

mkdir book
mkdir book/dataset
mkdir book/chapter01

我们可以像这样为每个章节创建一个单独的目录。 另外,需要一个单独的数据集目录来存储我们的数据。 创建指定的目录结构后,运行以下命令序列:

cd book
tree

我们可以在tree命令的以下输出中看到目录结构:

图 3.7 –用于保存该书程序的目录结构

我们可以使用 IDLE 的保存对话框或 Raspberry Pi OS 的文件管理器应用来创建相同的目录结构。

创建与当前章节对应的目录后,将文件另存为prog00.py。 您只需要输入文件名即可; IDLE 将自动为文件分配.py扩展名。 然后,该文件将由 Python 3 解释器执行,并且输出将在解释器外壳中可见,如下所示:

图 3.8 –在 IDLE 中执行 Python 3 程序

我们也可以使用nano编辑器编写相同的代码。 唯一的区别是,保存时我们还需要提供扩展名。 我们可以导航到具有prog00.py文件的目录,然后运行以下命令将该文件提供给 Python 3 解释器:

python3 prog00.py

Python 3 解释器将执行程序并打印输出,如下所示:

图 3.9 –在 LXTerminal 中执行 Python 3 程序

以交互模式使用 Python 3

我们已经看到如何使用 IDLE 和 Nano 编辑器编写 Python 3 程序。 我们还看到了如何使用 IDLE 和 Raspberry Pi OS 的命令提示符启动程序。 以这种方式运行 Python 3 程序称为脚本模式。

还有另一种模式-交互模式。 在交互模式下,我们启动 Python 解释器,它充当命令行解释器。 当我们输入并运行一条语句时,我们会立即得到口译员的反馈。 我们可以通过两种方式启动交互模式。 我们已经看到了第一种方法。 当我们启动 IDLE 时,它将打开解释器,我们可以使用它来运行 Python 3 语句。 另一种方法是在命令提示符中运行python3命令。 这将在命令提示符中调用 Python 3 解释器,如下所示:

Figure 3.10 – Python 3 in interactive mode on the Command Prompt

图 3.10 –命令提示符下处于交互模式的 Python 3

在提示符下键入以下语句:

>>> print('Hello World!')

然后,按Enter。 将执行,输出将显示在下一行。 这样,我们可以像这样执行单行语句和小的代码段。 本章将广泛使用交互模式。 从下一章开始,我们将使用脚本模式-也就是说,我们将程序保存在文件中,并从命令提示符或 IDLE 中启动它们。

Python 3 编程的基础

让我们从开始学习 Python 3 编程的基础。 打开 Python 3 交互式提示。 输入以下语句:

>>> pi = 3.14
>>> print(pi)

这将显示pi变量的值。 运行以下语句:

>>> print(type(3.14))

显示以下输出:

<class 'float'>

您可能已经注意到我们没有在此处声明变量的数据类型。 这是因为 Python 是一种动态类型化的编程语言。 我们还说该变量属于类类型。 这意味着它是一个对象,对于 Python 中的所有变量和其他构造均适用。 一切都是 Python 中的对象。 这使 Python 成为真正的面向对象的编程语言。 几乎所有东西都有属性和方法。

为了退出命令提示符,请按Ctrl+D或运行exit()语句。

让我们创建自己的类和该类的对象。 保存文件并将其命名为prog01.py,然后向其中添加以下代码:

class Person:
    def __init__(self, name='', age=0):
        self.name = name
        self.age = age
    def show(self):
        print(self.name)
        print(self.age)

在前面的代码中,我们定义了Person.__init__()类是初始化函数,每当创建Person类的对象时,就会自动调用它。 self参数是对类的当前实例的引用,用于访问类定义内属于该类的变量。

让我们向prog01.py添加更多代码。 我们将创建一个类对象,如下所示:

p1 = Person('Ashwin', 25)
p1.show()

我们创建了p1类,然后使用show()函数调用显示了对象的属性。 在这里,我们在创建类对象时将值分配给类成员变量。

让我们看看另一种创建对象并将值分配给成员变量的方法。 将以下代码添加到文件中:

p2 = Person()
p2.name = 'Jane'
p2.age = 20
print(p2.name)
print(p2.age)

在前面的代码中,我们正在创建一个对象,并使用默认参数调用初始化函数。 然后,我们将这些值分配给类变量,并使用类对象直接访问它们。 运行程序并查看输出。

现在,打开 Python 3 解释器并运行以下语句:

>>> import sys
>>> print(sys.platform)

这将返回当前操作系统(Linux)的名称。 第一条语句导入sys,这是一个 Python 标准库。 它随附在 Python 解释器中。 这意味着 Python 解释器带有大量的有用库。sys.platform返回当前操作系统名称字符串。

让我们尝试另一个示例。 在上一章中,我们安装了 OpenCV 库。 让我们现在再次导入。 我们已经从 Raspberry Pi OS 命令提示符直接对其进行了测试。 让我们尝试在交互模式下执行相同的操作:

>>> import cv2
>>> print(cv2.__version__)

第一条语句将 OpenCV 库导入当前会话。 第二条语句返回一个字符串,其中包含已安装的 OpenCV 库的版本号。

Python 3 编程的基础知识涉及很多主题,但是很难涵盖所有主题。 此外,这样做超出了本书的范围。 但是,我们将使用本书中经常学习的主题。

在下一部分中,我们将探索 SciPy 生态系统库。

SciPy 生态系统

SciPy 生态系统是库的集合,用于编程科学,数学和工程功能。 它具有以下库作为核心组件:

  • NumPy
  • SciPy
  • Matplotlib
  • IPython
  • SymPy
  • Pandas

在本书中,我们将使用除 SymPy 和 pandas 之外的所有库。 在本节中,我们将介绍 NumPy 和 Matplotlib 库。 在本书的后续章节中,我们将学习其他两个库的有用方面。

NumPy 的基础

NumPy 是一个基本包,可用于使用 Python 进行数值计算。 它是线性代数的矩阵库。 NumPy ndarray也可以用作作为通用数据的有效多维容器。 也可以定义和使用任意数据类型。 NumPy 是 Python 编程语言的扩展。 它增加了对大型多维数组和矩阵的支持,以及可用于对这些数组进行操作的大型高级数学函数库。 在本书中,我们将使用 NumPy 数组表示图像并执行复杂的数学运算。 NumPy 带有许多用于所有这些操作的内置函数。 因此,我们不必担心基本的数组操作。 我们可以直接关注计算机视觉的概念和代码。 所有 OpenCV 数组结构都与 NumPy 数组相互转换。 因此,无论您在 NumPy 中执行什么操作,都可以始终将 NumPy 与 OpenCV 结合使用。

本书中,我们将在 OpenCV 中大量使用 NumPy。 让我们从一些简单的示例程序开始,它们将演示 NumPy 的真正功能。

NumPy 已预装在 Raspberry Pi OS 上。 因此,我们不必单独安装它。

打开 Python 3 解释器,然后尝试以下示例。

ndarray的创建

让我们来看看关于ndarray创建的一些示例。 本书经常使用array()方法。 有很多方法可以创建不同类型的数组。 在本书的中,我们将在需要时探讨这些方法。 请遵循以下命令来创建ndarray

>>> import numpy as np
>>> x=np.array([1,2,3])
>>> x
array([1, 2, 3])
>>> y=arange(10)
>>> y=np.arange(10)
>>> y
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

ndarray的基本操作

现在将学习linspace()函数。 它采用三个参数-start_numend_numcount。 这将创建一个具有等距点的数组,该数组从start_num开始,以end_num结尾。 您可以尝试以下示例:

>>> y=np.arange(10)
>>> y
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a=np.array([1,3,6,9])
>>> a
array([1, 3, 6, 9])
>>> b=np.linspace(0,15,4)
>>> b
array([ 0.,  5., 10., 15.])
>>> c = a - b
>>> c
array([ 1., -2., -4., -6.])

以下是可以用来计算数组中每个元素的平方的代码:

>>> a**2
array([ 1,  9, 36, 81], dtype=int32)

使用ndarray的线性代数

让我们探索一些与线性代数有关的示例。 您将学习如何使用transpose()inv()solve()dot()函数, 在执行与线性代数有关的运算时很有用:

>>> a = np.array([[1,2,3],[4,5,6],[7,8,9]])
>>> a.transpose()
array([[1, 4, 7],
       [2, 5, 8],
       [3, 6, 9]])
>>> np.linalg.inv(a)
array([[-4.50359963e+15,  9.00719925e+15, -4.50359963e+15],
       [ 9.00719925e+15, -1.80143985e+16,  9.00719925e+15],
       [-4.50359963e+15,  9.00719925e+15, -4.50359963e+15]])
>>> b = np.array([3, 2, 1])
>>> np.linalg.solve(a, b)
array([-9.66666667, 15.33333333, -6\.        ])
>>> c = np.random.rand(3, 3)
>>> c
array([[0.91827923, 0.75063499, 0.40049332],
       [0.09520566, 0.16718726, 0.6577751 ],
       [0.95343917, 0.50972786, 0.65649385]])
>>> np.dot(a, c)
array([[ 3.96900804,  2.61419309,  3.68552507],
       [ 9.86978019,  6.89684344,  8.82981189],
       [15.77055234, 11.17949379, 13.9740987 ]])

注意:

您可以在这里更详细地研究 NumPy。

Matplotlib

Matplotlib 是一个用于 Python 的绘图库,它生成发布质量的图形。 它可以产生各种类型的可视化效果,例如绘图,三维可视化效果和图像。 了解 Matplotlib 的基础知识以与任何计算机视觉库(例如 OpenCV)一起使用非常重要。

Matplotlib 由 John D. Hunter 开发,并且由开源社区不断开发。 它是 SciPy 生态系统的组成部分。 SciPy 生态系统中的所有其他库都使用 Matplotlib 进行数据可视化。 pyplot是 Matplotlib 中的一个模块,它提供了类似于 MATLAB 的接口来可视化数据。

在开始使用 Matplotlib 进行编程之前,我们需要先安装它,因为它尚未预先安装在 Raspberry Pi OS 上。

我们可以使用pip3工具进行安装。 我们已经了解了安装 OpenCV 时如何使用此工具。 让我们更详细地看一下。pip表示 PIP 安装包PIP 安装 Python。 它是递归的首字母缩写(意味着首字母本身是首字母缩写的一部分)。 它是 Python 解释器随附的命令行工具,用于安装库。 pip3是此工具的 Python 3 版本。 它首先连接到 Python 包索引,它是 Python 库的存储库。 然后,它下载并安装我们需要的库。

注意:

您可以在这个页面这个页面阅读更多有关 Python 包索引的和pip的信息。

我们可以通过运行以下命令来安装 Matplotlib:

pip3 install matplotlib

Matplotlib 是一个很大的库,并且具有许多必备库。 所有这些先决条件库都由pip3自动安装,然后安装了 Matplotlib。 这样做会花费一些时间,具体取决于您的互联网连接速度。

安装完成后,我们可以编写一些示例程序。 我们将在脚本模式下使用 Python 3,并使用 IDLE 或 Nano 编辑器来编写程序。 创建一个新文件prog02.py,并向其中添加以下代码:

import matplotlib.pyplot as plt
import numpy as np
x = np.array([1, 2, 3, 4], dtype=np.uint8)
y = x**2 + 1
plt.plot(x, y)
plt.grid('on')
plt.show()

在前面的代码的第一行中,我们导入别名为plt的 Matplotlib 的pyplot模块。 然后,我们导入 NumPy。 我们使用array()函数调用,通过向其传递一个 8 位无符号整数列表来创建线性ndarrayuint8是指数据类型)。 然后,我们定义y = x * 2 + 1plot()函数图将yx绘制。 通过将'on''off'传递给grid()函数调用,可以打开或关闭网格。show()函数调用启动事件循环,查找所有当前活动的可视化对象,然后打开一个可视化窗口以显示图或其他可视化。 以下是输出:

Figure 3.11 – Visualization with Matplotlib

图 3.11 – Matplotlib 可视化

如我们所见,这向我们展示了可视化。 网格在打开时可见。 我们还在底部有图像控件,可以在其中保存,缩放和执行可视化操作。 请注意,我们需要使用命令提示符上的 IDLE 或使用远程桌面直接在 RPi 上运行程序。 从远程 SSH 命令行运行该程序不会引发任何错误,但也不会显示任何输出。

prog02.py代码文件另存为prog03.py。 在plot()函数调用之后以及grid()调用之前,添加以下行:

y = x + 1
plt.plot(x, y)
plt.title('Graph')
plt.xlabel('X-Axis')
plt.ylabel('Y-Axis')

其余代码保持原样。 保存程序。 在这里,我们演示了在同一窗口中多个图的可视化。 我们还在图形中添加了标题和标签。 运行prog03.py文件,输出如下:

Figure 3.12 – Multiple graphs with Matplotlib

图 3.12 – Matplotlib 的多个图形

我们可以在plt.show()之前添加以下行,以将可视化文件保存在磁盘上:

plt.savefig('test1.png', dpi=300, bbox_inches='tight')

这会将可视化文件保存在当前目录中,并将其命名为test1.png

让我们继续进行更有趣的部分。 我们可以使用imshow()函数将ndarray可视化为图像。 让我们来看一个例子。 创建一个名为prog04.py的新文件,并向其中添加以下代码:

import matplotlib.pyplot as plt
import numpy as np
x = np.array([[0, 1, 2, 3, 4],
              [5, 6, 7, 8, 9]], dtype = np.uint8 )
plt.imshow(x)
plt.show()

在前面的代码中,我们将创建一个二维数组(大小为5x2),并通过imshow()调用将其可视化为图像。 以下是输出:

Figure 3.13 – Visualizing numbers as an image

图 3.13 –将数字可视化为图像

由于这是一幅图像,因此我们确实不需要网格和轴刻度。 我们可以在plt.show()之前的代码中添加以下两行以将其关闭:

plt.axis('off')
plt.grid('off')

运行修改后的代码并观察输出。

图像已被渲染,我们称之为默认颜色图。 配色图是用于可视化的配色方案。 如果将plt.imshow(x)更改为plt.imshow(x, cmap='gray'),则输出如下:

Figure 3.13 – The image in greyscale mode

图 3.14 –灰度模式下的图像

有很多颜色图。 我们甚至可以创建自己的自定义颜色图; 但是,对于计算机视觉算法的演示,由于现有的颜色图已足够,因此不需要。 如果您对我们可以使用的可用颜色图名称感到好奇,可以按以下方法查找它们。 以交互方式打开 Python 3 解释器,然后将pyplot模块导入 Matplotlib:

>>> import matplotlib.pyplot as plt

plt.colormaps()列表具有所有颜色图的名称。 首先,我们检查有多少个颜色图,这很容易。 运行以下语句:

>>> print(len(plt.colormaps()))

这将打印彩色图。 最后,运行以下语句以查看所有可用颜色图的列表:

>>> print(plt.colormaps())

该列表相当长,我们将仅使用列表中的一些色图进行演示。 在prog04.py文件中,将plt.imshow(x)更改为plt.imshow(x, cmap='Accent'),以下内容将是输出:

Figure 3.14 – The image with the Accent colormap

图 3.14 –带有重音色图的图像

Matplotlib 的这些知识对于使用 OpenCV 和计算机视觉起步来说绰绰有余。

到目前为止,我们已经看到了一维和二维ndarray可视化的示例。 现在,让我们看看如何创建一个随机的三维ndarray以及如何对其进行可视化。 观察以下代码:

import matplotlib.pyplot as plt
import numpy as np
x = np.random.rand(3, 3, 3)
plt.imshow(x)
plt.axis('off')
plt.grid('off')
plt.show()

在前面的代码中,我们使用np.random.rand()创建一个随机的三维数组。 我们只需要传递每个尺寸的大小即可。 在前面的示例中,我们正在创建尺寸为3x3x3的三维矩阵。 运行前面的代码,然后自己查看输出。 我们将在本书中使用的所有图像都表示为二维或三维ndarray。 一旦开始使用图像,这种数据可视化知识将非常有用。

Python 3 RPi GPIO 编程

板载 GPIO 引脚是 RPi 和类似的单板计算机的主要独特销售点之一。 某些 RPi 板的早期型号具有 26 针。 最新型号有 40 个 GPIO 引脚。 我们可以通过在命令提示符上运行pinout命令来获取板上引脚的详细信息。 以下是我的 RPi 4B 板的命令输出:

Figure 3.16 – Part 1 of the command pinout

图 3.16 –命令引出线的第 1 部分

在左上方,我们可以看到 GPIO 的 40 个引脚。 引脚号 1 在那里标记。 其上方的红色圆圈是 2 号引脚。与 1 号引脚相邻的引脚是 3 号引脚,依此类推。 输出的以下部分显示了所有引脚的编号:

图 3.17 –命令引出线的第 2 部分

从前面的输出中可以看到,我们有电源引脚(3V35VGND)和数字 I/O 引脚,标记为 GPIOxx

使用 GPIO 的 LED 编程

现在,我们将看到如何使用 GPIO 引脚作为输出引脚对 LED 进行编程。 首先,我们准备一个简单电路来使 LED 闪烁。

为此,我们需要跨接电缆,一个 LED 和一个 220 欧姆的电阻。 如下图所示准备电路:

Figure 3.18 – LED-resistor circuit

图 3.18 – LED 电阻电路

正如我们在前面的电路图中所看到的,我们通过 220 欧姆电阻将 LED 的阳极连接到物理引脚 8,LED 的阴极连接到物理引脚 6,该引脚是接地GND)引脚。

注意:

在本书中,您会发现许多类似的漂亮电路图。 我使用了名为 Fritzing 的开源软件来生成它们。 您可以通过这个页面访问 Fritzing 的主页。 Fritzing 文件的扩展名为.fzz。 这些文件是本书可下载代码包的一部分。

现在,让我们进入代码。 为此,我们需要安装 GPIO 库。 Raspberry Pi OS 的最新版本带有已安装的 GPIO 库。 但是,如果不存在,我们可以通过运行以下命令进行安装:

sudo apt-get install python3-rpi.gpio -y

现在,在同一目录中创建一个新文件prog05.py,并向其中添加以下代码:

import RPi.GPIO as GPIO
from time import sleep
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(8, GPIO.OUT, initial=GPIO.LOW)
while True:
    GPIO.output(8, GPIO.HIGH)
    sleep(1)
    GPIO.output(8, GPIO.LOW)
    sleep(1)

在前面的代码中,前两行导入所需的库。setwarnings(False)禁用所有警告,setmode()用于设置引脚寻址模式。 有两种模式,GPIO.BOARDGPIO.BCM。 在GPIO.BOARD模式下,我们通过引脚的物理位置编号来引用它们。 在GPIO.BCM模式下,我们通过管脚 Broadcom SOC 通道号引用这些管脚。 我更喜欢GPIO.BOARD模式,因为易于通过引脚的物理位置号记住它们。setup()用于将每个 GPIO 引脚设置为输入或输出。

在前面的代码中,第一个参数是引脚号,第二个参数是模式,第三个参数是引脚的初始状态。output()用于向引脚发送HIGHLOW信号。sleep()是从time库中导入的,它会产生给定秒数的延迟。 运行前面的程序,使 LED 闪烁。 为了终止程序,请按键盘上的Ctrl + C

同样,我们可以为同一电路编写以下代码,以使 LED 闪烁以视觉方式传达求救SOS)消息:

import RPi.GPIO as GPIO
from time import sleep
GPIO.setwarnings(False)   # Ignore Warnings
GPIO.setmode(GPIO.BOARD)  # Use Physical Pin Numbering
GPIO.setup(8, GPIO.OUT, initial=GPIO.LOW)
def flash(led, duration):
    GPIO.output(led, GPIO.HIGH)
    sleep(duration)
    GPIO.output(led, GPIO.LOW)
    sleep(duration)
while True:
    flash(8, 0.2)
    flash(8, 0.2)
    flash(8, 0.2)
    sleep(0.3)
    flash(8, 0.5)
    flash(8, 0.5)
    flash(8, 0.5)

    flash(8, 0.2)
    flash(8, 0.2)
    flash(8, 0.2)
    sleep(1)

在前面的程序中,我们定义了一个自定义flash()函数,该函数接受引脚号和闪存的持续时间。 然后,在给定的持续时间内,将提供的引脚设置为HIGH,然后在给定的持续时间内将其引脚设置为LOW。 因此,在给定的时间内,连接到该引脚的 LED 交替点亮和熄灭。 当它以. . . - - - . . .(三点后接三点划线,后接三点)的模式发生时,即 SOS 的莫尔斯电码,被称为遇险信号。 对于每个.(点)字符,我们使 LED 闪烁 0.2 秒,对于-(破折号)字符,我们将其闪烁半秒。 我们已将所有这些都添加到之前的无限while循环中。 当我们运行程序时,它将开始闪烁 SOS 消息,直到通过按键盘上的Ctrl + C终止它为止。

让我们看看更多的 GPIO 和 Python 3 编程。 如下图所示准备电路:

Figure 3.19 – A circuit diagram with two LEDs

图 3.19 –具有两个 LED 的电路图

正如我们在这里看到的,我们只需要通过 220 欧姆电阻将另一个 LED 的阳极连接到引脚 10,并将同一 LED 的阴极连接到 GND 引脚。 我们将使两个 LED 交替闪烁。 以下是此代码:

import RPi.GPIO as GPIO
from time import sleep
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(8, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(10, GPIO.OUT, initial=GPIO.LOW)
while True:
    GPIO.output(8, GPIO.HIGH)
    GPIO.output(10, GPIO.LOW)
    sleep(1)

    GPIO.output(8, GPIO.LOW)
    GPIO.output(10, GPIO.HIGH)
    sleep(1)

您现在应该熟悉我们在前面的两个示例中讨论的上述代码中的所有功能。 该代码在执行后使 LED 交替闪烁 1 秒钟。

现在,有另一种方法来产生相同的输出。 看一下以下程序:

import RPi.GPIO as GPIO
from time import sleep
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(8, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(10, GPIO.OUT, initial=GPIO.LOW)
counter = 0
while True:
    if counter % 2 == 0:
        led1 = 8
        led2 = 10
    else:
        led1 = 10
        led2 = 8
    GPIO.output(led1, GPIO.HIGH)
    GPIO.output(led2, GPIO.LOW)
    sleep(1)
    counter = counter + 1

在前面的程序中,如果使用 Python 3 中的语句,我们将使用稍微不同的逻辑来演示它的用法。我们有一个名为counter的变量,该变量设置为0开头。 在while循环中,我们检查counter的值是偶数还是奇数,并据此设置要打开和关闭哪个 LED。 在循环的末端,我们将counter递增1。 该程序的输出与早期的相同,可以通过按Ctrl + C来终止。

现在,让我们尝试许多 LED。 为此,我们将需要一块面包板。 如下图所示准备电路:

Figure 3.20 – A diagram for the chaser circuit

图 3.20 –追赶器电路图

对于编程,我们将尝试一种不同的方法。 我们将使用 Python 3 的gpiozero库。如果默认情况下未在 Raspbian 发行版中安装该库,则可以使用以下命令将其安装为:

pip3 install gpiozero

在寻址引脚时,它使用 BCM 编号系统。 将以下代码保存在 Python 文件中,然后运行以查看漂亮的追赶效果:

from gpiozero import LED
from time import sleep
led1 = LED(2)
led2 = LED(3)
led3 = LED(4)
led4 = LED(17)
led5 = LED(27)
led6 = LED(22)
led7 = LED(10)
led8 = LED(9)
led9 = LED(11)
sleeptime = 0.2
while True:
	led1.on()
	sleep(sleeptime)
	led1.off()
	led2.on()
	sleep(sleeptime)
	led2.off()
	led3.on()
	sleep(sleeptime)
	led3.off()
	led4.on()
	sleep(sleeptime)
	led4.off()
	led5.on()
	sleep(sleeptime)
	led5.off()
	led6.on()
	sleep(sleeptime)
	led6.off()
	led7.on()
	sleep(sleeptime)
	led7.off()
	led8.on()
	sleep(sleeptime)
	led8.off()
	led9.on()
	sleep(sleeptime)
	led9.off()

前面的所有代码都是不言自明的,现在您应该很容易理解。 在第一行中,我们导入了LED。 我们可以将 BCM 引脚号作为参数传递给它。 可以将分配给一个变量,然后该变量可以调用on()off()函数分别打开和关闭与之关联的 LED。 我们还在on()off()之间将sleep()称为。

使用 GPIO 的按钮编程

现在,我们将了解如何将按钮连接到具有内部上拉电阻的 RPi 板。 如下图所示准备电路:

Figure 3.21 – A diagram for interfacing a push button

图 3.21 –接口按钮图

在前面的电路中,我们将按钮的一端连接到引脚号 7,另一端连接到 GND。 将以下代码保存到 Python 文件中:

from time import sleep
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
button = 7
GPIO.setup(button, GPIO.IN, GPIO.PUD_UP)
while True:
    button_state = GPIO.input(button)
    if button_state == GPIO.HIGH:
        print ("HIGH")
    else:
        print ("LOW")
    sleep(0.5)

在前面的代码中,我们将引脚 7 初始化为输入引脚。 在setup()中,第二个参数决定 GPIO 引脚的模式(INOUT)。 第三个参数GPIO.PUD_UP决定是否将其连接到内部上拉电阻。 如果我们将按钮连接到内部上拉电阻,则在未按下按钮时,按钮所连接的 GPIO 引脚将设置为HIGH。 如果按下按钮,则将其设置为LOWGPIO.input()并返回按钮状态。 启动程序,如果按钮打开,输出将显示HIGH,如果按下按钮,输出将显示LOW。 以下是输出:

Figure 3.22 – The output of the push button program

图 3.22 –按钮程序的输出

因此,这就是我们可以检测到按键按下的方式。 可以通过按Ctrl + C终止程序。

我们也可以尝试略有不同的电路和代码。 准备电路,如下所示:

Figure 3.23 – Another circuit for the push button

图 3.23 –按钮的另一个电路

在前面的电路中,我们将按钮的一端连接到引脚 7,的另一端连接到 3V3 引脚。 不要将此端连接到 5V 引脚,因为当我们按下按钮时,它将连接到引脚 7,而 GPIO 引脚最多只能处理 3V3(3.3 V)。 将它们连接到 5V 电源会损坏引脚和电路板。 准备电路,并使用以下命令复制代码:

cp prog06.py prog07.py

在新的prog07.py文件中,我们只需要对setup()函数调用进行一些小的更改,如下所示:

GPIO.setup(button, GPIO.IN, GPIO.PUD_DOWN)

这会将按钮连接到内部下拉电阻。 当按钮打开时,与按钮相连的引脚保持设置为LOW;在按下按钮时,其引脚设置为HIGH。 运行程序,输出如下所示::

Figure 3.24 – The output of the second push button program

图 3.24 –第二个按钮程序的输出

程序将在开始显示LOW。 如果按下按钮,它将变为HIGH。 可以通过按Ctrl + C终止程序。 这是检测按键的另一种方法。

总结

在本章中,我们学习了 Python 3 编程的基础。 我们还了解了 SciPy 生态系统,并尝试了 NumPy 和 Matplotlib 库。 最后,我们看到了如何将 RPi 的 GPIO 引脚与 LED 和按钮一起使用。

在下一章中,我们将开始使用 Python 3 和 OpenCV 编程。 我们还将尝试许多动手练习,以学习有关使用网络摄像头和 RPi 摄像头模块进行编程的知识。

四、计算机视觉入门

在上一章中,我们学习了 Python 3,NumPy,Matplotlib 和通用输入输出GPIO)编程的基础。 在本章中,我们将重点介绍图像和视频的获取。 本章有很多编码示例,我们将在整本书中使用。

在本章中,我们将介绍以下主题:

  • 探索图像数据集
  • 使用 OpenCV 处理图像
  • 使用 Matplotlib 可视化图像
  • 使用 OpenCV 和 NumPy 绘制几何形状
  • 使用 GUI
  • 事件处理和原始绘画应用
  • 使用 USB 网络摄像头
  • Pi 相机模块

技术要求

可以在 GitHub 上找到本章的代码文件。

观看以下视频,以查看这个页面上的“正在执行的代码”。

探索图像数据集

对于使用 Python 和 OpenCV 的计算机视觉程序,我们将需要示例图像。 我们可以在网上找到很多图片。 但是,其中许多图像均受版权保护。 大多数计算机视觉研究人员和专业人员都使用标准的图像数据集。 我们更喜欢一直使用以下图像数据集:

下载这些数据集。 它们将以压缩的 zip 格式。 将它们解压缩到~/book/dataset目录中。 从本章开始,我们将编写许多需要图像的计算机视觉程序,并将满足所有需求使用这些数据集中的图像。 图像的另一种选择是使用网络摄像机和 RPi 摄像机模块来捕获图像,我们将在本章的后面部分中学习有关图像的信息。

使用 OpenCV 处理图像

在本节中,我们将学习使用 OpenCV API 和 Python 读取和存储图像。 本书中的所有程序都将使用 OpenCV 库。 可以使用以下 Python 3 语句导入它:

import cv2

cv2.imread()函数从磁盘读取图像并将其存储在 NumPy ndarray中。 它接受两个参数。 第一个参数是磁盘上映像文件的名称。 该图像应该位于保存当前 Python 3 脚本的目录中,或者必须将图像文件的绝对路径作为参数传递给cv2.imread()函数。

第二个参数是一个标志,用于指定应读取图像的模式。 该标志可以具有以下值之一:

  • cv2.IMREAD_GRAYSCALE:这将以灰度模式从磁盘读取图像。 对应于该标志的数值是0
  • cv2.IMREAD_UNCHANGED:这将从磁盘上按原样读取图像。 对应于该标志的数值是-1
  • cv2.IMREAD_COLOR:这将以彩色模式读取图像,它是参数自变量的默认值。 对应于该标志的数值是1。 这是参数的默认值。

以下是在彩色模式下读取图像的代码:

import cv2
img = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', cv2.IMREAD_COLOR)

我们可以用以下标志重写最后一行:

img = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 1)

前面的编写代码以读取带有数字标志的源图像的样式非常简单。 因此,我们将在整本书中使用它:

cv2.imshow('Mandrill', img)
cv2.waitKey(0)
cv2.destroyWindow('Mandrill')

cv2.imshow()函数在屏幕上的窗口中显示图像。 它接受两个参数。 作为窗口名称的字符串是第一个参数,具有要显示图像的 NumPy ndarray变量是第二个变量。

cv2.waitKey()函数是用于绑定键盘事件的函数。 它接受一个参数,该参数是函数检测键盘的按键需要等待的毫秒数。 如果我们将其传递为0,则它将无限期地等待键盘上的某个按键按下。 它是 OpenCV 库中唯一可以处理键盘事件的函数。 我们必须在调用cv2.imshow()函数之后立即调用它。 如果我们不这样称呼,则不会在屏幕上显示图像的窗口,因为cv2.waitKey()是唯一获取和处理事件的函数。

cv2.destroyWindow()函数接受要销毁的窗口的名称作为参数。 当必须删除当前程序显示的所有窗口时,我们使用cv2.destoyAllWindows()函数来执行此操作。 本书将在几乎所有的 OpenCV 程序中使用这些函数。

我们还可以预先创建一个具有特定名称的窗口,然后在以后需要时在程序中将图像与该窗口关联。 建议在处理图像之前先创建一个窗口。 以下代码片段演示了这一点:

cv2.namedWindow('Lena', cv2.WINDOW_AUTOSIZE)
cv2.imshow('Mandrill', img)
cv2.waitKey(0)
cv2.destroyWindow('Mandrill')

让我们将它们放在一起以获得以下脚本:

import cv2
img = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 1)
cv2.imshow('Mandrill', img)
cv2.waitKey(0)
cv2.destroyWindow('Mandrill')

前面的代码导入图像,将其显示在屏幕上,然后等待键盘上的击键关闭图像窗口。 以下是上述代码输出的屏幕截图:

Figure 1: Reading and visualizing a color image with OpenCV

图 1:使用 OpenCV 读取和可视化彩色图像

cv2.imwrite()函数将 NumPy ndarray保存到磁盘上的特定路径。 第一个参数是字符串,它是我们用来保存图像的文件的名称,第二个参数是具有图像的 NumPy 数组的名称。 此外, cv2.waitKey()函数可以检测键盘上的特定按键。 让我们看一下两个函数的演示,如下所示:

import cv2
img = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 1)
cv2.imshow('Mandrill', img)
keyPress = cv2.waitKey(0)
if keyPress == ord('q'):
    cv2.destroyWindow('Mandrill')
elif keyPress == ord('s'):
    cv2.imwrite('test.jpg', img)
    cv2.destroyWindow('Mandrill')

此处,keyPress = cv2.waitKey(0)行将键盘上的击键值保存在keyPress变量中。ord()函数接受单个字符,并返回一个整数,该整数表示字符的 Unicode(如果它是 Unicode 对象)。 基于keyPress变量,我们可以立即退出,也可以将映像保存到磁盘后退出。 例如,如果我们按Esc键,则cv2.waitKey()函数将返回27的值。

使用 Matplotlib 可视化图像

Matplotlib 是针对 Python 3 编程语言的非常强大的数据可视化库。 它还能够可视化图像。 它还提供了多种绘图选项,我们将在本书的后续章节中学习其许多功能。 让我们编写一个程序,该程序显示带有 Matplotlib 的图像,该图像使用 OpenCV cv2.imread()函数以灰度模式读取:

import cv2
img = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 0)
import matplotlib.pyplot as plt
plt.imshow(img)
plt.title('Mandrill')
plt.axis('off')
plt.show()

注意

您可以从这个页面上的帐户下载示例代码文件,以获取已购买的所有 Packt Publishing 图书。 如果您在其他地方购买了此书,则可以访问这个页面并注册以将文件直接通过电子邮件发送给您。

在前面的示例中,我们以灰度模式读取图像,并使用 Matplotlib plt.imshow()函数显示该图像。 以下是上述程序的输出:

Figure 2: Visualizing a BGR image as an RGB image with matplotlib

图 2:使用 Matplotlib 将 BGR 图像可视化为 RGB 图像

我知道图像看起来不自然。 这是因为我们在灰度模式下读取图像并使用默认的颜色图将其可视化。 在plt.imshow()中进行以下更改,我们将发现输出对我们而言更可口。 以下是输出:

Figure 3: Visualizing a grayscale image

图 3:可视化灰度图像

这全都与灰度图像有关。

cv2.imread()函数也可用于彩色图像。 它将它们读取并保存为蓝色,绿色和红色(BGR)像素的三维ndarray

但是,Matplotlib plt.imshow()函数将 NumPy ndarray显示为 RGB 颜色空间中的图像。 如果我们使用 OpenCV 的默认 BGR 格式使用cv2.imread()函数读取图像,并使用plt.imshow()函数显示图像,则plt.imshow()函数会将的蓝色强度值视为红色的强度值,反之亦然。 这会使图像出现失真的颜色。 对在相应行中的先前代码进行以下更改,然后再次运行该程序:

img = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 1)
plt.imshow(img)

进行更改并运行代码以查看颜色失真的彩色图像。 要解决此问题,我们必须将cv2.imread()函数在 BGR 颜色空间中读取的图像转换为 RGB 颜色空间,以便plt.imshow()函数将以对人眼和大脑有意义的方式进行渲染。 我们将使用cv2.cvtColor()函数来执行此任务,并且我们将在本书后面的部分中对此进行更详细的了解。

使用 OpenCV 和 NumPy 绘制几何形状

让我们学习如何使用 OpenCV 绘图函数绘制各种几何形状。 我们也将在此处使用 NumPy。

以下代码导入了此演示所需的所有必需库:

import cv2
import numpy as np

以下代码创建全零的 RGB ndarray。 它是所有像素均为黑色的图像:

image = np.zeros((200, 200, 3), np.uint8)

我们正在使用np.zeros()函数创建一个所有零元素的ndarray

我们先画一条线,因为它是一个简单的几何形状。 在以下代码的帮助下,我们将绘制一条线,其坐标为(0, 199)(199, 0),颜色为红色(BGR 中的(0, 0, 255)),厚度为 2 个像素 :

cv2.line(image, (0, 199), (199, 0), (0, 0, 255), 2)

用于绘制的所有 OpenCV 函数具有以下公共参数:

  • img:这是我们需要绘制几何形状的图像。
  • color:作为(B, G, R)的元组传递,以表示每种颜色的强度值介于0之间的颜色 ]和255
  • thickness:此参数的参数默认值为1。 对于所有几何上闭合的形状(例如椭圆,圆形和矩形),-1会使用指定为参数的颜色完全填充形状。
  • lineType: 它可以具有以下三个值之一:

8:八行连接(这是此参数的参数的默认值)。

4:四连接线。

cv2.LINE_AA:这表示抗锯齿(通常与具有曲线(例如椭圆或圆形)的几何形状一起使用)。

以下代码行将帮助我们绘制一个矩形,该矩形的对角线顶点为(20, 20)(60, 60),蓝色为蓝色:

cv2.rectangle(image, (20, 20), (60, 60), (255, 0, 0), 1)

下面的代码将帮助绘制一个圆,其中心位于(80, 80),半径为 10 个像素,绿色为填充色:

cv2.circle(image, (80, 80), 10, (0, 255, 0), -1)

以下代码行将帮助我们绘制不旋转的完整椭圆,以像素(99, 99)为中心的长轴和短轴的长度分别为 40 像素和 20 像素:

cv2.ellipse(image, (99, 99), (40, 20), 0, 0, 360, (128, 128, 128), -1)

以下代码绘制了具有四个点的多边形。 定义如下:

points = np.array([[100, 5], [125, 30], [175, 20], [185, 10]], np.int32)
points = points.reshape((-1, 1, 2))
cv2.polylines(image, [points], True, (255, 255, 0))

如果在polylines()函数的调用中将False传递为第三个参数的值,则它将所有点与线段连接在一起,并且不会绘制闭合形状。

我们还可以使用cv2.putText()函数在图像中打印文本。 以下代码将文本添加到图像的左下角(80, 180),将HERSHEY_DUPLEX作为字体,并且大小为文字 1,文字颜色为粉红色:

cv2.putText(image, 'Test', (80, 180), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 0, 255))

cv2.putText()函数接受以下字体之一作为参数:

  • FONT_HERSHEY_DUPLEX
  • FONT_HERSHEY_COMPLEX
  • FONT_HERSHEY_SIMPLEX
  • FONT_HERSHEY_PLAIN
  • FONT_HERSHEY_SCRIPT_SIMPLEX
  • FONT_HERSHEY_SCRIPT_COMPLEX
  • FONT_HERSHEY_TRIPLEX
  • FONT_HERSHEY_COMPLEX_SMALL

使用以下熟悉的代码片段显示输出图像:

cv2.imshow('Shapes', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

前面代码的输出如下:

Figure 4: Drawing geometric shapes

图 4:绘制几何形状

注意

如果几何形状的像素重叠,则这些像素将始终具有由最新几何函数分配的值。 例如,椭圆与上图中的线和圆重叠。

作为练习,更改传递给所有几何函数的参数值,然后再次运行代码以更好地理解功能。

使用 GUI

到目前为止,我们已经知道如何使用 OpenCV cv2.namedWindow()函数的调用来创建命名窗口。 现在将演示如何使用cv2.CreateTrackbar()函数创建跟踪栏,如何将其与命名窗口关联,以及如何使用这些跟踪栏选择颜色通道的值。 RGB 色彩空间。 让我们从下面的代码开始:

import numpy as np
import cv2
def empty(z):
    pass
image = np.zeros((300, 512, 3), np.uint8)
cv2.namedWindow('Palette')
cv2.createTrackbar('B', 'Palette', 0, 255, empty)
cv2.createTrackbar('G', 'Palette', 0, 255, empty)
cv2.createTrackbar('R', 'Palette', 0, 255, empty)
while(True):
    cv2.imshow('Palette', image)
    if cv2.waitKey(1) == 27 :
        break
    blue = cv2.getTrackbarPos('B', 'Palette')
    green = cv2.getTrackbarPos('G', 'Palette')
    red = cv2.getTrackbarPos('R', 'Palette')
    image[:] = [blue, green, red]
cv2.destroyWindow('Palette')

在前面的代码中,我们首先创建一个图像,其中所有像素都涂成黑色,并创建一个名称为面板为的窗口。 cv2.createTrackbar()函数创建一个跟踪栏。 以下是此函数接受的参数列表:

  • name:轨迹栏的名称。
  • window_name:与跟踪栏关联的输出窗口的名称。
  • value:创建跟踪栏滑块时的初始值。
  • count:轨迹栏滑块的最大值(滑块的最小值始终为 0)。
  • Onchange():当我们更改轨迹栏滑块的位置时,将调用此函数。

我们创建了一个函数并将其命名为empty()。 更改轨迹栏的滑块时,我们不打算执行任何活动。 我们只是将此函数的调用传递给cv2.createTrackbar()函数。 cv2.getTrackbarPos()函数的调用返回轨迹栏滑块的最新位置。 根据所有三个轨迹栏的滑块的位置,我们设置调色板的颜色。 当我们按下键盘上的Esc键时,应用关闭。 我们创建的应用应如下所示:

Figure 5: A BGR color palette

图 5:BGR 调色板

OpenCV 还提供了许多处理事件的功能。 接下来,我们将进行探讨。

事件处理和原始绘图应用

OpenCV 可以识别各种键盘和鼠标事件。 我们可以按照以下说明通过查看事件列表。 通过在命令提示符下运行python3命令以交互方式打开 Python 3 解释器,然后运行以下语句:

>>> import cv2
>>> events = [i for i in dir(cv2) if 'EVENT' in i]
>>> print(events)

它将显示以下输出:

['EVENT_FLAG_ALTKEY', 'EVENT_FLAG_CTRLKEY', 'EVENT_FLAG_LBUTTON', 'EVENT_FLAG_MBUTTON', 'EVENT_FLAG_RBUTTON', 'EVENT_FLAG_SHIFTKEY', 'EVENT_LBUTTONDBLCLK', 'EVENT_LBUTTONDOWN', 'EVENT_LBUTTONUP', 'EVENT_MBUTTONDBLCLK', 'EVENT_MBUTTONDOWN', 'EVENT_MBUTTONUP', 'EVENT_MOUSEHWHEEL', 'EVENT_MOUSEMOVE', 'EVENT_MOUSEWHEEL', 'EVENT_RBUTTONDBLCLK', 'EVENT_RBUTTONDOWN', 'EVENT_RBUTTONUP']

我们可以编写代码来处理其中的一些事件,并创建一个简单而原始的绘画应用。 让我们使用以下代码导入所需的库:

import cv2
import numpy as np

创建一个黑色背景和一个命名窗口:

windowName = 'Drawing'
img = np.zeros((512, 512, 3), np.uint8)
cv2.namedWindow(windowName)

定义一个自定义函数,称为draw_circle()

def draw_circle(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDBLCLK:
        cv2.circle(img, (x, y), 40, (0, 255, 0), -1)
    if event == cv2.EVENT_MBUTTONDOWN:
        cv2.circle(img, (x, y), 20, (0, 0, 255), -1)
    if event == cv2.EVENT_LBUTTONDOWN:
        cv2.circle(img, (x, y), 30, (255, 0, 0), -1)

在前面的定义中,我们正在使用鼠标事件上的各种属性绘制圆。 现在,让我们调用setMouseCallback()函数,并将窗口的名称和draw_circle()函数作为参数传递给它:

cv2.setMouseCallback(windowName, draw_circle)

此调用将draw_circle()函数与给定窗口的鼠标事件绑定在一起。 最后,我们编写显示图像窗口的循环,并在按下Esc键时退出:

while(True):
    cv2.imshow(windowName, img)
    if cv2.waitKey(20) == 27:
        break
cv2.destroyAllWindows()

运行整个代码,您将看到以下输出:

Figure 6: A simple paint application

图 6:一个简单的绘画应用

当我们对左,中和下按钮的双击事件进行编程时,根据这些事件和光标的位置,您的输出将有所不同。

本书中我们将少量使用 OpenCV 中的绘图 API。 我们在本书中最常使用的功能与网络摄像头有关。 下一部分将专门介绍网络摄像头与 OpenCV 和 Raspberry Pi 的接口和使用。

使用 USB 网络摄像头

相机是图像传感器。 就是说,模拟相机和电影胶卷相机在胶片上记录图像。 数码相机具有用于捕获图像的数码传感器,它们以电子格式存储在各种类型的存储介质上。 数码相机的一个子集是 USB 网络摄像头。 顾名思义,这些网络摄像头可以通过 USB 连接到计算机,因此名称为 USB 网络摄像头。 在本节中,我们将详细了解 USB 网络摄像头与 Raspberry Pi 的接口以及如何使用 Shell 脚本,Python 3 和 OpenCV 进行编程。

注意

所有这些网络摄像头均可与 Raspberry Pi 板一起使用。 但是,一些网络摄像头可能有问题。 这个 URL 包含许多网络摄像头的列表以及有关兼容性的详细信息。

本书中的所有程序均已通过 RPi 4B 和 Logitech C310 网络摄像头进行了测试。 您可以在这个页面上查看其产品页面。

使用开发板上的 USB 端口将 USB 网络摄像头连接到 RPi,然后在终端中运行以下命令:

lsusb

此命令的输出显示连接到 Linux 计算机的所有 USB 设备的列表。 以下是我的 RPi 板上显示的输出:

pi@raspberrypi:~/book/chapter04 $ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 005: ID 046d:081b Logitech, Inc. Webcam C310
Bus 001 Device 004: ID 1c4f:0002 SiGma Micro Keyboard TRACER Gamma Ivory
Bus 001 Device 003: ID 046d:c077 Logitech, Inc. M105 Optical Mouse
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

从前面的输出中可以看到,第二个条目对应于连接到 RPi 板的 USB 网络摄像头。 我们还可以看到 USB 鼠标和 USB 键盘连接到 RPi 板。

使用网络摄像头捕获图像

现在,让我们演示如何使用连接到 RPi 的 USB 网络摄像头捕获图像。 我们可以通过在终端上运行以下命令来安装fswebcam工具:

sudo apt-get install fswebcam

安装后,我们可以运行以下命令以使用 USB 网络摄像头捕获照片:

fswebcam -r 1280x960 --no-banner ~/book/chapter04/camtest.png

此命令使用连接到 RPi 的 USB 网络摄像头捕获分辨率为1280 x 960像素的图像。 传递给命令的命令行--no-banner参数禁用时间戳记的标题。 图像以文件名作为最后一个参数保存。 如果我们重复运行此命令,则捕获的新照片将被覆盖到同一文件中。 因此,下一次运行命令时,如果我们不想覆盖先前的文件,则必须将不同的文件名作为参数传递给命令。

注意

如果您想了解有关fswebcam(或与此相关的任何 Linux 命令)的更多信息,则可以在命令提示符下运行man fswebcam命令。

缩时摄影

用相机以固定间隔捕获照片,并以比其捕获时更高的帧频回放它们,这被称为缩时摄影。 例如,如果我们以每分钟一张照片的速度拍摄 10 小时的照片,那么我们将有 600 张照片。 如果我们将它们全部拼接成视频并以每秒 30 张照片的帧速率播放,我们将获得 20 秒的视频。 该视频是,也称为间隔拍摄视频。 我们可以将 RPi 板与 USB 网络摄像头配合使用。 我们已经学习了如何将 USB 网络摄像头与 RPi 板配合使用。 我们还了解了fswebcam工具的用法。 我们将编写一个脚本来捕获文件名中带有时间戳的图像。 然后,我们将该脚本添加到crontab中,以定期执行该脚本。 Cron 是针对类似 Unix 操作系统的作业计划。 它由名为crontab(Cron 表)的文件驱动。 它是 Unix 配置文件,用于指定要在特定时间或间隔运行的脚本或程序。

让我们创建一个名称为timelapse.sh的 Shell 脚本,并将其保存在磁盘上我们选择的位置。 我们必须将以下代码添加到脚本文件并保存:

#!/bin/bash
DATE=$(date +"%Y-%m-%d_%H%M")
fswebcam -r 1280x960 --no-banner Image_$DATE.png 

通过运行以下命令,使脚本的模式可执行:

chmod +x timelapse.sh

该脚本使用 USB 网络摄像头拍摄照片,然后将其保存到磁盘中的某个位置。 每次捕获的图像都有一个新的文件名,因为捕获图像时文件名带有时间戳。 我们必须手动执行一次此脚本,以确保它可以正常工作并且以Image_<timestamp>.png的文件名格式捕获图像。

检查脚本是否存在任何问题后,必须定期执行脚本以捕获时间间隔序列的图像。 为此,我们必须将其添加到crontab中。crontab中条目的语法如下:

1 2 3 4 5 /location/command

让我们检查语法中术语的含义:

  • 1:分钟的位置(范围为 0-59)
  • 2:小时的位置(范围为 0-23)
  • 3:日期的位置(范围为 0-31)
  • 4:月份的位置(范围为 0 到 12,1 月份为 1)
  • 5:星期的位置(范围为 0-7,星期日为 7 或 0)
  • /location/command:要计划的脚本或命令名称

因此,crontab每分钟运行一次timelapse.sh脚本的条目如下:

* * * * * /home/pi/book/chapter04/timelapse.sh 2>&1

使用以下命令打开用户picrontab

crontab –e

这将打开crontab。 当我们第一次在 RPi 上执行此操作时,它将询问您选择哪个文本编辑器。 通过输入1选择nano选项。 将上一行作为条目添加到crontab中。 然后保存并退出。

一旦退出crontab,它将向我们显示以下消息:

crontab: installing new crontab

一旦执行此操作,就可以开始设置游戏中时光倒流。 我们可以将crontab条目中的设置更改为我们选择的设置。 要每 5 分钟运行一次脚本,请使用以下命令:

*/5 * * * * /home/pi/book/chapter04/timelapse.sh 2>&1

要每 2 小时运行一次脚本,请使用以下命令:

* */2 * * * /home/pi/book/chapter04/timelapse.sh 2>&1

一旦捕获了所有用于延时拍摄的图像,我们就必须将它们编码为每秒 24、25、29 或 30 帧(FPS)的帧频的视频。 这些均为标准帧速率。 我更喜欢使用 30 FPS 对视频进行编码。 Raspberry Pi 是用于视频编辑的慢速计算机。 建议您将图像复制到速度更快的计算机上以对视频进行编码。 对于 Linux 计算机,我更喜欢使用命令行mencoder工具。 我们也可以使用其他工具或视频编辑工具来完成此任务。 以下是在 Raspberry Pi 或任何其他 Linux 计算机上使用mencoder创建延时视频的步骤:

  1. 在命令提示符上使用以下命令安装 MEncoder:

     sudo apt-get install mencoder -y
    
  2. 发出以下命令,导航到输出目录:

     cd /home/pi/book/chapter04
    
  3. 使用以下命令创建在间隔拍摄序列中要使用的图像的列表:

     ls Image_*.png > timelapse.txt
    
  4. 最后,我们可以使用以下命令来创建漂亮的延时视频:

    mencoder -nosound -ovc lavc -lavcopts vcodec=mpeg4:aspect=16/9:vbitrate=8000000 -vf scale=1280:960 -o timelapse.avi -mf type=jpeg:fps=30 mf://@timelapse.txt
    

此在我们正在运行命令的当前目录(也称为当前工作目录)的当前目录中创建带有timelapse.avi文件名的视频。 。 视频的帧频为 30 FPS。 很快,我们将学习如何播放此视频文件。

使用网络摄像头的录像

我们可以使用连接到 RPi 的 USB 网络摄像头,通过命令行ffmpeg工具录制实时视频。 我们可以使用以下命令安装ffmpeg工具:

sudo apt-get install ffmpeg

我们可以使用以下命令来录制视频:

ffmpeg -f video4linux2 -r 25 -s 544x288 -i /dev/video0 test.avi

我们可以通过按键盘上的Ctrl + C终止录制视频的操作。

我们可以使用命令行omxplayer工具播放视频。 它预装了 Raspbian 的最新版本,因此我们不必单独安装它。 要播放具有timelapse.avi文件名的文件,请使用命令提示符导航到视频文件的位置,然后运行以下命令:

omxplayer timelapse.avi

我们甚至可以双击 Raspbian GUI 中的视频文件,以使用 VLC 媒体播放器进行播放。

使用 Python 和 OpenCV 和网络摄像头捕获图像

让我们学习如何使用 Python 3 和 OpenCV 通过连接到 RPi 的网络摄像头捕获图像:

import cv2
import matplotlib.pyplot as plt
cap = cv2.VideoCapture(0)
if cap.isOpened():
    ret, frame = cap.read()
else:
    ret = False
print(ret)
print(type(frame))
cv2.imshow('Frame from Webcam', frame)
cv2.waitKey(0)
cap.release()
cv2.destroyAllWindows()

在先前的代码片段中, cv2.VideoCapture()函数创建一个对象,以使用连接到 RPi 的网络摄像头捕获视频。 它的参数可以是视频设备的索引或视频文件。 在这种情况下,我们正在传递视频设备的索引,即0。 如果我们有更多的摄像机连接到 RPi 板上,则可以根据选择的摄像机来传递适当的设备索引。 如果仅连接一台摄像机,则只需传递0即可。

通过运行以下命令,我们可以找出摄像机的数量以及与这些摄像机关联的设备索引:

ls -l /dev/video*

cap.read()函数返回布尔值ret值和包含捕获的图像的 NumPy ndarray。 如果捕获图像的操作成功,则ret的布尔值将为True; 否则,它将具有布尔值False。 前面的代码使用/dev/video0标识的 USB 摄像机捕获图像,将其显示在屏幕上,然后最终将其保存为文件名test.png到磁盘。 cap.release()函数释放视频捕获设备。

使用 Python 和 OpenCV 和网络摄像头一起直播视频

我们可以对使用先前的代码,并对进行一些修改,以显示来自 USB 网络摄像头的实时视频流:

import cv2
windowName = "Live Video Feed"
cv2.namedWindow(windowName)
cap = cv2.VideoCapture(0)
if cap.isOpened():
    ret, frame = cap.read()
else:
    ret = False
while ret:
    ret, frame = cap.read()
    cv2.imshow(windowName, frame)
    if cv2.waitKey(1) == 27:
        break
cv2.destroyAllWindows()
cap.release()

之前的代码显示了网络摄像头捕获的实时视频,直到我们按键盘上的Esc键。 前面的代码示例是使用连接到 RPi 板的 USB 网络摄像头捕获的实时视频的处理的所有代码示例的模板。

网络摄像头的分辨率

我们可以使用cap.get()读取网络摄像头的属性。 我们必须传递3以获得宽度,传递4以获得高度。 我们也可以用cap.set()设置属性。 以下代码演示了这一点:

import cv2
windowName = "Live Video Feed"
cv2.namedWindow(windowName)
cap = cv2.VideoCapture(0)
print('Width : ' + str(cap.get(3)))
print('Height : ' + str(cap.get(4)))
cap.set(3, 5000)
cap.set(4, 5000)
print('Width : ' + str(cap.get(3)))
print('Height : ' + str(cap.get(4)))
if cap.isOpened():
    ret, frame = cap.read()
else:
    ret = False
while ret:
    ret, frame = cap.read()
    cv2.imshow(windowName, frame)
    if cv2.waitKey(1) == 27:
        break
cv2.destroyAllWindows()
cap.release()

在前面的代码中,我们将高度和宽度都设置为5000。 网络摄像头不支持此分辨率,因此高度和宽度都将设置为网络摄像头支持的最大分辨率。 运行前面的代码,并观察命令提示符中打印的输出。

网络摄像头的 FPS

我们可以检索我们正在使用的网络摄像头的 FPS,也可以自己计算实际的 FPS。 作为网络摄像头属性检索的 FPS 和计算出的 FPS 可能不同。 让我们检查一下。 导入所有必需的库:

import time
import cv2

接下来,启动一个用于视频捕获的对象:

cap = cv2.VideoCapture(0)

我们可以使用cap.get()获取相机分辨率,如下所示:

fps = cap.get(cv2.CAP_PROP_FPS)
print("FPS with CAP_PROP_FPS : {0}".format(fps))

然后,我们将连续捕获 120 帧。 我们记录操作前后的时间,如下所示:

num_frames = 120
print("Capturing {0} frames".format(num_frames))
start = time.time()
for i in range(0, num_frames):
    ret, frame = cap.read()
end = time.time()

然后,最后,我们计算捕获帧所需的实际时间,然后可以使用以下公式计算 FPS:

代码如下:

seconds = end - start
print("Time taken : {0} seconds".format(seconds))
fps = num_frames / seconds
print("Actual FPS calculated : {0}".format(fps))
cap.release()

运行整个程序,输出应如下所示:

FPS with CAP_PROP_FPS : 30.0
Capturing 120 frames
Time taken : 9.86509919166565 seconds
Actual FPS calculated : 12.164094619685105

由于硬件限制,我们通常永远不会从属性中获取 FPS。

保存网络摄像头视频

我们使用 OpenCV cv2.VideoWriter()函数将实时 USB 网络摄像头流保存到磁盘上的视频文件中。 以下代码演示了这一点:

import cv2
windowName = "Live Video Feed"
cv2.namedWindow(windowName)
cap = cv2.VideoCapture(0)
filename = 'output.avi'
codec = cv2.VideoWriter_fourcc('W', 'M', 'V', '2')
framerate = 30
resolution = (640, 480)
Output = cv2.VideoWriter(filename, codec,
                         framerate, resolution)
if cap.isOpened():
    ret, frame = cap.read()
else:
    ret = False
while ret:
    ret, frame = cap.read()
    Output.write(frame)
    cv2.imshow(windowName, frame)
    if cv2.waitKey(1) == 27:
        break
cv2.destroyAllWindows()
cap.release()

在前面的代码中, cv2.VideoWriter()函数的调用接受的参数,其参数如下:

  • filename:这是要写入磁盘的视频文件的名称。
  • fourcc:此表示四字符代码。 为此,我们使用cv2.VideoWriter_fourcc()函数。 此函数接受四字符代码作为参数。 一些受支持的四字符代码格式是WMV2MJPGH264WMV1DIVXXVID。 您可以在这个页面上了解有关四字符代码的更多信息。
  • framerate:这是指要捕获的视频的 FPS。
  • resolution:这是捕获视频并将其保存在磁盘上时的像素分辨率。

上面的代码记录视频,直到按下键盘上的Esc键,然后使用cv2.VideoWriter()函数的参数中指定的文件名将其保存在磁盘上。 。

使用 OpenCV 播放视频

我们可以轻松使用 OpenCV 播放视频。 我们只需要将视频文件的名称传递给VideoCapture()函数即可代替网络摄像头的索引(在本例中为0)。 为了确定播放的 FPS,我们需要将适当的参数传递给waitKey()函数的调用。 假设我们要以 25 FPS 的速度播放视频,那么可以使用1000/25 = 40公式来计算要传递的参数。 我们知道waitKey()等待我们将作为参数传递给它的毫秒数。 并且,一秒具有 1,000 毫秒,因此是公式。 对于 30 FPS,这将是 33.3。 让我们看下面的代码:

import cv2
windowName = "OpenCV Video Player"
cv2.namedWindow(windowName)
filename = 'output.avi'
cap = cv2.VideoCapture(filename)
while(cap.isOpened()):
    ret, frame = cap.read()
    if ret:
        cv2.imshow(windowName, frame)
        if cv2.waitKey(33) == 27:
            break
    else:
        break
cv2.destroyAllWindows()
cap.release()

前面的程序以 30 FPS 的帧速率播放视频文件,并在最后一帧之后或当我们按键盘上的Esc键时终止。 您可能想使用该程序,并尝试通过将参数的值更改为cv2.waitKey()函数的调用来更改输出帧速率。

在下一节中,我们将更详细地研究 Pi 相机模块。

Pi 摄像头模块

网络摄像头使用 USB 端口与计算机连接。 这就是为什么我们可以在具有 USB 端口的任何计算机上使用它的原因。 Pi 相机模块(也称为 Pi 相机板的)是专门为 RPi 板制造的传感器。 Raspberry Pi 基金会和许多其他第三方制造商生产它们。 基本上,它们是带有专用图像传感器的 PCB(这就是为什么它们被称为 Pi 相机板)的原因。

Pi 相机板没有 USB 端口。 它通过摄像机串行接口CSI)接口板连接到 Raspberry Pi。 由于使用 CSI 的专用连接,Pi 相机板的性能比 USB 网络摄像头要好得多。 我们可以将 Python 3 与连接到 RPi 的 Pi 摄像机模块一起使用,以编程方式捕获视频和静止图像。 除 Raspberry Pi 外,其他任何计算机都不能使用 Pi 相机板(以及少数支持单板相机连接的单板计算机)。

摄像头模块有两种类型:摄像头模块和 NoIR 模块。 摄像头模块非常适合白天和光线充足的场景。 NoIR 模块实质上是不带红外IR)过滤器的相机模块。 在白天或光线充足的场景中,效果不佳。 但是,当与红外光一起使用时,它在低光或黑暗场景中非常有用。

您可以在这个页面产品页面 上找到这两个模块的最新版本。这些相机板/模块已经出现了几代,即 V1 和 V2。 V1 的像素为 5 百万像素,不再生产。 V2 是最新的,并且具有 8 百万像素传感器。 您可以在这个页面上了解它们之间的区别。

所有摄像机都带有可拆卸的色带,可通过摄像机串行接口CSI)端口将其连接到 RPi 板上。 以下是摄像头模块和色带的照片:

Figure 7: The Pi camera board and the CSI interface ribbon

图 7:Pi 摄像机板和 CSI 接口功能区

我们必须将蓝色端连接到 RPi 板的 CSI 端口,另一端连接到摄像头板。

RPi Zero 和 RPi Zero W 配备了较小的 CSI 端口。 有单独的功能区。 以下是这种丝带的照片:

Figure 8: Mini CSI ribbon

图 8:迷你 CSI 功能区

以下是连接到 RPi Zero 板的 Pi NoIR 板的照片:

Figure 9: Pi NoIR with RPi Zero

图 9:RPi 为零的 Pi NoIR

我已经提到 Pi Camera V1 不再生产。 您会在网上找到许多这些低价的 V1 模块(从 5 美元到 7 美元)。 此外,还有其他制造商生产与 RPi CSI 端口兼容的类似板。 它们也可以在线购买。

使用raspistillraspivid工具捕获图像和视频

为了使用 RPi 的摄像头模块捕获静态照片和运动视频,我们需要使用命令行raspistillraspivid工具。 要捕获图像,请运行以下命令:

raspistill -o test.png

此命令使用test.png文件名捕获图像并将其保存在当前目录中。

要使用 RPi 摄像机模块捕获 20 秒的视频,请在命令提示符中运行以下命令:

raspivid -o vid.h264 -t 20000

fswebcamffmpeg工具不同,raspistillraspivid工具不会向命令提示符写入任何内容。 因此,我们必须检查当前目录中是否有任何输出。 此外,在执行raspistillraspivid工具之后,我们可以运行以下命令来检查这些命令是否已成功执行:

echo $?

许多计算机和操作系统无法直接播放 H.264 格式的视频。 为此,我们需要将它们包装为流行且广泛支持的 MP4 格式。 为此,我们需要一个称为MP4Box的命令行工具。 我们可以通过在命令提示符上运行以下命令来安装它:

sudo apt install -y gpac

现在,录制 H.264 视频:

raspivid -t 30000 -w 640 -h 480 -fps 25 -b 1200000 -p 0,0,640,480 -o pivideo.h264

将其包装为 MP4 格式,然后删除原始文件(如果需要),如下所示:

MP4Box -add pivideo.h264 pivideo.mp4
rm pivideo.h264

就像fswebcam工具一样,raspistill工具也可以用于捕获时间间隔序列。 在我们先前准备的timelapse.shShell 脚本中,用适当的raspistill命令替换调用fswebcam工具的行,以记录定时拍摄的照片序列。 然后,使用 RPi 或任何其他 Linux 计算机上的mencoder工具来创建一段精美的延时视频。

在 Python 3 中使用 picamera

picamera是 Python 包,可为 RPi 摄像机模块提供编程接口。 Raspbian 的最新版本已安装picamera。 如果尚未安装,则可以通过运行以下命令进行安装:

pip3 install picamera
pip3 install "picamera[array]"

以下程序快速演示了picamera模块捕获图片的基本用法:

from time import sleep
from picamera import PiCamera
camera = PiCamera()
camera.resolution = (1024, 768)
camera.start_preview()
sleep(2)
camera.capture('test.png')

我们在前两行中导入了timepicamera库。 调用start_preview()函数将开始预览要捕获的场景。 sleep(5)函数将等待 5 秒钟,然后capture()函数将照片捕获并将其保存到参数中指定的文件中。

picamera模块提供用于延时摄影的capture_continuous()函数。 让我们演示如何在以下程序中使用它:

camera = PiCamera()
camera.start_preview()
sleep(2)
for filename in camera.capture_continuous('img{counter:03d}.png'):
        print('Captured %s' % filename)
        sleep(1)

在前面的代码中,capture_continuous()函数通过将 Pi 摄像机板连接到 RPi 来记录延时拍摄的照片。 这样,我们不必依赖crontab工具来连续调用该脚本,因为我们可以通过编程更好地控制它。

我们可以使用start_recording()wait_recording()stop_recording()函数来录制视频,如下所示:

import picamera
camera = picamera.PiCamera()
camera.resolution = (320, 240)
camera.start_recording('my_video.h264')
camera.wait_recording(5)
camera.stop_recording()

我们可以向图像添加文本,如下所示:

from time import sleep
from picamera import PiCamera
camera = PiCamera()
camera.resolution = (1024, 768)
camera.start_preview()
camera.annotate_text = 'Hello World!'
sleep(2)
camera.capture('test.png')

我们可以将图像存储在三维 NumPy 数组中,如下所示:

import time, picamera
import numpy as np
with picamera.PiCamera() as camera:
        camera.resolution = (320, 240)
        camera.framerate = 24
        time.sleep(2)
        output = np.empty((240, 320, 3), dtype=np.uint8)
        camera.capture(output, 'rgb')
        print(type(output))

我们还可以将捕获的图像存储在与 OpenCV 图像格式(BGR)兼容的 NumPy 数组中:

import time, picamera
import numpy as np
with picamera.PiCamera() as camera:
 camera.resolution = (320, 240)
 camera.framerate = 24
 time.sleep(2)
 image = np.empty((240 * 320 * 3, ), dtype=np.uint8)
 camera.capture(image, 'bgr')
 image = image.reshape((240, 320, 3))
 print(type(image))

这将以 OpenCV 首选的 BGR 格式存储图像。 我们可以使用cv2.imshow()函数显示此图像。

使用 RPi 相机模块和 Python 3 录制视频

我们已经学习了如何使用连接到 RPi 的 USB 网络摄像头以及 Python 3 和 OpenCV 的组合来录制视频。 我注意到,相同的代码也适用于 RPi 摄像机模块。 我们只需要将 RPi 摄像机模块连接到 RPi 并断开 USB 网络摄像头,即可使代码与 RPi 摄像机模块一起使用并使用该代码录制视频。 请继续尝试一下!

总结

在本章中,我们学习了如何处理图像和视频。 我们还学习了如何使用 USB 网络摄像头和 RPi 相机板捕获图像。 我们还学习了 GUI 的基础知识以及 OpenCV 提供的事件处理功能。 我们已经获得了有关 Shell 和 Python 3 编程的良好动手经验。 在本书中,我们将使用在这里学到的图像和视频采集与处理技术。

在下一章中,我们将学习图像处理的基础知识以及如何使用 NumPy 和 OpenCV.s 编写程序。

五、图像处理基础

在上一章中,我们了解并演示了用于图像处理和计算机视觉应用的捕获图像和视频的各种方法。 我们学习了如何广泛使用命令提示符和 Python 3 编程来读取图像以及与 USB 网络摄像头和 Raspberry Pi 摄像头模块进行接口。

在本章中,我们将研究如何使用 NumPy,OpenCV 和 Matplotlib 对图像执行基本的算术和逻辑运算。 我们还将详细了解不同的颜色通道和图像属性。

以下是本章将涵盖的主题列表:

  • 检索图像属性
  • 图像的基本操作
  • 图像上的算术运算
  • 融合和过渡图像
  • 将图像与常数相乘
  • 创建图像底片
  • 图像上的按位逻辑运算

本章有许多使用 Python 3 编程的动手练习。 我们将使用许多概念,例如从磁盘读取图像并对其进行可视化,这是我们在本章中演示图像操作时在前几章中学到的。

技术要求

可以在 GitHub 上找到本章的代码文件。

观看以下视频,以查看这个页面上的“正在执行的代码”。

检索图像属性

我们可以使用 NumPy 检索和使用许多属性,例如图像的字节的数据类型,尺寸,形状和大小。 通过在命令提示符下运行python3命令来打开 Python 3 解释器。 然后,一个接一个地运行以下语句:

>>> import cv2
>>> img = cv2.imread('/home/pi/book/dataset/4.1.01.tiff', 0)
>>> print(type(img))

以下是这些语句的输出:

<class 'numpy.ndarray'>

前面的输出确认 OpenCVimread()函数读取图像并将其存储为 NumPy 的ndarray格式。 以下语句打印其读取的图像的尺寸:

>>> print(img.ndim)
2

以灰度模式读取图像,这就是为什么它是二维图像。 它只有一个由灰度强度组成的通道。 现在,让我们看看它的形状:

>>> print(img.shape)
(256, 256)

前面的语句以像素为单位打印高度和宽度。 让我们看看图像的大小:

>>> print(img.size)
65536

如果我们也将图像的高度和宽度相乘,则会得到前面的数字。 让我们看看 NumPyndarray的数据类型:

>>> print(img.dtype)
uint8

这是 8 位无符号整数,用于存储像素的灰度强度值。 强度从0255有所不同,这是 8 位无符号数据类型的限制。 每个像素消耗一些内存字节。 让我们看看如何找出它总共消耗了多少字节,如下所示:

>>> print(img.nbytes)
65536

现在,让我们对彩色图像重复相同的练习。 为此,让我们在彩色模式下读取相同的图像:

>>> img = cv2.imread('/home/pi/book/dataset/4.1.01.tiff', 1)
>>> print(type(img))

以下是此输出:

<class 'numpy.ndarray'>

让我们检查尺寸数:

>>> print(img.ndim)
3

我们已经以彩色模式读取了图像,它是三维 NumPy ndarray。 这两个维度之一表示高度和宽度,而其中一个维度表示颜色通道。 让我们现在检查形状:

>>> print(img.shape)
(256, 256, 3)

前两个值代表像素的宽度和高度。 最后一个值代表通道数。 这些通道代表像素的蓝色,绿色和红色的强度值。 让我们看看图像的大小:

>>> print(img.size)
196608

如果我们将先前输出中的所有三个数字相乘(2562563),我们将得到19,6608ndarray的数据类型将相同(uint8)。 让我们确认一下:

>>> print(img.dtype)
uint8

让我们看看图像在主内存中占据了多少字节:

>>> print(img.nbytes)
196608

在下一节中,我们将学习有关图像的基本操作。

图像的基本操作

让我们执行的一些基本操作,例如拆分和合并彩色图像的通道以及向图像添加边框。 我们将以交互模式继续此演示。 让我们导入 OpenCV 并读取彩色图像,如下所示:

>>> import cv2
>>> img = cv2.imread('/home/pi/book/dataset/4.1.01.tiff', 1)

对于任何图像,原点-(0, 0)像素-是左上角的像素。 通过运行以下语句,我们可以检索所有通道的强度值:

>>> print(img[10, 10])
[34 38 44]

这些分别是像素(10, 10)的蓝色,绿色和红色通道的强度值。 如果只想访问单个通道的像素,请运行以下语句:

>>> print(img[10, 10, 0])
34

前面的输出34是蓝色通道的强度。 同样,我们可以分别通过img[10, 10, 0]img[10, 10, 0]访问绿色和红色通道。

将图像分成通道

让我们编写一个简单的程序,将图像分成其组成通道。 有多种方法可以做到这一点。 OpenCV 提供split()函数来执行此操作。 让我们看一下这个例子:

>>> import cv2
>>> img = cv2.imread('/home/pi/book/dataset/4.1.01.tiff', 1)
>>> b, g, r = cv2.split(img)

上一个列表中的最后一条语句将彩色图像拆分为其组成通道。 我们还可以使用 NumPyndarray索引,以一种更快的方法来分离通道,如下所示:

>>> b = img[:, :, 0]
>>> g = img[:, :, 1]
>>> r = img[:, :, 2]

split()函数(从计算上来说)比以前的 NumPy 索引方法要昂贵一些。 我们还可以合并渠道,如下所示:

>>> img1 = cv2.merge((b, g, r))

前面的代码合并所有组成通道以形成原始图像。 您可能还想创建一个 Python 3 脚本文件,向其中添加所有前面的代码,并使用cv2.imshow()函数可视化图像。

接下来,我们将学习如何为图像添加边框。

为图像添加边框

我们可以使用copyMakeBorder()函数在图像中添加边框。 它接受以下参数:

  • src:图像

  • topbottomleftright:以像素数表示的边框宽度

  • borderType: 边框的类型。 可以是以下类型之一:

    a)cv2.BORDER_REFLECT

    b)cv2.BORDER_REFLECT_101cv2.BORDER_DEFAULT

    c)cv2.BORDER_REPLICATE

    d)cv2.BORDER_WRAP

    e)cv2.BORDER_CONSTANT:添加具有恒定颜色的边框。 边框颜色的值是以下参数。

  • value:边框类型为cv2.BORDER_CONSTANT时边框的颜色

让我们来看一些图像边框的例子。 考虑以下程序:

import cv2
img = cv2.imread('/home/pi/book/dataset/4.1.01.tiff', 1)
b1 = cv2.copyMakeBorder(img, 10, 10, 10, 10, cv2.BORDER_WRAP)
b2 = cv2.copyMakeBorder(img, 10, 10, 10, 10, cv2.BORDER_CONSTANT, value=[255, 0, 0])
cv2.imshow('Wrap', b1)
cv2.imshow('Constant', b2)
cv2.waitKey(0)
cv2.destroyAllWindows()

前面代码的输出如下:

igure 5.1 – Demonstration of borders

图 5.1 –边界演示

您可能要尝试其他一些边框选项。 下面的代码创建一个复制样式的边框:

cv2.copyMakeBorder(img, 10, 10, 10, 10, cv2.BORDER_REPLICATE)

下面的代码创建一个不同的复制样式边框:

cv2.copyMakeBorder(img, 10, 10, 10, 10, cv2.BORDER_REFLECT)
cv2.copyMakeBorder(img, 10, 10, 10, 10, cv2.BORDER_REFLECT_101)

这就是我们为图像创建各种类型的边框的方式。 在下一节中,我们将研究对图像执行算术运算。

图像的算术运算

我们知道图像只不过是 NumPy ndarray,我们可以对图像执行算术运算,就像我们可以对ndarray进行运算一样。 如果我们知道如何将数值或算术运算应用于矩阵,那么当这些运算的操作数是图像时,进行相同操作就不会有任何麻烦。 图像必须具有相同的大小,并且必须具有相同数量的通道,以便我们对它们执行算术运算,并且这些运算是在单个像素上执行的。 有许多算术运算,例如加法和减法。 首先是加法运算。 我们可以使用 OpenCV 中的 NumPy 加法add()函数来添加两个图像,如下所示:

import cv2
img1 = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 1)
img2 = cv2.imread('/home/pi/book/dataset/4.2.05.tiff', 1)
cv2.imshow('NumPy Addition', img1 + img2 )
cv2.imshow('OpenCV Addition', cv2.add(img1, img2))
cv2.waitKey(0)
cv2.destroyAllWindows()

以下是以上代码的输出:

Figure 5.2 – Addition with OpenCV and NumPy

图 5.2 –使用 OpenCV 和 NumPy 进行加法

我们可以清楚地看到出现在输出中的两个图像之间的差异。 原因是 OpenCV 的add()函数是饱和运算,而 NumPy 的加法运算符是模运算。 让我们详细了解这意味着什么。 以交互方式打开 Python 3 并运行以下语句:

>>> import numpy as np
>>> import cv2
>>> a = np.array([240], np.uint8)
>>> b = np.array([20], np.uint8)
>>> a + b
array([4], dtype=uint8)

我们知道,uint8可以存储的最大值是255。 然后,将超出255的任何值除以256,余数存储在uint8数据类型中:

>>> cv2.add(a, b)
array([[255]], dtype=uint8)

如您在前面的代码中所见,对于cv2.add(),它仅将255的值设置为255 uint8数据类型。

同样,我们可以使用 NumPy 减法和cv2.subtract()计算减法。 以下是一个示例:

import cv2
img1 = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 1)
img2 = cv2.imread('/home/pi/book/dataset/4.2.05.tiff', 1)
cv2.imshow('NumPy Subtract', img1 - img2)
cv2.imshow('OpenCV Subtract', cv2.subtract(img1, img2))
cv2.waitKey(0)
cv2.destroyAllWindows()

以上代码的结果如下:

Figure 5.3 – Subtraction with NumPy and OpenCV

图 5.3 –用 NumPy 和 OpenCV 减去

让我们尝试一个练习,以交互模式理解使用 NumPy 的减法运算和使用 OpenCV 的减法运算之间的区别,如下所示:

>>> import cv2
>>> import numpy as np
>>> a = np.array([240], np.uint8)
>>> b = np.array([20], np.uint8)
>>> b - a
array([36], dtype=uint8)

我们知道uint8可以存储的最低编号是0。 如果数字为负,则 NumPy 将uint8数据类型添加为256

>>> cv2.subtract(b, a)
array([[0]], dtype=uint8)

如所示,在cv2.subtract()的情况下,对于uint8数据类型,负值仅四舍五入为0

注意:

我们知道减法运算不是可交换的。 这意味着a – b在大多数情况下不等于b – a。 因此,如果两个图像的大小和类型都相同,则cv2.subtract(img1, img2)cv2.subtract(img2, img1)会产生不同的结果。 但是,加法运算是可交换的。 因此,cv2.add(img1, img2)cv2.add(img2, img1)总是产生相同的结果。

融合和过渡图像

cv2.addWeighted()函数计算我们将作为参数传递的两个图像的加权和。 这导致它们融合。 以下是演示此混合概念的一些代码:

import cv2
img1 = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 1)
img2 = cv2.imread('/home/pi/book/dataset/4.2.05.tiff', 1)
cv2.imshow('Blended Image',
           cv2.addWeighted(img1, 0.5, img2, 0.5, 0))
cv2.waitKey(0)
cv2.destroyAllWindows()

在前面的代码中,我们将以下五个参数传递给 OpenCV cv2.addWeighted()函数:

  • img1:第一张图片
  • alpha:第一张图片的系数(在前面的示例中为0.5
  • img2:第二张图片
  • beta:第二张图片的系数(在前面的示例中为0.5
  • gamma:标量值(在前面的示例中为0

OpenCV 使用以下公式来计算输出图像:

输出图像 = (alpha * img1) + (beta * img2) + gamma

使用此公式计算输出图像的每个像素,以下是前面代码的输出:

Figure 5.4 – Image blending

图 5.4 –图像融合

我们可以使用相同的 OpenCV 功能来创建过渡效果(在电影和视频编辑软件中可以看到)。 下面的代码示例创建从一个图像到另一个图像的非常平滑的过渡:

import cv2
import time
import numpy as np
img1 = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 1)
img2 = cv2.imread('/home/pi/book/dataset/4.2.05.tiff', 1)
for i in np.linspace(0, 1, 100):
    alpha = i
    beta = 1-alpha
    print('ALPHA =' + str(alpha) + ' BETA =' + str(beta))
    cv2.imshow('Image Transition',
    cv2.addWeighted(img1, alpha, img2, beta, 0))
    time.sleep(0.05)
    if cv2.waitKey(1) == 27 :
        break
cv2.destroyAllWindows()

前面代码的输出将创建过渡效果。

我们还可以使用跟踪栏创建一个不错的应用,如下所示:

import cv2
import time
import numpy as np
def emptyFunction():
    pass
img1 = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 1)
img2 = cv2.imread('/home/pi/book/dataset/4.2.05.tiff', 1)
output = cv2.addWeighted(img1, 0.5, img2, 0.5, 0)
windowName = "Transition Demo"
cv2.namedWindow(windowName)
cv2.createTrackbar('Alpha', windowName, 0,
                   1000, emptyFunction)
while(True):
    cv2.imshow(windowName, output)
    if cv2.waitKey(1) == 27:
        break
    alpha = cv2.getTrackbarPos('Alpha', windowName) / 1000
    beta = 1 - alpha
    output = cv2.addWeighted(img1, alpha, img2, beta, 0)
    print(alpha, beta)
cv2.destroyAllWindows()

前面代码的输出创建了一个不错的过渡应用。 我们甚至可以在上拉配置中将两个按钮连接到 Raspberry Pi 的 GPIO,如下所示:

Figure 5.5 –  A circuit with push buttons

图 5.5 –带有按钮的电路

我们可以编写以下代码,以将按钮与图像转换功能集成在一起:

import time
import RPi.GPIO as GPIO
import cv2
alpha = 0
img1 = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 1)
img2 = cv2.imread('/home/pi/book/dataset/4.2.05.tiff', 1)
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
button1 = 7
button2 = 11
GPIO.setup(button1, GPIO.IN, GPIO.PUD_UP)
GPIO.setup(button2, GPIO.IN, GPIO.PUD_UP)
while True:
    button1_state = GPIO.input(button1)
    if button1_state == GPIO.LOW and alpha < 1:
        alpha = alpha + 0.2
    button2_state = GPIO.input(button2)
    if button2_state == GPIO.LOW:
        if (alpha > 0):
            alpha = alpha - 0.2
    if (alpha < 0):
        alpha = 0
    beta = 1 - alpha
    output = cv2.addWeighted(img1, alpha, img2, beta, 0)
    cv2.imshow('Transition App', output)
    if cv2.waitKey(1) == 27:
        break
    time.sleep(0.5)
    print(alpha)
cv2.destroyAllWindows()

按下按钮,前面的代码将更改alpha变量的值和图像的混合比例。 运行前面的程序,然后按按钮查看操作。 在本书中,我们将使用前面的电路和程序作为许多程序的模板。

在下一节中,我们将了解如何将图像彼此和常数相乘。

将图像乘以一个常数

就像普通的矩阵或 NumPy ndarray一样,图像可以乘以一个常数并彼此相乘。 我们可以将图像乘以一个常数,如下所示:

import cv2
img1 = cv2.imread('/home/pi/book/dataset/4.2.03.tiff', 1)
img2 = cv2.imread('/home/pi/book/dataset/4.2.05.tiff', 1)
cv2.imshow('Image1', img1 * 2)
cv2.waitKey(0)
cv2.destroyAllWindows()

在前面的代码中,表示图像的ndarray的每个元素都与2相乘。 运行前面的程序,然后查看输出。 我们还可以将图像彼此相乘,如下所示:

cv2.imshow('Image1', img1 * 2)

结果可能看起来像噪音。

创建图像底片

就纯数学而言,当我们反转图像的颜色时,它将创建图像的负片。 可以通过从255中减去像素的颜色来计算此反转操作。 如果是彩色图像,我们反转所有平面的颜色。 对于灰度图像,我们可以通过从255中减去它来直接计算反演,如下所示:

import cv2
img = cv2.imread('/home/pi/book/dataset/4.2.07.tiff', 0)
negative = abs(255 - img)
cv2.imshow('Grayscale', img)
cv2.imshow('Negative', negative)
cv2.waitKey(0)
cv2.destroyAllWindows()

以下是的输出:

Figure 5.6 – A negative of an image

图 5.6 –图像的底片

尝试查找彩色图像的底片,我们只需要在前面的程序中以彩色模式读取图像即可。

注意:

负片的负片将是原始灰度图像。 您可以自己计算一次,方法是再次为我们的彩色和灰度图像计算负片的负片。

对图像进行按位逻辑运算

OpenCV 库具有许多用于计算图像上按位逻辑运算的功能。 我们可以计算按位逻辑异或XOR),(反转)操作。 演示这些功能如何工作的最佳方法是将其与二进制(黑白)图像一起使用:

import cv2
import numpy as np
import matplotlib.pyplot as plt
a = [0, 255, 0]
img1 = np.array([a, a, a], dtype=np.uint8)
img2 = np.transpose(img1)
not_out = cv2.bitwise_not(img1 )
and_out = cv2.bitwise_and(img1, img2)
or_out = cv2.bitwise_or(img1, img2)
xor_out = cv2.bitwise_xor(img1, img2)
titles = ['Image 1', 'Image 2', 'Image 1 NOT', 'AND', 'OR', 'XOR']
images = [img1, img2, not_out, and_out, or_out, xor_out]
for i in range(6):
        plt.subplot(2, 3, i+1)
        plt.imshow(images[i], cmap='gray')
        plt.title(titles[i])
        plt.axis('off')
plt.show()

我们创建了自己的自定义二进制图片,以更好地演示按位逻辑异或操作的功能, 分别。 我们将使用 Matplotlibplt.subplot()函数来同时可视化多个图像。

在前面的示例中,我们创建了一个两行三列的网格,以显示原始输入图像和使用 OpenCV 函数的按位逻辑运算的计算输出。 每个图像显示在网格的一部分中。 第一个位置在左上角,第二个位置与左上角相邻,依此类推。 我们可以更改行并将其设置为plt.subplot(3, 2, i + 1),以创建一个三行两列的网格。 本书稍后还将使用这种技术。 我们将使用它在单个输出窗口中并排显示图像。

我们也可以不循环使用plt.subplot()函数。 对于每个图像,我们必须在下面的语句集中编写。 我正在为一个图像编写代码块。 为其他图像写相同的内容:

plt.subplot(2, 3, 1)
plt.imshow(img1, cmap='gray')
plt.title('Image 1')
plt.axis('off')

最后,我们使用plt.show()函数的调用在屏幕上显示所有内容。 我们使用这种技术来显示两个或三个图像。 如果我们有更多的图像,则可以使用循环技术在同一输出窗口中显示多个图像。 以下是我们的输出:

Figure 5.7 – Logical operations on images

图 5.7 –图像上的逻辑操作

您可能想要为灰度和彩色图像上的按位逻辑操作实现代码。

注意:

通过使用 NumPy 的逻辑运算,我们也可以达到相同的结果。

总结

在本章中,我们首先介绍使用 OpenCV 和 NumPy 进行图像处理。 我们了解了一些重要概念,例如图像通道,算术和逻辑运算以及图像的负片。 在此过程中,我们还学会了在 Python 3 和 NumPy 库中使用更多功能。 我们今天学习的按位逻辑运算在下一章中编写用于按颜色进行对象跟踪功能的程序时将非常有用。

在下一章中,我们将研究色彩空间,变换和阈值图像。