具体代码及实现方法请见上期内容
车牌识别
树莓派自动开关机执行延时录像
esp32侧程序,功能有3个,1、是连接Wi-Fi同步网络时间,2、是通过UART口跟树莓派进行通信,检查树莓派是否在预定时间内工作或关机,3、根据时间段控制树莓派开机或关机。该程序在esp32上的文件名为main.py(记得要改名),这样esp32一通电即可工作。程序如下:
上位机(树莓派)与esp32的通信程序check.py,程序如下:
import serial
Port = "/dev/ttyAMA0"
ser = serial.Serial(Port, baudRate, timeout=10)
while True:
send = '2'
ser.write(send.encode())
str = ser.readline().decode()
if(str !=""):
print(str)
ser.close()
该程序需要开机自动运行,具体可以将其加入rc.local,具体:
sudo nano /etc/rc.local
在最后一行前加入:python check.py,保存退出重启即可
注意:
树莓派使用串口需要进行以下工作:
1、sudo raspi-config
根据以下步骤进行设置:
选择Interfacing Options
选择serial
再选择 no,禁用串口登录功能,将串口用于通信。
再选择 yes,启动串口硬件。
禁用蓝牙(硬件串口与mini串口默认映射对换
接线:(树莓派(左)——esp32(右))
RXD(GPIO15) <——> TXD(IO12)、TXD(GPIO14) <——> RXD(IO13)、GND <——> GND

sudo nano /boot/config.txt
在打开的文件最后面添加:
(注意:树莓派4b也一样是pi3)
dtoverlay=pi3-disable-bt
修改保存后重启树莓派:
reboot
安装serial,具体如下:
sudo apt-get install python3-serial
烧录esp8266注意事项
很多人第一次用USB转串口模块烧录esp8266失败,这里有几个注意事项:
USB转串口模块与esp8266的接线方式如下:
1、RX->TX,2、TX->RX,3、3V3->3V3,4、GND->GND,以上接线需要确认好。
还有就是要特别注意的一点:烧录时IO0需要接地,烧录成功后需要断开IO0与GND的连接。另外固件选择要对,我的模块很便宜,选择是1M flash的,具体可以到micropython.org上下载对应固件,thonny这个软件就可以直接烧录,而不需要下载esptool工具。
密码保护:树莓派通过Esp32定时开、关机
密码保护:autodial
树莓派通过usb摄像头定时拍照
树莓派使用usb摄像头需要安装fswebcam,具体如下:
1、sudo apt-get install fswebcam
2、sudo nano capture.sh
#!/bin/bash
#DATE=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)
DATE=$(date +%Y-%m-%H-%M)
fswebcam -d /dev/video0 --no-banner -r 1024X768 -S 60 -D 2 -F 2 /home/pi/$DATE.jpg
3、chmod +x capture.sh
4、crontab -e
* * * * * /home/pi/capture.sh >/dev/null 2>&1
5、记得重启crontab
#重启crontab程序
sudo /etc/init.d/cron restart
fswebcam参数详解
-? –help 显示此帮助页面并退出
-c –config <文件名> 从文件加载配置
-q –quiet 隐藏所有消息(错误除外)
-v –verbose 捕获时显示额外的消息
–version 显示版本并退出
-l –loop <秒> 循环运行
-b –background 在后台运行
-o –output <文件名> 将日志输出到文件
-d –device <摄像头> 设置要使用的摄像头
-i –input 选择要使用的输入
-t –tuner 选择要使用的调谐器
-f –frequency 选择频率使用
-p –palette 选择要使用的调色板格式
-D –delay 设置预捕获延迟时间(秒)
-r –resolution <宽x高> 设置拍摄分辨率
–fps <帧率> 设置捕获帧率
-F –frames 设置要拍摄的帧数
-S –skip 设置要跳过的帧数
–dumpframe 将原始帧转储到文件
-s –set = 设定参数值
–revert 恢复原始捕获的图像
–flip 翻转图像
–crop [,] 裁剪图像的一部分
–scale 缩放图像
–rotate 垂直旋转图像
–deinterlace 减少隔行失真
–invert 反转图像颜色
–greyscale 去除图像的颜色
–swapchannels 交换c1和c2的通道
–no-banner 隐藏横幅
–top-banner 将横幅放在顶部
–bottom-banner 将横幅放在底部(默认)
–banner-colour 设置横幅颜色(#AARRGGBB)
–line-colour 设置横幅线条颜色
–text-colour 设置文字颜色
–font <[name][:size]> 设置字体和和大小
–no-shadow 禁用文字阴影
–shadow 启用文字阴影
–title 设置主标题(左上方)
–no-title 清除主标题
–subtitle 设置字幕 (左下方)
–no-subtitle 清除字幕
–timestamp 设置时间戳格式(右上)
–no-timestamp 清除时间戳记
–gmt 使用GMT代替本地时区
–info 设置信息文本(右下)
–no-info 清除信息文本
–underlay 设置参考图像
–no-underlay 清除底衬
–overlay 设置覆盖图像
–no-overlay 清除覆盖
–jpeg 输出JPEG图像
–png 输出PNG图像(-1, 0 – 10)
–save <文件名> 将图像保存到文件
–exec <命令> 执行命令并等待其完成
pico+tensorflow
Raspberry Pi Pico,上市不足3个月,售价仅有4美元,擅长低时延的 I/O 通信和模拟信号输入,功耗低,可以弥补树莓派在与物理世界互动方面的不足。今天我们项目分享就用Pi Pico+Edge Impulse实现入侵者监测系统。
该项目将热像仪数据给Raspberry Pi Pico,再使用Tensorflow Lite模型分析,达到在黑暗中检测入侵者的目的。这个项目有两大优点:
1)虽然很多家庭会安装摄像头,但有一些是无法在黑暗中工作的。项目中使用的热像仪监测就没有这个问题了。
2)如果一个动物入侵,我们不希望电子设备误报,摄像头肯定是做不到的。这个项目借助Edge Impulse可以准确无误判断当前入侵的是不是人。
注:Tensorflow Lite是针对移动、嵌入式以及IoT设备的工具,能够在终端本地运行机器学习模型的能力,而不必将数据上传到云端服务器进行分析。这样不仅节省网络流量,并且可以用户自己的隐私。
Edge Impulse在线网站自行训练进行分类识别。使用EdgeImpulse在线训练主要分为以下四个步骤:数据集采集、上传、训练以及部署。
01 项目使用到的软硬件
硬件部分:
- 树莓派PICO开发板
- MLX90640热成像夜视摄像头
- Seeed Wio 终端
- LED灯
开发软件:
- Raspberry Pi Pico C / C ++ SDK

02 训练数据集
在机器学习项目中,第一步也是最重要的一步是收集训练数据,使训练集能够涵盖给定任务的大多数代表性的案例。
这里使用Seeed Wio终端进行数据采集。Wio终端上的3个按钮用于标记3个类(人,目标和背景)。把收集到的数据保存到Wio终端内置的micro SD卡中。每个热图像数据被捕获为一个单独的文件。该文件不包含标题行,只包含以逗号分隔的768(24×32)个温度读数。示例文件内容如下所示:

采集到的数据的直观表现如下:


03 将数据上传至Edge Impulse
当前,Edge Impulse不支持非时间序列数据类型(图像除外)。为了使用Edge Impulse,数据被假定为时间序列的实例。下面的代码是将原始数据转换为Edge Impluse的数据采集JSON格式。(全部代码可以在“达尔闻说”回复:入侵检测。)
- import json
- import time
- import hmac
- import hashlib
- import os
- HMAC_KEY = “<insert your edge impulse hmac key>”
- labels = {
- ‘1’: ‘Person’,
- ‘2’: ‘Object’,
- ‘3’: ‘Background’
- }
- dir = ‘raw_data’
- for filename in os.listdir(dir):
- if filename.endswith(‘.csv’):
- prefix, ext = os.path.splitext(filename)
- label = labels[prefix[-1]]
- outfilename = os.path.join(‘formatted_data’, ‘{}.{}.json’.format(label, prefix))
- with open(os.path.join(dir, filename)) as fp:
- values = [[float(i)] for i in fp.read().split(‘,’)]
- emptySignature = ”.join([‘0’] * 64)
- data = { “
- protected”: {
- “ver”: “v1”,
- “alg”: “HS256”,
- “iat”: time.time()
- },
- “signature”: emptySignature,
- “payload”: {
- “device_name”: “A0:C0:D3:00:43:11”,
- “device_type”: “Raspberry_Pi_Pico”,
- “interval_ms”: 1,
- “sensors”: [
- { “name”: “temperature”, “units”: “Cel” },
- ],
- “values”: values
- }
- }
- # encode in JSON
- encoded = json.dumps(data)
- # sign message signature = hmac.new(bytes(HMAC_KEY, ‘utf-8’), msg = encoded.encode(‘utf-8’), digestmod = hashlib.sha256).hexdigest() #
- set the signature again in the message, and encode again data[‘signature’] = signature encoded = json.dumps(data, indent=4)
- with open(outfilename, ‘w’) as fout:
- fout.write(encoded)
数据使用Edge Impulse CLI上传,需要先在Edge Impulse注册一个账户,并创建一个新项目来上传数据。下面的命令用于上传所有JSON文件,这些文件会自动分成训练和测试数据集。$ edge-impulse-uploader –category split *.json
上传的数据可以在Edge Impulse Studio查看。

训练数据是以1 ms为间隔的时间序列,但通过设置窗口大小768 ms(等于32×24=768热读数),它被用作单个数据实例。由于这不是一个时间序列数据,我们将使用原始数据块(无需预处理),直接反馈到神经网络块。下面是训练输出。因为没有很多的实例样本数据作为支撑,所以准确率只能达到80%,但是之后通过大量的实例训练数据集可以进一步提高。

04 硬件设置
Pi Pico通过I2C协议与MLX90640热像仪连接,通过SPI协议连接TFT显示器。TFT显示屏用于演示。红色LED灯连接到树莓派Pico的GPIO引脚3上。
Raspberry Pi Pico —– MLX90640热感相机
GP8 —————— SDA
GP9 —————— SCL
3V3(OUT) —————– 5V/3V3
GND. —————— GND
Raspberry Pi Pico —— TFT Display
GP14 —————— MISO
GP13 —————— CS
GP6 —————— SCK
GP7 —————– MOSI
3V3(OUT) —————– 5V
GND. —————— GND
GP15 —————– DC
GP14 —————– RST
05 Edge Impluse移植
只要根据其他平台可用的移植代码,创建一个移植代码文件,就可以在Edge Impulse SDK中加入对Raspberry Pico硬件的支持。以下是代码片段。(在写这篇文章的时候,Edge Impulse还没有正式支持Raspberry Pi Pico,但是相信在不久的将来会支持它的。)#include “ei_classifier_porting.h”
#include “pico/stdlib.h”
#define EI_WEAK_FN __attribute__((weak))
EI_WEAK_FN EI_IMPULSE_ERROR ei_run_impulse_check_canceled() {
return EI_IMPULSE_OK;
}
EI_WEAK_FN EI_IMPULSE_ERROR ei_sleep(int32_t time_ms) {
sleep_ms(time_ms);
return EI_IMPULSE_OK;
}
uint64_t ei_read_timer_ms() {
return to_ms_since_boot(get_absolute_time());}
uint64_t ei_read_timer_us() {
return to_us_since_boot(get_absolute_time());}
06 在Pi Pico上进行模型分类
复制下面代码:$ git clone https://github.com/metanav/pico_person_detection_thermal.git$ cd pico_person_detection_thermal$ mkdir build$ cd build$ cmake ..$ make -j4
可以通过下面的步骤将生成的pico_person_detection_thermal.uf2二进制文件烧录到Raspberry Pi Pico上。1. 按住BOOTSEL按钮,将Raspberry Pi Pico插入电脑的USB接口,就会显示一个名为RPI-RP2的大容量存储设备。2. 将pico_person_detection_thermal.uf2二进制文件拖放到RPI-RP2上。烧录二进制文件后,Raspberry Pi Pico将重新启动,开始执行检测。
最终如上面演示视频所示,就可以看到,当有一个人出现时,摄像头检测到数据,随后树莓派Pico分析出是人,LED灯亮起。
用Edge Impulse实现MCU机器学习
将AI应用带到边缘终端设备(智能手机、可穿戴、汽车和物联网设备等)可以降低功耗,并减少数据中心资源的负载,其中在MCU上实现机器学习是这种愿景中最为活跃的领域之一,tinyML是一个致力于该领域的新的研究方向。本文讲述了使用Edge Impulse训练验证模型、模型转换、生成代码并部署的过程。Edge Impulse是一个在边缘设备上进行机器学习的在线开发平台,正在获得嵌入式系统产业界的广泛支持。
业界对机器学习 的兴趣近来大增,已经有许多云应用帮助我们进行机器学习的开发。但是,如果应用缺乏互联网连接,需要处理敏感数据,或者要求低延迟,在小型微控制器上运行机器学习会更为合理。本文通过一个样例项目演示使用Edge Impulse在小型、低功耗、低成本微控制器上进行机器学习的开发。
在应用端运行机器学习(ML)算法被称为边缘计算, 比如在工业园区的一个厂房内。在工业4.0和物联网中通过边缘计算使用ML意味着数据不会离开现场,基于ML的决策也可以被快速采用。模型的训练需要大量的计算,但是我们可以在小型、高性价比的微控制器上运行这些计算。基于非对称的ML的大量研究是tinyML基金会的成立的前奏。在嵌入式设备上运行机器学习模型的主要原因有三个:
•本地处理:避免传输传感器收集的数据。不需要互联网连接的话,设备部署的限制就会更少。
•低能耗:微控制器的能耗非常低。一个电池驱动的微控制器可以持续运行图像辨识算法一年。
•成本和部署:微控制器普适而且低成本,方便ML模型的部署。通过简单的软件升级,它们可以很快部署新的或者升级后的模型,而不需要替换硬件。
tinyML依然是一个比较新的研究方向,但是其应用非常广泛,包括农业、医疗和预测性维护等。
Edge Impluse
创建账户并登录之后,你就可以创建你的第一个tinyML项目了,如图1所示,SDK的界面简洁、直观。

图1 Edge Impulse SDK仪表盘
登录之后,仪表盘(Dashboard)显示项目的概况,以及如何启动你的第一个项目,或者继续一个现有项目的指南。界面左侧,在仪表盘下方是其他的主菜单选项,它们的顺序反映了开发的不同阶段,比如设备(Devices)页显示已连接的设备列表,数据获取(Data acquisition)则显示已经收集好的测试和训练数据。Impulse设计(Impulse design)创建一个Impulse,Impulse是一个接收原始数据、运用信号处理提取特性和通过学习块对新数据进行分类的过程。
连接设备时,需要参照仪表盘画面中间的指示,也可以前往设备栏点击连接新设备(Connect a new device)。开发环境支持下列设备:
•ST IoT Discovery Kit:即B-L745E-IOT01A,开发板上搭载使用Cortex-M4处理器的微控制器、MEMS动作传感器、麦克风和WiFi。
•Arduino Nano 33 BLE Sense:一个小尺寸的开发板,上面搭载基于Cortex-M4的微控制器、动作传感器、麦克风和BLE。
•AI ECM3532 Eta Compute Sensor:一个小尺寸的开发板,上面的TENSAI ECM3532 SoC搭载基于Cortex-M3的微控制器和一个单独的CoolFlux DSP用于机器学习的加速。开发板上有两个麦克风,一个6轴加速度计/陀螺仪和一个气压/温度传感器。ECM3532 SoC支持连续电压频率缩放,这允许系统在运行时通过缩放时钟频率和电压调整到最佳的电源效率,从而支持超低功耗机器学习。
•智能手机:Edge Impulse支持能够运行现代浏览器的任何智能手机,过程和方法和其他设备一样。最终,任何数据/模型都可以部署到嵌入式设备上。
图2 设备页显示已连接的设备
收集数据
首先,需要收集用于训练机器学习模型的音频数据。为了检测到水从水龙头流出的声音,我们需要收集一些流水的声音,以及典型的背景噪声(没有流水)的声音样本,这样模型就能够学习两者间的区别。这两类样本代表着我们模型中的两个类型:背景噪声和水龙头开启。在SDK的数据获取(Data acquisition)页可以收集设备传感器数据,这里是存储原始数据的地方。如果设备已经连接到远程管理API,就可以从数据获取页开始新的数据采样。
现在让我们先收录一段水龙头没有打开时的背景噪声,在收录新数据(Record new data)部分选择数据,设置标签(Label)为噪声,定义样本长度(Sample length)为1000,传感器(Sensor)选择内建麦克风。这意味着我们将收录一秒的声音,并将其标记为噪声,标签之后还可以再修改。点击开始采样(Start sampling),设备会录音一秒,并将数据传输到Edge Impulse。数据载入之后,已收集的数据(Collected data)下会出现新的一行记录,可以在界面上分析信号波形或者重播音频(见图3)。

图3 数据收集页收录和回放音频数据的部分
构建数据集
接下来,开始创建一个数据集。对于一个比较简单的音频分类模型,我们应该收集大约10分钟的数据。两个类型的样本应该基本均衡,所以我们需要收集:五分钟的背景噪声,标记为“噪声”;五分钟水龙头开启时的声音,标记为“水龙头”。
在真实世界中,我们感兴趣的声音往往会和其他声音混杂在一起,比如水龙头的流水声往往会伴随着洗碗的声音或者厨房的交谈声,背景噪声还可能有电视、小孩玩耍或者车辆从附近经过的声音。训练用的数据必须包含这些实际环境的声音,如果模型没有接触到它们,准确度会降低。在本文中将收录下面的样本:
•背景噪声:
o没有其他声音:两分钟
o同时有电视/音乐的声音:一分钟
o同时有偶尔的讲话/谈话:一分钟
o同时有做家务的声音:一分钟
•水龙头开启的声音:
o水龙头开启流水的声音:一分钟
o另一个不同的水龙头开启流水的声音:一分钟
o同时有电视/音乐的声音:一分钟
o同时有偶尔的聊天/对话:一分钟
o同时有做家务的声音:一分钟
即使无法取得全部的样本,也不必担心。我们的目标是每个分类都有五分钟的真实世界数据,在具有代表性的数据集上训练出的模型会更加准确有效。无法保证模型能准确分辨数据集以外的声音,所以我们必须尽可能地贴近真实世界,在数据集中包括多种声音。另一个选项是从Edge Impulse下载现成的十分钟样本 ,解压到本地后,在数据获取页点击上传数据(Upload data)图标(见图4),然后上传下载的文件,并添加标签。
图4 数据获取页,用于上传预先准备好的数据集
一次性可以收录的音频长度取决于特定硬件的存储大小,ST B-L745E-IOT01A板的存储容量允许一次性录音60秒,Arduino Nano 33 BLE Sense则只能录音16秒。录音60秒意味着需要将样本长度设置为60 000。从开发板上传输数据往往很慢,在Edge Impulse中采集60秒的样本大约需要7分钟。取得需要的10分钟数据后,就可以开始设计Impulse了。
设计Impulse
Impulse读取原始数据,将其分割为多个更小的窗口,然后通过信号处理块提取特征,通过学习块分类新的数据。信号处理块的作用是简化原始数据的处理,针对同样的输入同一个信号处理块总会返回一致的数值,因此学习块可从之前的经验中学习。在本文使用MFCC信号处理块,MFCC指梅尔频率倒谱系数[ ],这听上去很复杂,实际上就是从声音数据中移除大量的冗余信息。接下来,我们会将简化后的音频数据送到一个神经网络块(学习块)中,学习块将会学习如何区分两个音频类别(水龙头开启和噪声)。现在,打开创建Impulse(Create impulse)页面,你会看到原始数据(Raw data)部分。
同之前提过的一样,Edge Impulse在训练时将样本分割为小的窗口,然后将数据发送到机器学习模型中。窗口大小(Window size)参数控制每个数据段的长度(单位毫秒),一秒长的音频样本足够确定水龙头是否开启,所以设置为1000毫秒。每一个原始样本被分为多个窗口,窗口间距(Window increase)的大小决定下一个窗口和上一个窗口的间距,1000毫秒的窗口间距意味着每个窗口在上一个窗口开始的一秒后开始。
如果窗口间距的数值小于窗口大小,则可创建互相重叠的窗口。虽然窗口间会包括一部分相似的数据,每一个样本依然是独特的。互相重叠的窗口帮助我们高效地利用训练数据,比如1000毫秒的窗口大小和100毫秒的窗口间距意味着可以从两秒的数据中提取十个独特的窗口。本文中设置窗口大小为1000毫秒,窗口间距为300毫秒。点击添加处理块(Add a processing block),选择MFCC块,然后点击添加学习块(Add a learning block),选择Keras神经网络块(Neural Network – Keras),最后点击保存。创建Impulse的页面如图5所示。

图5 创建Impulse
MFCC块配置
目前已经凑齐了Impulse所需要的基本组成模块,现在可以单独配置每个部分了。在左侧的导航菜单中点击MFCC标签进入块配置页,界面上可以预览数据将如何被转换。在右侧可以看到一个频谱图上显示了MFCC处理音频输入后的输出结果。MFCC块将一个音频样本转换为一个数据表格,每一行是一个频率范围,每一列是本时间区段内频率范围内声音的强度。频谱图用颜色填充每个单元格,颜色的深浅表示振幅的高低。图6中对比了噪声和水龙头声音的频谱图。

图6 噪声(上)和水龙头声音(下)的频谱图
我们的双眼很难分辨出两张图的区别,但对于一个神经网络而言,它们间的差别足够用来学习两个类别的不同。在参数(Parameter)框中可以配置MFCC块,Edge Impulse的默认数值适用于多数情形,这里不做修改。MFCC块产生的频谱图会被发送到神经网络中,神经网络的特性意味着它特别适合学习此类列表数据背后的模式。
在训练神经网络之前,需要从采集的所有音频窗口中产生MFCC特征。点击页面上方的产生特征(Generate feature),然后点击绿色的同名按钮,分析十分钟的样本需要几分钟的时间,这一过程结束后,特征浏览器(Features explorer)会以可视化的形式显示数据集,你可以检查不同类型间的区别是否明显,并查找错误的数据标签。为了能在三维空间内显示所有的特性,这里用到了降维手段。
神经网络配置
神经网络借鉴人脑的工作方式,能够学习辨识训练数据中的模式。我们训练的神经网络从MFCC中取得数据,然后决定输入数据属于噪声类别还是水龙头类别。点击左侧菜单的神经网络分类器(NN Classifier)进入块配置窗口。一个神经网络由多层虚拟的神经元组成,在页面的左侧可以看到当前的状态。输入数据(来自MFCC的频谱图)首先进入第一层神经元,这些神经元有着各自独特的内部状态,会对输入进行过滤和转换。第一层神经元的输出会被送到第二层神经元,以此类推,神经网络会一点点转变原始输入,最终变成完全不同的输出。在本例中,频谱图经过4个中间层后变成两个数字,分别是输入数据代表噪声和水龙头开启的可能性。
在训练中,为了让神经网络能够正确地转换输入并输出我们想要的结果,每个神经元的内部状态都会逐渐转变。当输入一个样本时,根据网络输出结果和正确的响应(标签)间的距离,神经元的内部状态会被调整,这样下次网络给出正确响应的可能性就会更大。重复这一过程几千次,就会得到经过训练的网络。
对于神经网络而言,神经元层的安排被称为“结构”,不同的结构适用于不同的任务。虽然Edge Impulse中默认的神经网络结构很适合当前的项目,但也可以定义你自己的结构。在开始训练模型前,需要更改配置中的一些数值。首先将训练周期数(Number of training cycles)设为300,这意味着整个数据集在训练中会被处理300次。如果周期数太少,网络无法从训练数据中充分学习,但是如果周期数太多,网络可能会过度切合训练数据,无法正确处理新数据,这一问题被称为“过拟合”。
接下来,将最低置信度(Minimum confidence rating)设为0.7,这意味着当神经网络作出预测时,除非声音是水龙头流水声的概率为0.7以上(比如0.8),Edge Impulse将会忽略这一预测。现在我们可以点击开始训练(Start training),训练将会花上几分钟时间,结束后会在页面下方看到上次训练表现面板(见图7)。

图7 训练后网络的表现
虽然完成了Edge Impulse中神经网络的训练,但是结果中这些数字都意味着什么?在训练开始前,20%的数据被预设为验证数据,它们被用来验证模型的性能,而不是用做训练数据。上次训练表现(Last training performance)面板上显示验证的结果,从而提供关于模型性能的信息,你看到的数字可能会和这里的不同。准确度(Accuracy)指被正确分类的音频样本的百分比,数字越高越好,不过一般不太可能接近100%,极高的准确度往往也意味着过拟合。就许多应用而言,高于80%的准确度已经很好了。面板中央的混淆矩阵 显示正确和错误分类的数目。
分类新的数据
虽然上一步中的性能数据说明模型在训练数据上的表现很好,但在部署前用新数据测试它依然是非常重要的,这能帮助我们确认模型没有过拟合。Edge Impulse内建了一些测试工具,帮助从设备上实时抓取数据并立即进行分类。点击左侧菜单上的实时分类(Live classification)按钮,你的设备应该出现在分类新数据(Classify new data)面板上,点击开始采样(Start sampling)将会收集5秒的背景噪声数据。
测试模型
通过实时分类(Live classification)标签页可以快速测试你的模型,了解模型的行为。不过,如果想要确保你的模型的正确性,需要更加严格地测试你的模型。模型测试(Model testing)标签页正是为此而存在的。理想状况下,你的测试数据集大小应该至少是训练数据集的25%,假设训练数据是10分钟,测试数据至少需要2分30秒。此外,还需要确保测试数据能够反映大量的真实情形,这样能够以不同的输入测试模型的性能。
比如说,收集若干不同水龙头的声音是一个很好的主意,可以从数据获取(Data acquisition)标签页管理你的测试数据。打开标签页,点击上方的测试数据(Test data),使用上传(Upload)功能导入数据,过程同之前上传训练数据一样。确保数据的标签是正确的,结束后返回模型测试(Model testing)标签页,选择全部样本,然后点击分类选中的数据(Classify selected)。
图8是分类的结果,面板显示模型的准确率是73.42%。针对每个样本,面板上会显示其准确率,比如其中一个样本的分类准确度为67%。有很多误分类的样本非常宝贵,这样的样本中有许多模型目前不拟合的音频类型,通常需要将此类样本加入到训练数据中。

图8 测试数据分类的结果
在硬件设备上部署模型
设计、训练并验证了我们的impulse之后,就可以在硬件设备上部署了,这意味着模型可以在没有互联网的情况下以最小的延迟和最低的功耗运行。Edge Impulse可以将整个impulse包装为一个C++代码库以供编程使用,包括MFCC算法、神经网络和分类代码。点击菜单中的部署(Deployment)开始模型的导出过程,接下来在构建固件(Build firmware)下选择正确的开发选项,然后点击构建(Build),就会针对特定的开发板将impulse输出为二进制可执行文件。构建过程完成后程序会提供二进制文件下载,将文件保存到本地。点击构建(Build)按钮时,程序会弹出窗口、提供关于部署的说明,指导你在构建后进行测试。可以在命令行上运行如下命令以打开连接硬件固件的串行接口:$ edge-impulse-run-impulse
这一命令会打开麦克风收录声音,在数据上运行MFCC代码,并对产生的频谱图进行分类。
结 语
在微控制器上运行机器学习是一个比较新的领域,对于不熟悉人工智能的开发者而言很难上手。Edge Impulse简化了数据的收集和分析、神经网络的训练和构建,以及在微控制器上部署的过程。这类模型有着诸多应用,从监控工业机械到辨识语音命令都可以适用。Edge Impulse易于使用,界面直观,而且免费,这意味着可以立即开始探索在嵌入式设备上运行tinyML。(本文首先在Elettronica Open Source 上以意大利文发表。)
相关参考链接:
https://www.expert.ai/blog/machine-learning-definition/.
https://www.dataschool.io/simple-guide-to-confusion-matrix-terminology/.
树莓派GPIO使用指南
RPi.GPIO是 Python的一个module( 模块 ), 树莓派官方系统默认已经安装, 仍在不断更新中, 截至20180521, 最新版0.6.3, 适配了树莓派3B+, 可以访问 python主页下载源码 . 本文根据树莓派RPI.GPIO模块的官方文档翻译,当时的模块版本为0.6.3。官方的帮助文档的链接: https://sourceforge.net/p/raspberry-gpio-python/wiki/BasicUsage/。
1、导入模块
要导入RPi.GPIO模块,请执行以下操作:
import RPi.GPIO as GPIO
通过这样做,您可以通过脚本的其余部分将其称为GPIO。
导入模块并检查它是否成功:
try:
import RPi.GPIO as GPIO
except RuntimeError :
print(
"Error importing RPi.GPIO! This is probably because you need superuser privileges. You can achieve this by using 'sudo' to run your script"
)
2、引脚编号
在RPi.GPIO中,有两种方法可以对Raspberry Pi上的IO引脚进行编号。第一种是使用BOARD编号系统。这是指Raspberry Pi板上P1接头上的引脚号。使用这种编号系统的优点是,无论树莓派的电路板版本如何,您的硬件都能正常工作。你不需要重新连接你的连接器或更改你的代码。
第二个编号系统是BCM号码。这是一种较低级别的工作方式 – 它指的是Broadcom SOC上的通道号码。您必须始终使用那个通道编号所对应的树莓派板上哪个引脚的图表。您的脚本程序可能会在Raspberry Pi板的硬件修订后而不能使用。
树莓派引脚有BOARD和BCM两种编号方式( 使用python时? 似乎使用C还有一种wringPi编号方式 ), BOARD具有很好的适用性( 不用看接口图,数引脚1~40就可以接线 ), 不论树莓派1 2 3, 都不用修改代码, 吼啊! BCM编号方式换个版本再接线时数引脚是不行的, 需要看下下面的接口图…不难看出推荐用BOARD编号方式. 但很多程序中使用BCM方式.
下面给出一张树莓派2B的硬件接口图( 来源找不到了,侵删 ):

图中的GPIOxx的方框即是BCM编码方式, 直接写数字的深灰框是BOARD编码方式, 如BCM编码方式的 GPIO02 对应BOARD编码方式的 3.
只需要使用BCM编号方式时, 用下面这两张:


要指定您使用引脚编号方式:
GPIO.setmode(GPIO.BOARD) # or GPIO.setmode(GPIO.BCM)
要检测哪个引脚编号系统已被设置模式(例如,由另一个Python模块配置过模式):
mode = GPIO.getmode()
模式将是GPIO.BOARD,GPIO.BCM或None
3、警告
您可能在Raspberry Pi的GPIO上有多个脚本/电路。因此,如果RPi.GPIO检测到引脚已被配置为默认(输入)以外的其他引脚,则在尝试配置脚本时会收到警告。要禁用这些警告:
GPIO.setwarnings(False)
4、设置一个通道
您需要设置您用作输入或输出的每个通道。将通道配置为输入:
GPIO.setup(channel, GPIO.IN)
(其中通道是基于您指定的编号系统(BOARD或BCM)的通道编号)。
有关设置输入通道的更多高级信息可以在这里找到。
要将通道设置为输出:
GPIO.setup(channel, GPIO.OUT)
(其中通道是基于您指定的编号系统(BOARD或BCM)的通道编号)。
您还可以为您的输出通道指定一个初始值:
GPIO.setup(channel, GPIO.OUT, initial=GPIO.HIGH)
4、设置多个频道
您可以一次设置多个通道(从0.5.8开始)。例如:
chan_list = [ 11 ,12 ] #加你想尽可能多的渠道!
#你可以用元组代替,即:
#chan_list =(11,12)
GPIO.setup(chan_list, GPIO.OUT)
5、输入
读取GPIO引脚的值:
GPIO.input(channel)
( 其中通道是基于您指定的编号系统(BOARD或BCM)的通道编号)。这将返回0 / GPIO.LOW / False或1 / GPIO.HIGH / True。
有几种方法可以将GPIO输入到您的程序中。第一种也是最简单的方法是在某个时间点检查输入值。这就是所谓的“轮询”,如果你的程序在错误的时间读取了值,可能会错过输入。轮询在循环中执行,并可能是处理器密集型的。响应GPIO输入的另一种方式是使用’中断’(边沿检测)。边沿是从高电平到低电平(下降沿)或从低电平到高电平(上升沿)的意思。
5.1 上拉/下拉电阻
如果你没有连接到任何输入引脚,它将’浮空’。换句话说,读入的值是未定义的,因为它只有在按下按钮或开关时才会连接到任何东西。由于引脚会接收到干扰,可能读取到变化的值。
为了解决这个问题,我们使用上拉或下拉电阻。这样,可以设置输入的默认值。可以在硬件上使用上拉/下拉电阻并使用软件。在硬件中,通常使用输入通道和3.3V(上拉)或0V(下拉)之间的10K电阻。RPi.GPIO模块允许您配置Broadcom SOC以在软件中执行此操作:
GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_UP) # or GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
(其中通道是基于您指定的编号系统的通道编号 – BOARD或BCM)。
5.2 测试输入(轮询)
您可以立即读取IO引脚的输入值:
if GPIO.input(channel):
print('Input was HIGH')
else:
print('Input was LOW')
要通过轮询轮询等待按钮按下:
while GPIO.input(channel) == GPIO.LOW:
time.sleep(0.01) # wait 10 ms to give CPU chance to do other things
(这里假设按下按钮将输入从LOW改变为HIGH)
5.3 中断和边缘检测
边沿是电信号从低电平变为高电平(上升沿)或从高电平变为低电平(下降沿)的状态变化。很多时候,我们更关心输入状态的变化而非价值。这种状态变化是一个事件。
为了避免在程序忙于做其他事情时按下按钮,有两种方法可以解决这个问题:
- wait_for_edge()函数
- event_detected()函数
- 在检测到边缘时运行线程的回调函数
wait_for_edge()函数
wait_for_edge()函数设计用于阻止程序的执行,直到检测到边缘。换句话说,上面等待按钮按下的示例可以被重写为:
GPIO.wait_for_edge(channel, GPIO.RISING)
请注意,您可以检测GPIO.RISING,GPIO.FALLING或GPIO.BOTH类型的边沿。这样做的好处是它使用的CPU时间可以忽略不计,因此CPU还有很多工作要做。
如果您只想等待一段时间,则可以使用timeout参数:
#上升沿等待最多5秒(超时以毫秒为单位)
channel = GPIO.wait_for_edge(channel, GPIO_RISING, timeout=5000)
if channel is None:
print('Timeout occurred')
else:
print('Edge detected on channel', channel)
event_detected()函数
event_detected()函数设计用于与其他工作一起循环使用,但与轮询不同,在CPU忙于处理其他事情时,不会错过输入状态的变化。当使用类似Pygame或PyQt的东西时,这可能很有用,因为主循环会及时监听和响应GUI事件。
GPIO.add_event_detect(channel, GPIO.RISING) # add rising edge detection on a channel
do_something()
if GPIO.event_detected(channel):
print('Button pressed')
请注意,您可以检测GPIO.RISING,GPIO.FALLING或GPIO.BOTH的事件。
Threaded回调
RPi.GPIO为回调函数运行第二个线程。这意味着回调函数可以与主程序同时运行,并立即响应边缘事件。例如:
def my_callback(channel):
print('This is a edge event callback function!')
print('Edge detected on channel %s'%channel)
print('This is run in a different thread to your main program')
GPIO.add_event_detect(channel, GPIO.RISING, callback=my_callback) # add rising edge detection on a channel
...the rest of your program...
如果你想要多个回调函数:
ef my_callback_one(channel):
print('Callback one')
def my_callback_two(channel):
print('Callback two')
GPIO.add_event_detect(channel, GPIO.RISING)
GPIO.add_event_callback(channel, my_callback_one)
GPIO.add_event_callback(channel, my_callback_two)
请注意,在这种情况下,回调函数按顺序运行,而不是同时运行。这是因为只有一个线程用于回调,每个回调都按照定义的顺序运行。
5.4开关抖动
您可能会注意到,每次按下按钮都会多次调用回调。这是所谓的“开关抖动”的结果。处理抖动有两种方法:
- 在开关输入脚上添加一个0.1uF的电容。
- 软件去除抖动
- 以上两种方法的结合
要使用软件去抖动,请将bouncetime =参数添加到指定回调函数的函数中。抖动时间应以毫秒为单位指定。例如:
#在通道上添加上升沿检测,忽略处理 GPIO的开关抖动操作的进一步边缘200ms 。
GPIO.add_event_detect(channel, GPIO.RISING, callback=my_callback, bouncetime=200)
要么
GPIO.add_event_callback(channel, my_callback, bouncetime=200)
5.5 删除事件检测
如果由于某种原因,您的程序不再希望检测边缘事件,则可以删除它们:
GPIO.remove_event_detect(channel)
6、输出
要设置GPIO引脚的输出状态,请执行以下操作:
GPIO.output(channel, state)
(其中通道是基于您指定的编号系统(BOARD或BCM)的通道编号)。
状态可以是0 / GPIO.LOW / False或1 / GPIO.HIGH / True。
A.设置输出高电平:
GPIO.output(12, GPIO.HIGH) # or GPIO.output(12, 1) # or GPIO.output(12, True)
B.设置输出低电平:
GPIO.output(12, GPIO.LOW) # or GPIO.output(12, 0) # or GPIO.output(12, False)
7、输出到几个通道
您可以一次设置输出多个频道(从0.5.8开始)。例如:
chan_list = [11,12] # also works with tuples GPIO.output(chan_list, GPIO.LOW) # sets all to GPIO.LOW GPIO.output(chan_list, (GPIO.HIGH, GPIO.LOW)) # sets first HIGH and second LOW
8.在RPi.GPIO中使用PWM
要创建一个PWM实例:
p = GPIO.PWM(channel, frequency)
要启动PWM:
p.start(dc) # where dc is the duty cycle (0.0 <= dc <= 100.0)
要更改频率:
p 。ChangeFrequency (freq ) #其中freq是以Hz为单位的新频率
要改变占空比:
p.ChangeDutyCycle(dc) # where 0.0 <= dc <= 100.0
要停止PWM:
p.stop()
请注意,如果实例变量’p’超出范围,PWM也会停止。
每两秒闪烁一次LED的示例:
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup(12, GPIO.OUT)
p = GPIO.PWM(12, 0.5)
p.start(1)
input('Press return to stop:') # use raw_input for Python 2
p.stop()
GPIO.cleanup()
增亮/调暗LED的示例:
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup(12, GPIO.OUT)
p = GPIO.PWM(12, 50) # channel=12 frequency=50Hz
p.start(0)
try:
while 1:
for dc in range(0, 101, 5):
p.ChangeDutyCycle(dc)
time.sleep(0.1)
for dc in range(100, -1, -5):
p.ChangeDutyCycle(dc)
time.sleep(0.1)
except KeyboardInterrupt:
pass
p.stop()
GPIO.cleanup()
9、GPIO恢复默认
在程序的末尾,清理您可能使用的任何资源是一种很好的做法。这与RPi.GPIO没有什么不同。通过将您用过的所使用的通道返回到到无上拉/下拉输入的状态,这样可以避免短接GPIO引脚来导致意外损坏您的树莓派。请注意,这样只会清除你写的脚本中使用的GPIO通道。请注意,GPIO.cleanup()也会清除正在使用的引脚编号系统。
在你的脚本程序的末尾写上:
GPIO.cleanup()
当您的程序退出时,可能不希望清理每个通道,而留下一些设置。您可以清理个别通道,使用通道的元组或列表做为参数输入:
GPIO.cleanup(channel) GPIO.cleanup( (channel1, channel2) ) GPIO.cleanup( [channel1, channel2] )
10、RPi板信息和RPi.GPIO版本
发现有关您的RPi的信息:
GPIO.RPI_INFO
发现Raspberry Pi电路板版本:
GPIO.RPI_INFO [ 'P1_REVISION'] GPIO.RPI_REVISION(不建议使用)
要发现RPi.GPIO的版本:
GPIO.VERSION
11、编写一个测试程序blinkled.py
这个测试程序控制树莓派上一GPIO 25每2秒变化一个电平,如果接一个LED灯到这个IO上面,就会看到这个灯亮2秒灭2秒。
#!/usr/bin/python#*coding:utf-8*#GPIO控制LED灯程序import RPi.GPIO as GPIOimport timepin = 25GPIO.setmode(GPIO.BCM)GPIO.setup(pin, GPIO.OUT) while True: GPIO.output(pin, GPIO.HIGH) time.sleep(2) GPIO.output(pin, GPIO.LOW) time.sleep(2)
在这个blinkled.py的目录中,在命令行中执行sudo python blinkled.py就可以运行此程序,LED就会一闪一闪的了