项目背景
根据《孤独症教育康复行业发展状况报告》,在全世界范围内每 54 个儿童就有一个儿童患有自闭症谱系障碍,目前中国的自闭症谱系障碍患者已经超过了 1300万,并且这个数量以每年近20万的速度增长。
我们通过调研发现我国关于自闭症谱系障碍方面的确诊缺乏统一的诊断标准,各大医院与医疗机构的主流诊断方案还是依托于量表等工具,误诊率高。但目前在确诊方面缺乏科学精准的检测仪器,导致被确诊为自闭症的患者平均年龄为4到5岁,远远滞后于18到24个月的最佳早期筛查诊断时机,使得患儿错过最佳康复治疗期。
我们团队基于以上痛点,以儿童说话声音作为原始数据,利用深度学习训练出的高精度模型对比分析自闭症谱系障碍儿童和正常儿童在声学特征上的差异,使用音频分析技术提取声学特征参数进行分析,基于润和大禹DAYU200从声学角度指导医生对待测儿童进行早期筛查,可将自闭症患者实际筛查确诊年龄提前至1到2岁,让自闭症谱系障碍儿童能极早地得到确诊,从而能够尽早进行干预治疗,最大限度地减少天生发育障碍对患者及整个家庭的影响。
团队介绍
守望星光团队于2021年6月在郑州轻工业大学梅科尔工作室成立,是一家专注于自闭症谱系障碍诊断技术研发的在校创新创业团队。梅科尔工作室的老师和同学们还必须在极为有限的条件下让价值最大化,工作室在老龄化、老年人康复、特殊人群关爱方向的漫漫征途。从拿着一封封介绍信去医院联系合作,一点点走访患者开始。如今,梅科尔工作室总计参与到60余个医疗项目的联合创新开发中,其中40多个是特殊人群关爱类项目。在脑卒中、自闭症和帕金森等领域完成了超过2000人次的病患数据样本收集,沉淀出300多个可用医疗案例。
设计思路
本项目通过神经网络和音频分析技术提取自闭症谱系障碍儿童的声学特征参数进行分析,筛选出最具有代表性的声学特征和分类识别性能最优的模型,从声学角度辅助医生对自闭症谱系障碍儿童进行早期诊断,并设计机器学习模型进行分类。构建模型,最终网络的准确性达到 93.8%。医生可在DAYU200端、网页Web端、桌面exe程序端、手机端鸿蒙APP查看结果,预测出自闭症谱系障碍准确率超过 70%,推荐可能患有自闭症谱系障碍孩子接受干预训练,避免错过最佳干预期。
开发设计技术框架图如下:
主要分为以下几步:
1、音频数据采集
我们项目的音频数据采集模块主要由三部分组成,分别是扬声器、三麦克风阵列数字信号处理器和Esp32-Korvo音频开发板。并通过回声消除算法、语音增强算法、降噪算法和音频自动增益算法收集音频数据,以此来保证受测者的音频数据质量。并通过蓝牙或WLAN传输音频数据至云服务器进行下一步处理。在内部布局上,通过精确测量开发板、扬声器、可充电电池的尺寸,合理规划设计了内部零件的位置,精准建模预留内部空间。
产品效果图如下:
2、音频数据预处理
我们将数据采集模块得到的音频数据上传至我们的华为云服务器上,利用利用我们自主开发的降噪算法对收集到的声音进行二次降噪,除去高斯噪声、白噪声等等的一 些噪音,然后利用 OpenSmile 提取声学特征,得到的处理过后的数据会进行 Librosa 库绘制MFCC 图像。
首先在音频转图像之前,通过 Berouti 谱减法对采样的自闭症患者语音中自带的加性噪声(背景噪声)得到噪声的频谱信息,并将其从频率空间中减去。同时为了避免提取困难,采用预加重技术将预加重滤波器加在原始音频上,强化高频部分,再通过分帧加窗使分析对象的信号变化不会突然消失。接着将连续的模拟信号转化为数字信号,通过快速傅里叶变化对分帧加窗后的各帧信号进行快速傅里叶变换得到各帧的频谱。并对语音信号的频谱取模平方得到语音信号的功率谱。接着利用三角带通滤波器对频谱进行平滑化,并消除谐波的作用,突显原先语音的共振峰。因此一段语音的音调或音高,是不会呈现在梅尔倒谱系数内。换言之,以梅尔倒谱系数为特征的声学特征滤波识别诊断系统,并不会受到输入语音的音调不同而有所影响。此外,还可以降低运算量。
最后,经离散余弦变换(DCT)得到 MFCC 系数。
音频转图像具体流程展示图如下:
提取声学特征并绘制出的 MFCC 图像如下:
3、图像数据分析
在深度学习模型预测程序中预测待测者自闭症谱系障碍的患病概率,采用CNN卷积神经网络来进行图像识别,包含4个卷积层和3个全连接层,每个卷积层后面有一个LeakyRelu (激活函数)增强非线性,每两个卷积层分别紧跟一个最大值池化层缩小特征图组成一个卷积组模块,卷积层的输出通道数按顺序分别是64, 32, 128, 64,卷积层的输出特征图进入全连接层前平铺成向量,然后进入三个线性变换层逐层降低向量的维度,每个线性变换紧跟一个Dropout层防止过拟合,线性变换的输出长度分别是128, 64, 1,最后输出的一维向量用于二分类,输出自闭症谱系障碍儿童的概率和正常儿童的概率。基于深度神经网络的算法,通过计算机较强的学习能力来学习自闭症患者与正常人的声学特征,以此达到对自闭症患者语音数据的有效识别。
开发环境
- DevEco Studio for OpenHarmony3.0.0.900
- OpenHarmony版本:3.1_Release
- 开发板:DAYU200
开发过程
页面开发
护星健康系统图标
在config.json文件下修改软件图标及软件名称。
"abilities": [
{
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
],
"orientation": "unspecified",
"visible": true,
"srcPath": "MainAbility",
"name": ".MainAbility",
"srcLanguage": "js",
"icon": "$media:icon1",
"description": "$string:MainAbility_desc",
"formsEnabled": false,
"label": "$string:MainAbility_label",
"type": "page",
"launchType": "standard"
}
]
起始页面
起始页面用于加载系统,用户点击该页面即可跳转主页面。
hml源码:
<div id="wrapper">
<div id="div1" onclick="divclick">
<text id="text1">{{title}}</text>
<image id="image1" src="common/images/background.png"></image>
</div>
</div>
css源码:
#wrapper {
flex-direction: column;
width: 100%;
}
#div1 {
width: 100%;
height: 100%;
position: relative;
display: flex;
visibility: visible;
opacity: 1;
flex-direction: column;
align-items: flex-start;
}
#text1 {
height: 600px;
width: 60px;
position: relative;
visibility: visible;
font-size: 60px;
/* line-height: -1px;*/
text-align: center;
padding-left: 0;
margin-left: 45%;
margin-top: 200px;
color: #7468BE;
letter-spacing:10px;
}
#image1 {
width: 100%;
height: 300px;
top: 80px;
padding-bottom: 0;
}
js源码:
import router from '@system.router';
export default {
data: {
title: "守护星星的孩子",
},
divclick(){
router.push({
uri:'pages/sign_in/sign_in',//指定要跳转的页面
})
}
}
登录页面
将账户名及MIMA上传至服务器,服务器上使用MD5加密加盐算法进行验证账号、MIMA是否正确。
hml源码:
<div id="wrapper">
<div id="div1">
<text id="text1">登录</text>
<input id="input1" placeholder="{{title1}}" onchange="accountChange" value="XXXXX" ></input>
<input id="input2" type="password" placeholder="{{pass}}" onchange="passwordChange" value="XXXXX" ></input>
<text id="text3">{{message}}</text>
<button id="button1" value="{{enter}}" onclick="btnclick"></button>
</div>
</div>
css源码:
#wrapper {
flex-direction: column;
width: 100%;
}
#div1 {
width: 100%;
height: 100%;
flex-direction: column;
}
#text1 {
height: 100px;
width: 150px;
font-size: 58px;
text-align: center;
font-weight: 600;
font-style: normal;
top: 250px;
position: absolute;
margin-left: 80px;
}
#input1 {
width: 70.69827263766581%;
height: 80px;
background-color: #f5f5f5;
font-size: 26px;
position: absolute;
top: 400px;
margin-left: 80px;
}
#input2 {
width: 70.69827263766581%;
height: 80px;
background-color: #f5f5f5;
font-size: 26px;
position: absolute;
top: 500px;
margin-left: 80px;
}
#text3 {
height: 45.43705332600329px;
width: 100%;
font-size: 28px;
color: #C8C8C8;
font-weight: bold;
font-size-step: 0;
position: absolute;
top: 620px;
margin-left: 15%;
}
#button1 {
width: 50%;
height: 80px;
border-radius: 50px;
font-size: 38px;
font-weight: normal;
background-color: #7468BE;
margin-left: 150px;
top: 700px;
display: flex;
position: absolute;
}
js源码:
import prompt from '@system.prompt';
import fetch from '@system.fetch';
import router from '@system.router';
export default {
data: {
title1: "请输入用户名",
pass: "请输入MIMA",
message: "未注册的手机号验证后自动创建账号",
enter: "登录",
Account: "XXXX",
Password: "XXXX",
result: ""
},
accountChange(e) {
this.Account = e.value
},
passwordChange(e) {
this.Password = e.value
},
btnclick() {
router.push({
uri:'pages/page/page',//指定要跳转的页面
})
console.info(this.Account)
console.info(JSON.stringify({"username":this.Account,"password":this.Password}));
fetch.fetch({
url: `https://XXXXXX:80/login?username=` + this.Account + `&password=` + this.Password,
method: 'POST',
responseType:"json",
success: (response) => {
console.info("fetch success");
console.info(JSON.stringify(response.data));
this.result = JSON.parse(response.data);
console.info(JSON.stringify(this.result.data));
if (this.result.data == "True") {
console.info("Login Success");
router.push({
uri: 'pages/index2/index2', //指定要跳转的页面
});
} else {
console.info("Login Failed");
prompt.showToast({
message: "MIMA错误",
duration: 3000,
});
}
}
});
}
}
主页面
目前仅完成语音识别功能开发,用户点击语音识别按钮即可进入该功能模块下。
hml源码:
<!--<element name='comp' src='../../components/tabbar/tabbar.hml'></element>-->
<element name='comp' src='../../components/tabbar/tabbar.hml'></element>
<div id="wrapper">
<image id="image1" src="common/images/21.png"></image>
<input id="input1" placeholder="{{title}}"></input>
<image id="image2" src="common/images/22.png"></image>
<swiper class="container" index="{{index}}" autoplay="true">
<div class="swiper-item primary-item">
<image src="common/images/23.png"></image>
</div>
<div class="swiper-item warning-item">
<image src="common/images/sw4.png"></image>
</div>
<div class="swiper-item success-item">
<image src="common/images/sw3.png"></image>
</div>
</swiper>
<text id="text1">功能选择</text>
<div id="div1" onclick="divclick">
<image id="image5" src="common/images/26.png"></image>
<text id="text2">语音识别</text>
<text id="text3">点击进入语音识别</text>
<image id="image30" src="common/images/h4.png"></image>
</div>
<image id="image7" src="common/images/h2.png"></image>
<div id="div2">
<image id="image8" src="common/images/27_h.png"></image>
<text id="text4">眼动识别</text>
<text id="text5">该功能待开发</text>
<image id="image32" src="common/images/h5.png"></image>
</div>
<image id="image10" src="common/images/h2.png"></image>
<div id="div3">
<image id="image11" src="common/images/28_h.png"></image>
<text id="text6">肢体行为检测</text>
<text id="text7">该功能待开发</text>
<image id="image31" src="common/images/h6.png"></image>
</div>
<image id="image13" src="common/images/h2.png"></image>
<comp index="0"></comp>
</div>
css源码:
#wrapper {
flex-direction: column;
width: 100%;
height: 100%;
position: absolute;
}
#image1 {
width: 40px;
height: 40px;
margin-left: 10px;
margin-top: 10px;
padding: 5px;
}
#input1 {
width: 66.65016628828752%;
height: 36.51376146788991px;
margin-top: -35px;
margin-left: 60px;
}
#image2 {
width: 40px;
height: 35px;
margin-top: -35px;
object-fit: fill;
margin-left: 305px;
padding: -3px;
}
#image3 {
width: 360px;
height: 180.91743119266056px;
margin-top: 10px;
}
#text1 {
height: 30px;
width: 100px;
font-size: 22px;
margin-top: 14px;
margin-left: 15px;
font-weight: 600;
}
#div1 {
width: 100%;
height: 100px;
margin-top: 8px;
}
#image5 {
width: 35.59633027522935px;
height: 38.89908256880733px;
margin-top: 40px;
margin-left: 30px;
object-fit: fill;
}
#text2 {
height: 30px;
width: 100px;
font-size: 20px;
margin-top: 25px;
margin-left: 20px;
color: #7468BE;
font-weight: bold;
}
#text3 {
height: 30px;
width: 128.07339449541286px;
font-size: 15px;
margin-top: 60px;
margin-left: -100px;
color: #999999;
}
#image30 {
width: 100px;
height: 100px;
object-fit: fill;
margin-left: 19px;
margin-top: 6px;
padding: 1px;
border-radius: 50px;
}
#image7 {
width: 360px;
height: 30px;
object-fit: fill;
}
#div2 {
width: 100%;
height: 100px;
}
#image8 {
width: 60.36697247706422px;
height: 63.669724770642205px;
margin-top: 20px;
object-fit: fill;
margin-left: 19px;
padding: 8px;
}
#text4 {
height: 30px;
width: 100px;
font-size: 20px;
color: #7468BE;
font-weight: bold;
margin-top: 25px;
margin-left: 4px;
}
#text5 {
height: 30px;
width: 100px;
font-size: 15px;
color: #999999;
margin-top: 60px;
margin-left: -100px;
}
#image31 {
width: 100px;
height: 100px;
margin-top: 6px;
margin-left: 48px;
object-fit: fill;
border-radius: 50px;
}
#image10 {
width: 360px;
height: 30px;
object-fit: fill;
}
#div3 {
width: 100%;
height: 100px;
}
#image11 {
width: 62.01834862385321px;
height: 57.06422018348623px;
object-fit: fill;
margin-left: 19px;
padding: 6px;
margin-top: 16px;
}
#text6 {
height: 30px;
width: 123.11926605504587px;
font-size: 20px;
color: #7468BE;
font-weight: bold;
margin-top: 16px;
}
#text7 {
height: 30px;
width: 100px;
font-size: 15px;
color: #999999;
margin-left: -125px;
margin-top: 50px;
}
#image32 {
width: 100px;
height: 100px;
object-fit: fill;
margin-left: 53px;
margin-top: 0;
border-radius: 50px;
}
#image13 {
width: 360px;
height: 30px;
object-fit: fill;
/* padding-top: 90%;*/
/* padding-top: 500px;*/
}
.container1 {
flex-direction: column;
justify-content: center;
align-items: center;
}
.container {
left: 0px;
top: 0px;
width: 454px;
height: 200px;
padding-top: 3%;
}
.swiper-item {
width: 454px;
height: 200px;
justify-content: center;
align-items: center;
}
选择患儿页面
选择对应患儿进入该患儿的信息界面。
源码放太多了,文章太长,下面几个页面就不放源码了。
患儿近况页面
从服务器中获取近几次的预测概率,使用canva绘制曲线图。
连接设备页面
使用蓝牙连接设备,调用麦克风录音。
录音页面
录制10-15s音频,点击按钮,可将音频上传至服务器进行分析。
结果分析中页面
由于使用的服务器比较菜,分析时间较长。
分析结果页面
从服务器获取到预测概率后,在结果分析页面进行显示。
Flask后端代码
后端代码较长,这里仅放下登录接口的源码:
import json
import pymysql
import os
from flask import Flask, request, Blueprint
import sys
import acoustic_feature as af
import torch
import base64
# 接收json数据,验证数据,返回json数据
.route('/login', methods=['POST', 'GET'])
def verify():
return_dict = {
'return_code': '200',
'return_msg': 'success',
'data': {None}
}
print(request.values)
name = request.values.get('username')
pwd = request.values.get('password')
try:
db_password = select("select password from user where account = '%s'" % name)
except Exception as e:
return_dict['return_code'] = '500'
return_dict['data'] = 'database error'
return json.dumps(return_dict)
input_password_md5_code = md5_verify(pwd, 'XXXX')
print(input_password_md5_code)
print(db_password)
if input_password_md5_code == db_password:
return_dict['data'] = 'True'
else:
return_dict['data'] = 'False'
print(return_dict)
return json.dumps(return_dict, ensure_ascii=False)
# md5加密加盐验证
def md5_verify(password, salt):
import hashlib
md5 = hashlib.md5()
md5.update(password.encode('utf-8'))
md5.update(salt.encode('utf-8'))
return md5.hexdigest()
运行效果
演示视频
文章相关附件可以点击下面的原文链接前往下载。
https://ost.51cto.com/resource/2138。
https://ost.51cto.com/resource/2142。