用于自閉症譜系障礙早期篩查的聲紋特征濾波識别診斷系統
(目錄)
項目背景
根據《孤獨症教育康複行業發展狀況報告》,在全世界範圍内每 54 個兒童就有一個兒童患有自閉症譜系障礙,目前中國的自閉症譜系障礙患者已經超過了 1300萬,并且這個數量以每年近20萬的速度增長。
我們通過調研發現我國關于自閉症譜系障礙方面的确診缺乏統一的診斷标準,各大醫院與醫療機構的主流診斷方案還是依托于量表等工具,誤診率高。但目前在确診方面缺乏科學精準的檢測儀器,導緻被确診為自閉症的患者平均年齡為4到5歲,遠遠滞後于18到24個月的最佳早期篩查診斷時機,使得患兒錯過最佳康複治療期。
我們團隊基于以上痛點,以兒童說話聲音作為原始資料,利用深度學習訓練出的高精度模型對比分析自閉症譜系障礙兒童和正常兒童在聲學特征上的差異,使用音頻分析技術提取聲學特征參數進行分析,基于潤和大禹DAYU200從聲學角度指導醫生對待測兒童進行早期篩查,可将自閉症患者實際篩查确診年齡提前至1到2歲,讓自閉症譜系障礙兒童能極早地得到确診,進而能夠盡早進行幹預治療,最大限度地減少天生發育障礙對患者及整個家庭的影響。
團隊介紹
守望星光團隊于2021年6月在鄭州輕工業大學梅科爾工作室成立,是一家專注于自閉症譜系障礙診斷技術研發的在校創新創業團隊。梅科爾工作室的老師和同學們還必須在極為有限的條件下讓價值最大化,工作室在老齡化、老年人康複、特殊人群關愛方向的漫漫征途。從拿着一封封介紹信去醫院聯系合作,一點點走訪患者開始。如今,梅科爾工作室總計參與到60餘個醫療項目的聯合創新開發中,其中40多個是特殊人群關愛類項目。在腦卒中、自閉症和帕金森等領域完成了超過2000人次的病患資料樣本收集,沉澱出300多個可用醫療案例。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiN1QzMfhGLwIDOfdHLlpXazVmcvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5yY4UDOmhTNiJ2N5YzY2MWO5YDN2UmZidjNwkzN3kDO1YDZkljNy8CX3AjMyAjMvw1cldWYtl2Lc12bj5yb0NWM14ycvlnbv1mchhWLsR2Lc9CX6MHc0RHaiojIsJye.jpg)
華為雲介紹視訊
腦極體采訪-照亮曠野的,是少年開發者眼中的炬火
設計思路
本項目通過神經網絡和音頻分析技術提取自閉症譜系障礙兒童的聲學特征參數進行分析,篩選出最具有代表性的聲學特征和分類識别性能最優的模型,從聲學角度輔助醫生對自閉症譜系障礙兒童進行早期診斷,并設計機器學習模型進行分類。構模組化型,最終網絡的準确性達到 93.8%。醫生可在DAYU200端、網頁Web端、桌面exe程式端、手機端鴻蒙APP檢視結果,預測出自閉症譜系障礙準确率超過 70%,推薦可能患有自閉症譜系障礙孩子接受幹預訓練,避免錯過最佳幹預期。
開發設計技術架構圖如下:
主要分為以下幾步:
- 音頻資料采集
我們項目的音頻資料采集子產品主要由三部分組成,分别是揚聲器、三麥克風陣列數字信号處理器和Esp32-Korvo音頻開發闆。并通過回聲消除算法、語音增強算法、降噪算法和音頻自動增益算法收集音頻資料,以此來保證受測者的音頻資料品質。并通過藍牙或WLAN傳輸音頻資料至雲伺服器進行下一步處理。在内部布局上,通過精确測量開發闆、揚聲器、可充電電池的尺寸,合理規劃設計了内部零件的位置,精準模組化預留内部空間。
産品效果圖如下:
- 音頻資料預處理
我們将資料采集子產品得到的音頻資料上傳至我們的華為雲伺服器上,利用利用我們自主開發的降噪算法對收集到的聲音進行二次降噪,除去高斯噪聲、白噪聲等等的一 些噪音,然後利用 OpenSmile 提取聲學特征,得到的處理過後的資料會進行 Librosa 庫繪制MFCC 圖像。
首先在音頻轉圖像之前,通過 Berouti 譜減法對采樣的自閉症患者語音中自帶的加性噪聲(背景噪聲)得到噪聲的頻譜資訊,并将其從頻率空間中減去。同時為了避免提取困難,采用預加重技術将預加重濾波器加在原始音頻上,強化高頻部分,再通過分幀加窗使分析對象的信号變化不會突然消失。接着将連續的模拟信号轉化為數字信号,通過快速傅裡葉變化對分幀加窗後的各幀信号進行快速傅裡葉變換得到各幀的頻譜。并對語音信号的頻譜取模平方得到語音信号的功率譜。接着利用三角帶通濾波器對頻譜進行平滑化,并消除諧波的作用,突顯原先語音的共振峰。是以一段語音的音調或音高,是不會呈現在梅爾倒譜系數内。換言之,以梅爾倒譜系數為特征的聲學特征濾波識别診斷系統,并不會受到輸入語音的音調不同而有所影響。此外,還可以降低運算量。
最後,經離散餘弦變換(DCT)得到 MFCC 系數。
音頻轉圖像具體流程展示圖如下:
提取聲學特征并繪制出的 MFCC 圖像如下:
- 圖像資料分析
在深度學習模型預測程式中預測待測者自閉症譜系障礙的患病機率,采用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資料
@app.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()
運作效果
示範視訊
附件連結:
ScoutForStars源碼.zip (https://ost.51cto.com/resource/2142)
entry-default-signed.hap (https://ost.51cto.com/resource/2138)