基于MAX78000实现语音查询城市天气
十分感谢本次由ADI公司赞助硬禾学堂举办的MAX78000人工智能应用设计大赛,通过参加这次比赛我学习到很多关于人工智能的知识。本项目是基于MAX78000语音识别,使用者通过语音来实现对城市天气情况的查询。
标签
嵌入式系统
语音识别
ESP8266
MAX78000人工智能应用设计大赛第二季
小新卖蜡笔
更新2024-01-09
华北科技学院
276

1、项目介绍

每天的天气状况影响着我们每个人的日常出行与生活,因此我们需要能时刻掌握我们所在城市的天气状况,以便我们能对未来的天气状况提前做好相应的准备。本项目是基于MAX78000实现语音查询城市天气,项目的主控用的是ESP8266,MAX78000的作用是用于语音识别以及向ESP8266发送语音识别的结果,ESP8266根据这一结果联网查询当地的天气情况并将天气展示给我们用户查看。

2、项目设计的基本思路

MAX78000FTHR开发板是一块很强大的开发板,在如此小的面积上不仅集成了拍照、录音等模块,而且还能实现低功耗AI推理,非常适合边缘智能系统的开发。在没有参加这个比赛之前,我是想过制DIY一个基于ESP8266的天气时钟的桌面小摆件,不过这个想法因为一些其他的原因耽搁了,后面拿到MAX78000FTHR开发板就想着这两个是不是可以结合一下制作一个能通过语音识别来查询天气的桌面时钟。

本项目作为一个通过语音识别来查询天气的桌面时钟,我认为它应该拥有两个不同的信息显示界面,一个是在我们没有查询天气时作为时钟的界面,另一个界面则是查询天气时显示查询地天气信息的界面。系统的工作流程是:当MAX78000采集到语音后开始识别,若成功识别出对应城市名,识别的结果将通过串口发送给ESP8266,ESP8266根据串口数据查询其对应地址的天气信息并输出显示在屏幕上;若未成功识别,则继续显示时钟界面。

3、语音素材的收集

环境配置完成后,我们开始尝试实现自己的项目,因为要做语音识别类项目,所以我们选择在kws20_demo的基础上修改。要训练模型首先录制自己需要的语音数据,录制软件我用的是Audacity,如下图,是一个功能十分强大的开源软件。

语音训练方面考虑到在前面测试官方demo的时候训练就很慢的实际情况,我准备录制四组语音关键词作为自己的训练数据,这里我选择录制的四个分别是”北京“、”上海“、”深圳“、”郑州“。为了能更快速的获取语音数据,我们录制的时候对同一关键词连续多次录音,录制单声道16kHz 采样的WAV音频文件,然后再将这些音频剪切为一秒的可用数据,语音的剪切如果全靠人工剪辑的话显然是一个很大的工作量,特别是像我们需要大量数据的情况,我们可以使用脚本实现快速剪切,这里我使用的是官方的convert_segment_wav.py脚本来实现,使用这脚本在连续录制音频时在两次读出关键词之间尽量不要出现杂音,否则剪切出来的音频可能会不太准确。

最后将这些关键词数据分别存储在ai8x-training\data\KWS\raw路径下新创建的文件夹”beijing“、”shanghai“、”shenzhen“、”zhengzhou“下。

FvqsKoHAGFBjsD9_DNAVIe0JxdtY

4、模型的训练和部署

在语音数据收集完成后开始准备模型的训练和部署,模型的训练和部署可以分成四个步骤:训练、量化,合成、烧录。开始训练前我们需要做一些的准备工作:第一步修改kws20.py文件添加关键词如下:

#修改class_dict中的内容将新加入的数据按字母顺序放入
class_dict = {'backward': 0, 'bed': 1, "beijing":2, 'bird': 3, 'cat': 4, 'dog': 5, 'down': 6,
'eight': 7, 'five': 8, 'follow': 9, 'forward': 10, 'four': 11, 'go': 12,
'happy': 13, 'house': 14, 'learn': 15, 'left': 16, 'librispeech': 17,
'marvin': 18, 'nine': 19, 'no': 20, 'off': 21, 'on': 22, 'one': 23,
'right': 24, 'seven': 25, 'shanghai': 26, 'sheila': 27, "shenzhen":28, 'six': 29, 'stop': 30,
'three': 31, 'tree': 32, 'two': 33, 'up': 34, 'visual': 35, 'wow': 36,
'yes': 37, 'zero': 38, "zhengzhou":39}


#修改datasets中的'output''weight'如下
datasets = [
{
'name': 'KWS_20', # 20 keywords
'input': (128, 128),
'output': ('one', 'two', 'yes', 'beijing', 'shanghai', 'shenzhen', 'zhengzhou', 'UNKNOWN'),
'weight': (1, 1, 1, 1, 1, 1, 1, 0.07),
'loader': KWS_20_get_datasets,
},
#
def KWS_get_datasets(data, load_train=True, load_test=True, num_classes=6):
(data_dir, args) = data

transform = transforms.Compose([
ai8x.normalize(args=args)
])

if num_classes in (6, 7):#修改此处

classes = next((e for _, e in enumerate(datasets)
if len(e['output']) - 1 == num_classes))['output'][:-1]
else:
raise ValueError(f'Unsupported num_classes {num_classes}')

augmentation = {'aug_num': 2, 'shift': {'min': -0.15, 'max': 0.15},
'noise_var': {'min': 0, 'max': 1.0}}
quantization_scheme = {'compand': False, 'mu': 10}
#
def KWS_20_get_datasets(data, load_train=True, load_test=True):

return KWS_get_datasets(data, load_train, load_test, num_classes=7)#修改此处num_class

第二步修改ai8x-training\models路径下的网络模型参数ai85net-kws20.py文件

class AI85KWS20Net(nn.Module):
"""
Compound KWS20 Audio net, starting with Conv1Ds with kernel_size=1
and then switching to Conv2Ds
"""

# num_classes = n keywords + 1 unknown
def __init__(
self,
num_classes=8,#修改此处 num_classes = n keywords + 1 unknown,也就是kws20.py中修改的加上一个UNKNOWN
num_channels=128,
dimensions=(128, 1), # pylint: disable=unused-argument
fc_inputs=7,
bias=False,
**kwargs
):

检查修改无误后,就可以开始训练我们自己的模型了,打开我们配置好的ai8x-training环境进入AI\ai8x-training路径下,运行如下命令开始训练:

python train.py --epochs 200 --optimizer Adam --lr 0.001 --wd 0 --deterministic --compress policies/schedule_kws20.yaml --model ai85kws20net --dataset KWS_20 --confusion --device MAX78000 "$@"

训练完成我们在ai8x-training/logs目录下会生成训练文件,我们将其中的四个TAR压缩文件复制到ai8x-synthesis/trained目录下,然后进行下一步量化,推出ai8x-training虚拟环境进入ai8x-synthesis虚拟环境并切换到ai8x-synthesis目录下执行下面的命令开始量化和生成C语言文件。

#量化
python quantize.py trained/qat_best.pth.tar trained/qat_best-q.pth.tar --device MAX78000 -v "$@"
#合成C语言文件
scripts/gen_kws20_max78000.sh

合成完成后我们将合成的C文件中的cnn.c、cnn.h、weights.h放入官方的kws20_demo中替换原本的这三个文件,修改mian.c中关键词数组,清理原本的项目,然后重新构建生成、运行

Fmc2NxGa1W0bQ5pwzw7hCMcO7Q32

最后打开串口助手测试训练出来的模型对我们设定关键词的识别效果

FgdPCL9XMcM-kNJgs1LE6BJzxl8VFg_F8GsJZD5w47uVepGhiMl7wn-_

 

5、ESP8266设计思路与过程

ESP8266程序的开发是在Arduino平台完成的,ESP8266的设计是根据网上的一个开源的太空人时钟为基础,根据我自己的需求魔改而来。思路:ESP8266联网后接收到来自MAX78000的城市名称数据后,根据对应该城市的城市天气代码查询该地的天气情况,并将各种天气信息其输出在屏幕的对应位置显示十秒,实际界面如下上海的天气情况。当我们没有查询天气时,显示的则是太空人时钟界面,而位置则是当前所在的位置。

Fj-IDpLeVw2JVmxTZw0KXfAe2fx_

未查询天气时界面如下:

FvNu24DzFKdf8F6WNZdDg9UDnUri

创建汉字库

ESP8266要想通过TFT屏幕显示天气数据需要我们创建一个要用到的汉字的汉字库,汉字库的建立我们可以使用软件”processing“参考汉字库的创建教程,我们先用软件processing打开Arduino库文件中的libraries\TFT_eSPI\Tools\Create_Smooth_Font\Create_font路径下的Create_font.pde文件。然后我们要确定添加中文字符的Unicode值,再将Unicode值中的\u转换为0x存入打开的Create_font.pde文件中,如下图:

FtfohTKe0_bAU4dSXiYy2qCzNxQ-

然后运行生成对应的VLW文件,我创建的汉字库中的汉字如下图包含了全国大多数城市名称(我这个里面有很多重复的汉字其实一个汉字只要有一个就可以了,当然重复了也没太大的影响)。

FrdzstBhZik0ZOW3nDnoL_nrl5BP

最后,我们将VLW文件转为hex数组,就完成了汉字库的创建,这样我们就可以再TFT屏幕输出我们想要的字符了。

FtgNK185sYT0K5os_sqch1LZmP_9

ESP8266获取天气数据的实现

ESP8266获取本地城市天气代码函数getCityCode()与获取城市天气情况函数getCityWeater2(String code)的实现,getCityCode()的作用是向天气网站发送请求,并从响应中获取城市代码。

getCityWeater2(String code)获取指定城市的天气数据。它向中国天气网站提供的天气 API 发送 HTTP GET 请求来获取城市的天气数据并通过weaterData2()函数打印在TFT屏幕上。

void getCityCode(){//获取本地城市天气代码
String URL = "http://wgeo.weather.com.cn/ip/?_="+String(now());
//创建 HTTPClient 对象
WiFiClient client;
HTTPClient httpClient;
httpClient.begin(client,URL);
httpClient.addHeader("Referer", "http://www.weather.com.cn/");
//启动连接并发送HTTP请求
int httpCode = httpClient.GET();
Serial.print("Send GET request to URL: ");
Serial.println(URL);
if (httpCode == HTTP_CODE_OK) {
String str = httpClient.getString();
int aa = str.indexOf("id=");
if(aa>-1){
cityCode[4] = str.substring(aa+4,aa+4+9);
Serial.println(cityCode[4]);
getCityWeater();
}else{
Serial.println("获取城市代码失败");
}
} else {
Serial.println("请求城市代码错误:");
Serial.println(httpCode);
}
//关闭ESP8266与服务器连接
httpClient.end();
}
void getCityWeater2(String code){//获取城市天气
String URL = "http://d1.weather.com.cn/weather_index/" + code + ".html?_="+String(now());
//创建 HTTPClient 对象
WiFiClient client;
HTTPClient httpClient;
httpClient.begin(client,URL);
httpClient.addHeader("Referer", "http://www.weather.com.cn/");
//启动连接并发送HTTP请求
int httpCode = httpClient.GET();
Serial.println("正在获取天气数据");
Serial.println(URL);
if (httpCode == HTTP_CODE_OK) {
String str = httpClient.getString();
int indexStart = str.indexOf("weatherinfo\":");
int indexEnd = str.indexOf("};var alarmDZ");
String jsonCityDZ = str.substring(indexStart+13,indexEnd);
Serial.println(jsonCityDZ);
indexStart = str.indexOf("dataSK =");
indexEnd = str.indexOf(";var dataZS");
String jsonDataSK = str.substring(indexStart+8,indexEnd);
Serial.println(jsonDataSK);
indexStart = str.indexOf("\"f\":[");
indexEnd = str.indexOf(",{\"fa");
String jsonFC = str.substring(indexStart+5,indexEnd);
Serial.println(jsonFC);
weaterData2(&jsonCityDZ,&jsonDataSK,&jsonFC);
Serial.println("获取成功");
} else {
Serial.println("请求城市天气错误:");
Serial.print(httpCode);
}
//关闭ESP8266与服务器连接
httpClient.end();
}

ESP8266选择查询城市天气的函数实现,cityCode数组里存储的是五个城市的城市代码,其中第五个是根据设备所在地通过天气网站获取的城市代码,会随地区改变而变化。

String cityCode[5] = {"101010100"/*北京*/,"101020100"/*上海*/,"101280601"/*深圳*/,"101180101"/*郑州*/,"101010100"/*当前所在位置*/};  //内部存储天气城市代码
void Select_CITY(int Code){
Clear_TFT();
getCityWeater2(cityCode[Code]);
JiShiTime = millis();
weaterTime = millis();
while(millis() - JiShiTime < 10000){
if (now() != prevDisplay) {
prevDisplay = now();
digitalClockDisplay2();
}
if(millis() - weaterTime > 5000){ //更新天气
weaterTime = millis();
getCityWeater2(cityCode[Code]);
}
scrollBanner2();
}
}

 

6、总结

本次项目也算是顺利完成了,这是我第一次接触有关人工智能类的项目,对这方面我完全就是一个小白,这也使得在完成的过程中也是遇到了许多的问题,也正是因为这些问题的存在让我在完成这次项目的过程中受益匪浅,学习到了许许多多的新知识,在跟随官方知道文件和例程做项目的同时也对人工智能的训练过程和部署,训练数据的采集,以及对MAX78000开发板也有了一个初步的了解。再次感谢感谢硬禾学堂和ADI公司提供的这个平台和资源,希望我们以后能一起努力,不断进步。

7、遇到的主要难题及解决方法

1、训练过程中因内存不足训练进程被Killed

FhLOZ4GonHGXgnhxkmd-McTswkgw

解决办法:解决办法我知道的有两种,一是直接加内存条简单粗暴,但是内存条现在挺贵的,所以我选择的二种,修改wsl的交换分区。在用户文件夹下创建 .wslconfig文件,文件里配置如下

[wsl2]
swap=16GB
swapfile=G:/swap/swap.vhdx

交换分区大小修改为16GB,问题解决。

2、对他人的语音识别不准确

我认为这个问题是因为在采集语音数据的时候,大多数的语音采集的是我本人的,加上采集到的数据量相对于官方的例程的数据量来说是很少的,所以会产生这个问题。

解决办法:增加采集的数据量,但是由于现有条件的限制这个问题还为彻底解决。

附件下载
ESP8266.7z
ESP8266代码
KWS_CITY.7z
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号