C---机器人编程实用指南-全-

C++ 机器人编程实用指南(全)

原文:zh.annas-archive.org/md5/E72C92D0A964D187E23464F49CAD88BE

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

C++是最受欢迎的传统编程语言之一,用于机器人技术,许多领先的行业都使用 C++和机器人硬件的组合。本书将弥合树莓派和 C/C++编程之间的差距,并使您能够为树莓派开发应用程序。要跟随本书中涵盖的项目,您可以使用 wiringPi 库在树莓派上实现 C 程序。

通过本书,您将开发一个完全功能的小车机器人,并编写程序以不同的方向移动它。然后,您将使用超声波传感器创建一个避障机器人。此外,您将了解如何使用您的 PC/Mac 无线控制机器人。本书还将帮助您使用 OpenCV 处理对象检测和跟踪,并指导您探索人脸检测技术。最后,您将创建一个 Android 应用程序,并使用 Android 智能手机无线控制机器人。

通过本书,您将获得使用树莓派和 C/C++编程开发机器人的经验。

这本书适合谁

本书适用于希望利用 C++构建激动人心的机器人应用程序的开发人员、程序员和机器人爱好者。需要一些 C++的先验知识。

本书涵盖的内容

第一章,树莓派简介,介绍了树莓派的不同模式和 GPIO 引脚配置。然后,我们将设置树莓派 B+和树莓派 Zero,并在其上安装 Raspbian 操作系统。我们还将学习如何通过 Wi-Fi 网络将树莓派无线连接到笔记本电脑。

第二章,使用 wiringPi 实现 Blink,介绍了 wiringPi 库的安装。在本章中,我们将了解树莓派的 wiringPi 引脚连接。然后,我们将编写两个 C++程序,并将它们上传到我们的树莓派上。

第三章,编程机器人,介绍了选择机器人底盘的标准。之后,我们将构建我们的小车,将电机驱动器连接到树莓派,并了解 H 桥电路的工作原理。最后,我们将编写程序,使机器人向前、向后、向左和向右移动。

第四章,构建避障机器人,介绍了超声波传感器的工作原理,并编写了一个测量距离值的程序。接下来,我们将编程 16 x 2 LCD 以读取超声波距离值。我们还将研究 I2C LCD,它将 16 个 LCD 引脚作为输入,并提供四个引脚作为输出,从而简化了接线连接。最后,我们将在机器人上安装超声波传感器,创建我们的避障机器人。当附近没有障碍物时,这个机器人将自由移动,如果它接近障碍物,它将通过转弯来避开。

第五章,使用笔记本电脑控制机器人,介绍了使用笔记本电脑控制机器人的两种不同技术。在第一种技术中,我们将使用 ncurses 库从键盘接收输入,以相应地移动机器人。在第二种技术中,我们将使用 QT Creator IDE 创建 GUI 按钮,然后使用这些按钮以不同的方向移动机器人。

第六章,使用 OpenCV 访问 Rpi 相机,重点介绍了在树莓派上安装 OpenCV。您还将了解树莓派相机模块,并在设置 Pi 相机后,使用 Pi 相机拍照和录制短视频剪辑。

第七章,使用 OpenCV 构建一个目标跟随机器人,介绍了 OpenCV 库中的一些重要功能。之后,我们将对这些功能进行测试,并尝试从图像中识别对象。然后,我们将学习如何从 Pi 摄像头读取视频源,如何对彩色球进行阈值处理,以及如何在其上放置一个红点。最后,我们将使用 Pi 摄像头和超声波传感器来检测球并跟随它。

第八章,使用 Haar 分类器进行面部检测和跟踪,使用 Haar 面部分类器从视频源中检测面部并在其周围绘制一个矩形。接下来,我们将检测给定面部上的眼睛和微笑,并创建一个围绕眼睛和嘴的圆圈。在使用这些面部和眼睛检测知识后,我们将在检测到眼睛和微笑时首先打开/关闭 LED。接下来,通过在面部中心创建一个白点,我们将使机器人跟随面部。

第九章,构建语音控制机器人,从创建我们的第一个 Android 应用程序 Talking Pi 开始,其中文本框中的文本将显示在标签中,并由智能手机朗读出来。然后,我们将为机器人开发一个语音控制的 Android 应用程序,该应用程序将识别我们的声音并通过蓝牙将文本发送到 RPi。之后,我们将使用终端窗口将 Android 智能手机的蓝牙与 RPi 的蓝牙配对。最后,我们将研究套接字编程,并编写 VoiceBot 程序,以建立与 Android 智能手机蓝牙的连接,以控制机器人。

为了充分利用本书

要使用本书中的代码,需要 Raspberry Pi 3B+或 Raspberry Pi Zero 板。每章的技术要求部分中提到了额外的硬件和软件。

下载示例代码文件

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

您可以按照以下步骤下载代码文件:

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

  2. 选择 SUPPORT 选项卡。

  3. 单击“代码下载和勘误”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

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

  • WinRAR/7-Zip 适用于 Windows

  • Mac 上的 Zipeg/iZip/UnRarX

  • 7-Zip/PeaZip 适用于 Linux

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Hands-On-Robotics-Programming-with-Cpp。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还提供了来自我们丰富书籍和视频目录的其他代码包,网址为github.com/PacktPublishing/。请查看!

下载彩色图像

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

使用的约定

本书中使用了许多文本约定。

CodeInText:指示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这是一个例子:“将轴向和径向转向的代码添加到RobotMovement.cpp程序中。”

代码块设置如下:

digitalWrite(0,HIGH);           //PIN O & 2 will STOP the Left Motor
digitalWrite(2,HIGH);
digitalWrite(3,HIGH);          //PIN 3 & 4 will STOP the Right Motor
digitalWrite(4,HIGH);
delay(3000);

当我们希望引起您对代码块的特定部分的注意时,相关行或项目会以粗体显示:

digitalWrite(0,HIGH);           //PIN O & 2 will STOP the Left Motor
digitalWrite(2,HIGH);
digitalWrite(3,HIGH);          //PIN 3 & 4 will STOP the Right Motor
digitalWrite(4,HIGH);
delay(3000);

任何命令行输入或输出都将以以下方式书写:

sudo nano /boot/config.txt

粗体: 表示一个新术语、一个重要词或者屏幕上看到的词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这里有一个例子:"选择 记住密码 选项 然后按 确定。"

警告或重要说明会显示在这样。

提示和技巧会显示在这样。

第一部分:在树莓派上使用 wiringPi 入门

在本节中,您将首先介绍树莓派的基础知识,并学习如何在树莓派上安装 Raspbian 操作系统。接下来,您将使用 wiringPi 库,在树莓派上执行您的第一个 C 程序。

本节包括以下章节:

  • 第一章,树莓派简介

  • 第二章,使用 wiringPi 实现闪烁

第一章:Raspberry Pi 简介

最初的想法是在英国各地的学校教授和推广基本的计算机编程,Raspberry PiRPi)立即成为了一大热门。最初发布时的价格仅为 25 美元,因此受到了开发人员、爱好者和工程师的欢迎,并且至今仍然被全世界广泛使用。

在本章中,您将探索 Raspberry Pi 的基本概念。然后,您将学习在设备上安装操作系统。最后,您将配置 Raspberry Pi 上的 Wi-Fi,并学习如何通过 Wi-Fi 将其连接到笔记本电脑并设置远程桌面。

通过以下主题,您将实现这些目标:

  • 了解 Raspberry Pi

  • 在 Raspberry Pi 3B+上安装 Raspbian OS

  • 通过 Wi-Fi 将 Raspberry Pi 3B+连接到笔记本电脑

  • 在 Raspberry Pi Zero W 上安装 Raspbian OS

  • 通过 Wi-Fi 将 Raspberry Pi Zero W 连接到笔记本电脑

技术要求

对于本章,需要以下软件和硬件。

所需软件

如果您想按照本章的说明进行操作,请下载以下软件:

所有这些软件的安装都非常简单。保持默认设置选中,点击几次“下一步”按钮,然后在安装完成后点击“完成”按钮。

硬件要求

我们需要以下硬件来按照本章的说明进行操作。

适用于 Raspberry Pi 3B+和 Raspberry Pi Zero W

如果您使用 Raspberry Pi 3B+或 Raspberry Pi Zero W,您将需要以下硬件:

  • 键盘

  • 鼠标

  • SD 卡——应具有至少 8GB 的存储空间,但建议使用 32GB

  • MicroSD 卡读卡器

  • 显示器——具有 HDMI 端口的计算机显示器或电视

  • HDMI 电缆

  • 5V 移动充电器或移动电源。这将为 Raspberry Pi 供电

Raspberry Pi 3B+的额外硬件

Raspberry Pi 3B+需要以下额外的硬件:

  • 一根以太网电缆

Raspberry Pi Zero W 的额外硬件要求

由于 Raspberry Pi Zero 具有微型 USB 端口和 Micro HDMI 端口,因此需要以下额外的硬件:

  • USB 集线器

  • 一根微型 USB B 到 USB 连接器(也称为 OTG 连接器)

  • 一个 HDMI 到迷你 HDMI 连接器

了解树莓派

树莓派是一款信用卡大小的基于 Linux 的微型计算机,由树莓派基金会于 2012 年发明。第一款树莓派型号被称为树莓派 1B,随后推出了 A 型。树莓派板最初是为了推广学校的计算机科学课程。然而,它们廉价的硬件和免费的开源软件,很快使树莓派在黑客和机器人开发者中流行起来。

树莓派可以用作完全功能的计算机。它可以用于浏览互联网,玩游戏,观看高清视频,以及创建 Excel 和 Word 文档等任务。但它与普通计算机的真正区别在于其可编程的 GPIO 引脚。树莓派由40 个数字 I/O GPIO 引脚组成,可以进行编程。

简单来说,树莓派可以被认为是微型计算机电子硬件板的结合,因为它既可以用作完全功能的计算机,也可以用来创建电子和机器人项目。

有不同的树莓派型号。在本书中,我们将使用以下两个型号:

  • 树莓派 3B+

  • 树莓派 Zero W

树莓派 3B+

树莓派 3B+于 2018 年 2 月发布。其规格如下所示:

树莓派 3B+的规格如下:

  • Broadcom BCM2837 四核 1.4 GHz 处理器

  • 1 GB RAM

  • Broadcom VideoCore GPU

  • 蓝牙 4.2

  • 双频 2.4 GHz 和 5 GHz Wi-Fi

  • 一个以太网端口

  • 通过 microSD 插槽使用 microSD 卡进行存储

  • 40 个可编程的 GPIO 引脚

  • 四个 USB 2.0 端口

  • 一个 HDMI 端口

  • 3.5 毫米音频插孔

  • 摄像头串行接口CSI),用于将树莓派摄像头直接连接到树莓派

树莓派 Zero W

如果我们正在寻找一个更小尺寸的树莓派版本,我们可以选择树莓派 Zero W。W代表无线,因为树莓派 Zero W 具有内置 Wi-Fi。以下是树莓派 Zero W 的规格:

树莓派 Zero W 型号的成本约为 10 美元。还有一个没有W的树莓派 Zero,成本约为 5 美元,但它没有内置 Wi-Fi,这使得它非常难以连接到互联网。2017 年发布的树莓派 Zero W 基本上是 2015 年发布的树莓派 Zero 的升级版本。

在本书的后面,当我们设计我们的机器人时,我们将学习如何通过 Wi-Fi 网络从笔记本电脑无线上传程序到我们的树莓派。如果你选择购买树莓派的较小版本,我建议你选择树莓派 Zero W,而不是树莓派 Zero,以便使用更加方便。

树莓派 Zero W 由于尺寸较小,有一些缺点。首先,它比树莓派 3B+慢一些。其次,如果我们想将其用作微型计算机,我们需要购买不同的扩展设备来连接外围设备,如键盘、鼠标或显示器。然而,如果我们打算将树莓派 Zero W 用于构建电子和机器人项目,我们就不需要担心这个缺点。在本书的后面,我们将学习如何通过 Wi-Fi 将树莓派 Zero W 连接到笔记本电脑,并如何使用笔记本电脑来控制它。

树莓派 Zero W 的规格如下:

  • Broadcom ARM11 1 GHz 处理器

  • 512 MB RAM

  • Broadcom VideoCore GPU

  • 蓝牙 4.0

  • 双频 2.4 GHz 和 5 GHz Wi-Fi

  • 通过 microSD 插槽使用 microSD 卡进行存储

  • 40 个可编程的 GPIO 引脚

  • 一个迷你 HDMI 端口

  • 摄像头串行接口CSI),用于将树莓派摄像头直接连接到树莓派

设置树莓派 3B+作为台式电脑

为了在树莓派 3B+上设置和安装 Raspbian OS,我们需要各种硬件和软件组件。硬件组件包括以下内容:

  • 一台笔记本电脑,用于在 microSD 卡上安装 Raspbian OS。

  • 一个键盘。

  • 一只老鼠。

  • 一个 SD 卡-至少 8GB 的存储卡就足够了,但是使用 8GB 卡,默认 OS 将占据存储卡空间的 50%。在本章后面,我们还将在树莓派上安装 OpenCV,由于 OpenCV 也会占用存储卡上的大量空间,因此您需要卸载一些默认软件。因此,我建议您使用 16GB 或 32GB 存储卡-使用 32GB 存储卡,默认 OS 仅占据卡空间的 15%。

  • 一个 SD 卡读卡器。

  • 显示单元-这可以是计算机显示器或电视,只要它具有 HDMI 端口。

  • 一个 HDMI 电缆。

  • 移动充电器或移动电源为树莓派供电。

所需的软件组件包括以下内容:

  • 刻录机

  • 带桌面的 Raspbian Stretch 操作系统

现在我们知道需要安装 OS,让我们开始安装。

在 SD 卡上安装 Raspbian OS

要在 microSD 卡上安装 Raspbian OS,我们首先将在计算机上安装Etcher。之后,我们将把 microSD 卡插入 microSD 卡读卡器,并将其连接到计算机上。

下载和安装 Etcher

Etcher 将首先格式化 microSD 卡,然后将 Raspbian Stretch 图像写入其中。让我们开始安装 Etcher:

  1. 在浏览器中,转到www.etcher.io/.

  2. 从下拉菜单中选择您的操作系统。Etcher 将开始下载,如下面的屏幕截图所示:

  1. 下载完成后,打开安装文件并安装 Etcher。

现在 Etcher 已经设置好了,让我们继续进行 Raspbian 的安装。

下载 Raspbian Stretch 图像

现在我们必须下载一个 OS 来在树莓派上运行。虽然有许多第三方树莓派 OS 可用,但我们将安装 Raspbian OS。这个 OS 基于 Debian,专门为树莓派开发。最新版本称为Raspbian Stretch

要下载 Raspbian Stretch 图像,请访问www.raspberrypi.org/downloads/raspbian/,查找 RASPBIAN STRETCH WITH DESKTOP ZIP 文件,并单击“下载 ZIP”按钮,如下面的屏幕截图所示:

现在我们在笔记本电脑上有了 Raspbian Stretch 的副本,让我们继续将其写入我们的 microSD 卡。

将 Raspbian Stretch 图像写入 microSD 卡

下载 Etcher 和 Raspbian Stretch 图像后,让我们将 Raspbian Stretch 写入我们的 microSD 卡:

  1. 将 microSD 卡插入 microSD 卡读卡器,然后通过 USB 将读卡器连接到笔记本电脑:

  1. 接下来,打开 Etcher 并单击“选择图像”按钮。然后,选择 Raspbian Stretch ZIP 文件并单击“打开”:

  1. 之后,请确保选择了 microSD 卡读卡器驱动器,如下面的屏幕截图所示。如果错误地选择了其他驱动器,请单击“更改”按钮并选择 microSD 卡驱动器。单击“闪存!”按钮将 Raspbian OS 写入 microSD 卡:

将图像写入 SD 卡的过程也称为启动

Etcher 将花大约 15-20 分钟来用 Raspbian OS 刷写您的 SD 卡:

一旦 OS 被写入 SD 卡,Etcher 将自动弹出 microSD 卡读卡器。

现在我们已经将 Raspbian Stretch 写入我们的 microSD 卡,让我们开始设置树莓派 3B+。

设置树莓派 3B+

从 microSD 卡引导 Raspbian 操作系统后,我们将通过连接不同的外围设备来设置树莓派,如下所示:

  1. 将 microSD 卡插入位于树莓派 3B+背面的 SD 卡槽中:

  1. 将键盘和鼠标连接到树莓派 3B+的 USB 端口。也可以使用无线键盘和鼠标:

  1. 树莓派 3B+包含一个 HDMI 端口,我们可以用它连接 RPi 到显示单元,比如计算机显示器或电视。将 HDMI 电缆的一端连接到树莓派的 HDMI 端口,另一端连接到显示单元:

  1. 最后,为了打开树莓派,我们需要提供电源。一个典型的树莓派需要 5V 的电压和理想情况下 2.5A 的电流。我们可以使用两种方法为树莓派提供电源:
    • 智能手机充电器:大多数智能手机充电器提供 5V 的电压输出和 2.5A 的电流输出。如果你仔细看一下你的智能手机充电器,你会发现最大电压和电流输出值印在上面,如下图所示。在我的充电器上,3A 的电流输出表示最大电流输出。然而,充电器只会根据 RPi 的需求提供电流输出,而不是最大电流 3A。请注意,树莓派包含一个 micro USB B端口,因此,为了连接到树莓派的电源端口,我们需要用 micro USB B线连接到我们的充电器:

    • 移动电源或电池组:另外,我们可以使用移动电源或电池组。如前所述,我们需要通过 micro USB B 端口将移动电源连接到树莓派,并且我们还需要确保它提供 5V 的电压输出和大约 2.5A 的电流输出:

  1. 一切都插好后,打开显示单元,确保选择了正确的 HDMI 选项。

  2. 接下来,打开电源。你会看到树莓派上的红色 LED 灯亮起。等待大约 10-20 秒,等待树莓派启动。一旦完成,你会看到以下屏幕:

现在我们的树莓派 3B+已经运行起来了,让我们把它连接到互联网。

连接树莓派 3B+到互联网

我们可以使用两种方法为树莓派提供互联网连接:

  • 以太网电缆:树莓派 3B+包含一个以太网端口。要通过以太网端口提供互联网连接,只需将以太网电缆连接到它。

  • Wi-Fi:通过 Wi-Fi 连接树莓派也非常简单。点击任务栏中的 Wi-Fi 图标。选择你的 Wi-Fi 网络,输入正确的密码,树莓派将连接到所需的 Wi-Fi 网络:

在将树莓派 3B+设置为桌面电脑后,我们可以简单地打开任何代码编辑器,开始编写程序来控制树莓派的电机或 LED。

由于我们将使用树莓派创建一个可移动的机器人,因此桌面电脑设置将无法使用。这是因为显示器、键盘和鼠标都直接连接到 Pi,将限制其移动。在下一节中,为了能够在没有这些外围设备的情况下使用它,我们将看看如何通过 Wi-Fi 将树莓派 3B+无线连接到笔记本电脑。

通过 Wi-Fi 将树莓派 3B+连接到笔记本电脑

要通过 Wi-Fi 将树莓派 3B+无线连接到笔记本电脑,我们首先需要使用一个名为 PuTTY 的软件将 RPi 连接到 Wi-Fi 网络。之后,我们可以找出树莓派的 IP 地址,并将其输入到一个名为VNC Viewer的软件中,以将树莓派连接到笔记本电脑。为了成功执行此任务,树莓派和笔记本电脑必须连接到同一个 Wi-Fi 网络。

所需的硬件包括以下内容:

  • 以太网电缆:以太网电缆将直接连接到树莓派 3B+的以太网端口和笔记本电脑的以太网端口。如果您的笔记本电脑不包含以太网端口,则需要为您的笔记本电脑购买一个USB 到以太网连接器。

  • Micro USB B 电缆:这是连接树莓派 3B+和笔记本电脑的标准 Micro USB B 电缆。

所需的软件是PuTTY,VNC Viewer 和 Bonjour。

在 microSD 卡上创建一个 SSH 文件

安装了上述软件后,我们需要在 microSD 卡上创建一个 SSH 文件,以启用树莓派 3B+的 SSH。为此,请执行以下步骤:

  1. 打开分配给 SD 卡的驱动器。在我们的案例中,这是boot (F:)驱动器。如下面的截图所示,microSD 卡上有一些文件:

  1. 要创建 SSH 文件,请在驱动器中右键单击,然后单击新建,选择文本文档,如此处所示:

  1. 给这个文本文件命名为ssh,但不要包括.txt扩展名。我们会收到一个警告,指出这个文件将变得不稳定,因为它没有扩展名。点击按钮:

  1. 接下来,右键单击ssh文件,然后选择属性选项。在属性中,点击常规选项卡。我们应该看到文件类型设置为文件。点击确定:

在 microSD 卡上创建一个 SSH 文件后,从笔记本电脑中取出读卡器,并将 microSD 卡插入树莓派 3B+。

在下一节中,我们将看看如何将 RPi 3B+连接到 Wi-Fi 网络。设置是在 Windows 系统上完成的。如果你有一台 Mac,那么你可以按照以下教程视频之一进行操作:

使用 PuTTY 将树莓派 3B+连接到 Wi-Fi 网络

将 microSD 卡插入 RPi 后,让我们看看如何使用 PuTTY 将树莓派连接到 Wi-Fi 网络:

  1. 首先,将以太网电缆的一端连接到树莓派的以太网端口,另一端连接到笔记本电脑的以太网端口。

  2. 接下来,通过使用 Micro USB B 电缆将树莓派 3B+连接到笔记本电脑来启动树莓派 3B+。我们会看到红色的电源 LED 灯亮起。我们还会看到以太网端口的黄色 LED 灯亮起并持续闪烁。

  3. 之后,打开 PuTTY 软件。在主机名框中,输入raspberrypi.local,然后点击打开按钮:

  1. 然后我们会看到一个 PuTTY 安全警告消息。点击

  1. 在 PuTTY 中,我们需要输入树莓派的凭据。默认登录名是pi,密码是raspberry。输入密码后,按Enter

  1. 之后,要将树莓派 3B+连接到特定的 Wi-Fi 网络,请输入sudo nano /etc/wpa_supplicant/wpa_supplicant.conf命令,如此截图所示:

  1. 这个命令将打开 nano 编辑器,看起来如下:

  1. update_config=1行下,按照以下语法输入您的 Wi-Fi 名称和密码:
network={
*ssid="*Wifi name*"* psk="Wifi password"
}

确保将前面的代码精确地添加到update_config=1行下方。Wi-Fi 名称和密码应该用双引号("")括起来,如此处所示:

输入 Wi-Fi 名称和密码后,按下Ctrl + O键保存更改。然后按Enter。之后,按下Ctrl + X键退出 nano 编辑器。

  1. 要重新配置并将树莓派连接到 Wi-Fi 网络,请输入以下命令:sudo wpa_cli reconfigure。如果连接成功,您将看到接口类型和OK消息:

  1. 然后我们需要重新启动树莓派。要做到这一点,输入sudo shutdown now。一旦树莓派关闭,关闭 PuTTY 软件。

  2. 接下来,从笔记本电脑上拔下 USB 电缆。

  3. 之后,拔下连接到树莓派和笔记本电脑的以太网电缆。

  4. 重新连接 USB 电缆,以便树莓派开机。

  5. 打开 PuTTY。在主机名字段中再次输入raspberrypi.local,然后按打开按钮:

  1. 输入我们之前使用的用户名和密码。

  2. 一旦树莓派连接到 Wi-Fi 网络,Wi-Fi 网络将为其分配一个 IP 地址。要查找 IP 地址,请输入ifconfig wlan0命令并按Enter。您会注意到现在已经分配了一个 IP 地址:

在我的情况下,IP 地址是192.168.0.108。请在某处记下您的 IP 地址,因为在使用 VNC Viewer 软件时需要输入它。

启用 VNC 服务器

要查看树莓派显示,我们需要从树莓派配置窗口启用 VNC 服务器:

  1. 要打开配置窗口,我们需要在 PuTTY 终端中键入sudo raspi-config并按Enter。然后我们可以打开接口选项,如下所示:

  1. 然后我们可以打开VNC选项:

  1. 要启用 VNC 服务器,请导航到“Yes”选项并按Enter

  1. 启用 VNC 服务器后,按 OK:

  1. 按 Finish 退出树莓派配置窗口:

启用 VNC 服务器后,我们将打开 VNC Viewer 软件,以便可以看到树莓派显示屏。

在 VNC Viewer 上查看树莓派输出

要在 VNC Viewer 上查看树莓派输出,请按照以下说明操作:

  1. 打开 VNC Viewer 软件后,在 VNC Viewer 中输入您的树莓派 IP 地址,然后按Enter

  1. 您将收到一个弹出消息,指出 VNC Viewer 没有此 VNC 服务器的记录。按继续:

  1. 输入用户名pi和密码raspberry 选择记住密码选项,然后按 OK:

现在我们应该能够在 VNC Viewer 软件中查看树莓派显示输出:

现在我们已经通过 Wi-Fi 将树莓派连接到笔记本电脑,就不需要再通过 USB 电缆将树莓派连接到笔记本电脑了。下次,我们可以简单地使用移动电源或移动充电器为树莓派供电。当我们选择我们的树莓派的 IP 地址时,我们可以使用 VNC Viewer 软件查看树莓派显示输出。

如前所述,请确保在使用笔记本电脑进行远程桌面访问时,树莓派和笔记本电脑都连接到同一 Wi-Fi 网络。

增加 VNC 的屏幕分辨率

在 VNC Viewer 中查看 RPi 的显示输出后,你会注意到 VNC Viewer 的屏幕分辨率很小,没有覆盖整个屏幕。为了增加屏幕分辨率,我们需要编辑config.txt文件:

  1. 在终端窗口中输入以下命令:
sudo nano /boot/config.txt
  1. 接下来,在#hdmi_mode=1代码下面,输入以下三行:
hdmi_ignore_edid=0xa5000080
hdmi_group=2
hdmi_mode=85
  1. 之后,按下Ctrl + O,然后按Enter保存文件。按下Ctrl + X退出:

  1. 接下来,重新启动你的 RPi 以应用这些更改:
sudo reboot

重新启动后,你会注意到 VNC 的屏幕分辨率已经增加,现在覆盖了整个屏幕。

处理 VNC 和 PuTTY 错误

在 VNC Viewer 中,有时当你选择 RPi 的 IP 地址时,你可能会看到以下弹出错误消息,而不是 RPi 的显示:

你可能还会看到以下消息:

如果你遇到以上任何错误,请点击笔记本电脑上的 Wi-Fi 图标,并确保你连接到与 RPi 连接的相同的 Wi-Fi 网络。如果是这种情况,你的 RPi 的 IP 地址在 Wi-Fi 网络中可能已经改变,这在新设备连接到 Wi-Fi 网络时有时会发生。

要找到新的 IP 地址,请按照以下步骤操作:

  1. 打开 PuTTY,输入raspberrypi.local到主机名框中。

  2. 在 PuTTY 的终端窗口中输入命令ifconfig wlan0。如果你的 IP 地址已经改变,你会在inet选项中看到新的 IP 地址。

  3. 在 VNC Viewer 中输入新的 IP 地址以查看 RPi 的显示输出。

有时,你可能也无法连接到 Putty,并且会看到以下错误:

要解决 PuTTY 中的前述错误,请按照以下步骤操作:

  1. 将 LAN 电缆连接到 RPi 和笔记本电脑。

  2. 打开你的 RPi 并尝试通过在主机名框中输入raspberrypi.local来连接 putty。通过 LAN 电缆连接到 RPi 和笔记本电脑,你应该能够访问 PuTTY 终端窗口。

  3. 按照之前的步骤找到 RPi 的新 IP 地址。

  4. 一旦你在 VNC Viewer 中看到 RPi 的显示,你可以拔掉 LAN 电缆。

设置树莓派 Zero W 为台式电脑

正如我们所说,树莓派 Zero W 是树莓派 3B+的简化版本。树莓派 Zero W 的连接非常有限,因此为了连接不同的外围设备,我们需要购买一些额外的组件。我们需要以下硬件组件:

  • 一个键盘

  • 一个鼠标

  • 一个至少 8GB 的 microSD 卡(推荐 32GB)

  • 一个 microSD 卡读卡器

  • 一个 HDMI 电缆

  • 一个显示单元,最好是带有 HDMI 端口的 LED 屏幕或电视

  • 一个移动充电器或移动电源来为树莓派供电

  • 一个 micro USB B 到 USB 连接器(也称为 OTG 连接器),看起来像这样:

  • 一个迷你 HDMI 到 HDMI 连接器,如下所示:

  • 一个 USB 集线器,如图所示:

现在我们知道需要哪些硬件,让我们设置我们的树莓派 Zero W。

设置树莓派 Zero W

将 Raspbian OS 安装到 microSD 卡上的步骤与在“在 SD 卡上安装 Raspbian OS”部分中已经列出的树莓派 3B+完全相同。一旦你的 SD 卡准备好了,按照以下步骤设置树莓派 Zero W:

  1. 首先,将 microSD 卡插入树莓派 Zero W 的 SD 卡槽中。

  2. mini HDMI 到 HDMI 连接器(H2HC)的一端插入树莓派 Zero W 的 HDMI 端口,将 H2HC 连接器的另一端插入 HDMI 电缆。

  3. 将 OTG 连接器连接到 Micro USB 数据端口(而不是电源端口),并将 USB 集线器连接到 OTG 连接器。

  4. 将键盘和鼠标连接到 USB 集线器。

  5. 将移动充电器或电池组连接到电源单元的 Micro USB 端口。

  6. 接下来,将 HDMI 电缆连接到电视或监视器的 HDMI 端口。

  7. 将移动充电器连接到主电源以为树莓派 Zero W 供电。然后,当树莓派 Zero W 开机时,您将看到绿色 LED 闪烁一段时间。

  8. 如果您已将 HDMI 电缆连接到电视,请选择正确的 HDMI 输入通道。以下有注释的照片显示了这里提到的连接:

树莓派 Zero W 连接

  1. 树莓派 Zero W 启动大约需要两到三分钟。一旦准备好,您将在电视或监视器屏幕上看到以下窗口:

  1. 要关闭树莓派,按树莓派图标,然后单击关闭。

现在设置好了,让我们将树莓派 Zero W 连接到笔记本电脑。

通过 Wi-Fi 将树莓派 Zero W 连接到笔记本电脑

当树莓派 Zero 于 2015 年首次推出时,它没有内置的 Wi-Fi 模块,这使得连接到互联网变得困难。一些树莓派开发人员想出了有用的黑客技巧,以连接树莓派到互联网,一些公司也为树莓派 Zero 创建了以太网和 Wi-Fi 模块。

然而,2017 年,树莓派 Zero W 推出。这款产品具有内置的 Wi-Fi 模块,这意味着树莓派开发人员不再需要执行任何 DIY 黑客或购买单独的组件来添加互联网连接。具有内置 Wi-Fi 还有助于我们将树莓派 Zero W 无线连接到笔记本电脑。让我们看看如何做到这一点。

将树莓派 Zero W 连接到笔记本电脑的 Wi-Fi 的过程与树莓派 3B+类似。但是,由于树莓派 Zero W 没有以太网端口,因此我们将不得不在cmdline.txtconfig.txt文件中写入一些代码。

尽管cmdline.txtconfig.txt文本TXT)文件,但这些文件中的代码在 Microsoft 的记事本软件中无法正确打开。要编辑这些文件,我们需要使用代码编辑器软件,例如 Notepad++(仅适用于 Windows)或 Brackets(适用于 Linux 和 macOS)。

安装其中一个后,让我们按以下方式自定义 microSD 卡:

  1. 在树莓派 Zero W 中,我们还需要在 microSD 卡上创建一个 SSH 文件。有关如何在 microSD 卡上创建 SSH 文件的说明,请参阅在 microSD 卡上创建 SSH 文件部分。

  2. 创建 SSH 文件后,右键单击config.txt文件,并在 Notepad++或 Brackets 中打开它。在这种情况下,我们将在 Notepad++中打开它:

向下滚动到此代码的底部,并在末尾添加行dtoverlay=dwc2。添加代码后,保存并关闭文件。

  1. 接下来,打开 Notepad++中的cmdline.txt文件。cmdline文件中的整个代码将显示在一行上。接下来,请确保在consolesmodules之间只添加一个空格。

plymouth.ignore-serial-consoles代码旁边输入行modules-load=dwc2,g_ether

  1. 接下来,使用数据传输 USB 电缆将树莓派 Zero W 连接到笔记本电脑。将 USB 电缆连接到树莓派 Zero W 的数据端口,而不是电源端口。

  1. 确保您连接到树莓派 Zero W 和笔记本电脑的 USB 电缆支持数据传输。例如,查看以下照片:

在上面的照片中,有两根相似但重要不同的电缆:

    • 左侧的小型 USB 电缆是随我的移动电源套件一起提供的。这个 USB 电缆提供电源,但不支持数据传输。
  • 右侧的 USB 电缆是随新的安卓智能手机一起购买的。这些支持数据传输。

检查您的 USB 是否支持数据传输的一个简单方法是将其连接到智能手机和笔记本电脑上。如果您的智能手机被检测到,这意味着您的 USB 电缆支持数据传输。如果没有,您将需要购买一根支持数据传输的 USB 电缆。以下截图显示了 PC 检测到智能手机,这意味着正在使用的电缆是数据电缆:

如果您的 USB 电缆被检测到但经常断开连接,我建议您购买一根新的 USB 电缆。有时,由于磨损,旧的 USB 电缆可能无法正常工作。

使用 PuTTY 将树莓派 Zero W 连接到 Wi-Fi 网络

要将树莓派 Zero W 连接到 Wi-Fi 网络,请参阅使用 PuTTY 将树莓派 3B+连接到 Wi-Fi 网络部分。连接树莓派 Zero W 到 Wi-Fi 网络的步骤完全相同。

为树莓派 Zero W 启用 VNC Viewer

要为树莓派 Zero W 启用 VNC Viewer,请参阅启用 VNC 服务器部分。

在 VNC Viewer 上查看树莓派 Zero W 的输出

要在 VNC Viewer 中查看树莓派 Zero W 的输出,请参阅在 VNC Viewer 上查看树莓派输出部分

总结

在本章中,我们已经学习了如何将树莓派 3B+和树莓派 Zero W 设置为普通的台式电脑。我们还学会了如何通过 Wi-Fi 网络将树莓派连接到笔记本电脑。现在,您可以在不需要连接键盘、鼠标和显示器的情况下,通过笔记本电脑远程控制树莓派。

在下一章中,我们将首先了解一些在树莓派 OS 中操作的基本命令。我们将在树莓派上安装一个名为 Wiring Pi 的 C++库,并了解该库的引脚配置。最后,我们将编写我们的第一个 C++程序,并将其无线上传到我们的树莓派。

问题

  1. 树莓派 3B+上有哪种处理器?

  2. 树莓派 3B+上有多少个 GPIO 引脚?

  3. 我们用于在笔记本电脑上查看树莓派显示输出的软件是什么?

  4. 树莓派的默认用户名和密码是什么?

  5. 用于访问树莓派内部配置的命令是什么?

第二章:使用 wiringPi 实现 Blink

设置树莓派后,现在是时候连接不同的电子元件并使用 C++编程语言对其进行编程了。要使用 C++,我们首先需要下载并安装一个名为wiringPi的库。

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

  • 在树莓派内安装wiringPi

  • 让 LED 闪烁

  • 智能灯—使用数字传感器

  • 使用 softPwm 进行脉宽调制

技术要求

本章的硬件要求如下:

  • 1 个 LED(任何颜色)

  • 1 LDR光敏电阻)传感器模块

  • 树莓派 3B+

  • 5-6 个母对母连接线

本章的代码文件可以从以下网址下载:github.com/PacktPublishing/Hands-On-Robotics-Programming-with-Cpp/tree/master/Chapter02

在树莓派中安装 wiringPi 库

wiringPi 是一个基于引脚的 GPIO 访问库,用 C 语言编写。使用这个库,你可以用 C/C++编程控制树莓派。wiringPi库很容易设置。一旦安装,树莓派 GPIO 引脚将具有 wiringPi 引脚编号。让我们看看如何下载和安装 wiringPi:

  1. 首先,点击任务栏上的图标打开终端窗口:

  1. 在安装wiringPi库之前,我们首先需要验证我们的树莓派是否有更新。如果你的树莓派没有更新,安装wiringPi库时可能会出现错误。要更新你的树莓派,输入以下命令:
$ sudo apt-get update 

上述命令的输出如下:

根据你的互联网速度,更新下载和安装需要大约 10-15 分钟。确保你将树莓派放在 Wi-Fi 路由器附近。

  1. 更新后,输入以下命令升级树莓派:
$ sudo apt-get upgrade

在升级过程中,你可能会收到一个要求下载特定组件的消息。输入Y然后按Enter。升级需要大约 30-40 分钟。升级完成后,你会看到以下消息:

  1. 更新树莓派后,你需要在树莓派内下载和安装git-core。要安装 Git,输入以下命令:
$ sudo apt-get install git-core

  1. 之后,要从git下载wiringPi库,输入以下命令:
git clone git://git.drogon.net/wiringPi

  1. 现在,如果你点击文件管理器选项并点击pi文件夹,你应该会看到wiringPi文件夹:

  1. 接下来,更改目录到wiringPi,以便 wiringPi 文件被下载并安装到这个特定文件夹内。更改目录的命令是cd
$ cd ~/wiringPi (The ~ symbol is above the Tab key and it points to pi directory)

现在你应该看到指向wiringPi文件夹的目录。

  1. 接下来,为了从origin目录获取 Git 文件,输入以下命令:
$ git pull origin

  1. 最后,为了构建文件,输入以下命令:
$ ./build 

一切都完成后,你会看到一个All done消息:

现在我们已经安装了 wiringPi 库,我们可以继续了解 RPi 上的 wiringPi 引脚配置。

通过 wiringPi 访问树莓派 GPIO 引脚

由于我们已经安装了 wiringPi,现在我们可以看一下 wiringPi 引脚编号,如下截图所示:

物理列代表树莓派编号从1-40。在物理列的两侧,您将看到 wiringPi(wPi)列。从物理列指向wPi的箭头代表树莓派的特定物理引脚的 wiringPi 引脚编号。

看一下以下示例:

  • 物理引脚号 3 的 wiringPi 引脚号为 8

  • 物理引脚号 5 的 wiringPi 引脚号为 9

  • 物理引脚号 8 的 wiringPi 引脚号为 15

  • 物理引脚号 11 的 wiringPi 引脚号为 0

  • 物理引脚号 40 的 wiringPi 引脚号为 29

通过查阅这个表,您可以找出剩下的物理引脚对应的 wiringPi 引脚。

wiringPi 引脚号从 17 到 20 不存在。在 wPi 引脚 16 之后,我们直接跳到 wPi 引脚 21。

为了更好地理解 wiringPi 引脚和物理引脚之间的关系,您可以参考以下图表:

wiringPi 引脚编号是编程时需要记住的。我们可以使用总共 28 个 wiringPi 引脚进行编程。除此之外,我们还有以下引脚,可以用于提供电源并可用作接地引脚:

  • 物理引脚号 6、9、14、20、25、30、34 和 39 是接地引脚

  • 物理引脚号 2 和 4 提供+5V 电源

  • 物理引脚号 1 和 17 提供+3.3V 电源

让我们继续编写我们的第一个树莓派 C++程序。

让 LED 闪烁

我们要创建的第一个项目是让 LED 闪烁。对于这个项目,我们需要以下硬件组件:

  • 树莓派

  • 1 个 LED

  • 两根母对母导线

接线连接

将 LED 连接到树莓派非常简单。不过,在这之前,让我们仔细看一下 LED 的引脚:

LED 包含一个正极引脚和一个负极引脚。长引脚是正极引脚,可以连接到树莓派的任何数据引脚上。短引脚是负极引脚,可以连接到树莓派的接地引脚上。

让我们连接它。首先,将 LED 的负极引脚连接到树莓派的接地引脚(物理引脚号 6)。接下来,将 LED 的正极引脚连接到 wiringPi 引脚号 15:

** **

现在我们已经将 LED 连接到树莓派,让我们编写一个程序让 LED 闪烁。

闪烁程序

要编写我们的第一个 C++程序,我们将使用 Geany 程序编辑器。要打开 Geany,点击树莓图标,转到编程,然后选择Geany 程序编辑器

打开 Geany 后,您会看到一个名为Untitled的未保存文件。我们需要做的第一件事是保存文件。点击文件|另存为,并将此文件命名为Blink.cpp

在这个文件中,写入以下代码使 LED 闪烁。您可以从 GitHub 存储库的Chapter02文件夹中下载Blink.cpp程序:

#include <iostream>
#include <wiringPi.h>

int main(void)
{
wiringPiSetup();
pinMode(15,OUTPUT);

 for(;;)
 {
digitalWrite(15,HIGH);
delay(1000);
digitalWrite(15,LOW);
delay(1000);
 }
return 0;
 }

如果您以前做过 Arduino 编程,您可能已经理解了这段代码的大约 90%。这是因为 wiringPi 允许我们以 Arduino 格式编写 C++程序:

  1. 在上面的代码中,我们首先导入了iostreamwiringPi库。

  2. 接下来,我们有主函数,称为int main。由于这个函数没有任何参数,我们在圆括号内写入void语句。

  3. 之后,wiringPisetup()函数初始化了wiringPi。它假定这个程序将使用 wiringPi 编号方案。

  4. 接下来,使用pinMode(15, OUTPUT)命令,我们将 wiringPi 引脚号 15 设置为OUTPUT引脚。这是我们连接到 LED 正极引脚的引脚。

  5. 之后,我们有一个无限的for循环。其中写入的代码将无限运行,除非我们从编码编辑器手动停止它。

  6. 通过digitalWrite(15,HIGH)命令,我们在 LED 上写入HIGH信号,这意味着 LED 将打开。我们也可以使用数字1代替HIGH

  7. 接下来,通过delay(1000)命令,我们确保 LED 只亮一秒

  8. 接下来,通过digitalWrite(15,LOW)命令,在 LED 上写入LOW信号。这意味着 LED 将关闭一秒钟。

  9. 由于此代码位于 for 循环中,LED 将保持,直到我们另行指示为止。

将代码上传到树莓派

由于我们使用的是 wiringPi 编号约定,我们将在 Build 命令中添加-lwiringPi命令,以便我们的 C++程序能够成功编译和构建wiringPi库。要打开 Build 命令,点击 Build | Set Build Commands。在 Compile 和 Build 按钮旁的命令框中,添加-lwiringPi,然后点击 OK:

接下来,要编译代码,请点击编译按钮(棕色图标)。最后,要将代码上传到树莓派,请按构建按钮(飞机图标):

编译图标将检查代码中的错误。如果没有错误,点击构建图标以测试闪烁输出。构建代码后,构建图标将变成红色圆圈。点击红色圆圈停止程序。

智能灯 - 与数字传感器一起工作

在为树莓派编写我们的第一个 C/C++程序之后,我们现在可以编写一个程序,该程序将从 LDR 传感器接收输入并控制 LED 的开关。对于这个项目,您将需要以下硬件组件:

  • 1 个 LDR 传感器模块

  • 1 个 LED

  • 树莓派

  • 5 根母对母连接线

首先,让我们探讨一下 LDR 传感器的工作原理。

LDR 传感器及其工作原理

LDR 传感器是一种模拟输入传感器,由可变电阻器组成,其电阻取决于其表面上落下的光线数量。当房间里没有光时,LDR 传感器的电阻很高(高达 1 兆欧姆),而在有光的情况下,LDR 传感器的电阻很低。LDR 传感器由两个引脚组成。这些引脚没有正负极性。我们可以使用任何引脚作为数据或地引脚,因此 LDR 传感器有时被称为特殊类型的电阻器。LDR 传感器的图像如下图所示:

由于 LDR 是模拟传感器,我们不能直接将其连接到 RPi,因为 RPi 不包含模拟到数字转换器ADC)电路。因此,RPi 无法读取来自 LDR 传感器的模拟数据。因此,我们将使用 LDR 数字传感器模块,而不是 LDR 传感器,该模块将向 RPi 提供数字数据:

LDR 传感器模块将读取来自 LDR 传感器的模拟数据,并以高电平或低电平的形式提供数字数据作为输出。LDR 传感器模块由 3 个引脚组成:D0数据输出)、地和 Vcc。D0 将提供数字数据作为输出,然后作为输入提供给 RPi 引脚。在光线较暗时,D0 引脚将为高电平,在有光时,D0 引脚将为低电平。传感器模块还包括一个电位器传感器,可用于改变 LDR 传感器的电阻。

LDR 传感器模块的实际用途可见于街灯,它们在白天自动关闭,在夜晚自动打开。我们将要编写的智能灯程序与此应用有些类似,但我们将使用 LED 来简化事情,而不是街灯。

现在我们已经了解了 LDR 传感器的基本工作原理,接下来让我们将 LDR 传感器模块连接到树莓派。

接线连接

通过接线连接,我们可以将 LDR 传感器模块和 LED 连接到 RPi:

接线连接如下:

  • RPi 的 wiringPi 引脚 8 连接到 LDR 传感器模块的 D0 引脚

  • RPi 的物理引脚 2 连接到 LDR 传感器模块的 Vcc 引脚

  • RPi 的物理引脚 6 连接到 LDR 传感器模块的 Gnd 引脚

  • wiringPi 引脚 0 连接到 LED 的正极

  • 物理引脚 14 连接到 LED 的负极

现在我们已经连接了 LDR 传感器模块和 LED 到 RPi,让我们编写程序,通过从 LDR 传感器获取输入来控制 LED 的开关。

智能灯程序

在这个智能灯程序中,我们将首先从 LDR 传感器读取输入,并根据输入值来控制 LED 的开关。智能灯的程序描述如下。您可以从本书的 GitHub 存储库的Chapter02文件夹中下载SmartLight.cpp程序:

#include <iostream>
#include <wiringPi.h>

int main(void)
{

wiringPiSetup();

pinMode(0,OUTPUT); 
pinMode(8,INPUT); 

for(;;)
{
int ldrstate = digitalRead(8); 
if(ldrstate == HIGH) 
{
digitalWrite(0,HIGH); 
}
else
{
digitalWrite(0,LOW); 
}
 }
return 0;
 }

上述程序的解释如下:

  • main函数中,我们将 wiringPi 引脚 8 设置为输入引脚,将 wiringPi 引脚 0 设置为输出引脚。

  • 接下来,在for循环中,使用digitalRead(8)函数,我们从 LDR 传感器的数字引脚(D0)读取传入的数字数据,并将其存储在ldrstate变量中。从 LDR 传感器,我们将接收 HIGH(1)数据或 LOW(0)数据。当没有光时,ldrstate变量将为 HIGH,当有光时,ldrstate变量将为 LOW。

  • 接下来,我们将检查ldrstate变量内的数据是 HIGH 还是 LOW,使用if...else条件。

  • 使用if(ldrstate == HIGH),我们比较ldrstate变量内的数据是否为 HIGH。如果是 HIGH,我们使用digitalWrite(0,HIGH)来打开 LED。

  • 如果ldrstate为 LOW,则else条件将执行,并且通过使用digitalWrite(0,LOW),我们将关闭 LED。接下来,您可以单击“编译”按钮来编译代码,然后单击“构建”按钮来测试代码。

现在我们了解了 SmartLight 程序,我们将探讨脉宽调制PWM)的概念,并使用一个名为 softPWM 的库来改变 LED 的亮度。

使用 softPWM 的脉宽调制

PWM 是一种强大的技术,可以用来控制传递给 LED 和电机等电子元件的电源。使用 PWM,我们可以执行控制 LED 亮度或减速电机速度等操作。在本节中,我们将首先了解 PWM 的工作原理,然后逐步编写一个简单的 PWM 程序来增加 LED 的亮度。

PWM 的工作原理

在之前的Blink.cpp程序中,我们将数字信号从 RPi 应用到 LED。数字信号可以处于 HIGH 状态或 LOW 状态。在 HIGH 状态下,树莓派引脚产生 3.3V 的电压,在 LOW 状态下,引脚产生 0V 的电压。因此,在 3.3V 时,LED 以全亮度开启,在 0V 时,LED 关闭:

为了降低 LED 的亮度,我们需要降低电压。为了降低电压,我们使用 PWM。在 PWM 中,一个完整的重复波形称为一个周期,完成一个周期所需的时间称为周期。在下图中,红线代表一个完整的周期。完成该周期所需的时间称为周期:

信号保持高电平的时间称为占空比,如下图所示:

占空比以百分比格式表示,计算占空比的公式如下:

占空比 =(高信号的时间持续时间/总时间)X 100

在上图中,信号保持高电平 7 毫秒,单个周期的总时间为 10 毫秒:

占空比 = 70% 或 0.7

因此,占空比为 0.7 或 70%。接下来,为了找到新的电压值,我们需要将占空比乘以最大电压值 3.3V:

Vout = 占空比 X Vmax

Vout = 0.7 X 3.3

Vout = 2.31V

在 70%的占空比下,提供给 LED 的电压将为 2.31V,LED 的亮度将略有降低。

现在,如果我们将占空比降低到 40%,那么提供给 LED 的电压将为 1.32V,如下图所示:

现在我们已经了解了 PWM 如何用于降低 RPi 数据引脚的电压,让我们来看看 softPWM 库,使用该库可以将数据引脚用作 PWM 引脚。

softPWM 库

wiringPi 包含一个 softPWM 库,使用该库可以从 RPi 的任何数据引脚获得 PWM 信号输出。softPWM 库包含两个主要函数:softPwmCreatesoftPwmWrite。这两个函数的工作原理如下:

softPwmCreate(pin number, initial duty cycle value, max duty cycle value);

softPwmCreate函数用于创建 PWM 引脚。它包括三个主要参数:

  • 引脚编号:引脚编号表示我们要设置为 PWM 引脚的 wiringPi 引脚。

  • 初始占空比值:在初始占空比值中,我们必须提供作为占空比最小值的值。初始占空比值理想情况下设置为0

  • 最大占空比值:在最大占空比值中,我们必须提供占空比的最大值。此值必须设置为100

softPwmWrite(pin number, duty cycle value);

softPwmWrite函数用于在输出设备(例如 LED)上写入 PWM 数据。它包括两个参数:

  • 引脚编号:引脚编号表示我们必须在其上写入 PWM 数据的 wiringPi 引脚。

  • 占空比值:在此参数中,我们必须提供占空比值。占空比值必须在初始占空比值和最大占空比值之间,即在 0 到 100 的范围内。

现在我们了解了 softPWM 库中的两个函数,我们将编写一个简单的 C++程序,以使 LED 以不同的强度闪烁。

使用 softPWM 库使 LED 闪烁

对于使用 softPWM 的 LED 闪烁程序,您将需要一个 LED。在我的情况下,我已将 LED 的负极连接到 RPi 的物理引脚 6(地引脚),LED 的正极连接到 wiringPi 引脚 15。连接方式如下图所示:

将 LED 连接到 RPi 后,是时候编写程序了。使用 softPWM 库闪烁 LED 的程序如下。此程序称为Soft_PWM_Blink.cpp,您可以从本书的 GitHub 存储库的Chapter02文件夹中下载此程序:

#include <iostream>
#include <wiringPi.h>
#include <softPwm.h>
int main(void)
{
 wiringPiSetup();
 softPwmCreate (15, 0, 100) ;
 for(;;)
 {
 softPwmWrite (15, 25);
 delay(1000);
 softPwmWrite (15, 0);
 delay(1000);
 softPwmWrite (15, 50);
 delay(1000);
 softPwmWrite (15, 0);
 delay(1000);
 softPwmWrite (15, 100);
 delay(1000);
 softPwmWrite (15, 0);
 delay(1000);
 }
return 0;
 }

前面程序的解释如下:

  • 在此程序中,我们首先导入了wiringPiiostream库,以及softPwm库。

  • 接下来,在main函数中,使用softPwmCreate函数,我们将 wiringPi 引脚 15 设置为 PWM 引脚。初始占空比值设置为0,最大占空比值设置为100

  • 之后,在for循环内,我们有六个softPwmWrite函数,通过使用这些函数,我们以不同的亮度级别打开 LED。

  • 使用softPwmWrite(15,25)函数代码,LED 将以 25%的亮度保持高电平。由于延迟设置为 1,000,LED 将保持高电平 1 秒。

  • 之后,由于占空比值设置为0,LED 将在softPwmWrite(15 , 0)函数代码中保持低电平 1 秒。

  • 接下来,使用softPwmWrite(15,50)命令,LED 将以 50%的亮度保持高电平 1 秒。之后,我们再次将 LED 设置为低电平 1 秒。

  • 最后,使用softPwmWrite(15 , 100)函数代码,LED 将以 100%的亮度保持高电平 1 秒。接下来,我们再次将 LED 关闭 1 秒。

  • 编写代码后,您可以单击编译按钮来编译代码,然后点击构建按钮来测试代码。

这就是我们如何使用 softPWM 库来控制 LED 亮度的方法。

摘要

恭喜您成功地编写了您的第一个 C++程序并在树莓派上运行!在本章中,我们首先安装了wiringPi库,并了解了树莓派的 wiringPi 引脚连接。接下来,我们编写了一个简单的 C++程序来让 LED 闪烁。之后,我们了解了 LDR 传感器模块的工作原理,并根据 LDR 传感器模块的输入打开/关闭 LED。之后,我们了解了 PWM,并使用 softPWM 库编写了一个程序来改变 LED 的亮度。

在下一章中,我们将看看创建汽车机器人所需的不同部件。接下来,我们将了解直流电机和电机驱动器的工作原理,并学习如何创建汽车机器人。之后,我们将编写一个 C++程序来控制机器人朝不同方向移动。

问题

  1. 树莓派上有多少个接地针脚?

  2. 在黑暗环境中,LDR 传感器的电阻是高还是低?

  3. 用于从传感器读取值的命令是什么?

  4. 使 LED 闪烁六次的 for 循环命令是什么?

  5. 假设最大电压为 5V,占空比为 20%时的输出电压是多少?

第二部分:树莓派机器人技术

在这一部分,你将首先开发一个小车机器人。之后,你将了解 L298N 电机驱动器的工作原理,以及如何使机器人朝不同方向移动。你还将连接超声波传感器和 LCD 模块到机器人,并最终创建一个避障机器人。

这一部分包括以下章节:

  • 第三章,编程机器人

  • 第四章,构建避障机器人

  • 第五章,使用笔记本电脑控制机器人

第三章:编程机器人

在树莓派上编写了几个 C++程序并测试了它们的输出后,现在是时候创建我们自己的小车机器人,并使其向前、向后、向左和向右移动了。

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

  • 选择一个好的机器人底盘

  • 构建和连接机器人

  • 使用 H 桥

  • 移动机器人

技术要求

本章的主要硬件要求如下:

  • 机器人底盘(机器人底盘中包含的零件在“构建和连接机器人”部分中有解释)

  • 两个直流电机

  • L298N 电机驱动器

  • 母对母连接线

本章的代码文件可以从github.com/PacktPublishing/Hands-On-Robotics-Programming-with-Cpp/tree/master/Chapter03下载。

选择机器人底盘

在开始制作机器人之前,选择一个好的机器人底盘是最重要的活动之一。机器人的底盘就像人的骨架。我们的骨架由提供适当支撑我们器官的骨骼组成。同样,一个好的底盘将为电子元件提供适当的支撑并将它们固定在一起。

您可以从亚马逊和 eBay 等电子商务网站购买机器人底盘,也可以直接从处理机器人设备的供应商那里购买。在亚马逊上快速搜索“机器人底盘”将为您提供不同变体的机器人底盘列表。如果您以前没有制作过机器人,从所有这些选项中进行选择可能是一项艰巨的任务。在选择机器人底盘时,请记住以下要点:

  • 确保机器人底盘包括两块板(一个上板和一个下板),这样您可以将电子元件放在两块板之间以及在上板上,如下图所示:

  • 选择一个仅支持两个直流电机的机器人底盘,就像前面的照片中所示的那样。也有支持四个直流电机的机器人底盘,但您需要额外的电机驱动器来驱动四轮机器人。

  • 最后,选择一个具有直流电机(两个单位)、车轮(两个单位)和一个脚轮的机器人底盘作为完整套件的一部分,这样您就不必单独购买这些组件。

在前面的照片中显示的机器人底盘是我将用于创建我的小车机器人的底盘,因为它由两块板组成,并包括必要的组件(直流电机、车轮、脚轮、螺丝和间隔柱)作为完整套件的一部分。

构建和连接机器人

正确构建机器人是最重要的步骤之一。一个正确构建的机器人将能够平稳移动而不会受到任何阻碍。在构建机器人之前,让我们看一下您将需要的所有组件的完整清单。

构建机器人所需的零件包括以下内容:

  • 机器人底盘,必须包括以下组件:

  • 一个上板和一个下板

  • 两个 BO 直流电机(BO 是一种通常为黄色的直流电机)

  • 两个车轮

  • 一个脚轮

  • 间隔柱

  • 连接不同部件的螺丝

  • 一个螺丝刀

  • 一个L298N 电机驱动器

  • 七到八根连接线

  • 一个电池夹

  • 一个9V 电池

由于这些机器人底盘是由小规模公司制造的,并且没有国际上可用的标准机器人底盘,我用于此项目的机器人底盘将与您国家可用的机器人底盘不同。

在网上购买机器人底盘时,请检查产品的用户评论。

制作机器人

当包括上下板、直流电机、车轮、万向轮和间隔器在内的组件都包含在一个单一的机器人底盘套件中时,构建机器人变得更加容易。如果您单独购买这些组件,有可能某些组件不会合适,这会使整个机器人的组装变得不稳定。虽然我使用的底盘可能与您使用的不同,但大多数双轮机器人的构造都是相似的。

您可以在 GitHub 存储库的Chapter03文件夹中查看机器人的构建。

将电机驱动器连接到树莓派

构建完机器人后,是时候将树莓派连接到电机驱动器,这样我们就可以对机器人进行编程并使其朝不同方向移动。然而,在这之前,让我们先了解一下电机驱动器是什么。

什么是电机驱动器?

电机驱动器是一个包含电机驱动集成电路IC)的分立板。电机驱动器基本上与电流放大器相同,其主要目的是接收低电流信号并转换为高电流信号以驱动电机。下图显示了 L298N 电机驱动器:

我们需要电机驱动器的主要原因是,诸如电机之类的组件不能直接连接到树莓派,因为它们无法从树莓派获得足够的电流,如下图所示:

这就是为什么我们首先将电机连接到电机驱动器并使用电池为电机供电的原因,如下图所示:

接线连接

L298N 电机驱动器由四个输入引脚,四个输出插座(每个电机两个插座),以及两个电源插座组成。树莓派引脚连接到电机驱动器的输入引脚。直流电机线连接到电机驱动器的输出插座,电池夹连接到电源插座。L298N 电机驱动器的四个输入引脚标有IN1IN2IN3IN4。输出插座标有OUT1OUT2OUT3OUT4。下图显示了树莓派、电机驱动器和电机的接线连接:

如前图所示,wiringPi 引脚编号0234连接到电机驱动器的输入插座,如下所示:

  • wiringPi no 0连接到IN1

  • wiringPi no 2连接到IN2

  • wiringPi no 3连接到IN3

  • wiringPi no 4连接到IN4

  • 左电机线连接到OUT1OUT2插座

  • 右电机线连接到OUT3OUT4插座

  • 电池夹的红线连接到电机驱动器的VCC插座,黑线连接到地面插座

  • 树莓派的地针连接到地面插座

使用 H 桥

L298N 电机驱动 IC 可以同时控制两个电机。它由双 H 桥电路组成。这意味着它由两个电路组成,每个电路看起来像下图所示的电路,每个电机一个:

H 桥电路由四个开关S1S2组成。这些开关将根据我们提供给 L298N IC 的输入而打开和关闭。

现在,由于我们有两个电机,我们可以向 L298N IC 提供四种可能的输入组合,如下所示:

  • 高 高(1, 1)

  • 高 低(1, 0)

  • 低 高(0, 1)

  • 低 低(0, 0)

我们将向S1S2开关提供高(1)和低(0)信号,如下所示:

  1. 首先,当S1 = 1S2 =0时,S1开关将关闭,S2开关将保持打开。,或,将为 0,因此开关将打开。,或,将为 1,因此开关将关闭。现在,由于S1开关都关闭,电流将从Vcc流向S1,然后流向电机,然后流向,最后到达 GND。电机将以顺时针方向旋转,如下图所示:

  1. S1 = 0S2 = 1时,S1开关将打开,S2开关将关闭,将关闭,将打开。现在,由于S2开关关闭,电流将从Vcc流向S2,然后流向电机,然后流向,最后到达GND。电机将以逆时针方向旋转,如下图所示:

  1. S1 = 0S2 = 0时,S1开关将打开,S2开关将打开,开关将关闭,开关将关闭。现在,由于S1S2开关都打开,电流无法流向电机。在这种情况下,电机将停止,如下图所示:

  1. S1 = 1S2 = 1时,S1S2开关将关闭,而开关将打开。由于S1S2开关都关闭,这将产生短路条件,电流将无法通过电机。在这种情况下,电机将停止,与之前的情况一样:

如前所述,由于 L298N IC 由两个 H 桥组成,当我们提供高低信号时,另一个 H 桥将发生相同的过程。第二个 H 桥将控制另一个电机。

移动机器人

现在我们已经了解了 H 桥电路,我们将编写一个名为Forward.cpp的程序来使我们的机器人向前移动。之后,我们将编写一个程序来使机器人向后、向左、向右移动,然后停止。您可以从 GitHub 存储库的Chapter03下载Forward.cpp程序。

移动机器人向前的程序如下:

#include <stdio.h>
#include <wiringPi.h>

int main(void)
{
wiringPiSetup();
pinMode(0,OUTPUT); 
pinMode(2,OUTPUT); 
pinMode(3,OUTPUT);
pinMode(4,OUTPUT); 

 for(int i=0; i<1;i++)
 {
digitalWrite(0,HIGH); //PIN O & 2 will move the Left Motor
digitalWrite(2,LOW);
digitalWrite(3,HIGH); //PIN 3 & 4 will move the Right Motor
digitalWrite(4,LOW);
delay(3000);
 }
return 0;
 }

让我们看看这个程序是如何工作的:

  1. 首先,我们将 wiringPi 引脚(编号 0、1、2 和 3)设置为输出引脚。

  2. 接下来,使用以下两行,左电机向前移动:

digitalWrite(0,HIGH);
digitalWrite(2,LOW);
  1. 然后,接下来的两行使右电机向前移动:
digitalWrite(3,HIGH);
digitalWrite(4,LOW);
  1. 之后,delay命令意味着电机将向前移动三秒。由于我们目前在一个for循环中,电机将持续旋转。

  2. 编译程序以检查是否有任何错误。

  3. 接下来,将 9V 电池连接到电池夹子并上传程序。但在这之前,请确保将机器人的轮子抬起。这是因为当机器人开始移动时,您可能会得到以下三种输出中的一种:

  • 两个电机都向前移动。如果您得到这个输出,这意味着您的机器人将在放在地面上后向前移动。

  • 一个电机向前移动,另一个电机向后移动。如果您得到这种输出,请交换在电机驱动器上向后移动的电机的电线。例如,如果右电机向后移动,请将M3-OUT线插入M4-OUT插座,将M4-OUT线插入M3-OUT插座,如下图所示:

    • 两个电机都向后移动。在这种情况下,您的机器人将向后移动。如果您得到这种输出,请交换电机驱动器上左右两个电机的电线。要为左电机执行此操作,请将M1-OUT插座线连接到M2-OUT插座,将M2-OUT插座线连接到M1-OUT插座。对于右电机,将M3-OUT插座线连接到M4-OUT插座,将M4-OUT插座线连接到M3-OUT插座,如下图所示:

或者,您也可以交换 RPi 上的引脚以使机器人向前移动;将引脚 0 连接到左电机的引脚 2 的位置,将引脚 2 连接到引脚 0 的位置。同样,将引脚 3 连接到右电机的引脚 4 的位置,将引脚 4 连接到引脚 3 的位置。

  1. 点击上传按钮并检查最终输出。由于这个程序在一个for循环中,电机将持续运转。在测试输出后,断开电池与电池夹的连接,以便通过电机驱动器关闭电机的电源,停止电机运动。

使机器人向后移动

要使机器人向后移动,我们只需要交换HIGH信号和LOW信号。以这种方式移动机器人的完整程序编写在RobotMovement.cpp文件中,可以从 GitHub 存储库的Chapter03中下载:

digitalWrite(0,LOW);           //PIN O & 2 will move the Left Motor
digitalWrite(2,HIGH);
digitalWrite(3,LOW);          //PIN 3 & 4 will move the Right Motor
digitalWrite(4,HIGH);
delay(3000);

前两行将使左电机向后移动,而接下来的两行将使右电机向后移动。最后一行表示机器人将移动三秒钟。

停止机器人

要停止机器人移动,可以向引脚提供HIGH信号或LOW信号。在使机器人向后移动的代码中,添加以下命令以停止电机三秒钟:

digitalWrite(0,HIGH);           //PIN O & 2 will STOP the Left Motor
digitalWrite(2,HIGH);
digitalWrite(3,HIGH);          //PIN 3 & 4 will STOP the Right Motor
digitalWrite(4,HIGH);
delay(3000);

不同类型的转弯

机器人可以进行两种类型的转弯:

  • 轴向转动

  • 径向转动

轴向和径向转弯的代码已添加到RobotMovement.cpp程序中。

轴向转动

在轴向转动中,机器人的一个车轮向后移动,另一个车轮向前移动。机器人可以在原地转弯而不移动。如果机器人在转弯时有空间限制,例如在迷宫中移动,通常会进行轴向转弯。机器人可以进行轴向左转或轴向右转。

轴向左转

在轴向左转中,机器人的左电机向后移动,右电机向前移动,因此机器人向左转,如下图所示:

如果您已经了解了 H 桥的工作原理,您可能能够猜出进行轴向转弯的代码。如果不是,代码如下:

digitalWrite(0,LOW);
digitalWrite(2,HIGH);
digitalWrite(3,HIGH);
digitalWrite(4,LOW);
delay(500);

您需要稍微调整延迟值,以确保机器人向左正确转向。如果延迟值较高,机器人将转过 90°以上,而如果延迟值较低,机器人将转过 90°以下。

轴向右转

在轴向左转中,机器人的左电机向前移动,右电机向后移动,从而向右转,如下图所示:

轴向右转的代码如下:

digitalWrite(0,HIGH);
digitalWrite(2,LOW);
digitalWrite(3,LOW);
digitalWrite(4,HIGH);
delay(500);

径向转动

在径向转弯中,机器人的一个电机停止,另一个电机向前移动。停止的轮子作为圆的中心,移动的轮子作为圆周。电机之间的距离代表半径,这就是为什么这种转弯被称为径向转弯。机器人可以进行径向左转或径向右转。

径向左转

在径向左转中,左侧电机停止,右侧电机向前移动,因此机器人向左转,如下图所示:

进行径向左转的代码如下:

digitalWrite(0,HIGH);
digitalWrite(2,HIGH);
digitalWrite(3,HIGH);
digitalWrite(4,LOW);
delay(1000);

径向右转

在径向右转中,左侧电机向前移动,右侧电机停止,因此机器人向右转,如下图所示:

进行径向右转的代码如下:

digitalWrite(0,HIGH);
digitalWrite(2,LOW);
digitalWrite(3,HIGH);
digitalWrite(4,HIGH);
delay(1000);

总结

在本章中,我们已经查看了选择机器人底盘的某些标准。之后,我们构建了我们的小车,将电机驱动器连接到树莓派,并了解了 H 桥电路的工作原理。最后,我们编写程序使机器人向前、向后、向左和向右移动。

在下一章中,在了解了本章中移动机器人的基本原理之后,我们将首先编写一个程序来使用超声波传感器测量距离。接下来,我们将使用这些距离值来避开障碍物,也就是说,如果机器人靠近墙壁,超声波传感器将感应到并命令机器人转弯,从而避开障碍物。

问题

  1. 我们使用哪种电机驱动器来控制机器人?

  2. L298N 电机驱动 IC 包括哪个桥?

  3. 将机器人向前移动的 C 程序是什么?

  4. S1 = 0(低)和S2 = 1(高),将使机器人向哪个方向移动?

  5. 进行径向左转的代码是什么?

  6. 轴向右转的代码是什么?

第四章:构建避障机器人

现在我们可以让机器人以多个方向移动指定的时间,让我们考虑如何从超声波传感器中读取数值,以创建一个可以避开障碍物的机器人。我们还将使用 LCD 显示器,并用它来打印距离数值。

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

  • 使用超声波传感器

  • 使用 LCD

  • 创建一个避障机器人

技术要求

本章的主要硬件要求如下:

  • 一个 HC-SR04 超声波传感器

  • 一个 16x2 LCD 或带有 I2C LCD 模块的 16x2 LCD

  • 一个面包板

  • 一个 1KΩ的电阻

  • 一个 2KΩ的电阻

  • 12-13 根连接线

本章的代码文件可以从github.com/PacktPublishing/Hands-On-Robotics-Programming-with-Cpp/tree/master/Chapter04下载。

使用超声波传感器

超声波传感器用于测量障碍物或物体之间的距离。超声波传感器由发射换能器和接收换能器组成。发射换能器(触发)发出超声脉冲(也称为超声波),与附近的障碍物碰撞并被接收换能器(回波)接收。传感器通过测量超声波发送和接收之间的时间差来确定目标之间的距离。下图说明了这个过程:

我们将用于此项目的超声波传感器称为HC-SR04 超声波传感器,这是最广泛使用的超声波传感器之一。它可以测量 0-180 厘米范围内的距离,分辨率约为 0.3 厘米。它的频率约为 40 千赫。HC-SR04 传感器由以下四个引脚组成:

  • VCC 引脚

  • 一个地线引脚

  • 一个触发引脚

  • 一个回波引脚

触发引脚连接到发射换能器,发射脉冲,回波引脚连接到接收换能器,接收脉冲,如 HC-SR04 超声波传感器的照片所示:

超声波传感器如何测量距离

现在我们已经了解了超声波传感器的基本工作原理,让我们思考一下超声波传感器如何测量距离:

为了测量距离,超声波传感器会产生超声脉冲。为了产生这个超声脉冲,触发引脚被设置为状态,持续10 微秒。这产生了一个以声速传播的八周期声波,在与物体碰撞后被回波引脚接收。当接收到这个八周期声波时,回波将变高,并且会保持高电平一段时间,这段时间与超声脉冲到达回波引脚的时间成比例。如果超声脉冲到达回波引脚花费了 20 微秒,回波引脚将保持高电平 20 微秒。

确定所花时间的算术方程

让我们首先看一下计算距离的算术方程,如下图所示:

如前图所示,假设传感器和物体之间的距离为 30 厘米。超声波传感器的传播速度为 340 米/秒,或 0.034 厘米/微秒。

为了计算时间,我们将使用以下方程:

如果我们将时间移到左边,速度移到右边,我们得到以下方程:

如果我们输入前面的数字,我们得到以下结果:

这个方程的结果是所花时间为 882.35 微秒。

尽管时间值为 882.35μs,但回波引脚保持高电平的时间持续值实际上将是 882.35μs 的两倍,即 1764.70μs。这是因为超声波首先朝着物体传播,然后从物体反射回来后被回波接收。它传播的距离是相同的:首先从传感器到物体,然后从物体到传感器。如果时间值加倍,距离值也将加倍。我们可以修改上述方程来找到距离,如下所示:

请记下这个方程,因为我们稍后将使用它来找到距离,一旦我们得到时间持续值。

将超声波传感器连接到树莓派

HC-SRO4 传感器由四个引脚组成:VCCGNDtriggerTrig)和echo,因此 RPi 和超声波传感器的接线连接应如下所示:

  • 将传感器的VCC引脚连接到引脚编号 4。

  • 将传感器的GND引脚连接到引脚编号 9。

  • 将传感器的Trig引脚连接到 wiringPi 引脚编号 12。

  • 传感器的echo引脚通过电压分压器连接到 wiringPi 引脚编号 13。电压分压器电路中使用的两个电阻的电阻值分别为 1KΩ(R1)和 2KΩ(R2)。电压分压器电路用于将来自回波引脚(到 RPi)的输入 5V 信号降低到 3.3V。RPi 和 HC-SR04 的接线连接如下图所示:

将传入电压转换为 3.3V 的公式如下:

Vin是来自回波引脚的输入电压,R1是第一个电阻,R2是第二个电阻。Vin为 5V,R1为 1KΩ,R2为 2KΩ:

HC-SR04 传感器程序

将 HC-SR04 传感器连接到树莓派后,让我们编写一个程序来测量超声波传感器到物体之间的距离。距离测量程序名为DistanceMeasurement.cpp,您可以从 GitHub 存储库的Chapter04文件夹中下载。

测量距离的代码如下:

#include <stdio.h>
#include <iostream>
#include <wiringPi.h>

using namespace std;

#define trigger 12
#define echo 13

long startTime;
long stopTime;

int main()
{

 wiringPiSetup();

 pinMode(trigger,OUTPUT);
 pinMode(echo, INPUT); 

for(;;){
 digitalWrite(trigger,LOW);
 delay(500);

 digitalWrite(trigger,HIGH);
 delayMicroseconds(10);

 digitalWrite(trigger,LOW); 

 while(digitalRead(echo) == LOW);
 startTime = micros();

 while(digitalRead(echo) == HIGH);
 stopTime = micros(); 

long totalTime= stopTime - startTime; 
 float distance = (totalTime * 0.034)/2;

 cout << "Distance is: " << distance << " cm"<<endl;
 delay(2000);
}
return 0;
}

在上述代码中,我们声明了wiringPistdioiostream库。之后,我们声明了std命名空间:

  1. 之后,使用#define trigger 12#define echo 13这两行,我们将 wiringPi 引脚编号 12 声明为触发引脚,将 wiringPi 引脚编号 13 声明为回波引脚。

  2. 然后,我们声明了两个名为startTimestopTime的变量,它们的数据类型为LongstartTime变量将记录触发引脚发送超声波脉冲的时间,stopTime变量将记录回波引脚接收超声波脉冲的时间。

  3. 在主函数内,将触发引脚设置为OUTPUT,因为它将产生超声波脉冲。将回波引脚设置为INPUT,因为它将接收超声波脉冲。

  4. 在一个for循环内,将触发引脚设置为 500 毫秒或 0.5 秒的LOW

  5. 为了产生超声波脉冲,将触发引脚设置为HIGHdigitalWrite(trigger,HIGH))持续 10 微秒(delayMicroseconds(10))。产生了 10 微秒的脉冲后,我们再次将触发引脚设置为LOW

  6. 接下来,我们有两个while循环,在这两个循环内,有两个micros()函数。micros()将以毫秒为单位返回当前时间值。第一个while循环(digitalRead(echo) == LOW)将记录脉冲开始时的时间,并将回波引脚为LOW的时间持续值存储在startTime变量中。

  7. 当回波引脚接收到脉冲时,第二个while循环(digitalRead(echo) == HIGH)将执行。此while循环中的micros()函数将返回超声脉冲到达回波引脚所花费的时间值。这个时间值将被存储在stopTime变量中。

  8. 接下来,为了找到总时间,我们将从stopTime中减去startTime,并将这个时间值存储在totalTime变量中。

  9. 找到totalTime后,我们使用以下公式来计算距离:

float distance = (totalTime x 0.034)/2

  1. 为了显示距离值,我们将使用cout语句。调用delay(2000);命令,以便每两秒打印一次距离值。

完成代码后,您可以编译和构建它以检查最终输出。您可以将一个物体放在传感器前面,物体距离传感器的距离将显示在控制台内。

在我的机器人底盘上,有一个额外的部件,我已经固定了超声波传感器。

使用 LCD

液晶显示器(LCD)是一种电子显示单元,通常用于计算机、电视、智能手机和相机。16x2 LCD 是一个基本的 LCD 模块,通常用于电子或 DIY 项目。顾名思义,16x2 LCD 由 16 列和 2 行组成。这意味着它有两行,每行最多可以显示 16 个字符。16x2 LCD 由从VSSK标记的 16 个引脚组成,如下图所示:

LCD 上的每个引脚可以描述如下:

引脚号 名称 工作原理
1 VSS (GND) 地线引脚。
2 VCC VCC 引脚需要 5V 电源才能打开 LCD 模块。
3 Vo 使用此引脚,我们可以调整 LCD 的对比度。我们可以将它连接到 GND 以获得最大对比度。如果您想要改变对比度,将其连接到电位器的数据引脚。
4 RS (RegisterSelect) LCD 由两个寄存器组成:命令寄存器和数据寄存器。RS 引脚用于在命令寄存器和数据寄存器之间切换。它被设置为高电平(1)以用于命令寄存器,低电平(0)用于数据寄存器。
5 R/W (Read Write) 将此引脚设置为低电平以写入寄存器,或将其设置为高电平以从寄存器中读取。
6 E (Enable) 此引脚使 LCD 的时钟启用,以便 LCD 可以执行指令。
7 D0 尽管 LCD 有八个数据引脚,我们可以将其用于八位模式或四位模式。在八位模式中,所有八个数据引脚(D0-D7)都连接到 RPi 引脚。在四位模式中,只有四个引脚(D4-D7)连接到 RPi。在这种情况下,我们将使用四位模式的 LCD,以便占用更少的 wiringPi 引脚。
8 D1
9 D2
10 D3
11 D4
12 D5
13 D6
14 D7
15 A (Anode) LCD 背光的+5V 引脚。
16 K (Cathode) LCD 背光的 GND 引脚。

由于 16x2 LCD 有 16 个引脚,正确连接所有引脚到树莓派有时可能会有问题。如果您犯了一个错误,例如将需要连接到 D0 的引脚连接到 D1,您可能会得到不正确的输出。

为了避免这种潜在的混淆,您可以选择购买一个 16x2 LCD 的I2C LCD 适配器模块。该模块将 LCD 的 16 个引脚作为输入,并提供 4 个引脚作为输出(VCC、GND、SDA、SCL)。这意味着您只需要连接 4 个引脚到树莓派,而不是 16 个引脚。

还有带有 I2C LCD 适配器焊接的 16x2 LCD,这可以节省一些时间。我用于这个项目的 16x2 LCD 已经焊接了 I2C LCD 适配器,如下图所示:

在接下来的章节中,我们将了解接线连接以及如何编程普通 LCD 和带有 I2C LCD 适配器的 LCD。

我将16x2 LCD 与 I2C LCD 适配器称为I2C LCD,以避免复杂化。

将 16x2 LCD 连接到 Raspberry Pi

要将 16x2 LCD 连接到 Raspberry Pi,您将需要一个迷你面包板,因为有几个引脚需要连接到 VCC 和 GND。 RPi 和 16x2 LCD 的接线连接如下:

首先,将 Raspberry Pi 的引脚号 2 或引脚号 4 连接到面包板的一个水平引脚,以便我们可以将该行用作 VCC 行。同样,将 Raspberry Pi 的一个地引脚连接到面包板的一个水平引脚,以便我们可以将该行用作地行。接下来,按照以下说明进行操作:

  1. 将 VSS(GND)引脚连接到面包板的地行

  2. 将 VCC 引脚连接到面包板的 VCC 行

  3. 将 V0 引脚连接到面包板的地行

  4. 寄存器选择(RS)引脚连接到 RPi 的 wiringPi 引脚号 22

  5. 将 R/W 引脚连接到面包板的地行,因为我们将关闭 LCD 的寄存器

  6. 将使能引脚连接到 RPi 的 wiringPi 引脚号 26

  7. 我们将使用四位模式的 LCD,因此 D0 到 D3 引脚将保持未连接状态

  8. 引脚 D4 应连接到 RPi 的 wiringPi 引脚号 24

  9. 引脚 D5 应连接到 RPi 的 wiringPi 引脚号 25

  10. 引脚 D6 应连接到 RPi 的 wiringPi 引脚号 27

  11. 引脚 D7 应连接到 RPi 的 wiringPi 引脚号 28

  12. 将阳极引脚连接到面包板的 VCC 行

  13. 将阴极引脚连接到面包板的地行

为了测试 LCD 程序,在“Build | Set Build Commands”中打开 Build 选项,并在 Compile and Build 选项中添加-lwiringPiDev命令,如下截图所示:

将 16X2 LCD 连接到 RPi 后,让我们编程 LCD。

编程 LCD

我们将使用普通的 16x2 LCD 编写两个程序。在第一个程序中,我们将在 16x2 LCD 上打印一个值。在第二个程序中,我们将在 LCD 屏幕上打印超声波传感器值。第一个程序称为LCDdisplay.cpp,您可以从Chapter04的 GitHub 存储库中下载。

LCD 程序

将 LCD 连接到 Raspberry Pi 后,让我们检查在 LCD 上打印值的程序,如下所示:

#include <wiringPi.h> 
#include <lcd.h> 

#define RS 22 //Register Select
#define E 26 //Enable

#define D4 24 //Data pin 4
#define D5 25 //Data pin 5
#define D6 27 //Data pin 6
#define D7 28 //Data pin 7

int main()
{

int fd; 
wiringPiSetup(); 
fd= lcdInit (2, 16, 4, RS, E, D4, D5, D6, D7, 0, 0, 0, 0); 
lcdPuts(fd, "LCD OUTPUT"); 

}

以下是前面程序的详细信息:

  1. 首先,我们调用LCD.h库。LCD.h库包含了我们可以用来打印、定位和移动文本以及清除 LCD 屏幕的所有重要函数。

  2. 接下来,我们定义引脚号 RS、E、D4、D5、D6 和 D7。

  3. lcdInit函数内部,第一个数字2代表 LCD 中的行数,而数字16代表列数。数字4表示我们正在使用四位模式的 LCD。接下来是 RS 和 E 引脚,最后是四个数据引脚。由于我们没有将 D0、D1、D2 和 D3 数据引脚连接到 RPi,因此在末尾有四个零。

  4. lcdPuts用于在 LCD 上打印数据。它有两个输入参数:fd变量和需要显示的文本值。

  5. 完成此代码后,您可以编译和构建代码以测试最终输出。

  6. 在输出中,您会注意到文本输出将从第一列开始,而不是从第零列开始。

  7. 为了将文本定位在极左侧,或列0,行0,我们需要使用lcdPosition()函数。lcdPosition(fd,列位置,行位置)函数由三个参数组成,并且应该在lcdPuts函数之前写入,如下所示:

fd= lcdInit (2, 16, 4, RS, E, D4, D5, D6, D7, 0, 0, 0, 0);
lcdPosition(fd, 0, 0); 
lcdPuts(fd, "LCD OUTPUT");

如果文本未定位在列 0 和行 0,请重新启动 RPi 并再次测试代码。

LCD 和超声波传感器程序

在 LCD 上打印简单的文本值后,让我们看看如何在 LCD 屏幕上查看超声波距离值。HC-SR04 超声波传感器、16x2 LCD 和 RPi 的接线连接如下:

LCD 连接到 RPi 保持不变。超声波触发引脚连接到 wiringPi 引脚 12 号,回波引脚连接到 wiringPi 引脚 13 号。现在让我们看看程序。该程序称为LCDdm.cppdm代表距离测量),您可以从Chapter04的 GitHub 存储库中下载。LCDdm.cpp程序是LCDdisplay.cppDistanceMeasurement.cpp程序的组合:

int main()
{
...
for(;;)
{
...
cout << "Distance is: " << distance << " cm"<<endl;
lcdPosition(fd, 0, 0);           //position the cursor on column 0, row 0
lcdPuts(fd, "Distance: ");      //this code will print Distance text
lcdPosition(fd, 0, 1);          //position the cursor on column 0, row 1
lcdPrintf(fd, distance);        // print the distance value
lcdPuts(fd, " cm");
delay(2000);
clear();                     
}
return 0
}

在上述代码中,找到距离值后,我们使用lcdPosition(fd, 0, 0);命令将光标定位在第零行,第零列。接下来,使用lcdPuts(fd, "Distance: ")代码,我们显示距离文本。然后,我们将光标定位在第一行的第零列。最后,使用lcdPrintf(fd, distance);命令打印距离值。由于我们将延迟设置为两秒,因此每两秒将打印一次距离值。然后它将被清除(clear())并替换为新值。

I2C 协议是什么?

I2C 协议用于许多电子设备。我们用它来连接一个主设备到多个从设备,或者多个主设备到多个从设备。I2C 协议的主要优势在于主设备只需要两个引脚与多个从设备通信。

在 I2C 总线中,所有设备都并行连接到相同的双线总线。我们可以使用 7 位寻址连接总共 128 个设备,使用 10 位寻址连接总共 1,024 个设备,如下图所示:

使用 I2C 协议连接的每个设备都有一个唯一的 ID,这使得可以与多个设备通信。I2C 协议中的两个主要引脚是串行数据(SDA)引脚和串行时钟(SCA)引脚:

  • SDA:SDA 线用于传输数据。

  • SCL:SCL 由主设备生成。它是一个时钟信号,用于同步连接在 I2C 中的设备之间的数据传输。

现在我们已经了解了 I2C 协议的基础知识,让我们看看如何连接 I2C LCD 和树莓派。

连接 I2C LCD 和树莓派

在树莓派上,物理引脚 3 是 SDA 引脚,而物理引脚 5 是 SCA 引脚,如下图所示:

以下是连接 LCD 与 RPi 的详细信息:

  1. 将树莓派的 3 号引脚连接到 LCD 的 SDA 引脚

  2. 将树莓派的 5 号引脚连接到 LCD 的 SCA 引脚

  3. 将 LCD 的 GND 引脚连接到 RPi 的 GND 引脚

  4. 将 LCD 的 VCC 引脚连接到树莓派的 2 号引脚或 4 号引脚

使用 I2C LCD 模块编程 LCD

在编写程序之前,我们首先需要从树莓派配置中启用 I2C 协议。为此,请打开命令窗口并输入以下命令:

sudo raspi-config

在配置中,打开接口选项,如下所示:

接下来,打开 I2C 选项,如下面的屏幕截图所示:

选择“是”选项并按Enter键启用 I2C,如下所示:

启用 I2C 后,选择“确定”选项并退出配置,如下所示:

在树莓派内部启用 I2C 协议后,让我们编写程序将值打印到 LCD 上。该程序称为I2CLCD.cpp,您可以从Chapter04的 GitHub 存储库中下载。

由于这个 LCD 连接了一个 I2C 模块,我们之前用过的LCD.h库在这个程序中将无法使用。相反,我创建了五个主要函数,用于初始化 LCD,打印消息和清除 LCD 屏幕,如下所示:

  • init_lcd(): 该函数将初始化(设置)LCD

  • printmessage(): 该函数用于在 LCD 上打印字符串

  • printInt(): 该函数用于显示整数值

  • printfloat(): 该函数用于显示浮点值

  • clear(): 该函数将清除 LCD 屏幕

#include <wiringPiI2C.h>
#include <wiringPi.h>
#include <stdlib.h>
#include <stdio.h>

#define I2C_DEVICE_ADDRESS 0x27 
#define firstrow 0x80 // 1st line
#define secondrow 0xC0 // 2nd line
int lcdaddr;
  1. 我们通过声明wiringPiI2C.h库来启动程序。接下来,我们有wiringPi库和另外两个标准 C 库。

  2. 然后,使用#define I2C_DEVICE_ADDRESS 0x27命令,我们定义了 I2C 设备地址,即0x27

  3. 0x80命令代表第一行:第零行,第零列。使用#define firstrow 0x80命令,我们初始化 LCD 的第一行。

  4. 同样,0xC0代表 LCD 的第二行:第一行,第零列。使用#define secondrow 0xC0命令,我们初始化 LCD 的第二行。

  5. 接下来,在lcdaddr变量内,我们将存储 I2C LCD 的地址,如下所示:

int main() {

 wiringPiSetup();

 lcdaddr = wiringPiI2CSetup(I2C_DEVICE_ADDRESS);

 init_lcd(); // initializing OR setting up the LCD 
 for(;;) {

 moveCursor(firstrow);
 printmessage("LCD OUTPUT");
 moveCursor(secondrow);
 printmessage("USING I2C");
 delay(2000);
 clear();

 moveCursor(firstrow);
 printmessage("Integer: ");
 int iNumber = 314;
 printInt(iNumber);

 moveCursor(secondrow);
 printmessage("Float: ");
 float fNumber= 3.14;
 printFloat(fNumber);
 delay(2000);
 clear();
 }
 return 0;
}
  1. main()函数内,我们将设备地址存储在lcdaddr变量中。

  2. 然后,我们使用init_lcd();命令初始化 LCD。

  3. 接下来,在for循环中,我们使用moveCursor(firstrow);命令将光标移动到第一行。

  4. 现在,由于光标在第一行,所以在printmessage("LCD OUTPUT"代码中的LCD OUTPUT文本将被打印在第一行。

  5. 然后,使用moveCursor(secondrow)命令将光标移动到第二行。在该行上打印USING I2C文本。

  6. 第一行和第二行的文本将在两秒内可见,之后 LCD 屏幕将被clear()命令清除。

  7. 之后,使用接下来的四行,在第一行上打印一个整数314printInt(iNumber)函数用于显示整数值。

  8. 同样,printFloat(iFloat)函数用于显示浮点值。在接下来的四行中,将在第二行上打印float 3.14

  9. 之后,我们再次清除 LCD。

这就是我们如何在 I2C LCD 内显示字符串,数字和浮点值。

I2C LCD 和超声波传感器程序

要在 I2C LCD 内读取超声波传感器值,请将超声波传感器和 I2C LCD 连接到 RPi。您可以从Chapter04的 GitHub 存储库中下载名为I2CLCDdm.cpp的完整程序。I2C LCD,超声波传感器和 RPi 的接线连接如下图所示:

这个I2CLCDdm.cpp程序基本上是DistanceMeasurement.cppI2CLCD.cpp程序的组合。在这个程序中,在cout << "Distance: "<<distance << "cm" << endl行下面声明了与超声波传感器和 I2C LCD 相关的所有必要库和变量,我们需要添加以下代码:

 moveCursor(firstrow);
 printmessage("DISTANCE");
 moveCursor(secondrow);
 printFloat(distance);
 printmessage(" cm");
 delay(2000);
 clear();

使用printmessage("DISTANCE")命令将在第一行上打印文本DISTANCE。之后,在第二行上,使用printFloat(distance)命令将打印距离值,因为代码仍在第二行上。使用printmessage(" cm")命令,cm文本将在距离值旁边打印出来。

控制台内的距离值和 I2C LCD 将在两秒内可见。接下来,使用clear()函数,旧的距离值将被清除并替换为新值。然而,在控制台中,新值将显示在下一行。

构建避障机器人

在这种情况下,我们的机器人将在给定空间内自由移动,但一旦靠近物体或障碍物,它将转向或向后移动,从而避开障碍物。在这种项目中,我们通常使用超声波传感器。当机器人移动时,超声波传感器不断测量它与物体的距离。当传感器检测到距离值非常低,并且机器人可能与附近物体碰撞时,它将命令机器人改变方向,从而避开障碍物。

要创建一个避障机器人,您首先需要将超声波传感器安装在机器人上。在我的机器人套件中,已经有一个附件可以让我将超声波传感器安装在机器人上。这个附件如下所示:

在机器人上安装超声波传感器后,最终装配如下所示:

接线连接

超声波传感器的触发引脚连接到 wiringPi 引脚号 12,而回波引脚通过电压分压电路连接到 wiringPi 引脚号 13。超声波传感器的 VCC 引脚连接到 RPi 的物理引脚 2(5V),超声波传感器的地线引脚连接到 RPi 的物理引脚 6。其余连接如下:

  • WiringPi 引脚 0连接到 L298N 电机驱动器的IN1 引脚

  • WiringPi 引脚 2连接到 L298N 电机驱动器的IN2 引脚

  • WiringPi 引脚 3连接到 L298N 电机驱动器的IN3 引脚

  • WiringPi 引脚 4连接到 L298N 电机驱动器的IN4 引脚

  • 电机驱动器的地线引脚连接到 RPi 的物理引脚 3

  • 我正在使用 I2C LCD,因此 I2C LCD 的SDA 引脚连接到RPi 的物理引脚 3SCL 引脚连接到物理引脚 5I2C LCD 的地线引脚连接到物理引脚 9I2C LCD 的 VCC 引脚连接到 RPi 的物理引脚 4

将 LCD 显示器连接到机器人完全取决于您。如果机器人上有足够的空间可以放置 LCD,那就加上去。如果没有,这不是必需的。

编程避障机器人

在这个程序中,我们将首先使用超声波传感器找出附近物体的距离。接下来,我们将创建一个if条件来监测距离数值。如果距离低于某个数值,我们将命令机器人转向。否则,机器人将继续向前移动。您可以从 GitHub 存储库的Chapter04中下载名为ObstacleAvoiderRobot.cpp的完整代码:

int main()
{
...
for(;;)
{
...
if(distance < 7)
{
digitalWrite(0,LOW);
digitalWrite(2,HIGH);
digitalWrite(3,HIGH);
digitalWrite(4,LOW);
delay(500);
moveCursor(firstrow);
printmessage("Obstacle Status");
moveCursor(secondrow);
printmessage("Obstacle detected");
clear();
}
else
{
digitalWrite(0,HIGH);
digitalWrite(2,LOW);
digitalWrite(3,HIGH);
digitalWrite(4,LOW);
moveCursor(firstrow);
printmessage("Obstacle Status");
moveCursor(secondrow);
printmessage("No Obstacle");
clear();
}
}
return 0;
}

在这段代码中,如果距离大于7 厘米,机器人将继续向前移动。只要障碍物不存在,LCD 将在第二行显示No Obstacle的消息。如果检测到障碍物,机器人将首先进行 0.5 秒的径向左转,I2C LCD 将在第二行显示Obstacle detected的文本。您可以根据电机速度增加或减少延迟值。

总结

在本章中,我们看了超声波传感器的工作原理,并编写了一个程序来测量距离值。接下来,我们编程 16x2 LCD,并使用它读取超声波距离值。我们还研究了 I2C LCD,它将 16 个 LCD 引脚作为输入,并提供四个引脚作为输出,从而简化了接线连接。最后,我们将超声波传感器安装在我们的机器人上,创建了我们的避障机器人。这个机器人在附近没有障碍物时自由移动,如果靠近障碍物,它将通过转向来避开。

在下一章中,我们将创建两种不同类型的 PC 控制机器人。在第一个 PC 控制机器人中,我们将使用一个叫做ncurses的库,并使用键盘作为输入。在第二个 PC 控制机器人中,我们将使用 QT 创建 UI 按钮,然后使用它们来移动机器人。

问题

  1. 超声波传感器发送什么类型的脉冲?

  2. LCD 代表什么?

  3. HC-SR04 超声波传感器可以测量到多远的距离?

  4. lcdPosition(fd, 4,1)命令会从哪一行和哪一列开始打印文本?

  5. LCD 的阳极引脚(引脚 15)和阴极引脚(引脚 16)在 LCD 上有什么功能?

第五章:使用笔记本电脑控制机器人

使用计算机控制机器人是一件迷人的事情。计算机成为遥控器,机器人根据键盘提供的命令移动。在本章中,我们将介绍使用笔记本电脑无线控制机器人的两种技术。

我们将涵盖以下主题:

  • 安装ncurses

  • 使用ncurses控制 LED 和蜂鸣器

  • 使用笔记本电脑键盘控制一辆漫游车(RPi 机器人)

  • 安装和设置 QT5

  • 使用 GUI 按钮控制 LED

  • 使用 QT5 在笔记本电脑上控制漫游车

技术要求

您需要此项目的主要硬件组件如下:

  • 两个 LED

  • 一个蜂鸣器

  • 一个 RPi 机器人

本章的代码文件可以从github.com/PacktPublishing/Hands-On-Robotics-Programming-with-Cpp/tree/master/Chapter05下载。

安装ncurses

New cursesncurses)是一个编程库,允许开发人员创建基于文本的用户界面。它通常用于创建基于 GUI 的应用程序或软件。ncurses库的一个关键特性是我们可以用它来从键盘键获取输入,并在输出端控制硬件设备。我们将使用ncurses库编写程序来检测键以相应地控制我们的机器人。例如,如果我们按上箭头,我们希望我们的机器人向前移动。如果我们按左箭头,我们希望我们的机器人向左转。

要安装ncurses库,我们首先必须打开命令窗口。要安装ncurses,请输入以下命令并按Enter

sudo apt-get install libncurses5-dev libncursesw5-dev 

接下来,您将被问及是否要安装该库。输入Y(表示是)并按Enterncurses库将需要大约三到五分钟的时间下载并安装到您的 RPi 中。

确保您的 RPi 靠近 Wi-Fi 路由器,以便库文件可以快速下载。

ncurses 函数

安装ncurses库后,让我们探索一些属于该库的重要函数:

  • initscr(): initscr()函数初始化屏幕。它设置内存,并清除命令窗口屏幕。

  • refresh(): 刷新函数刷新屏幕。

  • getch(): 此函数将检测用户的触摸,并返回该特定键的 ASCII 编号。然后将 ASCII 编号存储在整数变量中,以供后续比较使用。

  • printw(): 此函数用于在命令窗口中打印字符串值。

  • keypad(): 如果键盘函数设置为 true,则我们还可以从功能键和箭头键中获取用户的输入。

  • break: 如果程序在循环中运行,则使用此函数退出程序。

  • endwin(): endwin()函数释放内存,并结束ncurses

整个ncurses程序必须在initscr()endwin()函数之间编写:

#include <ncurses.h>
...
int main()
{
...
initscr();
...
...
endwin();
return 0;
}

使用ncurses编写 HelloWorld 程序

现在让我们编写一个简单的ncurses程序来打印Hello World。我将这个程序命名为HelloWorld.cppHelloWorld.cpp程序可以从 GitHub 存储库的Chapter05文件夹中下载:

#include <ncurses.h>
#include <stdio.h>

int main()
{
initscr(); //initializes and clear the screen
int keypressed = getch(); 
if(keypressed == 'h' || keypressed == 'H')
{
printw("Hello World"); //will print Hello World message
}
getch();
refresh(); 

endwin(); // frees up memory and ends ncurses
return 0;
}

使用ncurses库编译和运行 C++程序的程序与其他程序不同。首先,我们需要理解程序。之后,我们将学习如何编译和运行它。

在上面的代码片段中,我们首先声明了ncurses库和wiringPi库。接下来,我们执行以下步骤:

  1. main函数中,我们声明initscr()函数来初始化和清除屏幕。

  2. 接下来,当用户按下一个键时,将调用getch函数,并将该键的 ASCII 数字存储在keypressed变量中,该变量是int类型。

  3. 之后,使用for循环,我们检查按下的键是否为'h'或(||)'H'。确保将字母 H 放在单引号中。当我们将字母放在单引号中时,我们会得到该字符的 ASCII 数字。例如,'h'返回 ASCII 数字104,而'H'返回 ASCII 数字72。您也可以写入hH键按下的 ASCII 数字,分别为 104 和 72。这将如下所示:if(keypressed == 72 || keypressed == 104)。数字不应该在引号内。

  4. 然后,如果您按下'h''H'键,Hello World将在命令窗口内打印出来:

  1. 如果要在下一行上打印Hello World,您可以在Hello World文本之前简单地放置\n。这将如下所示:printw("\nHello World")

  2. 之后,当您按下一个键时,在if条件下方的getch()函数将被调用,程序将终止。

编译和运行程序

要编译和运行HelloWorld.cpp程序,请打开终端窗口。在终端窗口内,输入ls并按Enter。现在您将看到您的 RPi 内所有文件夹名称的列表:

HelloWorld.cpp存储在Cprograms文件夹中。要打开Cprograms文件夹,输入cd(更改目录)后跟文件夹名称,然后按Enter

cd Cprograms

可以看到上一个命令的输出如下:

接下来,要查看Cprograms文件夹的内容,我们将再次输入ls

Cprograms文件夹中,有一个Data文件夹和一些.cpp程序。我们感兴趣的程序是HelloWorld.cpp程序,因为我们想要编译和构建这个程序。要执行此操作,请输入以下命令并按Enter

gcc -o HelloWorld -lncurses HelloWorld.cpp 

以下屏幕截图显示编译成功:

对于任何使用ncurses库的代码进行编译,代码如下:

gcc -o Programname -lncurses Programname.cpp

之后,输入./HelloWorld并按Enter运行代码:

按下Enter后,整个终端窗口将被清除:

接下来,按下hH键,Hello World文本将在终端窗口中打印出来。要退出终端窗口,请按任意键:

现在我们已经创建了一个简单的HelloWorld程序,并测试了ncurses库在终端窗口内的工作,让我们编写一个程序来控制 LED 和蜂鸣器。

使用 ncurses 控制 LED 和蜂鸣器

在编译和测试您的第一个ncurses程序之后,让我们编写一个程序,通过从键盘提供输入来控制 LED 和蜂鸣器。

接线连接

对于这个特定的例子,我们将需要两个 LED 和一个蜂鸣器。LED 和蜂鸣器与 RPi 的接线连接如下:

我们可以从连接图中看到以下内容:

  • 第一个 LED 的正极(阳极)引脚连接到 wiringPi 引脚号 15,负极(阴极)引脚连接到物理引脚号 6(地引脚)。

  • 第二个 LED 的正极引脚连接到 wiringPi 引脚号 4,负极引脚连接到物理引脚号 14(地引脚)。

  • 蜂鸣器的一根引脚连接到 wiringPi 引脚号 27,另一根引脚连接到物理引脚号 34(地引脚)。

编写 LEDBuzzer.cpp 程序

我们的程序名为LEDBuzzer.cppLEDBuzzer.cpp程序可以从 GitHub 存储库的Chapter05文件夹中下载。LEDBuzzer程序如下:

#include <ncurses.h>
#include <wiringPi.h>
#include <stdio.h>
int main()
{
 wiringPiSetup();

 pinMode(15,OUTPUT); //LED 1 pin
 pinMode(4, OUTPUT); //LED 2 pin
 pinMode(27,OUTPUT); //Buzzer pin

for(;;){

initscr();

int keypressed = getch();

if(keypressed=='L' || keypressed=='l')
{
 digitalWrite(15,HIGH);
 delay(1000);
 digitalWrite(15,LOW);
 delay(1000);
}

if(keypressed== 69 || keypressed=='e')       // 69 is ASCII number for E.
{
 digitalWrite(4,HIGH);
 delay(1000);
 digitalWrite(4,LOW);
 delay(1000);
}

if(keypressed=='D' || keypressed=='d')
{
 digitalWrite(15,HIGH);
 delay(1000);
 digitalWrite(15,LOW);
 delay(1000);
 digitalWrite(4,HIGH);
 delay(1000);
 digitalWrite(4,LOW);
 delay(1000);
}

if(keypressed=='B' || keypressed== 98)        //98 is ASCII number for b
{
 digitalWrite(27,HIGH);
 delay(1000);
 digitalWrite(27,LOW);
 delay(1000);
 digitalWrite(27,HIGH);
 delay(1000);
 digitalWrite(27,LOW);
 delay(1000);
}

if(keypressed=='x' || keypressed =='X')
{
break; 
}

refresh();
}
endwin(); // 
return 0; 
}

编写程序后,让我们看看它是如何工作的:

  1. 在上述程序中,我们首先声明了ncurseswiringPi库,以及stdio C 库

  2. 接下来,引脚编号1547被声明为输出引脚

  3. 现在,当按下Ll键时,LED 1 将分别在一秒钟内变为HIGHLOW

  4. 同样,当按下Ee键时,LED 2 将分别在一秒钟内变为HIGHLOW

  5. 如果按下Dd键,LED 1 将分别在一秒钟内变为HIGHLOW,然后 LED 2 将分别在一秒钟内变为HIGHLOW

  6. 如果按下bB键,蜂鸣器将响两次

  7. 最后,如果按下xX键,C++程序将被终止

在编译代码时,您还必须包括wiringPi库的名称,即lwiringPi。最终的编译命令如下:

gcc -o LEDBuzzer -lncurses -lwiringPi LEDBuzzer.cpp

编译代码后,键入./LEDBuzzer来运行它:

接下来,按下LEDB键,LED 和蜂鸣器将相应地打开和关闭。

使用笔记本键盘控制一辆漫游车

在控制 LED 和蜂鸣器之后,让我们编写一个程序,从笔记本控制我们的漫游车(机器人):

我保持了与第三章中相同的接线连接,编程机器人

  • wiringPi 引脚编号 0 和 2 连接到电机驱动器的IN1IN2引脚

  • wiringPi 引脚编号 3 和 4 连接到IN3IN4引脚

  • 左电机引脚连接到电机驱动器的OUT1OUT2引脚

  • 右电机引脚连接到电机驱动器的OUT3OUT4引脚

  • 树莓派的引脚 6 连接到电机驱动器的地线插座

构建一个由笔记本控制的漫游车程序

如果您已经理解了前两个程序,那么现在您可能已经找到了我们笔记本控制的漫游车代码。在这个程序中,我们将使用上、下、左和右箭头键以及ASXWD键将机器人向前、向后、向左和向右移动。为了识别来自箭头键的输入,我们需要在程序中包含keypad()函数。Laptop_Controlled_Rover.cpp程序可以从GitHub存储库的Chapter05文件夹中下载:


int main()
{
...
for(;;)
{
initscr(); 
keypad(stdscr,TRUE);
refresh(); 
int keypressed = getch(); 
if(keypressed==KEY_UP || keypressed == 'W' || keypressed == 'w') 
//KEY_UP command is for UP arrow key
{
printw("FORWARD");
digitalWrite(0,HIGH);
digitalWrite(2,LOW);
digitalWrite(3,HIGH);
digitalWrite(4,LOW);
}
if(keypressed==KEY_DOWN || keypressed == 'X' || keypressed == 'x')
//KEY_DOWN is for DOWN arrow key
{
printw("BACKWARD")
digitalWrite(0,LOW);
digitalWrite(2,HIGH);
digitalWrite(3,LOW);
digitalWrite(4,HIGH);
}

if(keypressed==KEY_LEFT || keypressed == 'A' || keypressed == 'a')
{
//KEY_LEFT is for LEFT arrow key
printw("LEFT TURN");
digitalWrite(0,LOW);
digitalWrite(2,HIGH);
digitalWrite(3,HIGH);
digitalWrite(4,LOW);
}

if(keypressed==KEY_RIGHT || keypressed == 'D' || keypressed == 'd')
{
//KEY_RIGHT is for right arrow keys
printw("RIGHT TURN");
digitalWrite(0,HIGH);
digitalWrite(2,LOW);
digitalWrite(3,LOW);
digitalWrite(4,HIGH);
}

if(keypressed=='S' || keypressed=='s')
{
printw("STOP");
digitalWrite(0,HIGH);
digitalWrite(2,HIGH);
digitalWrite(3,HIGH);
digitalWrite(4,HIGH);
}

if(keypressed=='E' || keypressed=='e')
{
break; 
}
}
endwin(); 
return 0; 
}

上述程序可以解释如下:

  1. 在上述程序中,如果按下上箭头键,这将被if条件内的KEY_UP代码识别。如果条件为TRUE,机器人将向前移动,并且终端中将打印FORWARD。类似地,如果按下Ww键,机器人也将向前移动。

  2. 如果按下下箭头键(KEY_DOWN)或Xx键,机器人将向后移动,并且终端中将打印BACKWARD

  3. 如果按下左箭头键(KEY_LEFT)或Aa键,机器人将向左转,终端中将打印LEFT TURN

  4. 如果按下右箭头键(KEY_RIGHT)或Dd键,机器人将向右转,终端中将打印RIGHT TURN

  5. 最后,如果按下Ss键,机器人将停止,并且终端中将打印STOP

  6. 要终止代码,我们可以按下Ee键。由于我们没有提供任何时间延迟,机器人将无限期地保持移动,除非您使用Ss键停止机器人。

在测试代码时,将树莓派连接到移动电源,这样你的机器人就完全无线,可以自由移动。

追踪一个正方形路径

在将机器人移动到不同方向后,让我们让机器人追踪一个正方形路径。为此,我们的机器人将按以下方式移动:向前->右转->向前->右转->向前->右转->向前->停止:

LaptopControlRover程序中,我们将创建另一个if条件。在这个if条件内,我们将编写一个程序来使机器人追踪一个正方形路径。if条件将如下所示:

if(keypressed == 'r' || keypressed == 'R')
{
forward(); //first forward movement
delay(2000);
rightturn(); //first left turn
delay(500); //delay needs to be such that the robot takes a perfect 90º right turn

forward(); //second forward movement
delay(2000);
rightturn(); //second right turn
delay(500);

forward(); //third forward movement
delay(2000);
rightturn(); //third and last left turn
delay(500);

forward(); //fourth and last forward movement
delay(2000);
stop(); //stop condition
}

为了追踪正方形路径,机器人将向前移动四次。它将右转三次,最后停下来。在main函数之外,我们需要创建forward()rightturn()stop()函数,这样,我们可以简单地调用必要的函数,而不是在主函数中多次编写digitalWrite代码。

向前条件 右转 停止

|

void forward()
{
digitalWrite(0,HIGH);
 digitalWrite(2,LOW);
 digitalWrite(3,HIGH);
 digitalWrite(4,LOW);
}

|

void rightturn()
{
digitalWrite(0,HIGH); 
 digitalWrite(2,LOW); 
 digitalWrite(3,LOW); 
 digitalWrite(4,HIGH);
}

|

void stop()
{
digitalWrite(0,HIGH); 
 digitalWrite(2,HIGH); 
 digitalWrite(3,HIGH); 
 digitalWrite(4,HIGH);
}

|

这是我们如何使用笔记本电脑控制机器人,借助键盘按键的帮助。接下来,让我们看看第二种技术,我们将使用 QT5 创建 GUI 按钮。当按下这些按钮时,机器人将朝不同的方向移动。

安装和设置 QT5

QT 是一个跨平台应用程序框架,通常用于嵌入式图形用户界面。QT 的最新版本是 5,因此也被称为 QT5。要在我们的 RPi 内安装 QT5 软件,打开终端窗口并输入以下命令:

sudo apt-get install qt5-default

上述命令的输出如下截图所示:

这个命令将下载在后台运行的必要的qt5文件。接下来,要下载和安装 QT5 IDE,输入以下命令:

sudo apt-get install qtcreator

QT5 IDE 的安装将需要大约 10 到 15 分钟,具体取决于您的互联网速度。如果在安装 QT5 时遇到任何问题,请尝试更新和升级您的 RPi。要做到这一点,请在终端窗口中输入以下命令:

sudo apt-get update
sudo apt-get upgrade -y

设置 QT5

在 QT5 中编写任何程序之前,我们首先需要设置它,以便它可以运行 C++程序。要打开 QT5,点击树莓图标,转到“编程”,然后选择“Qt Creator”:

QT5 在 RPi 上运行速度较慢,因此打开 IDE 需要一些时间。点击工具,然后选择“选项...”:

在“选项...”中,点击设备,确保类型设置为桌面。名称应为“本地 PC”,这是指 RPi:

之后,点击“构建和运行”选项。接下来,选择“工具包”选项卡,点击“桌面”(默认)选项:

选择“构建和运行”选项后,我们需要进行一些修改:

让我们逐步看修改:

  1. 保持名称为“桌面”。

  2. 将文件系统的名称设置为RPi

  3. 在设备类型中,选择桌面选项。

  4. 系统根(系统根)默认设置为/home/pi,这意味着当我们创建新的 QT5 应用程序时,它将被创建在pi文件夹内。现在,我们将在pi文件夹内创建一个名为QTPrograms的新文件夹,而不是在pi文件夹中创建我们的 QT 项目。要更改文件夹目录,点击“浏览”按钮。之后,点击文件夹选项。将此文件夹命名为QTPrograms,或者您想要的任何其他名称。选择QTPrograms文件夹,然后选择“选择”按钮:

  1. 接下来,我们必须将编译器设置为 GCC。要做到这一点,点击编译器选项卡。在里面,点击“添加”下拉按钮。转到 GCC 并选择 C++选项:

现在,在 C++选项下,您将看到 GCC 编译选项:

之后,点击 Apply 按钮应用更改,然后点击 OK 按钮。接下来,再次点击 Tools,打开 Options。在 Build and run 选项内,选择 Kits 选项卡,再次选择 Desktop 选项。这次,在 C++选项旁边,您将看到一个下拉选项。点击这个选项,选择 GCC 编译器:

  1. 接下来,检查调试器选项。它应该设置为位于/usr/bin/gdb 的 System GDB。

  2. 最后,检查 QT5 版本。目前,我正在使用最新版本的 QT,即 5.7.1。当您阅读到这一章时,最新版本可能已经更新。

进行这些更改后,点击 Apply,然后点击 OK。在设置 QT5 之后,让我们编写我们的第一个程序,使用 GUI 按钮来打开和关闭 LED。

使用 GUI 按钮控制 LED

在本节中,我们将创建一个简单的 QT5 程序,通过 GUI 按钮来控制 LED 的开关。对于这个项目,您将需要两个 LED:

LED 的接线与LEDBuzzer项目中的完全相同:

  • 第一个 LED 的阳极(正极)引脚连接到 wiringPi 引脚号 0,阴极(负极)引脚连接到物理引脚号 9(地线引脚)

  • 第二个 LED 的阳极引脚连接到 wiringPi 引脚号 2,阴极引脚连接到物理引脚号 14(地线引脚)

创建 QT 项目

用于打开和关闭 LED 的 QT5 项目称为LedOnOff。您可以从 GitHub 存储库的Chapter05文件夹中下载此项目。下载LedOnOff项目文件夹后,打开LedOnOff.pro文件以在 QT5 IDE 中查看项目。

按照以下步骤在 QT5 IDE 中创建项目:

  1. 点击 File 选项,然后点击 New File or Project...:

  1. 接下来,选择 QT Widgets Application 选项,然后点击 Choose 按钮:

  1. 之后,给您的项目命名。我将我的项目命名为LEDOnOff。之后,将目录更改为QTPrograms,以便在此文件夹中创建项目,然后点击 Next:

  1. 保持 Desktop 选项选中,然后点击 Next:

  1. 现在您应该看到某些文件名,这些是项目的一部分。保持名称不变,然后点击 Next:

  1. 最后,您将看到一个摘要窗口,其中将显示将要创建的所有文件的摘要。我们不需要在此窗口中进行任何更改,因此点击 Finish 创建项目:

在 IDE 的左侧,您将看到设计、C++和头文件。首先,我们将打开LEDOnOff.pro文件并添加wiringPi库的路径。在文件底部,添加以下代码:

LIBS += -L/usr/local/lib -lwiringPi

接下来,打开Forms文件夹内的mainwindow.ui文件。mainwindow.ui文件是设计文件,我们将在其中设计 GUI 按钮:

mainwindow.ui文件将在 Design 选项卡中打开。在 Design 选项卡的左侧是小部件框,其中包含按钮、列表视图和布局等小部件。中间是设计区域,我们将在其中拖动 UI 组件。在右下角,显示所选 UI 组件的属性:

接下来,要创建 GUI 按钮,将 Push Button 小部件拖到设计区域内。双击按钮,将文本更改为ON。之后,选中 Push Button,将 objectName(在属性窗口内)更改为on

之后,添加两个按钮。将一个按钮的名称设置为OFFobjectName设置为off。将另一个按钮的名称设置为ON / OFFobjectName设置为onoff

我们可以使用两种不同类型的按钮函数来打开和关闭 LED:

  • clicked(): clicked按钮函数将在按钮被点击时立即执行。

  • pressed()released(): pressed按钮函数会在您按住或按住按钮时一直执行。当我们使用pressed函数时,我们还必须使用released()函数。释放的函数包含指示按钮释放时应发生的操作的代码。

我们将把clicked()函数链接到ONOFF按钮,并将pressed()released()函数链接到ON/OFF按钮。接下来,要将clicked()函数链接到ON按钮,右键单击ON按钮,选择 Go to slot...选项,然后选择clicked()函数。然后,按下 OK:

现在,一旦您选择clicked()函数,mainwindow.cpp文件(此文件位于Sources文件夹中)中将创建一个名为on_on_clicked()on_buttonsobjectname_clicked)的点击函数。在此函数中,我们将编写打开 LED 的程序。但在此之前,我们需要在mainwindow.h文件中声明wiringPi库和引脚。此文件位于Headers文件夹中:

我们还需要声明QMainWindow库,它将创建一个包含我们按钮的窗口。接下来,我已将led1引脚设置为引脚0,将led2引脚设置为引脚2。之后,再次打开mainwindow.cpp文件。然后我们将执行以下操作:

  1. 首先,我们将声明wiringPiSetup();函数

  2. 接下来,我们将把led1led2设置为OUTPUT引脚

  3. 最后,在on_on_clicked()函数中,将led1led2引脚设置为HIGH

接下来,要关闭 LED 灯,再次打开mainwindow.ui文件,右键单击关闭按钮,选择 Go to slot...,然后再次选择clicked()函数。在mainwindow.cpp文件中,将创建一个名为on_off_clicked的新函数。在此函数中,我们将编写关闭 LED 灯的程序。

要编程 ON/OFF 按钮,右键单击它,选择 Go to slot...,然后选择pressed()函数。将在mainwindow.ui文件中创建一个名为on_onoff_pressed()的新函数。接下来,右键单击ON/OFF按钮,选择 Go to slot...,然后选择released()函数。现在将创建一个名为on _onoff_released()的新函数。

on_onoff_pressed()函数中,我们将编写一个程序来打开 LED 灯。在on_onoff_released()函数中,我们将编写一个程序来关闭 LED 灯:

在运行代码之前,单击文件,然后单击全部保存。接下来,要构建和运行代码,请单击构建,然后单击运行选项。MainWindow 出现需要大约 30 到 40 秒,在主窗口中,您将看到以下 GUI 按钮:

现在,当您点击 ON 按钮时,LED 将打开。当您点击 OFF 按钮时,LED 将关闭。最后,当您按住ON / OFF按钮时,LED 将一直打开,直到您松开为止,然后它们将关闭。

处理错误

在控制台中,您可能会看到一些次要错误。如果主窗口已打开,您可以忽略这些错误:

当您打开 Qt Creator IDE 时,GCC 编译器可能会不断重置。因此,在运行项目后,您将收到以下错误:

Error while building/deploying project LEDOnOff (kit: Desktop)
 When executing step "qmake"

如果您遇到此错误,请转到工具,然后选项,并将 C++编译器设置为 GCC,如“设置 QT5”部分的步骤 5中所示。

使用 QT5 控制笔记本电脑的小车

现在我们可以控制 LED 灯,让我们看看如何使用 QT5 控制小车。在 Qt Creator IDE 中,创建一个新项目并命名为QTRover。您可以从本章的 GitHub 存储库中下载QTRover项目文件夹。我们现在可以使用clicked()函数和pressed()released()函数来创建这个QTRover项目。为此,我们有以下选项:

  1. 如果我们只使用clicked()函数创建这个项目,我们需要创建五个按钮:前进、后退、左转、右转和停止。在这种情况下,我们需要每次按下停止按钮来停止机器人。

  2. 如果我们只使用pressed()released()函数创建这个项目,我们只需要创建四个按钮:前进、后退、左转和右转。在这种情况下,我们不需要停止按钮,因为当按钮释放时,小车会停止。

  3. 或者,我们也可以使用clicked()pressed()released()函数的组合,其中前进、后退和停止按钮将链接到clicked()函数,左右按钮将链接到pressed()released()函数。

在这个项目中,我们将选择第三个选项,即clicked()pressed()released()函数的组合。在创建这个项目之前,我们将关闭LEDOnOff项目,因为如果LEDOnOffQTRover项目都保持打开状态,有可能如果您在一个项目中进行 UI 更改,代码可能会在另一个项目中更改,从而影响到您的两个项目文件。要关闭LEDOnOff项目,请右键单击它,然后选择关闭项目LEDOnOff选项。

接下来,在QTRover.pro文件中添加wiringPi库路径:

LIBS += -L/usr/local/lib -lwiringPi

之后,打开mainwindow.ui文件并创建五个按钮。将它们标记为FORWARDBACKWARDLEFTRIGHTSTOP

将按钮对象的名称设置如下:

  • FORWARD按钮对象名称设置为 forward

  • BACKWARD按钮对象名称设置为 backward

  • LEFT按钮对象名称设置为 left

  • RIGHT按钮对象名称设置为 right

  • STOP按钮对象名称设置为 stop

之后,右键单击前进、后退和停止按钮,并将clicked()函数添加到这三个按钮。同样,右键单击左和右按钮,并将pressed()released()函数添加到这些按钮。

接下来,打开mainwindow.h文件并声明wiringPiQMainWindow库。还要声明四个wiringPi引脚号。在我的情况下,我使用引脚号0234

mainwindow.cpp文件内,我们将有三个on_click函数来向前移动(on_forward_clicked)、向后移动(on_backward_clicked)和停止(on_stop_clicked)。

我们还有两个on_pressedon_released函数用于左(on_left_pressedon_left_released)和右(on_right_pressedon_right_released)按钮。

以下步骤描述了移动机器人在不同方向上所需的步骤:

  1. on_forward_clicked()函数内,我们将编写程序来使机器人向前移动:
digitalWrite(leftmotor1, HIGH);
digitalWrite(leftmotor2, LOW);
digitalWrite(rightmotor1, HIGH);
digitalWrite(rightmotor2, LOW);
  1. 接下来,在on_backward_clicked()函数内,我们将编写程序来使机器人向后移动:
digitalWrite(leftmotor1, HIGH);
digitalWrite(leftmotor2, LOW);
digitalWrite(rightmotor1, HIGH);
digitalWrite(rightmotor2, LOW);
  1. 之后,在on_left_pressed()函数内,我们将编写程序来进行轴向左转或径向左转:
digitalWrite(leftmotor1, LOW);
digitalWrite(leftmotor2, HIGH);
digitalWrite(rightmotor1, HIGH);
digitalWrite(rightmotor2, LOW);
  1. 然后,在on_right_pressed()函数内,我们将编写程序来进行轴向右转或径向右转:
digitalWrite(leftmotor1, HIGH);
digitalWrite(leftmotor2, LOW);
digitalWrite(rightmotor1, LOW);
digitalWrite(rightmotor2, HIGH);
  1. on_stop_clicked()函数内,我们将编写程序来停止机器人:
digitalWrite(leftmotor1, HIGH);
digitalWrite(leftmotor2, HIGH);
digitalWrite(rightmotor1, HIGH);
digitalWrite(rightmotor2, HIGH);

完成代码后,保存所有文件。之后,运行程序并测试最终输出。运行代码后,您将看到带有向前、向后、向左、向右和停止按钮的主窗口。按下每个 GUI 按钮以使机器人朝所需方向移动。

总结

在本章中,我们看了两种不同的技术来使用笔记本电脑控制机器人。在第一种技术中,我们使用ncurses库从键盘接收输入,以相应地移动机器人。在第二种技术中,我们使用 QT Creator IDE 创建 GUI 按钮,然后使用这些按钮来使机器人朝不同方向移动。

在下一章中,我们将在树莓派上安装 OpenCV 软件。之后,我们将使用树莓派摄像头记录图片和视频。

问题

  1. ncurses程序应该在哪两个函数之间编写?

  2. initscr()函数的目的是什么?

  3. 如何在终端窗口中编译ncurses代码?

  4. 我们在 QT Creator 中使用了哪个 C++编译器?

  5. 你会使用哪个按钮功能或功能来在按下按钮时使机器人向前移动?

第三部分:人脸和物体识别机器人

在本节中,您将使用 OpenCV 来检测人脸和现实世界中的物体。然后,我们将扩展 OpenCV 的功能,以识别不同的人脸,并在检测到正确的人脸时移动机器人。

本节包括以下章节:

  • 第六章,使用 OpenCV 访问 RPi 相机

  • 第七章,使用 OpenCV 构建一个物体跟随机器人

  • 第八章,使用 Haar 分类器进行人脸检测和跟踪

第六章:使用 OpenCV 访问 RPi 相机

我们可以使用树莓派连接到外部 USB 网络摄像头或树莓派相机RPi 相机)来识别对象和人脸,这是树莓派最令人兴奋的事情之一。

为了处理来自相机的输入,我们将使用 OpenCV 库。由于安装 OpenCV 需要很长时间并涉及多个步骤,本章将专门用于让您开始运行。

在本章中,您将探索以下主题:

  • 在树莓派上安装 OpenCV 4.0.0

  • 启用并连接 RPi 相机到 RPi

  • 使用 RPi 相机捕获图像和视频

  • 使用 OpenCV 读取图像

技术要求

在本章中,您将需要以下内容:

  • 树莓派相机模块-截至 2019 年,最新的 RPi 相机模块称为RPi 相机 V2 1080P

  • 树莓派相机外壳(安装支架)

本章的代码文件可以从github.com/PacktPublishing/Hands-On-Robotics-Programming-with-Cpp/tree/master/Chapter06下载。

在树莓派上安装 OpenCV 4.0.0

开源计算机视觉库OpenCV)是一个开源的计算机视觉和机器学习库。OpenCV 库包括 2500 多个计算机视觉和机器学习算法,可用于识别对象、检测颜色和跟踪现实生活中或视频中的运动物体。OpenCV 支持 C++、Python 和 Java 编程语言,并可以在 Windows、macOS、Android 和 Linux 上运行。

在树莓派上安装 OpenCV 是一个耗时且冗长的过程。除了 OpenCV 库,我们还必须安装多个库和文件,以使其正常工作。安装 OpenCV 的步骤将在我运行 Raspbian Stretch 的树莓派 3B+型号上执行。我们要安装的 OpenCV 版本是 OpenCV 4.0.0。

在安装 OpenCV 时,我们将下载多个文件。如果您住在大房子里,请确保您坐在 Wi-Fi 路由器附近,以便 RPi 接收良好的信号强度。如果 RPi 离 Wi-Fi 很远,下载速度可能会受到影响,安装 OpenCV 可能需要更长的时间。我在我的 RPi 3B+上安装 OpenCV 大约花了 3 个小时,下载速度大约为 500-560 Kbps。

卸载 Wolfram 和 LibreOffice

如果您使用 32GB 的 microSD 卡,Raspbian Stretch 将只占用存储空间的 15%,但如果您使用 8GB 的 microSD 卡,它将占用 50%的空间。如果您使用 8GB 的 microSD 卡,您需要释放一些空间。您可以通过卸载一些未使用的应用程序来实现。其中两个应用程序是 Wolfram 引擎和 LibreOffice。

在 Raspbian Stretch 上卸载应用程序很容易。您只需要在终端窗口中输入一个命令。让我们从卸载 Wolfram 引擎开始:

sudo apt-get purge wolfram-engine -y

接下来,使用相同的命令卸载 LibreOffice:

sudo apt-get purge libreoffice* -y

卸载两个软件后,我们可以使用两个简单的命令进行清理:

sudo apt-get clean
sudo apt-get autoremove -y

现在我们已经释放了一些空间,让我们更新 RPi。

更新您的 RPi

更新您的 RPi 涉及一些简单的步骤:

  1. 打开终端窗口,输入以下命令:
sudo apt-get update 
  1. 通过输入以下命令升级 RPi:
sudo apt-get upgrade -y
  1. 重新启动 RPi:
sudo shutdown -r now

一旦您的 RPi 重新启动,再次打开终端窗口。

在终端窗口运行某些命令时,您可能会收到提示,询问您是否要继续。在此过程的命令中,我们已经添加了-y命令(在行的末尾),它将自动应用yes命令到提示。

安装 cmake、image、video 和 gtk 软件包

cmake是一个配置实用程序。使用cmake,我们可以在安装后配置不同的 OpenCV 和 Python 模块。要安装cmake软件包,请输入以下命令:

sudo apt-get install build-essential cmake pkg-config -y

接下来,要安装图像 I/O 软件包,请输入以下命令:

sudo apt-get install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev -y

之后,我们将通过输入以下命令安装两个视频 I/O 软件包:

sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev -y
sudo apt-get install libxvidcore-dev libx264-dev -y

接下来,我们将下载并安装Gimp ToolkitGTK)软件包。此工具包用于为我们的程序制作图形界面。我们将执行以下命令来下载和安装 GTK 软件包:

sudo apt-get install libgtk2.0-dev libgtk-3-dev -y
sudo apt-get install libatlas-base-dev gfortran -y

下载和解压 OpenCV 4.0 及其贡献存储库

安装了这些软件包后,我们可以继续进行 OpenCV。让我们开始下载 Open CV 4.0:

  1. 在终端窗口中输入以下命令:
wget -O opencv.zip https://github.com/opencv/opencv/archive/4.0.0.zip
  1. 下载包含一些附加模块的opencv_contrib存储库。输入以下命令:
wget -O opencv_contrib.zip https://github.com/opencv/opencv_contrib/archive/4.0.0.zip

步骤 1步骤 2中的命令都是单行命令。

  1. 使用以下命令解压opencv.zip文件:
unzip opencv.zip
  1. 解压opencv_contrib.zip文件:
unzip opencv_contrib.zip

解压opencvopencv_contrib后,您应该在pi文件夹中看到opencv-4.0.0opencv_contrib-4.0.0文件夹。

安装 Python

接下来,我们将安装 Python 3 及其一些支持工具。即使我们将使用 C++编程 OpenCV,安装并链接 Python 包与 OpenCV 仍然是一个好主意,这样您就可以选择使用 OpenCV 编写或编译 Python 代码。

要安装 Python 及其开发工具,请输入以下命令:

sudo apt-get install python3 python3-setuptools python3-dev -y
wget https://bootstrap.pypa.io/get-pip.py
sudo python3 get-pip.py
sudo pip3 install numpy

安装 Python 软件包后,我们可以编译和构建 OpenCV。

编译和安装 OpenCV

要编译和安装 OpenCV,我们需要按照以下步骤进行:

  1. 进入opencv-4.0.0文件夹。使用以下命令更改目录到opencv-4.0.0文件夹:
cd opencv-4.0.0
  1. 在此文件夹中创建一个build文件夹。为此,请输入以下命令:
mkdir build
  1. 要打开build目录,请输入以下命令:
cd build
  1. 更改目录到build后,输入以下命令:
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D BUILD_opencv_java=OFF \
-D BUILD_opencv_python2=OFF \
-D BUILD_opencv_python3=ON \
-D PYTHON_DEFAULT_EXECUTABLE=$(which python3) \
-D INSTALL_C_EXAMPLES=ON \
-D INSTALL_PYTHON_EXAMPLES=ON \
-D BUILD_EXAMPLES=ON\
-D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib-4.0.0/modules \
-D WITH_CUDA=OFF \
-D BUILD_TESTS=OFF \
-D BUILD_PERF_TESTS= OFF ..

在输入此命令时,请确保在终端窗口中输入两个点..

  1. 要启用 RPi 的所有四个内核,请在 nano 编辑器中打开swapfile文件:
sudo nano /etc/dphys-swapfile
  1. 在此文件中,搜索CONF_SWAPSIZE=100代码,并将值从100更改为1024

  1. 按下Ctrl + O保存此文件。您将在文件底部收到提示,询问您是否要保存此文件。按Enter,然后按*Ctrl *+ X退出。

  2. 要应用这些更改,请输入以下两个命令:

sudo /etc/init.d/dphys-swapfile stop
sudo /etc/init.d/dphys-swapfile start
  1. 要使用 RPi 的所有四个内核编译 OpenCV,请输入以下命令:
make -j4

这是最耗时的步骤,需要 1.5 到 2 小时。如果在编译时遇到任何错误,请尝试使用单个内核进行编译。

要使用单个内核进行编译,请输入以下命令:

sudo make install
make

只有在使用make -j4命令时遇到错误时才使用前面的两个命令。

  1. 要安装 OpenCV 4.0.0,请输入以下命令:
sudo make install
sudo ldconfig 

我们现在已经编译并安装了 OpenCV。让我们将其连接到 Python。

将 OpenCV 链接到 Python

让我们按照以下步骤将 OpenCV 链接到 Python:

  1. 打开python 3.5文件夹(/usr/local/python/cv2/python-3.5):

在此文件夹中,您应该看到一个名为cv2.socv2.cpython-35m-arm-linux-gnueabihf.so的文件。如果文件名是cv2.so,则无需进行任何更改。如果文件名是cv2.cpython-35m-arm-linux-gnueabihf.so,则必须将其重命名为cv2.so。要重命名此文件,请输入以下命令更改目录到python 3.5

cd /usr/local/python/cv2/python-3.5

将此文件从cv2.cpython-35m-arm-linux-gnueabihf.so重命名为cv2.so,输入以下命令:

sudo mv /usr/local/python/cv2/python3.5/cv2.cpython-35m-arm-linux-gnueabihf.so cv2.so
  1. 使用以下命令将此文件移动到dist-package文件夹(/usr/local/lib/python3.5/dist-packages/):
sudo mv /usr/local/python/cv2/python-3.5/cv2.so /usr/local/lib/python3.5/dist-packages/cv2.so
  1. 要测试 OpenCV 4.0.0 是否正确链接到 Python 3,请在终端窗口中输入cd ~进入pi目录。接下来,输入python3

  1. 您应该看到一个三角括号。输入import cv2

  2. 要检查 OpenCV 版本,请输入cv2.__version__。如果看到opencv 4.0.0,这意味着 OpenCV 已成功安装并与 Python 软件包链接:

  1. 输入exit()并按Enter

安装 OpenCV 后,我们需要将CONF_SWAPSIZE重置为100

  1. 打开swapfile
sudo nano /etc/dphys-swapfile
  1. CONF_SWAPSIZE更改为100

  1. 要应用这些更改,请输入以下命令:
sudo /etc/init.d/dphys-swapfile stop
sudo /etc/init.d/dphys-swapfile start

您已成功在树莓派上安装了 OpenCV 4.0.0。我们现在准备将 RPi 相机连接到 RPi。

启用并连接 RPi 相机到 RPi

在连接 RPi 相机到 RPi 之前,我们需要从 RPi 配置中启用相机选项:

  1. 打开一个终端窗口并输入sudo raspi-config打开 RPi 配置。

  2. 选择“高级选项”并按Enter打开它:

  1. 选择相机选项并按Enter打开它:

  1. 选择“是”并按Enter启用相机选项:

  1. 选择确定并按Enter

  1. 退出 RPi 配置并关闭 RPi。

在连接 RPi 相机到 RPi 时,请确保 RPi 已关闭。

现在我们已经完成了设置,让我们连接相机。

连接 RPi 相机到 RPi

连接 RPi 相机到 RPi 是一个简单但又微妙的过程。RPi 相机有一根连接的带线。我们必须将这根带线插入 RPi 的相机插槽中,该插槽位于 LAN 端口和 HDMI 端口之间:

RPi 相机上的带线由前面的蓝色条组成,后面没有:

现在我们了解了组件和端口,让我们开始连接它们:

  1. 轻轻抬起相机插槽的盖子:

  1. 将相机带插入插槽,确保带子上的蓝色胶带面向 LAN 端口。

  2. 按下盖子锁定相机带线:

就是这样——您的 RPi 相机现在已准备好拍照和录制视频。

安装 RPi 相机在机器人上

让我们在机器人上安装 RPi 相机;您需要一个 RPi 相机盒子。在amazon.com上快速搜索RPi 相机盒子将显示以下情况:

我不推荐这个特定的情况,因为它没有正确安装我的 RPi 相机模块。当盒子关闭时,我的 RPi 相机的镜头没有正确对齐这个相机盒子的小孔。

由于我住在印度,在亚马逊印度网站(www.amazon.in)上找不到好的 RPi 相机盒子,而且可用的盒子价格昂贵。我最终使用的盒子来自一个名为www.robu.in的印度电子商务网站,只花了我 90 卢比(不到 2 美元)。在从电子商务网站购买相机盒子或相机支架之前,请检查评论以确保它不会损坏您的 RPi 相机。

我使用的 RPi 相机盒子的图像显示在以下图像中。我从一个名为www.robu.in的印度网站购买了这个盒子。在这个网站上,搜索树莓派相机支架模块以找到这个相机支架:

尽管此摄像头支架包含四个小螺母和螺栓将 RPi 摄像头固定到摄像头支架上,但我发现螺母和螺栓的螺纹不准确,并且将 RPi 摄像头固定到摄像头支架上非常困难。因此,我使用了四小块双面胶带,并将其粘贴到 RPi 摄像头的孔中:

接下来,我将 RPi 摄像头安装到摄像头支架上。在下图中,RPi 摄像头被倒置安装。因此,当我们捕获图像时,图像将呈倒置状态,为了正确查看图像,我们需要翻转它(在 OpenCV 中解释了在第七章中水平和垂直翻转图像的过程,使用 OpenCV 构建对象跟随机器人):

之后,我使用双面胶带在 RPi 外壳顶部安装了摄像头支架,从而将 RPi 摄像头安装在机器人上:

现在我们已经将摄像头外壳安装到机器人上,让我们看看如何使用 RPi 摄像头捕获图像和视频。

使用 RPi 摄像头捕获图像和视频

让我们看看如何在 RPi 上拍照和录制视频。打开终端窗口,输入以下命令:

raspistill -o image1.jpg

在此命令中,我们使用raspistill拍摄静态图片,并将其保存为image1.jpg

由于终端窗口指向pi目录,因此此图像保存在pi文件夹中。要打开此图像,请打开pi文件夹,在其中您将看到image1.jpg。使用 RPi 摄像头捕获的图像具有本机分辨率为 3,280 x 2,464 像素:

image1的输出如下截图所示:

如果我们想水平翻转图像,可以添加-hf命令,如果要垂直翻转图像,可以在raspistill代码中添加-vf命令:

raspistill -hf -vf -o image2.jpg

image2.jpg文件也保存在pi文件夹中,其输出如下截图所示:

现在我们已经使用 RPi 摄像头捕获了图像,让我们录制并查看视频。

使用 RPi 摄像头录制视频

现在我们知道如何使用 RPi 摄像头拍照,让我们看看如何录制视频。录制视频剪辑的命令如下:

raspivid -o video1.h264 -t 5000 

如下截图所示,上述命令不会产生任何输出:

在我们的命令中,我们使用raspivid录制视频,并将其命名为video1。我们以h264格式录制了视频。数字5000代表 5000 毫秒,也就是说,我们录制了一个 5 秒的视频。您可以打开pi文件夹,双击视频文件以打开它:

现在我们知道如何拍照和录制视频,让我们安装v4l2驱动程序,以便 OpenCV 库可以检测到 RPi 摄像头。

安装 v4l2 驱动程序

OpenCV 库默认可以识别连接到 RPi USB 端口的 USB 摄像头,但无法直接检测 RPi 摄像头。要识别我们的 RPi 摄像头,我们需要在模块文件中加载v4l2驱动程序。要打开此文件,请在终端窗口中输入以下命令:

sudo nano /etc/modules

要加载v4l2驱动程序,请在以下文件中添加bcm2835-v4l2

按下Ctrl + O,然后按Enter保存此文件,按下Ctrl + X退出文件,然后重新启动您的 RPi。重新启动后,OpenCV 库将识别 RPi 摄像头。

使用 OpenCV 读取图像

在 RPi 相机上玩了一会儿之后,让我们使用 OpenCV 函数编写一个简单的 C++程序来显示图像。在这个程序中,我们首先从一个特定的文件夹中读取图像,然后在新窗口中显示这个图像:

要显示图像,我们首先需要一张图像。在pi文件夹中,我创建了一个名为Data的新文件夹,在其中,我复制了一张名为Car.png的图像。在同一个文件夹中,我创建了DisplayImage.cpp文件,我们将在其中编写显示图像的程序。DisplayImage.cpp程序可以从本书的 GitHub 存储库的Chapter06文件夹中下载。代码如下:

#include <iostream>
#include <stdio.h>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;
int main()
{

Mat img;

img = imread("Car.jpg");

imshow("Car Image", img);

waitKey(0);

return 0;
}

在上述代码中,我们首先声明了opencv.hpp库,以及基本的 C++库。然后声明了cv命名空间,它是 OpenCV 库的一部分。在main函数内部,我们声明了一个名为img的矩阵(Mat)变量。

接下来,使用imread()函数读取Car.jpg图像,并将值存储在img变量中。如果图像和.cpp文件在同一个文件夹中,只需在imread()函数中写入图像名称。如果图像在不同的文件夹中,则应在imread函数中提及图像的位置。

imshow()函数用于在新窗口中显示汽车图像。imshow()函数接受两个参数作为输入。第一个参数是窗口文本("Car Image"),第二个参数是要显示的图像的变量名(img)。

waitKey(0)函数用于创建无限延迟,也就是说,waitKey(0)将无限地显示汽车图像,直到您按下任意键。按下键后,将执行下一组代码。由于在waitKey(0)函数之后没有任何代码,程序将终止,汽车图像窗口将关闭。

要在 RPi 内部编译和构建 OpenCV 代码,我们需要在编译和构建框内添加以下行:

  1. 单击构建选项,然后选择设置构建命令。在编译框内,输入以下命令:
g++ -Wall $(pkg-config --cflags opencv) -c "%f" -lwiringPi
  1. 在构建框内,输入以下命令,然后单击“确定”:
g++ -Wall $(pkg-config --libs opencv) -o "%e" "%f" -lwiringPi
  1. 单击编译按钮编译代码,然后单击构建按钮测试输出。在输出中,将创建一个新窗口,在其中将显示汽车图像:

  1. 如果按任意键,程序将终止,汽车图像窗口将关闭。

总结

在本章中,我们专注于在树莓派上安装 OpenCV。您已经了解了 RPi 相机模块。设置 RPi 相机后,您使用 RPi 相机拍摄了照片并录制了一个短视频剪辑。

在下一章中,我们将使用 OpenCV 库编写 C++程序。您将学习不同的图像处理概念,以便可以扫描、阈值化和识别对象。在识别对象之后,我们将为机器人编写程序,使其跟随该对象。

问题

  1. OpenCV 的全称是什么?

  2. RPi 相机拍摄的图像分辨率是多少?

  3. 使用 RPi 相机拍摄图像的命令是什么?

  4. 使用 RPi 相机录制视频的命令是什么?

  5. Raspbian OS 在 8GB 和 32GB SD 卡上占用的内存百分比是多少?

第七章:使用 OpenCV 构建一个目标跟随机器人

在上一章中安装了 OpenCV 之后,现在是时候使用 OpenCV 库执行图像处理操作了。在本章中,我们将涵盖以下主题:

  • 使用 OpenCV 进行图像处理

  • 查看来自 Pi 摄像头的视频源

  • 构建一个目标跟随机器人

技术要求

对于本章没有新的技术要求,但是您需要以下内容来执行示例:

  • 用于检测红色、绿色或蓝色的球

  • 安装在机器人上的 Pi 摄像头和超声波传感器

本章的代码文件可以从github.com/PacktPublishing/Hands-On-Robotics-Programming-with-Cpp/tree/master/Chapter07下载。

使用 OpenCV 进行图像处理

在本节中,我们将查看 OpenCV 库的重要函数。之后,我们将使用 OpenCV 库编写一个简单的 C++程序,并对图像执行不同的图像处理操作。

OpenCV 中的重要函数

在编写任何 OpenCV 程序之前,了解 OpenCV 中的一些主要函数以及这些函数可以给我们的输出是很重要的。让我们从查看这些函数开始:

  • imread(): imread()函数用于从 Pi 摄像头或网络摄像头读取图像或视频。在imread()函数内部,我们必须提供图像的位置。如果图像和程序文件在同一个文件夹中,我们只需要提供图像的名称。但是,如果图像存储在不同的文件夹中,那么我们需要在imread函数内提供图像的完整路径。我们将从imread()函数中存储的图像值存储在一个矩阵(Mat)变量中。

如果图像和.cpp文件在同一个文件夹中,代码如下所示:

Mat img = imread("abcd.jpg"); //abcd.jpg is the image name

如果图像和.cpp文件在不同的文件夹中,代码如下所示:

Mat img = imread("/home/pi/abcd.jpg"); //abcd image is in 
                                      // the Pi folder

  • imshow(): imshow()函数用于显示或查看图像:
imshow("Apple Image", img);

imshow()函数包括两个参数,如下:

    • 第一个参数是窗口文本
  • 第二个参数是要显示的图像的变量名

imshow()函数的输出如下:

  • resize(): resize()函数用于调整图像的尺寸。当用户同时使用多个窗口时,通常会使用此函数:
resize(img, rzimg, cvSize(400,400));  //new width is 400 
                                     //and height is 400

此函数包括三个参数:

    • 第一个参数是要调整大小的原始图像(img)的变量名。
  • 第二个参数是将调整大小的新图像(rzimg)的变量名。

  • 第三个参数是cvSize,在其中输入新宽度高度值

resize()函数的输出如下:

  • flip(): 此函数用于水平翻转、垂直翻转或同时进行两者:
flip(img, flipimage, 1)

此函数包括三个参数:

    • 第一个参数(img)是原始图像的变量名。
  • 第二个参数(flipimage)是翻转后的图像的变量名。

  • 第三个参数是翻转类型;0表示垂直翻转,1表示水平翻转,-1表示图像应同时水平和垂直翻转。

flip()函数的输出如下:

  • cvtColor(): 此函数用于将普通的 RGB 彩色图像转换为灰度图像:
cvtColor(img, grayimage, COLOR_BGR2GRAY)

此函数包括三个参数:

    • 第一个参数(img)是原始图像的变量名
  • 第二个参数(grayimage)是将转换为灰度的新图像的变量

  • 第三个参数,COLOR_BGR2GRAY,是转换类型;BGR 是 RGB 倒过来写的

cvtColor()函数的输出如下:

  • threshold(): 阈值化方法用于分离代表对象的图像区域。简单来说,阈值化用于识别图像中的特定对象。阈值化方法接受源图像(src)、阈值和最大阈值(255)作为输入。它通过比较源图像的像素值与阈值来生成输出图像(thresimg):
threshold(src, thresimg, threshold value, max threshold value, threshold type);

阈值函数由五个参数组成:

    • 第一个参数(src)是要进行阈值化的图像的变量名。
  • 第二个参数(thresimg)是阈值化图像的变量名。

  • 第三个参数(阈值)是阈值(从 0 到 255)。

  • 第四个参数(最大阈值)是最大阈值(255)。

  • 第五个参数(阈值类型)是阈值化类型。

一般来说,有五种类型的阈值化,如下所示:

    • 0-二进制:二进制阈值化是阈值化的最简单形式。在这种阈值化中,如果源图像(src)上的任何像素值大于阈值,则在输出图像(thresimg)中,该像素将被设置为最大阈值(255),并且将变为白色。另一方面,如果源图像上的任何像素值小于阈值,则在输出图像中,该像素值将被设置为0,并且将变为黑色。

例如,在以下代码中,阈值设置为85,最大阈值为255,阈值类型为用数字0表示的二进制:

threshold(src, thresimg,85, 255, 0);

因此,如果苹果图像源图像上的任何像素值大于阈值(即大于85),那么这些像素将在输出图像中变为白色。同样,源图像上值小于阈值的像素将在输出图像中变为黑色。

二进制阈值化

    • 1-二进制反转:二进制反转阈值化正好与二进制阈值化相反。在这种类型的阈值化中,如果源图像的像素值大于阈值,则输出图像的像素将变为黑色(0),如果源图像的像素值小于阈值,则输出图像的像素将变为白色(255):

二进制反转阈值化

    • 2-截断 阈值化:在截断阈值化中,如果src源图像上的任何像素值大于阈值,则在输出图像中,该像素将被设置为阈值。另一方面,如果src源图像上的任何像素值小于阈值,则在输出图像中,该像素将保留其原始颜色值:

截断阈值化

    • 3-阈值为零:在这种阈值化中,如果src源图像上的任何像素值大于阈值,则在输出图像中,该像素将保留其原始颜色值。另一方面,如果src源图像上的任何像素值小于阈值,则在输出图像中,该像素将被设置为0(即黑色):

阈值为零

    • 4-阈值为零反转:在这种阈值化中,如果src上的任何像素值大于阈值,则在输出图像中,该像素将被设置为0。如果src上的任何像素值小于阈值,则在输出图像中,该像素将保留其原始颜色值:

阈值为零反转

  • inRange(): inRange()函数是阈值函数的高级形式。在这个函数内部,我们必须输入我们想要识别的对象的最小和最大 RGB 颜色值。inRange()函数由四个参数组成:

  • 第一个参数(img)是要进行阈值处理的图像的变量名。

  • 有两个Scalar函数。在第一个Scalar函数中的第二个参数中,我们必须输入对象的最小 RGB 颜色。

  • 在第三个参数中,也就是第二个Scalar函数中,我们将输入对象的最大 RGB 颜色值。

  • 第四个参数(thresImage)代表阈值图像的输出:

inRange(img, Scalar(min B,min G,min R), Scalar(max B,max G,max R),thresImage)

图像矩——图像矩的概念源自,它在力学和统计学中用于描述一组点的空间分布。在图像处理或计算机视觉中,图像矩用于找到形状的质心,即形状中所有点的平均值。简单来说,图像矩用于在我们从整个图像中分割出对象后找到任何对象的中心。例如,在我们的情况下,我们可能想要找到苹果的中心。从图像计算对象的中心的图像矩公式如下:

    • x代表图像的宽度
  • y代表图像的高度

  • M10代表图像中所有x值的总和

  • M01代表图像中所有y值的总和

  • M00代表图像的整个区域

  • circle: 正如其名,这个函数用于画圆。它有五个参数作为输入:

  • 第一个参数(img)是你要在其上画圆的图像的变量名。

  • 第二个参数(point)是圆的中心(xy位置)点。

  • 第三个参数(radius)是圆的半径。

  • 第四个参数(Scalar(B,G,R))是为圆着色的;我们使用Scalar()函数来做到这一点。

  • 第五个参数(thickness)是圆的厚度:

circle(img, point, radius, Scalar(B,G,R),thickness);

使用 OpenCV 进行对象识别

现在我们已经了解了 OpenCV 的重要功能,让我们编写一个程序来从图像中检测一个有颜色的球。在我们开始之前,我们必须做的第一件事是拍摄球的合适照片。你可以用任何球来做这个项目,但要确保球是单色的(红色、绿色或蓝色的球是强烈推荐的),并且不是多色的。我在这个项目中使用了一个绿色的球:

拍摄图像

为了捕捉你的球的图像,把它放在一些黑色的表面上。我把我的绿色球放在一个黑色的手机壳上:

如果你的球是黑色,或者颜色较暗,你可以把球放在一个颜色较浅的表面上。这是为了确保球的颜色和背景的颜色之间有很高的对比度,这将有助于我们后面的阈值处理。

在拍摄图像时,确保球上没有白色斑块,因为这可能会在后面的阈值处理中造成问题:

左边的照片有一个大的白色区域,因为光线太亮。右边,球被适当照亮。

一旦你对拍摄的图像满意,将其传输到你的笔记本电脑上。

找到 RGB 像素值

现在我们将通过以下步骤检查球上不同点的 RGB 像素值来找到球的 RGB 像素值:

  1. 打开画图并打开保存的球的图像,如下:

  1. 接下来,使用取色器工具,在球的任何位置单击取样颜色:

颜色 1 框将显示被点击的颜色的样本。在我的情况下,这是绿色:

  1. 如果您点击“编辑颜色”选项,您将看到该像素的 RGB 颜色值。在我的情况下,绿色像素的 RGB 颜色值为红色:61,绿色:177,蓝色:66。记下这些值,以备后用:

  1. 现在,再次选择取色器选项,点击球的另一个彩色区域,找出该像素的 RGB 颜色值。再次记录这个值。重复 13 到 14 次,确保包括球上最浅和最暗的颜色:

我已经记录了球边缘六个点的 RGB 值,球周围随机位置的四个点的 RGB 值,以及颜色为浅绿色或深绿色的六个点的 RGB 值。找到 RGB 值后,突出显示最低的红色、绿色和蓝色值,以及最高的红色、绿色和蓝色值。我们将在程序中稍后使用这些值来对图像进行阈值处理。

  1. 现在,您需要将这个图像传输到您的 RPi。我通过Google Drive传输了我的图像。我通过将图像上传到 Google Drive,然后在我的 RPi 内打开默认的 Chromium 网络浏览器,登录我的 Gmail 账户,打开 Google Drive,并下载图像来完成这一步。

物体检测程序

用于检测绿色球的程序名为ObjectDetection.cpp,我将其保存在OpenCV_codes文件夹中。我还将greenball.png图像复制到了这个文件夹中。您可以从 GitHub 存储库的Chapter07文件夹中下载ObjectDetection.cpp程序。因此,用于检测绿色球的程序如下:

#include <iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{

 Mat img, resizeimg,thresimage;
 img = imread("greenball.png");
 imshow("Green Ball Image", img);
 waitKey(0);

 resize(img, resizeimg, cvSize(640, 480));
 imshow("Resized Image", resizeimg);
 waitKey(0);

 inRange(resizeimg, Scalar(39, 140, 34), Scalar(122, 245, 119), thresimage);
 imshow("Thresholded Image", thresimage);
 waitKey(0);

 Moments m = moments(thresimage,true);
 int x,y;
 x = m.m10/m.m00;
 y = m.m01/m.m00;
 Point p(x,y);
 circle(img, p, 5, Scalar(0,0,200), -1);
 imshow("Image with center",img);
 waitKey(0);

 return 0;
}

在前面的程序中,我们导入了四个 OpenCV 库,它们是opencv.hppcore.hpphighgui.hppimgproc.hpp。然后我们声明了 OpenCV 库的cv命名空间。

以下是前面程序的解释:

  1. main函数内,我们声明了三个矩阵变量,分别为imgresizeimgthresimage

  2. 接下来,imread()函数读取greenball.png文件,并将其存储在img变量中。

  3. imshow("Green Ball Image", img)行将在新窗口中显示图像,如下面的屏幕截图所示:

  1. 之后,waitKey(0)函数将等待键盘输入。然后执行下一组代码。一旦按下任意键,将执行调整图像大小的下两行代码。

  2. resize函数将调整图像的宽度和高度,使得图像的新宽度为640,高度为480

  1. 然后使用inRange函数执行阈值处理操作。在第一个Scalar函数内,我输入了我的球的绿色的最小 RGB 值,在第二个Scalar函数内,我输入了最大 RGB 值。阈化后的图像存储在thresimage变量中。

Scalar函数内,我们首先输入蓝色值,然后是绿色,最后是红色。

  1. 阈值处理后,球的颜色将变为白色,图像的其余部分将变为黑色。球中间的一些部分将呈现为黑色,这是正常的。如果白色内部出现大面积黑色,这意味着阈值处理没有正确进行。在这种情况下,您可以尝试修改Scalar函数内的 RGB 值:

  1. 接下来,使用moments函数,我们找到对象的中心。

  2. moments(thresimage,true)行,我们将thresimage变量作为输入。

  3. 在接下来的三行代码中,我们找到白色区域的中心并将该值存储在点变量p中。

  4. 之后,为了显示球的中心,我们使用circle函数。在圆函数内部,我们使用img变量,因为我们将在原始图像上显示圆点。接下来,点变量p告诉函数我们在哪里显示点。圆形点的宽度设置为5,圆形点的颜色将是红色,因为我们只填充了Scalar函数的最后一个参数,表示颜色为红色。如果要设置其他颜色,可以更改Scalar函数内的颜色值:

  1. 按任意键再次按下waitKey(0)函数,将关闭除终端窗口之外的所有窗口。要关闭终端窗口,请按Enter

通过上述程序,我们已经学会了如何调整大小、阈值处理,并在绿色球的图像上生成一个点(红点)。在下一节中,我们将对实时视频反馈执行一些图像识别操作。

OpenCV 相机反馈程序

现在,我们将编写一个简单的 C++程序来查看来自 Pi 相机的视频反馈。视频查看程序如下。该程序名为Camerafeed.cpp,您可以从 GitHub 存储库的Chaper07文件夹中下载:

int main()
{
 Mat videoframe;

VideoCapture vid(0);

if (!vid.isOpened())
 {
cout<<"Error opening camera"<<endl;
 return -1;
 }
 for(;;)
 {
 vid.read(videoframe);
 imshow("Frame", videoframe);
 if (waitKey(1) > 0) break;
 }
 return 0;
}

OpenCV 库和命名空间声明与先前程序类似:

  1. 首先,在main函数内部,我们声明了一个名为videoframe的矩阵变量。

  2. 接下来,使用VideoCapture数据类型从 Pi 相机捕获视频反馈。它有一个名为vid(0)的变量。vid(0)变量内的0数字表示相机的索引号。目前,由于我们只连接了一个相机到 RPi,Pi 相机的索引将为0。如果您将 USB 相机连接到树莓派,那么 USB 相机的索引将为1。通过更改索引号,您可以在 Pi 相机和 USB 相机之间切换。

  3. 接下来,我们指定如果相机无法捕获任何视频反馈,则应调用!vid.isOpened()条件。在这种情况下,终端将打印出"Error opening camera"消息。

  4. 之后,vid.read(videoframe)命令将读取相机反馈。

  5. 使用imshow("Video output", videoframe)行,我们现在可以查看相机反馈。

  6. waitKey命令将等待键盘输入。一旦按下任意键,它将退出代码。

这就是您可以使用 Pi 相机查看视频反馈的方法。

构建一个目标跟踪机器人

在对图像进行阈值处理并从 Pi 相机查看视频反馈之后,我们将结合这两个程序来创建我们的目标跟踪机器人程序。

在本节中,我们将编写两个程序。在第一个程序中,我们将球放在相机前面,并通过在球的中心创建一个点(使用矩形)来追踪它。接下来,我们将移动球,并记录相机上不同位置的点值。

在第二个程序中,我们将使用这些点值作为输入,并使机器人跟随球对象。

使用矩形进行球追踪

在跟踪球之前,机器人应首先能够使用 Pi 相机追踪它。在编写程序之前,让我们看看我们将如何追踪球。

编程逻辑

首先,我们将相机分辨率调整为 640 x 480,如下所示:

调整宽度和高度后,我们将相机屏幕水平分为三个相等的部分:

从 0 到 214 的x 坐标值代表左侧部分。从 214 到 428 的x 坐标值代表前进部分,而从 428 到 640 的x 坐标值代表右侧部分。我们不需要编写任何特定的程序来将摄像头屏幕划分为这三个不同的部分,我们只需要记住每个部分的最小和最大x 点值

接下来,我们将对球对象进行阈值处理。之后,我们将使用矩和在球的中心生成一个点。我们将在控制台中打印点值,并检查屏幕特定部分的xy点值:

如果球在前进部分,x 坐标值必须在214428之间。由于我们不是垂直地划分屏幕,所以不需要考虑y值。现在让我们开始球追踪程序。

球追踪程序

BallTracing.cpp程序如下。您可以从 GitHub 存储库的Chapter07文件夹中下载此程序:

int main()
{
  Mat videofeed,resizevideo,thresholdvideo;
  VideoCapture vid(0);
  if (!vid.isOpened())
  {
    return -1;
  } 
  for (;;)
  { 
    vid.read(videofeed);
  resize(videofeed, resizevideo, cvSize(640, 480));
  flip(resizevideo, resizevideo, 1);

  inRange(resizevideo, Scalar(39, 140, 34), Scalar(122, 245, 119), thresholdvideo); 

  Moments m = moments(thresholdvideo,true);
  int x,y;
  x = m.m10/m.m00;
  y = m.m01/m.m00; 
  Point p(x,y);

  circle(resizevideo, p, 10, Scalar(0,0,128), -1);

  imshow("Image with center",resizevideo);
    imshow("Thresolding Video",thresholdvideo);

  cout<<Mat(p)<< endl;

  if (waitKey(33) >= 0) break;
  }
  return 0;
}

main函数内,我们有三个矩阵变量,名为videofeedresizevideothresholdvideo。我们还声明了一个名为vid(0)VideoCapture变量来捕获视频。

以下步骤详细说明了BallTracing.cpp程序:

  1. for循环中,vid.read(videofeed)代码将读取摄像头视频。

  2. 使用resize函数,我们将摄像头分辨率调整为 640 x 480。调整大小后的视频存储在resizevideo变量中。

  3. 然后,使用flip函数,我们水平翻转调整大小后的图像。翻转后的视频输出再次存储在resizevideo变量中。如果我们不水平翻转视频,当你向左移动时,球会看起来好像在右侧移动,反之亦然。如果您将树莓派相机倒置安装,则需要垂直翻转调整大小后的图像。要垂直翻转,将第三个参数设置为0

  4. 接下来,使用inRange函数,我们对视频进行阈值处理,使彩色球从图像的其余部分中脱颖而出。阈值化后的视频输出存储在thresholdvideo变量中。

  5. 使用moments,我们找到了存储在点变量p中的球的中心。

  6. 使用circle函数,在resizevideo视频中显示一个红点在球上。

  7. 第一个imshow函数将显示调整大小后的(resizedvideo)视频,而第二个imshow函数将显示阈值化后的(thresholdvideo)视频:

在上面的屏幕截图中,左窗口显示了resizevideo的视频,我们看到绿色球上的红点。右窗口显示了阈值视频,其中只有球的区域是白色的。

  1. 最后,cout<<Mat(p)<<endl;代码将在控制台内显示红点的xy点值。当您移动球时,红点也会随之移动,并且红点的xy位置将显示在控制台内。

从上面的屏幕截图中,方括号内的值[298 ; 213]是点值。因此,我的情况下红点的x值在 298 到 306 的范围内,y值在 216 到 218 的范围内。

设置物体跟随机器人

跟踪球的位置后,剩下的就是让我们的机器人跟随球。我们将使用xy坐标值作为输入。然而,在跟随球的同时,我们还必须确保机器人与球的距离适当,以免与球或拿着球的人发生碰撞。为此,我们还将把超声波传感器连接到我们的机器人上。对于这个项目,我已经通过电压分压电路将超声波传感器的trigger引脚连接到wiringPi pin no 12,将echo引脚连接到wiringPi pin no 13

物体跟随机器人程序

物体跟随机器人程序基本上是第四章中的避障程序和前面的球追踪程序的组合。该程序名为ObjectFollowingRobot.cpp,您可以从 GitHub 存储库的Chapter07文件夹中下载:

int main()
 { 
...
 float distance = (totalTime * 0.034)/2;

 if(distance < 15)
 {
 cout<<"Object close to Robot"<< " " << Mat(p)<< " " <<distance << " cm" << endl;
 stop();
 }

 else{ 
      if(x<20 && y< 20)
      {
      cout<<"Object not found"<< " " << Mat(p)<< " " <<distance << " cm" << endl;
      stop();
      }
      if(x > 20 && x < 170 && y > 20 )
      {
      cout<<"LEFT TURN"<< " " << Mat(p)<< " " <<distance << " cm" << endl;
      left();
      }
      if(x > 170 && x < 470)
      {
      cout<<"FORWARD"<< " " << Mat(p)<< " " <<distance << " cm" << endl;
      forward();
      }
      if(x > 470 && x < 640)
      {
      cout<<"RIGHT TURN"<< " " << Mat(p)<< " " <<distance << " cm" << endl;
      right();
      }

      }
      if (waitKey(33) >= 0) break;
      }
       return 0;
}

main函数中,计算距离、对视频进行阈值处理并将点放在球的中心后,让我们来看看程序的其余部分:

  1. 第一个if条件(if(distance < 15))将检查机器人距离物体是否为 15 厘米。如果距离小于 15 厘米,机器人将停止。前进、左转、右转和停止功能在main函数上方声明。

  2. stop()函数下,cout语句将首先打印消息"Object close to Robot"。之后,它将打印点(x,y)值(Mat(p)),然后是distance值。在每个if条件内,cout语句将打印区域(如LEFTFORWARDRIGHT),点值和distance值。

  3. 如果距离大于 15 厘米,将执行else条件。在else条件内,有三个if条件来找到球的位置(使用上面的红点作为参考)。

  4. 现在,一旦摄像头被激活,或者当球移出摄像头的视野时,红点(点)将重置到屏幕的极左上角的位置x:0y:0else块内的第一个if条件(if(x<20 && y< 20))将检查红点的位置在xy轴上是否都小于 20。如果是,机器人将停止。

  5. 如果x位置在 20 和 170 之间,y位置大于 20,红点将在LEFT区域,机器人将向LEFT转动。

  6. 在这个程序中,我已经减小了LEFTRIGHT区域的宽度,并增加了FORWARD区域的宽度,如下图所示。您可以根据需要修改每个区域的宽度:

  1. 如果x位置在 170 和 470 之间,红点在FORWARD区域,机器人将向FORWARD移动。

  2. 如果x位置在 470 和 640 之间,红点在RIGHT区域,机器人将向RIGHT转动。

使用移动电源为您的机器人供电,以便它可以自由移动。接下来,编译程序并在您的 RPi 机器人上构建它。只要球不在机器人面前,红点将保持在屏幕的极左上角,机器人将不会移动。如果您将球移动到摄像头前,并且距离机器人 15 厘米,机器人将开始跟随球。

随着机器人跟随球,球的颜色会因外部因素(如阳光或房间内的光线)而变化。如果房间里的光线较暗,球对机器人来说会显得稍暗。同样,如果房间里的光线太亮,球的某些部分也可能显得白色。这可能导致阈值处理无法正常工作,这可能意味着机器人无法顺利跟随球。在这种情况下,您需要调整 RGB 值。

总结

在本章中,我们研究了 OpenCV 库中的一些重要函数。之后,我们对这些函数进行了测试,并从图像中识别出了一个物体。接下来,我们学习了如何从树莓派摄像头读取视频,如何对彩色球进行阈值处理,以及如何在球的顶部放置一个红点。最后,我们使用了树莓派摄像头和超声波传感器来检测球并跟随它。

在下一章中,我们将通过使用 Haar 级联来扩展我们的 OpenCV 知识,检测人脸。之后,我们将识别微笑并让机器人跟随人脸。

问题

  1. 从图像中分离出一个物体的过程叫什么?

  2. 垂直翻转图像的命令是什么?

  3. 如果 x>428 且 y>320,红点会在哪个区块?

  4. 用于调整摄像头分辨率的命令是什么?

  5. 如果物体不在摄像头前方,红点会放在哪里?

第八章:使用 Haar 分类器进行面部检测和跟踪

在上一章中,我们编程机器人来检测一个球体并跟随它。在本章中,我们将通过检测和跟踪人脸、检测人眼和识别微笑,将我们的检测技能提升到下一个水平。

在本章中,您将学习以下主题:

  • 使用 Haar 级联进行面部检测

  • 检测眼睛和微笑

  • 面部跟踪机器人

技术要求

在本章中,您将需要以下内容:

  • 三个 LED 灯

  • 一个树莓派(RPi)机器人(连接到 RPi 的树莓派摄像头模块)

本章的代码文件可以从github.com/PacktPublishing/Hands-On-Robotics-Programming-with-Cpp/tree/master/Chapter08下载。

使用 Haar 级联进行面部检测

Paul Viola 和 Micheal Jones 在他们的论文《使用增强级联简单特征的快速目标检测》中于 2001 年提出了基于 Haar 特征的级联分类器。Haar 特征的级联分类器是使用面部图像以及非面部图像进行训练的。Haar 级联分类器不仅可以检测正面人脸,还可以检测人的眼睛、嘴巴和鼻子。Haar 特征的分类器也被称为 Viola-Jones 算法。

Viola-Jones 算法的基本工作

因此,简而言之,Viola-Jones 算法使用 Haar 特征来检测人脸。Haar 通常包括两个主要特征:边缘特征线特征。我们将首先了解这两个特征,然后我们将看到这些特征如何用于检测人脸:

  • 边缘特征:通常用于检测边缘。边缘特征由白色和黑色像素组成。边缘特征可以进一步分为水平边缘特征和垂直边缘特征。在下图中,我们可以看到左侧块上的垂直边缘特征和右侧块上的水平边缘特征:

  • 线特征:通常用于检测线条。在线特征中,一个白色像素被夹在两个黑色像素之间,或者一个黑色像素被夹在两个白色像素之间。在下图中,您可以看到左侧的两个水平线特征,一个在另一个下方,以及右侧的垂直线特征,相邻在一起:

面部检测始终在灰度图像上执行,但这意味着在灰度图像中,我们可能没有完全黑色和白色的像素。因此,让我们将白色像素称为较亮的像素,黑色像素称为较暗的像素。如果我们看下面的灰度人脸图片,额头区域较亮(较亮的像素)与眉毛区域(较暗的像素)相比:

与眼睛和脸颊区域相比,鼻线区域更亮。同样,如果我们看口部区域,上唇区域较暗,牙齿区域较亮,下唇区域再次较暗:

这就是通过使用 Haar 级联的边缘和线特征,我们可以检测人脸中最相关的特征点,如眼睛、鼻子和嘴巴。

OpenCV 4.0 包括不同的预训练 Haar 检测器,可以用于检测人脸,包括眼睛、鼻子、微笑等。在Opencv-4.0.0文件夹中,有一个Data文件夹,在Data文件夹中,您会找到haarcascades文件夹。在这个文件夹中,您会找到不同的 Haar 级联分类器。对于正面人脸检测,我们将使用haarcascade_frontalface_alt2.xml检测器。在下面的截图中,您可以看到haarcascades文件夹的路径,其中包含不同的 Haar 级联分类器:

现在我们了解了 Viola-Jones 特征的基础知识,我们将编写程序,使我们的机器人使用 Haar 级联检测人脸。

人脸检测程序

让我们编写一个程序来检测人脸。我将这个程序命名为FaceDetection.cpp,您可以从本书的 GitHub 存储库的Chapter08文件夹中下载。

由于我们将使用haarcascade_frontalface_alt2.xml来检测人脸,请确保FaceDetection.cpphaarcascade_frontalface_alt2.xml文件在同一个文件夹中。

要编写人脸检测程序,请按照以下步骤进行:

  1. FaceDetection.cpp程序中,使用CascadeClassifier类加载 Haar 的预训练正面脸 XML,如下面的代码片段所示:
CascadeClassifier faceDetector("haarcascade_frontalface_alt2.xml");
  1. 声明两个矩阵变量,称为videofeedgrayfeed,以及一个名为vid(0)VideoCapture变量,以从 RPi 相机捕获视频:
Mat videofeed, grayfeed;
VideoCapture vid(0);
  1. for循环内,读取相机视频。然后,水平翻转相机视频。使用cvtColor函数,我们可以将我们的videofeed转换为grayscale。如果您的 Pi 相机放置颠倒,将flip函数内的第三个参数设置为0grayscale输出存储在grayfeed变量中。以下代码显示了如何完成此步骤:
vid.read(videofeed);
flip(videofeed, videofeed, 1);
cvtColor(videofeed, grayfeed, COLOR_BGR2GRAY);
  1. 让我们执行直方图均衡化,以改善videofeed的亮度和对比度。直方图均衡化是必需的,因为有时在光线较暗时,相机可能无法检测到人脸。为了执行直方图均衡化,我们将使用equalizeHist函数:
equalizeHist(grayfeed, grayfeed);
  1. 让我们检测一些人脸。为此,使用detectMultiScale函数,如下所示:
detectMultiScale(image, object, scalefactor, min neighbors,flags, min size, max size);

在前面的代码片段中显示的detectMultiScale函数由以下七个参数组成:

    • image:表示输入视频源。在我们的情况下,它是grayfeed,因为我们将从灰度视频中检测人脸。
  • object:表示矩形的向量,其中每个矩形包含检测到的人脸。

  • scalefactor:指定图像大小必须缩小多少。比例因子的理想值在 1.1 和 1.3 之间。

  • flags:此参数可以设置为CASCADE_SCALE_IMAGECASCADE_FIND_BIGGEST_OBJECTCASCADE_DO_ROUGH_SEARCHCASCADE_DO_CANNY_PRUNING

  • CASCADE_SCALE_IMAGE:这是最流行的标志;它通知分类器,用于检测人脸的 Haar 特征应用于视频或图像。

  • CASCADE_FIND_BIGGEST_OBJECT:此标志将告诉分类器在图像或视频中找到最大的脸

  • CASCADE_DO_ROUGH_SEARCH:此标志将在检测到人脸后停止分类器。

  • CASCADE_DO_CANNY_PRUNNING:此标志通知分类器不要检测锐利的边缘,从而增加检测到人脸的机会。

  • min neighbors:最小邻居参数影响检测到的人脸的质量。较高的最小邻居值将识别较少的人脸,但无论它检测到什么都一定是人脸。较低的min neighbors值可能会识别多个人脸,但有时也可能识别不是人脸的对象。检测人脸的理想min neighbors值在 3 和 5 之间。

  • min size:最小尺寸参数将检测最小的人脸尺寸。例如,如果我们将最小尺寸设置为 50 x 50 像素,分类器将只检测大于 50 x 50 像素的人脸,忽略小于 50 x 50 像素的人脸。理想情况下,我们可以将最小尺寸设置为 30 x 30 像素。

  • max size:最大尺寸参数将检测最大的人脸尺寸。例如,如果我们将最大尺寸设置为 80 x 80 像素,分类器将只检测小于 80 x 80 像素的人脸。因此,如果您离相机太近,您的脸的尺寸超过了最大尺寸,分类器将无法检测到您的脸。

  1. 由于detectMultiScale函数提供矩形的向量作为其输出,我们必须声明一个Rect类型的向量。变量名为facescalefactor设置为1.1min neighbors设置为5,最小比例大小设置为 30 x 30 像素。最大大小在这里被忽略,因为如果您的脸部尺寸变得大于最大尺寸,您的脸部将无法被检测到。要完成此步骤,请使用以下代码:
vector<Rect> face;
 faceDetector.detectMultiScale(grayfeed, faces, 1.3, 5, 0 | CASCADE_SCALE_IMAGE, Size(30, 30));

检测到脸部后,我们将在检测到的脸部周围创建一个矩形,并在矩形的左上方显示文本,指示“检测到脸部”:

for (size_t f = 0; f < face.size(); f++) 
 {
rectangle(videofeed, face[f], Scalar(255, 0, 0), 2);
putText(videofeed, "Face Detected", Point(face[f].x, face[f].y), FONT_HERSHEY_PLAIN, 1.0, Scalar(0, 255, 0), 2.0);
}

for循环内,我们使用face.size()函数来确定检测到了多少张脸。如果检测到一张脸,face.size()等于1for循环就会满足条件。在for循环内,我们有矩形和putText函数。

矩形函数将在检测到的脸部周围创建一个矩形。它由四个参数组成:

  • 第一个参数表示我们要在其上绘制矩形的图像或视频源,在我们的例子中是videofeed

  • face[f]的第二个参数表示我们要在其上绘制矩形的检测到的脸部

  • 第三个参数表示矩形的颜色(在此示例中,我们将颜色设置为蓝色)

  • 第四个和最后一个参数表示矩形的厚度

putText函数用于在图像或视频源中显示文本。它由七个参数组成:

  • 第一个参数表示我们要在其上绘制矩形的图像或视频源。

  • 第二个参数表示我们要显示的文本消息。

  • 第三个参数表示我们希望文本显示的位置。face[f].xface[f].y函数表示矩形的左上点,因此文本将显示在矩形的左上方。

  • 第四个参数表示字体类型,我们设置为FONT_HERSHEY_PLAIN

  • 第五个参数表示文本的字体大小,我们设置为1

  • 第六个参数表示文本的颜色,设置为绿色(Scalar(0,255,0))。

  • 第七个和最后一个参数表示字体的厚度,设置为1.0

最后,使用imshow函数,我们将查看视频源,以及矩形和文本:

imshow("Face Detection", videofeed);

使用上述代码后,如果您已经编译和构建了程序,您将看到在检测到的脸部周围画了一个矩形:

接下来,我们将检测人眼并识别微笑。一旦眼睛和微笑被识别出来,我们将在它们周围创建圆圈。

检测眼睛和微笑

用于检测眼睛和微笑的程序名为SmilingFace.cpp,您可以从本书的 GitHub 存储库的Chapter08文件夹中下载。

检测眼睛

SmilingFace.cpp程序基本上是FaceDetection.cpp程序的扩展,这意味着我们将首先找到感兴趣的区域,即脸部。接下来,使用 Haar 级联分类器检测眼睛,然后在它们周围画圆圈。

在编写程序之前,让我们首先了解不同的可用的眼睛CascadeClassifier。OpenCV 4.0 有三个主要的眼睛级联分类器:

  • haarcascade_eye.xml:此分类器将同时检测两只眼睛

  • haarcascade_lefteye_2splits.xml:此分类器将仅检测左眼

  • haarcascade_righteye_2splits.xml:此分类器将仅检测右眼

根据您的要求,您可以使用haarcascade_eye分类器来检测两只眼睛,或者您可以使用haarcascade_lefteye_2splits分类器仅检测左眼和haarcascade_righteye_2splits分类器仅检测右眼。在SmilingFace.cpp程序中,我们将首先使用haarcascade_eye分类器测试输出,然后我们将使用haarcascade_lefteye_2splitshaarcascade_righteye_2splits分类器测试输出。

使用haarcascade_eye进行眼睛检测

要测试haarcascade_eye的输出,观察以下步骤:

  1. 在我们的程序中加载这个分类器:
CascadeClassifier eyeDetector("haarcascade_eye.xml");
  1. 要检测眼睛,我们需要在图像(视频源)中找到脸部区域(感兴趣区域)。在脸部检测的for循环中,我们将创建一个名为faceroiMat变量。videofeed(face[f]),这将在videofeed中找到脸部并将它们存储在faceroi变量中:
Mat faceroi = videofeed(face[f]);
  1. 创建一个名为eyesRect类型的向量,然后使用detectMultiScale函数来检测眼睛区域:
vector<Rect> eyes;
eyeDetector.detectMultiScale(faceroi, eyes, 1.3, 5, 0 |CASCADE_SCALE_IMAGE,Size(30, 30));

detectMultiScale函数中,第一个参数设置为faceroi,这意味着我们只想从脸部区域检测眼睛,而不是从整个视频源检测。检测到的眼睛将存储在 eyes 变量中。

  1. 为了在眼睛周围创建圆圈,我们将使用一个for循环。让我们找到眼睛的中心。为了找到眼睛的中心,我们将使用Point数据类型,并且eyecenter变量中的方程将给出眼睛的中心:
for (size_t e = 0; e < eyes.size(); e++)
 {
 Point eyecenter(face[f].x + eyes[e].x + eyes[e].width/2, face[f].y + eyes[e].y + eyes[e].height/2);
 int radius = cvRound((eyes[e].width + eyes[e].height)*0.20);
 circle(videofeed, eyecenter, radius, Scalar(0, 0, 255), 2);
 }

这的结果可以在这里看到:

使用radius变量,我们计算了圆的半径,然后使用circle函数在眼睛周围创建红色的圆圈。

使用haarcascade_lefteye_2splitshaarcascade_righteye_2splits进行眼睛检测

使用haarcascade_eye分类器检测两只眼睛后,让我们尝试仅使用haarcascade_lefteye_2splitshaarcascade_righteye_2splits分类器分别检测左眼或右眼。

检测左眼

要检测左眼,执行以下步骤:

  1. 在我们的程序中加载haarcascade_lefteye_2splits级联分类器:
CascadeClassifier eyeDetectorleft("haarcascade_lefteye_2splits.xml");
  1. 由于我们想要在脸部区域检测左眼,我们将创建一个名为faceroiMat变量,并在其中存储脸部区域的值:
Mat faceroi = videofeed(face[f]);
  1. 使用detectMultiScale函数创建一个名为lefteyeRect类型的向量来检测左眼区域。min neighbors参数设置为25,以便分类器只检测左眼。如果我们将min neighbors设置为低于 25,haarcascade_lefteye_2splits分类器也可能检测到右眼,这不是我们想要的。要完成此步骤,请使用以下代码:
vector<Rect> lefteye;
eyeDetectorleft.detectMultiScale(faceROI, lefteye, 1.3, 25, 0 |CASCADE_SCALE_IMAGE,Size(30, 30));
 for (size_t le = 0; le < lefteye.size(); le++)
 {
 Point center(face[f].x + lefteye[le].x + lefteye[le].width*0.5, face[f].y + lefteye[le].y + lefteye[le].height*0.5);
 int radius = cvRound((lefteye[le].width + lefteye[le].height)*0.20);
 circle(videofeed, center, radius, Scalar(0, 0, 255), 2);
 }

上述代码的输出如下:

检测左右眼分开的for循环代码是SmilingFace.cpp程序的一部分,但是被注释掉了。要测试代码,首先注释掉同时检测两只眼睛的for循环,然后取消注释检测左眼和右眼的另外两个for循环。

检测右眼

检测右眼的编程逻辑与检测左眼非常相似。我们唯一需要改变的是分类器名称和一些变量名称,以区分左眼和右眼。要检测右眼,执行以下步骤:

  1. 加载haarcascade_righteye_2splits级联分类器:
CascadeClassifier eyeDetectorright("haarcascade_righteye_2splits.xml");
  1. 在脸部检测的for循环中,找到脸部区域。然后,使用detectMultiScale函数来检测右眼。使用circle函数在右眼周围创建一个绿色的圆圈。为此,请使用以下代码:
Mat faceroi = videofeed(face[f]); 
vector<Rect>  righteye;
eyeDetectorright.detectMultiScale(faceROI, righteye, 1.3, 25, 0 |CASCADE_SCALE_IMAGE,Size(30, 30));

for (size_t re = 0; re < righteye.size(); re++)
 {
 Point center(face[f].x + righteye[re].x + righteye[re].width*0.5, face[f].y + righteye[re].y + righteye[re].height*0.5);
 int radius = cvRound((righteye[re].width + righteye[re].height)*0.20);
 circle(videofeed, center, radius, Scalar(0, 255, 0), 2);
 }

上述代码的输出如下:

如果我们结合左眼和右眼的检测器代码,最终输出将如下所示:

正如我们所看到的,图片中的左眼被红色圆圈包围,右眼被绿色圆圈包围。

识别微笑

在从面部区域检测到眼睛后,让我们编写程序来识别笑脸。当网络摄像头检测到嘴巴周围的黑白黑线特征时,即上下嘴唇通常比牙齿区域略暗时,网络摄像头将识别出一个微笑的脸:

微笑识别的编程逻辑

微笑识别的编程逻辑与眼睛检测类似,我们还将在面部检测的for循环内编写微笑识别程序。要编写微笑识别程序,请按照以下步骤进行:

  1. 加载微笑CascadeClassifier
CascadeClassifier smileDetector("haarcascade_smile.xml");
  1. 我们需要检测面部区域,它位于面部区域内。面部区域再次是我们的感兴趣区域,为了从视频源中找到面部区域,我们将使用以下命令:
Mat faceroi = videofeed(face[f]);
  1. 声明一个smile变量,它是Rect类型的向量。然后使用detectMultiScale函数。在detectMultiScale函数中,将min neighbors设置为25,以便只有在人微笑时才创建一个圆圈(如果我们将最小邻居设置为低于 25,即使人没有微笑,也可能在嘴周围创建一个圆圈)。您可以在 25-35 之间变化min neighbors的值。接下来,在for循环内,我们编写了在嘴周围创建绿色圆圈的程序。要完成此步骤,请使用以下代码:
vector<Rect> smile; 
smileDetector.detectMultiScale(faceroi, smile, 1.3, 25, 0 |CASCADE_SCALE_IMAGE,Size(30, 30));
 for (size_t sm = 0; sm <smile.size(); sm++)
 {
 Point scenter(face[f].x + smile[sm].x + smile[sm].width*0.5, face[f].y + smile[sm].y + smile[sm].height*0.5);
 int sradius = cvRound((smile[sm].width + smile[sm].height)*0.20);
 circle(videofeed, scenter, sradius, Scalar(0, 255, 0), 2);
 }

前面代码的输出如下:

在接下来的部分中,当检测到眼睛和微笑时,我们将打开不同的 LED。当面部移动时,我们还将使我们的机器人跟随检测到的面部。

面部跟踪机器人

用于打开/关闭 LED 和跟踪人脸的程序称为Facetrackingrobot.cpp,您可以从本书的 GitHub 存储库的Chapter08文件夹中下载。

Facetrackingrobot程序中,我们将首先检测面部,然后是左眼、右眼和微笑。一旦检测到眼睛和微笑,我们将打开/关闭 LED。之后,我们将在面部矩形的中心创建一个小点,然后使用这个点作为移动机器人的参考。

接线

对于Facetrackingrobot程序,我们至少需要三个 LED:一个用于左眼,一个用于右眼,一个用于微笑识别。这三个 LED 显示在以下图表中:

LED 和机器人的接线如下:

  • 对应左眼的左 LED 连接到wiringPi pin 0

  • 对应右眼的右 LED 连接到wiringPi pin 2

  • 对应微笑的中间 LED 连接到wiringPi pin 3

  • 电机驱动器的IN1引脚连接到wiringPi pin 24

  • 电机驱动器的IN2引脚连接到wiringPi pin 27

  • 电机驱动器的IN3引脚连接到wiringPi pin 25

  • 电机驱动器的IN4引脚连接到wiringPi pin 28

在我的机器人上,我已经把左右 LED 贴在机器人的顶部底盘上。第三个 LED(中间 LED)贴在机器人的底盘上。我使用绿色 LED 作为眼睛,红色 LED 作为微笑:

编程逻辑

Facetrackingrobot程序中,将 wiringPi 引脚 0、2 和 3 设置为输出引脚:

 pinMode(0,OUTPUT);
 pinMode(2,OUTPUT);
 pinMode(3,OUTPUT);

从面部检测程序中,您可能已经注意到面部跟踪过程非常缓慢。因此,当您将脸部向左或向右移动时,必须确保电机不要移动得太快。为了减慢电机的速度,我们将使用softPwm.h库,这也是我们在第二章中使用的使用 wiringPi 实现眨眼

  1. softPwm.h库中,使用softPwmCreate函数声明四个电机引脚(24272528):
softPwmCreate(24,0,100); //pin 24 is left Motor pin
softPwmCreate(27,0,100); //pin 27 is left motor pin 
softPwmCreate(25,0,100); //pin 25 is right motor pin
softPwmCreate(28,0,100); //pin 28 is right motor pin

softPwmCreate函数中的第一个参数表示 RPi 的 wiringPi 引脚。第二个参数表示我们可以移动电机的最小速度,第三个参数表示我们可以移动电机的最大速度。

  1. 加载面部、左眼、右眼和微笑CascadeClassifiers
CascadeClassifier faceDetector("haarcascade_frontalface_alt2.xml");
CascadeClassifier eyeDetectorright("haarcascade_righteye_2splits.xml");
CascadeClassifier eyeDetectorleft("haarcascade_lefteye_2splits.xml");
CascadeClassifier smileDetector("haarcascade_smile.xml");
  1. for循环内,声明三个布尔变量,称为lefteyedetectrighteyedetectisSmiling。将这三个变量都设置为false。使用这三个变量,我们将检测左眼、右眼和微笑是否被检测到。声明facexfacey变量,用于找到脸部矩形的中心。要完成此步骤,请使用以下代码:
bool lefteyedetect = false;
bool righteyedetect = false;
bool isSmiling = false;
int facex, facey;
  1. 使用detectMultiScale函数检测面部,然后在for循环内编写程序创建检测到的面部周围的矩形:
vector<Rect> face;
faceDetector.detectMultiScale(grayfeed, face, 1.1, 5, 0 | CASCADE_SCALE_IMAGE,Size(30, 30)); 
 for (size_t f = 0; f < face.size(); f++) 
 {
 rectangle(videofeed, face[f], Scalar(255, 0, 0), 2);

 putText(videofeed, "Face Detected", Point(face[f].x, face[f].y), FONT_HERSHEY_PLAIN, 1.0, Scalar(0, 255, 0), 1.0); 

facex = face[f].x +face[f].width/2;
facey = face[f].y + face[f].height/2; 

Point facecenter(facex, facey);
circle(videofeed,facecenter,5,Scalar(255,255,255),-1);

face[f].x + face[f].width/2将返回矩形的x中心值,face[f].y + face[f].height/2将返回矩形的y中心值。 x中心值存储在facex变量中,y中心值存储在facey变量中。

  1. 提供facexfacey作为Point变量的输入,以找到矩形的中心,称为facecenter。在圆函数中,使用facecenter点变量作为输入,在脸部矩形的中心创建一个点:

  1. 当检测到左眼时,我们将在其周围创建一个红色圆圈,并将lefteyedetect变量设置为true
eyeDetectorleft.detectMultiScale(faceroi, lefteye, 1.3, 25, 0 |CASCADE_SCALE_IMAGE,Size(30, 30));
 for (size_t le = 0; le < lefteye.size(); le++)
 {
 Point center(face[f].x + lefteye[le].x + lefteye[le].width*0.5, face[f].y + lefteye[le].y + lefteye[le].height*0.5);
 int radius = cvRound((lefteye[le].width + lefteye[le].height)*0.25);
 circle(videofeed, center, radius, Scalar(0, 0, 255), 2);
 lefteyedetect = true;
 }
  1. 当检测到右眼时,我们将在其周围创建一个浅蓝色圆圈,并将righteyedetect变量设置为true
 eyeDetectorright.detectMultiScale(faceroi, righteye, 1.3, 25, 0 |CASCADE_SCALE_IMAGE,Size(30, 30));
 for (size_t re = 0; re < righteye.size(); re++)
 {
 Point center(face[f].x + righteye[re].x + righteye[re].width*0.5, face[f].y + righteye[re].y + righteye[re].height*0.5);
 int radius = cvRound((righteye[re].width + righteye[re].height)*0.25);
 circle(videofeed, center, radius, Scalar(255, 255, 0), 2);
 righteyedetect = true;
 }
  1. 当检测到微笑时,我们将在嘴周围创建一个绿色圆圈,并将isSmiling设置为true
 smileDetector.detectMultiScale(faceroi, smile, 1.3, 25, 0 |CASCADE_SCALE_IMAGE,Size(30, 30));
 for (size_t sm = 0; sm <smile.size(); sm++)
 {
 Point scenter(face[f].x + smile[sm].x + smile[sm].width*0.5, face[f].y + smile[sm].y + smile[sm].height*0.5);
 int sradius = cvRound((smile[sm].width + smile[sm].height)*0.25);
 circle(videofeed, scenter, sradius, Scalar(0, 255, 0), 2, 8, 0);
 isSmiling = true;
 }

在下面的屏幕截图中,您可以看到左眼周围画了一个红色圆圈,右眼周围画了一个浅蓝色圆圈,嘴周围画了一个绿色圆圈,并且在围绕脸部的蓝色矩形的中心有一个白点:

使用三个if条件,我们将检查lefteyedetectrighteyedetectisSmiling变量何时为true,并在它们为true时打开它们各自的 LED:

  • 当检测到左眼时,lefteyedetect变量将为true。当检测到左眼时,我们将打开连接到 wiringPi 引脚 0 的机器人上的左 LED,如下面的代码所示:
if(lefteyedetect == true){
digitalWrite(0,HIGH);
}
else
{
digitalWrite(0,LOW);
}
  • 当检测到右眼时,righteyedetect变量将为true。当检测到右眼时,我们将打开连接到 wiringPi 引脚 2 的机器人上的右 LED:
if(righteyedetect == true){
digitalWrite(2,HIGH);
}
else
{
digitalWrite(2,LOW);
}
  • 最后,当识别到微笑时,isSmiling变量将为 true。当识别到微笑时,我们将打开连接到 wiringPi 引脚 3 的中间 LED:
if(isSmiling == true){
 digitalWrite(3,HIGH);
 }
 else
 {
 digitalWrite(3,LOW);
 }

接下来,我们将使用脸部矩形上的白点(点)将机器人向左和向右移动。

使用脸部三角形上的白点移动机器人

与第七章类似,使用 OpenCV 构建一个目标跟踪机器人,我们将摄像头屏幕分为三个部分:左侧部分、中间部分和右侧部分。当白点位于左侧或右侧部分时,我们将向左或向右转动机器人,从而跟踪脸部。即使我没有调整videofeed的大小,videofeed的分辨率设置为 640 x 480(宽度为 640,高度为 480)。

您可以根据需要变化范围,但如下图所示,左侧部分设置为 x 范围从 0 到 280,中间部分设置为 280-360 的范围,右侧部分设置为 360 到 640 的范围:

当我们移动我们的脸时,脸部矩形将移动,当脸部矩形移动时,矩形中心的白点也会移动。当点移动时,facexfacey的值将发生变化。将摄像头屏幕分为三个部分时,我们将使用facex变量作为参考,然后我们将使用三个 if 条件来检查白点位于哪个部分。用于比较facex值的代码如下:

if(facex > 0 && facex < 280)
 {
 putText(videofeed, "Left", Point(320,10), FONT_HERSHEY_PLAIN, 1.0, CV_RGB(0, 0, 255), 2.0); 
 softPwmWrite(24, 0);
 softPwmWrite(27, 30);
 softPwmWrite(25, 30);
 softPwmWrite(28, 0); 
 } 

 if(facex > 360 && facex < 640)
 {
 putText(videofeed, "Right", Point(320,10), FONT_HERSHEY_PLAIN, 1.0, CV_RGB(0, 0, 255), 2.0); 
 softPwmWrite(24, 30);
 softPwmWrite(27, 0);
 softPwmWrite(25, 0);
 softPwmWrite(28, 30);

 }
 if(facex > 280 && facex < 360)
 {
 putText(videofeed, "Middle", Point(320,10), FONT_HERSHEY_PLAIN, 1.0, CV_RGB(0, 0, 255), 2.0); 
 softPwmWrite(24, 0);
 softPwmWrite(27, 0);
 softPwmWrite(25, 0);
 softPwmWrite(28, 0);
 }

如果满足第一个if条件,这意味着白点位于 0 到 280 之间。在这种情况下,我们在videofeed上打印Left文本,然后使用softPwmWrite函数,使机器人进行轴向左转。在softPwmWrite函数内,第一个参数代表引脚号,第二个参数代表我们的电机移动的速度。由于 wiringPi 引脚 24 设置为 0(低),wiringPi 引脚 27 设置为 30,左电机将以 30 的速度向后移动。同样,由于 wiringPi 引脚 25 设置为 30,wiringPi 引脚 28 设置为 0(低),右电机将以 30 的速度向前移动。

30 的速度值在 0 到 100 的范围内,我们在softPwmCreate函数中设置。您也可以改变速度值。

如果白点位于 360 到 640 之间,将打印Right文本,并且机器人将以 30 的速度进行轴向右转。

最后,当白点位于 280 到 360 之间时,将打印Middle文本,机器人将停止移动。

这就是我们如何让机器人跟踪脸部并跟随它。

摘要

在本章中,我们使用 Haar 面部分类器从视频源中检测面部,然后在其周围画一个矩形。接下来,我们从给定的面部检测眼睛和微笑,并在眼睛和嘴周围画圈。之后,利用我们对面部、眼睛和微笑检测的知识,当检测到眼睛和微笑时,我们打开和关闭机器人的 LED。最后,通过在脸部矩形中心创建一个白点,我们使机器人跟随我们的脸。

在下一章中,我们将学习如何使用我们的声音控制机器人。我们还将创建一个 Android 应用程序,用于识别我们所说的内容。当 Android 应用程序检测到特定关键词时,Android 智能手机的蓝牙将向树莓派蓝牙发送数据位。一旦我们的机器人识别出这些关键词,我们将使用它们来使机器人朝不同方向移动。

问题

  1. 我们用于检测面部的分类器的名称是什么?

  2. 当我们张开嘴时,会创建哪种类型的特征?

  3. 哪个级联可以用于仅检测左眼?

  4. 从面部检测眼睛时,该区域通常被称为什么?

  5. equalizeHist函数的用途是什么?

第四部分:智能手机控制的机器人

在本节中,我们将使用 AppInventor2 网站创建一个安卓应用程序,通过它你可以用智能手机控制机器人。

以下章节包括在本节中:

  • 第九章,构建一个语音控制的机器人

第九章:构建一个语音控制的机器人

2012 年,我想创建一个可以由 Android 智能手机控制的机器人。然而,当时我对 Android 编程并不了解。令我惊讶的是,我发现了一个名为 App Inventor 的神奇网站(www.appinventor.org/),它允许用户通过将编程块连接在一起的方式来开发 Android 应用,就像拼图一样。

在这最后一章中,我们将使用 App Inventor 网站,并学习如何使用我们的声音作为输入,用 Android 智能手机来控制我们的机器人。我们将涵盖以下主题:

  • App Inventor 简介

  • 创建语音应用

  • 通过蓝牙将 Android 智能手机和树莓派(RPi)配对

  • 为 RPi 开发蓝牙程序

技术要求

  • 运行 Android 版本 Lollipop(版本号 5.0-5.1.1)或更高版本的 Android 智能手机

  • 树莓派机器人

本章的代码文件可以从github.com/PacktPublishing/Hands-On-Robotics-Programming-with-Cpp/tree/master/Chapter09下载。

App Inventor 简介

App Inventor 是一个由 Google 最初开发的开源基于 Web 的应用程序。目前由麻省理工学院(MIT)维护。它允许用户使用类似于 Scratch 的最先进的图形编程界面开发 Android 应用程序。开发人员必须拖放可视块来创建一个 App Inventor 的 Android 应用。App Inventor 的当前版本被称为 App Inventor 2(版本 2)或 AI2。

在下图中,您可以看到每个编程块如何连接在一起,就像拼图一样:

在本节中,我们将看看如何创建 App Inventor 帐户,然后使用 App Inventor 创建我们的第一个 Android 应用。

创建 Talking Pi Android 应用

Talking Pi 是一个简单的 Android 应用程序,您可以在文本框中输入文本,智能手机会显示并朗读文本。在创建此 Android 应用程序之前,我们首先需要访问 App Inventor 2 仪表板。Talking Pi 应用程序的最终布局将如下所示:

要使用 App Inventor 2 创建 Android 应用程序,您必须拥有一个 Gmail 帐户。如果您已经有一个,请在您选择的浏览器上登录。如果没有,请创建一个。现在让我们看看将 App Inventor 2 与您的 Gmail 帐户链接的步骤:

  1. 登录后,转到以下链接:ai2.appinventor.mit.edu/。如果您在浏览器中使用多个 Gmail 帐户登录,您需要选择一个特定的 ID:

  1. 接下来,您需要同意 AI2 的服务条款。然后,您将准备好创建 Talking Pi 应用程序。要创建一个新的 Android 应用程序项目,请单击“开始新项目”按钮,如下所示:

  1. 接下来,将项目命名为TalkingPi,然后单击确定:

创建项目后,您将在 App Inventor 中看到以下四个主要面板,称为 Palette、Viewer、Components 和 Properties:

现在让我们了解每个面板的工作原理:

  • Palette 面板包括不同的组件,如按钮、文本框、画布、蓝牙、视频播放器等。

  • Viewer 面板由一个屏幕组成,我们可以从 Palette 中拖放 UI 组件。

  • 组件面板显示了添加到屏幕内的可见和不可见组件的列表。例如,按钮是一个可见组件,因为它在屏幕上可见。另一方面,蓝牙是一个不可见组件,因为它在屏幕上不可见,但它在后台起作用。所有不可见组件都显示在屏幕下方。

  • 属性面板允许我们修改组件的属性,这些组件在组件面板中被选中。

现在让我们继续设计应用程序。

设计应用程序

在我们的 Talking Pi 应用程序中,我们将添加四个主要组件:TextBox、Button、Label 和 TextToSpeech。TextBox、Button 和 Label 组件位于用户界面选项中。按照以下步骤:

  1. 您可以依次将 TextBox、Button 和 Label 组件拖放到屏幕上,如下所示:

  1. 添加这三个组件后,您会注意到它们都对齐到屏幕的左上角,看起来有点奇怪。为了将它们水平定位在屏幕的中心,从组件面板中选择Screen1,并将AlignHorizontal更改为Center,如下截图所示:

  1. 接下来,为了在三个组件之间添加一些间距,我们可以在 TextBox、Button 和 Label 组件之间添加布局组件。例如,您可以选择 HorizontalArrangement 或 VerticalArrangement:

  1. 如果要改变两个组件之间的距离,您需要更改 HorizontalArrangement 的高度。为此,请选择高度属性,并在 HorizontalArrangement 上设置特定的像素值,如下所示:

  1. 接下来,选择Button1组件,并将其文本更改为CLICK HERE

  1. 同样,选择Label1组件,将其文本更改为TalkingPi,并将其FontSize增加到24

  1. 最后,打开媒体选项并将 TextToSpeech 组件拖到屏幕上。由于 TextToSpeech 组件是一个不可见组件,它将显示在屏幕下方,如下所示:

我们现在基本上已经完成了 Talking Pi 应用程序的设计。现在让我们进入 Blocks 选项,并为显示文本并在点击按钮时将其转换为语音创建编程块。

编程块

设计应用程序的 UI 后,点击 Blocks 按钮,该按钮位于 Design 按钮旁边,如下截图所示:

在块部分,左侧将看到Screen1,其中包含我们拖放到屏幕上的所有组件(可见和不可见)。如果点击任何组件,您将注意到每个组件的以下类型的块:

我们将主要关注构成每个组件的三种类型的块。我们将这些称为主块中间块最终块。这些块中的每一个必须按正确的顺序连接以获得一个正常工作的程序,如下图所示:

让我们看看每个块。

主块

App Inventor 2 中的主块类似于when循环,表示发生某事时要执行的操作。主块始终连接到中间块。我们不能直接将最终块连接到主块。主块包括一个下拉菜单,我们可以从中选择多个相同类型的组件。例如,看下面的截图:

您可以看到,在有多个按钮的情况下,可以从下拉列表中选择特定的按钮。

中间块

中间块包括一个输入插座和一个输出插座输入插座连接到主块输出插座连接到最终块,如下图所示:

中间块包括两个下拉菜单。第一个下拉菜单代表相同类型的组件。例如,如果有多个标签,可以从第一个下拉菜单中选择特定的标签。第二个下拉菜单代表组件的属性。例如,对于Label1,我们有TextBackgroundColorWidth等,如下图所示:

例如,Label1 . Text表示我们要设置或更改Label1的文本。

最终块

最终块连接到中间块。它还包括两个下拉菜单,我们可以从中选择特定的组件及其特定属性,如下图所示:

我们将使用这三种类型的块来创建我们的 Talking Pi 程序。让我们从块编程开始。

Talking Pi 程序

Talking Pi 编程逻辑非常简单。当按下Button1时,Label1必须显示在Textbox1内键入的文本,并且TextToSpeech1必须朗读该文本。执行此块程序的步骤如下:

  1. 首先,点击Button1组件并选择when Button1.Click主块:

  1. 接下来,由于我们希望在单击Button1时更改Label1的文本,因此选择Label1组件中的Label1.Text块:

  1. 接下来,将Label1.Text块拖到Button1.Click块内以连接这两个块。一旦连接了两个块,就会听到点击声音:

  1. 现在,我们希望在标签组件中显示文本框内的文本。从TextBox1组件中,选择如下TextBox1.Text块:

  1. 接下来,将TextBox1.Text块连接到Label1.Text块。现在,当您按下按钮时,标签将显示文本框内的文本。Label1现在设置为显示TextBox1内的文本,如下所示:

  1. 之后,要朗读文本框内的文本,点击TextToSpeech1组件并选择call TextToSpeech1.Speak块,如下所示:

  1. 将此块连接到Label1.Text块下方。在消息插座内,连接TextBox1.Text最终块。这意味着文本框内键入的任何文本都将被TextToSpeech1块朗读,例如:

我们现在已经完成了设计我们的块程序。要在 Android 智能手机内构建和运行此应用程序,请单击“构建”下拉菜单,并在两种构建类型之间进行选择,如下图所示:

第一个选项,应用程序(为.apk 提供 QR 码),将生成一个 QR 码,您可以使用 Android 智能手机(使用 QR 扫描器应用程序)扫描。扫描 QR 码后,应用程序的.apk文件将下载到您的 Android 智能手机内。安装.apk文件以测试应用程序的输出。

第二个选项,应用程序(将.apk 保存到我的计算机),将在您的计算机内生成并下载一个.apk文件。您需要将.apk文件从计算机传输到智能手机并安装.apk文件。我个人更喜欢第一个选项,因为.apk文件直接下载到智能手机内。

您还可以从 Android 应用商店下载 MIT AI2 Companion 应用程序,并将其安装在您的 Android 智能手机上,以实时测试应用程序。因此,在应用商店中搜索MIT AI2 Companion,然后点击安装按钮安装该应用程序。应用程序页面如下截图所示:

在您的 Android 智能手机内安装 MIT AI2 Companion 应用程序后,点击扫描 QR 码按钮或输入 MIT AI2 Companion 应用程序内的六位字母代码(在 QR 码旁边),然后点击用代码连接按钮。要生成 QR 码或六位数,请点击连接,然后选择AI Companion,如下所示:

导入和导出应用程序的.aia 文件

您可以通过生成其.aia文件导出您的 Android 应用程序。要创建.aia文件,请执行以下任一步骤:

  • 点击项目,然后选择将选定的项目导出(.aia)到我的计算机

  • 同样,您可以点击项目,然后选择从我的计算机导入项目(.aia)

您可以从 GitHub 存储库的Chapter09文件夹中下载 Talking Pi 和语音控制机器人应用程序文件的.aia文件。

创建一个语音控制机器人应用

语音控制机器人应用程序是本章的主要焦点。创建语音控制机器人涉及以下三个主要部分:

  • 语音识别应用程序:语音识别应用程序将识别我们的声音,并在识别特定单词时发送数据为文本。例如,如果我们说向前,应用程序将向机器人发送F

  • 蓝牙连接:这涉及在智能手机的蓝牙和 RPi 的蓝牙之间建立工作连接。

  • RPi 机器人程序:在这一部分,我们将解码从智能手机传输的文本信息,并相应地移动机器人。例如,如果传入的文本是F,那么我们将编写一个程序来使机器人向前移动。

在这一部分,我们将创建一个语音识别应用程序。在后面的部分,我们将研究建立蓝牙连接和编程我们的机器人。您可以从 GitHub 存储库的Chapter09文件夹中下载VoiceControlBot.aia文件。

要创建VoiceControlBot应用程序,请点击项目,然后选择开始新项目:

将其命名为VoiceControlBot,然后按下确定按钮:

现在让我们继续进行设计部分。

设计应用程序

设计语音控制机器人应用程序非常容易。最终应用程序将如下所示:

以下组件将用于设计应用程序:

  • ListPicker:ListPicker 将显示连接到我们智能手机的蓝牙设备列表。

  • 语音识别器:语音识别器组件将听取我们说的话。

  • 语音识别按钮:单击语音识别按钮时,将调用语音识别器组件,该组件将听取我们说的话。

  • 断开按钮:断开按钮用于将智能手机与 RPi 断开连接。

  • 标签:标签组件将显示用户说的文本。

  • 蓝牙客户端:蓝牙客户端组件激活了我们智能手机的蓝牙连接。

  • 水平或垂直排列:我们有一个水平排列组件,将语音识别按钮正确放置在屏幕中央。

接下来让我们看看如何添加和自定义组件。

添加和自定义组件

为了设计VoiceControlBot应用程序,将ListPicker(不是 ListView)组件拖到屏幕中。接下来,拖一个水平排列,在里面拖一个按钮。在水平排列下方,拖一个标签,然后再拖一个按钮。如果你已经正确拖动了所有组件,你的屏幕应该如下所示:

在接下来的步骤中,我根据自己的需求定制了应用程序中的每个组件。您可以按照以下步骤自定义组件:

  1. 首先,选择ListPicker1,将背景颜色更改为绿色,将宽度设置为填充父级,并将文本更改为连接,如下所示:

  1. 接下来,选择HorizontalArrangement1,将其高度宽度都更改为填充父级。将AlignHorizontalAlignVertical更改为Center,以便 Button1 位于 HorizontalArrangement1 的中心,如下所示:

  1. 之后,选择Button1,单击重命名按钮,并将 Button1 重命名为SRButtonSR语音识别的缩写:

  1. 接下来,我们将在SRButton的背景中添加一个麦克风图像。您可以从 GitHub 存储库的Chapter09文件夹中下载此图像。要添加背景图像,将按钮的宽度高度更改为200 像素,使按钮成为正方形。接下来,从文本框中删除默认文本,如下所示:

  1. 之后,点击图像选项,然后选择麦克风图像将其设置为 SRButton 的背景图像:

  1. 接下来,选择Label1,将字体大小更改为20文本更改为WORD SPOKEN,如下所示:

  1. 之后,为了将 Label1 水平居中在屏幕上,选择 Screen1 并将AlignHorizontal设置为Center

  2. 最后,选择Button2并将其重命名为DeleteButton。将其背景颜色更改为红色宽度更改为填充父级文本更改为删除文本颜色更改为白色,如下所示:

  1. 设计应用程序的用户界面后,我们需要将蓝牙客户端和语音识别器组件拖到我们的屏幕上。蓝牙客户端在连接选项中,语音识别器组件在媒体选项中:

  1. 一旦您添加了所有必要的组件,您的屏幕应该如下所示:

现在让我们继续编程语音控制机器人块。

编程语音控制机器人块

设计应用程序后,是时候编程语音控制机器人应用程序了:

  1. 首先,我们将使用ListPicker1.BeforePicking块从ListPicker1中,并在列表中显示连接到我们智能手机的蓝牙设备。将此块连接到ListPicker1.Elements块。接下来,从BluetoothClient1组件中,将BluetoothClient1.AddressAndNames块连接到ListPicker1.Elements块,如下截图所示:

ListPicker1.Elements代表列表中的元素(列表项),这些元素是配对到我们智能手机蓝牙的设备的地址和名称。如果我们从列表中选择一个元素,ListPicker1.AfterPicking块就会起作用。

  1. ListPicker1.AfterPicking块用于连接到从列表中选择的AddressesAndNames蓝牙,如下截图所示:

  1. 一旦两台设备使用蓝牙连接,就从SRbutton中选择SRbutton.Click块。然后,从SpeechRecognizer1块中选择SpeechRecognizer1.GetText,并将其连接到SRbutton.Click块,如下所示:

SpeechRecognizer1.GetText块将激活 Google 语音识别器,并尝试识别您说的话。它将把您说的话转换成文本。

  1. 接下来,使用SpeechRecognizer1.AfterGettingText块,我们将在标签内显示口头文本,例如:

  1. 之后,使用if...then块,我们将确定口头词是前进、后退、左转、右转还是停止。如果检测到任何这些词,我们将使用 BluetoothClient 组件向我们的 RPi 机器人发送一个字母字符。if...then块位于Control选项内,如下截图所示:

  1. 选择if...then块,并将其放置在SpeechRecognizer1.AfterGettingText块内的Label1.Text块下方,如下所示:

  1. 接下来,为了理解口头词,我们将使用比较运算符,该运算符位于Logic选项内,如下截图所示:

  1. 将比较块连接到if...then块的if插座中,如下所示:

  1. 在比较运算符的左插座中,连接SpeechRecognizer1.Result块:

  1. 在比较运算符的右插座中,连接一个空的文本字符串框。文本字符串框位于 Text 选项内:

  1. 将文本字符串框连接到比较运算符后,如下输入文本forward

  1. 这意味着如果SpeechRecognizer1.Result等于forward,那么在 then 插座内,我们将添加一个BluetoothClient1.SendText块。之后,我们将连接一个文本框到BluetoothClient1.SendText块,并输入字母F,如下所示:

这意味着当检测到单词 forward 时,将从智能手机的蓝牙发送字符F到 RPi 的蓝牙。

右键单击if...then块并复制它,创建四个额外的块,用于单词backleftrightstop。当检测到单词back时,应发送字母B;当检测到单词left时,应发送L;当检测到单词right时,应发送R;最后,当检测到单词stop时,应发送S。如下截图所示:

  1. 之后,将BluetoothClient1.Disconnect块连接到Disconnectbutton,这样当按下此按钮时,蓝牙连接将断开:

我们现在已经完成了设计和编程我们的VoiceControlBot应用程序。您可以在 Android 智能手机上下载并安装此应用程序。在下一节中,我们将把我们的智能手机的蓝牙与 RPi 的蓝牙配对。启动您的 RPi,让我们开始配对过程。

通过蓝牙配对 Android 智能手机和 RPi

在本节中,我们将使用终端窗口将 Android 的蓝牙与 RPi 的蓝牙配对。在开始配对过程之前,我们需要在 RPi 内安装蓝牙软件包并对某些文件进行修改。为此,请按以下步骤操作:

  1. 首先,要安装蓝牙软件包,请在终端窗口中输入以下命令:
sudo apt-get install libbluetooth-dev 

可以在以下截图中看到上述命令的输出:

  1. 接下来,我们将打开bluetooth.service文件并进行一些小的修改。要打开文件,请输入以下命令:
sudo nano /lib/systemd/system/bluetooth.service
  1. 接下来,在/bluetoothd 后键入-C。这将打开 RPi 蓝牙的兼容模式如下:

  1. 之后,按下Ctrl + O,然后按Enter保存文件。接下来,按Ctrl + X退出文件。使用以下命令重新启动您的 RPi:
sudo reboot
  1. RPi 重新启动后,输入以下命令检查其状态:
sudo service bluetooth status

现在,您应该在bluetoothhd旁看到-C。如果在输入上述命令后,您看到lines 1-19/19(END)并且无法在终端窗口中输入任何新命令,请关闭终端窗口,然后再次打开:

  1. 接下来,要将 RPi 的蓝牙与智能手机的蓝牙配对,我们首先需要使 RPi 的蓝牙可发现。要配对设备,请输入以下命令:
sudo bluetoothctl
  1. 现在,您应该看到您的蓝牙的媒体访问控制MAC)地址以及其名称。MAC 地址是一个 12 位地址,对于每个蓝牙设备都是唯一的。您的 RPi 的蓝牙将具有默认名称raspberrypi,如下截图所示:

  1. 在输入下一组代码之前,打开您的 Android 设备的蓝牙并点击配对新设备选项如下:

  1. 之后,逐一输入以下五个命令,将 RPi 的蓝牙置于可发现模式:
power on     //turns on the Bluetooth

pairable on  //Bluetooth is ready to pair with other Bluetooth 
 discoverable on  //Bluetooth is now in discoverable mode

agent on      //Bluetooth agent is the one which 
 //manages Bluetooth pairing 
 //code. It can respond to incoming pairing 
              //code and it can also 
 //send out pairing code default-agent
  1. 打开可发现模式后,您应该在“可用设备”选项中看到名称为 raspberrypi。选择此选项:

  1. 选择raspberrypi选项后,您将看到一个弹出框,询问您是否要与 Raspberry Pi 的蓝牙配对。点击 PAIR 按钮:

  1. 接下来,在终端窗口中,您将看到一条消息,询问您是否要与智能手机的蓝牙配对。键入yes(小写字母)并按Enter

  1. 接下来,您将在终端窗口中看到一个小弹出框,询问您是否要接受配对请求。点击OK

  1. 然后,您可能会看到另一个弹出框,指出连接失败。忽略此错误并点击 OK:

  1. 之后,键入exit。您现在可以通过输入sudo bluetoothctl来检查设备是否已与 RPi 配对:

因此,我们已经完成了将 RPi 的蓝牙与 Android 智能手机的蓝牙配对。接下来,我们将启用 RPi 蓝牙的串行端口。

启用蓝牙串行端口

配对设备后,我们需要创建一个启用蓝牙串行端口的脚本。我们将命名此脚本为bt_serialbt代表蓝牙)。按照以下说明创建此脚本:

  1. 输入以下命令:
sudo nano bt_serial
  1. 在这个脚本中,输入以下行:
hciconfig hci0 piscan
sdptool add SP

  1. 接下来,保存(Ctrl + O)并退出(Ctrl + X)此脚本。

  2. 然后需要执行并运行此脚本。输入以下命令:sudo chmod +x bt_serial(这是执行命令)和sudo ./bt_serial(这是运行命令):

运行脚本后,您将看到消息Serial Port service registered

为 RPi 开发蓝牙程序

设计完 Android 应用程序,配对设备并启用串行端口后,现在是时候对 RPi 进行编程,以便它可以从 Android 智能手机接收文本数据。为了从智能手机接收传入的文本数据,我们将使用套接字编程中的套接字。

套接字编程

套接字是网络中双向通信系统的端点。我们创建套接字以便通过它们发送信息位。为了在设备之间建立蓝牙通信,我们需要创建一个套接字。一个套接字将位于服务器端,另一个将位于客户端。在我们的情况下,Android 智能手机是客户端,RPi 是服务器。

客户端套接字尝试与服务器套接字建立连接,而服务器套接字尝试监听来自客户端套接字的传入连接请求。在蓝牙编程中,我们可以在 RFCOMM 和 L2CAP 之间进行选择,如下图所示:

在套接字程序中,以下连接步骤必须依次从客户端和服务器端进行。每个步骤代表一个函数,该函数在客户端、服务器或两个脚本中声明。按照以下步骤进行:

  1. 套接字创建(客户端/服务器):在客户端和服务器程序中创建套接字如下:
socket(int domain, int type, int protocol)

在这个函数中,第一个参数是指通信域。在我们的情况下,通信域是蓝牙(AF_BLUETOOTH)。第二个参数是指通信类型(SOCK_STREAM)。第三个参数是指通信协议。在蓝牙编程中,我们可以选择无线电频率通信RFCOMM)协议或逻辑链路控制和适配协议L2CAP)。

  1. 连接(客户端):此函数尝试与服务器套接字建立连接:
connect(int sock, const struct sockaddr *server_address, socklen_t info)

在这个函数中,第一个参数是指套接字,第二个参数是指服务器的地址,第三个参数用于查找设备地址的大小。在我们的情况下,设备地址是树莓派的地址。

  1. 绑定(服务器)bind函数绑定服务器设备的地址和端口号。RPi 的 MAC 地址将存储在绑定函数内,如下所示:
bind(int sock, const struct sockaddr *address, socklen_t info);

在这个函数中,第一个参数是指套接字,第二个参数是指服务器设备(树莓派)的地址,第三个参数用于查找设备地址的大小。

  1. 监听(服务器):使用listen函数,服务器套接字等待客户端接近以建立连接:
listen(int sock, int backlog);

第一个参数是指套接字。等待队列通常设置为1

  1. 接受(服务器)accept函数等待传入的连接请求并创建一个新的套接字:
int new_socket = accept(int sock, struct sock_address *clientaddress, socklen_t info);

在这个函数中,第二个参数是指客户端的地址,即 Android 智能手机。

  1. 发送(客户端/服务器)send函数用于从客户端向服务器发送数据,反之亦然。

  2. 读取(客户端/服务器)read函数用于从客户端向服务器读取数据,反之亦然。

  3. 关闭(客户端/服务器)close函数关闭套接字并释放分配给套接字的内存。

现在,由于我们已经使用 App Inventor 创建了VoiceControlBot安卓应用程序,因此无需编写客户端程序。剩下的就是为我们的 RPi 机器人编写服务器程序。

VoiceBot 服务器程序

在这个VoiceBot服务器程序中,我们将首先使用套接字程序在设备之间建立套接字连接。接下来,我们将接收来自安卓智能手机发送的传入数据。最后,我们将根据发送的数据移动机器人。VoiceBot.c程序可以从 GitHub 存储库的Chapter09文件夹中下载。请按照以下步骤进行:

  1. 首先,声明所有必要的头文件如下:
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>               //Socket header file
#include <bluetooth/bluetooth.h>      //Bluetooth header file
#include <bluetooth/rfcomm.h>         //Radio frequency communication header file
#include <wiringPi.h>
  1. 接下来,在main()函数内,声明 wiringPi 引脚编号 0、2、3 和 4 为输出引脚如下:
 pinMode(0,OUTPUT);
 pinMode(2,OUTPUT);
 pinMode(3,OUTPUT);
 pinMode(4,OUTPUT);

为了与其他设备建立 RFCOMM,声明sockaddr_rc以及server_addressclient_addressdata变量将存储和显示传入的数据。sclientsocket分别用于存储服务器和客户端套接字的值。bytes变量将读取传入的字节信息。socklen_t opt包含client_address的大小。这在以下代码行中显示:

struct sockaddr_rc server_address = { 0 }, client_address = { 0 };
char data[1024] = { 0 };
int s, clientsocket, bytes;
socklen_t opt = sizeof(client_address);
  1. 接下来,创建s,通信域设置为AF_BLUETOOTH,通信类型设置为SOCK_STREAM,通信协议设置为BTPROTO_RFCOMM
s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
  1. 然后,使用bind函数将 RPi 的蓝牙 MAC 地址绑定到服务器套接字,并使s(服务器套接字)进入监听模式如下:
bind(s, (struct sockaddr *)&server_address, sizeof(server_address));
listen(s, 1);
  1. 一旦连接被接受,将使用accept函数创建一个新的客户端套接字如下:
clientsocket = accept(s, (struct sockaddr *)&client_address, &opt);
  1. 然后,使用ba2str函数将传入的字节数据转换为字符串。之后,显示连接的蓝牙的 MAC 地址如下:
 ba2str( &client_address.rc_bdaddr, data );
 fprintf(stderr, "Connected to %s\n", data);
  1. 之后,在for循环内,使用read函数读取传入的数据。如果字节变量中的值大于0,我们将按以下方式打印数据:
 for(;;){
 bytes = read(clientsocket, data, sizeof(data));
 if( bytes > 0 ) {
 printf("Alphabet: %s\n", data);

现在,我们将使用五个if条件来检查传入的数据是否包含字母FBLRS,如下所示:

 if(*data=='F')
 {
----Forward Code----
 }
 else if(*data=='B')
 {
----Backward Code----
 }
 else if(*data=='L')
 {
----Axial Left Turn Code----
 }
 else if(*data=='R')
 {
----Axial Right Turn Code----
 }
 else if(*data=='S')
 {
----Stop Code----
 }

上述代码可以解释如下:

  • *data == 'F':机器人将向前移动

  • *data == 'S':机器人将向后移动

  • *data == 'L':机器人将进行轴向左转

  • *data == 'R':机器人将进行轴向右转

  • *data == 'S':机器人将停止

  1. 最后,为了断开设备的连接,使用close函数关闭clientsockets如下:
 close(clientsocket);
 close(s);
  1. 接下来,由于此代码包含套接字和蓝牙头文件,因此在编译此代码时,您需要在构建命令中添加-lbluetooth命令。由于这是一个 C 程序而不是 C++程序,因此您还需要添加-lwiringPi命令来编译 wiringPi 代码,如下所示:

接下来让我们测试代码并检查最终输出。

测试代码

现在,在编译和构建程序之前,请确保您已经配对了安卓智能手机的蓝牙和 RPi 的蓝牙。如果它们没有配对,那么当您运行VoiceControlBot应用程序时,RPi 蓝牙名称(raspberrypi)将不会出现在蓝牙列表中。配对设备的步骤在通过蓝牙配对安卓智能手机和 RPi部分中列出。

完成此操作后,您需要在终端窗口内执行和运行我们之前创建的bt_serial脚本。执行和运行此脚本的命令如下:

sudo chmod +x bt_serial            //Execution code
sudo ./bt_serial                   //Run Code

你不需要每次运行程序都执行这个脚本,但是当你启动一个新的 RPi 会话并想要测试VoiceBot.c程序时,你需要执行和运行这个脚本。接下来,编译和构建VoiceBot.c程序。之后,打开VoiceControlBot Android 应用程序并按下CONNECT列表选择器。你将在蓝牙列表中看到树莓派的名称以及其 MAC 地址。选择树莓派选项以连接设备,如下所示:

一旦它们连接,你将在终端窗口内收到一个通知,显示Connected to:和 Android 蓝牙 MAC 地址,如下面的屏幕截图所示:

如果你收到以下错误 507:无法连接。设备是否打开?错误,请不要担心。点击连接按钮并再次选择树莓派蓝牙:

一旦设备连接,你可以点击语音识别器按钮并开始说话。如果你说单词forward,这应该显示在屏幕上,如下面的屏幕截图所示,并且字母F将被发送到 RPi:

同样,当你说单词backleftrightstop时,字母BLRS,如下面的屏幕截图所示,将被发送到 RPi 的蓝牙,机器人将朝适当的方向移动:

如果你说任何其他单词,语音识别器应该识别这个单词并在屏幕上显示出来,但不会发送任何文本数据到 RPi。

摘要

我们通过创建我们的第一个 Android 应用程序 Talking Pi 开始了这一章,其中文本框内的文本被显示在标签上,并由智能手机朗读出来。然后,我们开发了一个声控机器人 Android 应用程序,它可以识别我们的声音并通过蓝牙将文本发送到 RPi。之后,使用终端窗口,我们将 Android 智能手机的蓝牙与 RPi 的蓝牙配对。最后,我们研究了套接字编程,并编写了VoiceBot程序,以与 Android 智能手机的蓝牙建立连接来控制机器人。

问题

  1. 我们使用哪种通信协议通过蓝牙连接发送数据?

  2. 蓝牙设备有什么样的地址?

  3. VoiceControlBot应用程序中 ListPicker 的用途是什么?

  4. 在客户端,用什么函数将客户端套接字连接到服务器套接字?

  5. 你的 RPi 的默认蓝牙名称是什么?

第十章:评估

第一章:树莓派简介

  1. Broadcom BCM2837 四核 1.4 GHz 处理器

  2. 40

  3. VNC 查看器

  4. 用户名:pi,密码:raspberry

  5. sudo raspi-config

第二章:使用 wiringPi 实现闪烁

  1. 八个(引脚号 691420253034, 和 39

  2. digitalRead(pinnumber);

  3. for (int i=0; i<6;i++)

  4. 1V

第三章:编程机器人

  1. L298N 电机驱动 IC

  2. H 桥

  3. digitalWrite(0,HIGH);

digitalWrite(2,LOW);

digitalWrite(3,HIGH);

digitalWrite(4,LOW);

  1. 逆时针方向

  2. digitalWrite(0,HIGH);

digitalWrite(2,HIGH);

digitalWrite(3,HIGH);

digitalWrite(4,LOW);

  1. digitalWrite(0,HIGH);

digitalWrite(2,LOW);

digitalWrite(3,LOW);

digitalWrite(4,HIGH);

第四章:构建避障机器人

  1. 超声波脉冲以 340 m/s 的速度传播

  2. 液晶显示

  3. 180 厘米

  4. 第 4 列和第 1 行

  5. 它们用于调节 LCD 的背光

第五章:使用笔记本电脑控制机器人

  1. initscr() 和 endwin()

  2. initscr() 函数初始化屏幕。它设置内存并清除命令窗口屏幕

  3. gcc -o Programname-lncurses Programname.cpp

  4. GCC

  5. pressed() 按下按钮时移动机器人,released() 松开按钮时停止移动

第六章:使用 OpenCV 构建一个目标跟随机器人

  1. 开源计算机视觉

  2. 3,280 x 2,464 像素

  3. raspistill

  4. raspivid

  5. 8GB - 50% and  32 GB - 15%

第七章:使用 OpenCV 访问 RPi 相机

  1. 阈值处理

  2. flip(original_image, new_image, 0)

  3. 右下角的区块

  4. resize(original_image , resized_image , cvSize(640,480));

  5. 在屏幕的左上部分

第八章:使用 Haar 分类器进行人脸检测和跟踪

  1. haarcascade_frontalface_alt2.xml

  2. 水平线特征。

  3. haarcascade_lefteye_2splits.xml

  4. 感兴趣的区域。

  5. equalizeHist 函数改善图像的亮度和对比度。这很重要,因为在光线不足的情况下,相机可能无法从图像中区分出脸部。

第九章:构建语音控制机器人

  1. 无线电频率通信 (RFCOMM)

  2. 媒体访问控制 (MAC) 地址

  3. ListPicker 显示了所有已经与您的智能手机蓝牙配对的蓝牙设备列表

  4. 连接

  5. raspberrypi

posted @ 2024-05-15 15:26  绝不原创的飞龙  阅读(11)  评论(0编辑  收藏  举报