Compare commits
48 Commits
chenxiaofu
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
0d9222dd7e | 11 months ago |
|
|
0bfe81da12 | 11 months ago |
|
|
844104b3ef | 11 months ago |
|
|
14dbf9269b | 11 months ago |
|
|
22e2838843 | 11 months ago |
|
|
bccdfc9268 | 11 months ago |
|
|
4b8d0732a6 | 11 months ago |
|
|
1db9be8367 | 11 months ago |
|
|
776a1b8ce0 | 11 months ago |
|
|
6048141bd4 | 11 months ago |
|
|
e0bf3559fc | 11 months ago |
|
|
cdc17350db | 11 months ago |
|
|
73017258ed | 11 months ago |
|
|
a676058385 | 11 months ago |
|
|
1bc9e6f732 | 11 months ago |
|
|
0d6892df17 | 11 months ago |
|
|
7ff37fcee5 | 11 months ago |
|
|
8add84ec2e | 11 months ago |
|
|
2c1ee5a305 | 11 months ago |
|
|
d2f2568b16 | 11 months ago |
|
|
839b976075 | 11 months ago |
|
|
49b998e56a | 11 months ago |
|
|
53c4276d67 | 11 months ago |
|
|
91ac072e60 | 11 months ago |
|
|
5cfa22e4d3 | 11 months ago |
|
|
ecb971c91c | 11 months ago |
|
|
74c1701597 | 11 months ago |
|
|
3ec8eba5e9 | 12 months ago |
|
|
1dd4dbcbf2 | 1 year ago |
|
|
2c8abb5a32 | 1 year ago |
|
|
597624ff45 | 1 year ago |
|
|
85f93468f7 | 1 year ago |
|
|
8a78fd823f | 1 year ago |
|
|
6ffb17f1fe | 1 year ago |
|
|
3513cd8687 | 1 year ago |
|
|
b3aeef9ef1 | 1 year ago |
|
|
f8d993b224 | 1 year ago |
|
|
42b2fb2abc | 1 year ago |
|
|
fe1721ebed | 1 year ago |
|
|
05d3458252 | 1 year ago |
|
|
c7e798c01d | 1 year ago |
|
|
e8d7e7a9c1 | 1 year ago |
|
|
5b86e76a9f | 1 year ago |
|
|
5ba694b615 | 1 year ago |
|
|
f05016094b | 1 year ago |
|
|
8c83d5cc79 | 1 year ago |
|
|
25d9f1cb4d | 1 year ago |
|
|
da2a6edb9a | 1 year ago |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,32 @@
|
||||
@startuml
|
||||
title “启动声学监听与声源定位”用例实现顺序图
|
||||
|
||||
actor "用户" as User
|
||||
boundary "MainUI" as UI
|
||||
control "AcousticManager" as Manager
|
||||
entity "AcousticProcessor" as Processor
|
||||
|
||||
User -> UI: 启动声学监听功能
|
||||
activate UI
|
||||
|
||||
UI -> Manager: startAcousticMonitoring()
|
||||
activate Manager
|
||||
|
||||
Manager -> Processor: beginProcessing()
|
||||
activate Processor
|
||||
|
||||
Processor -> Processor: 采集原始声学信号(麦克风阵列)
|
||||
Processor -> Processor: 数字滤波降噪预处理
|
||||
Processor -> Processor: 枪声识别(深度神经网络)
|
||||
Processor -> Processor: 计算声源方位和距离(TDOA)
|
||||
|
||||
Processor --> Manager: 返回定位结果(/sound/location)
|
||||
deactivate Processor
|
||||
|
||||
Manager --> UI: 更新并显示声源定位信息
|
||||
deactivate Manager
|
||||
|
||||
UI --> User: 显示定位结果
|
||||
deactivate UI
|
||||
|
||||
@enduml
|
||||
@ -0,0 +1,30 @@
|
||||
@startuml
|
||||
title “基于声源定位的视觉目标确认”用例实现顺序图
|
||||
|
||||
actor "用户" as User
|
||||
control "VisionManager" as VM
|
||||
control "DroneController" as DC
|
||||
entity "VisionProcessor" as VP
|
||||
boundary "DetectionUI" as UI
|
||||
|
||||
User -> VM: 触发视觉目标确认场景
|
||||
activate VM
|
||||
|
||||
|
||||
VM -> DC: adjustView(location)
|
||||
activate DC
|
||||
DC -> DC: 调整云台朝向目标区域
|
||||
DC -> VP: 请求图像采集
|
||||
deactivate DC
|
||||
|
||||
activate VP
|
||||
VP -> VP: 采集多光谱图像 & YOLOv5s检测
|
||||
VP -> VP: 非极大值抑制(NMS)
|
||||
VP --> VM: 发送检测结果(/visual/detection)
|
||||
deactivate VP
|
||||
|
||||
VM -> UI: 展示威胁目标标注及告警
|
||||
deactivate VM
|
||||
|
||||
UI --> User: 显示视觉确认结果
|
||||
@enduml
|
||||
@ -0,0 +1,24 @@
|
||||
@startuml
|
||||
title “实时数据传输与指令交互”用例实现顺序图
|
||||
|
||||
actor "指挥中心" as CommandCenter
|
||||
control "CommunicationManager" as ComMgr
|
||||
control "DroneComModule" as Drone
|
||||
database "飞控指令解析/传感器数据" as FCData
|
||||
|
||||
CommandCenter -> ComMgr: 通过HTTPS(RESTful API)\n发送观测指令
|
||||
activate ComMgr
|
||||
|
||||
ComMgr -> Drone: 转发飞控指令\n(WebSocket)
|
||||
activate Drone
|
||||
|
||||
Drone -> FCData: 分析/执行指令调整\n(飞行轨迹 & 传感器参数)
|
||||
Drone --> ComMgr: 传回实时数据\n(声学特征 + 视觉流媒体)
|
||||
deactivate Drone
|
||||
|
||||
ComMgr --> CommandCenter: 通知指挥中心\n数据回传完成
|
||||
deactivate ComMgr
|
||||
|
||||
CommandCenter -> CommandCenter: 动态调整作战状态
|
||||
|
||||
@enduml
|
||||
@ -0,0 +1,28 @@
|
||||
@startuml
|
||||
title “融合数据生成战术地图”用例实现顺序图
|
||||
|
||||
actor "用户" as User
|
||||
control "MapManager" as MM
|
||||
entity "MapGenerator" as MG
|
||||
boundary "MapUI" as MUI
|
||||
|
||||
User -> MM: 触发战术地图生成流程
|
||||
activate MM
|
||||
|
||||
|
||||
|
||||
MM -> MG: fuseData(soundData, visualData)
|
||||
activate MG
|
||||
|
||||
|
||||
|
||||
MG --> MM: 返回战术地图数据
|
||||
deactivate MG
|
||||
|
||||
MM -> MUI: 渲染热力图 & 标注无人机位置
|
||||
activate MUI
|
||||
MUI --> User: 展示战术地图(威胁热力图、空间查询等)
|
||||
deactivate MUI
|
||||
|
||||
deactivate MM
|
||||
@enduml
|
||||
@ -0,0 +1,30 @@
|
||||
@startuml
|
||||
title 用户界面跳转关系顺序图
|
||||
|
||||
actor "用户" as User
|
||||
participant "主界面" as MainUI
|
||||
participant "无人机自动前出侦测界面" as AutoUI
|
||||
participant "手动操纵无人机界面" as ManualUI
|
||||
participant "战场地图标注界面" as MapUI
|
||||
participant "系统设置界面" as SettingUI
|
||||
|
||||
User -> MainUI: 打开系统主界面
|
||||
MainUI --> User: 显示主界面与功能导航
|
||||
|
||||
User -> MainUI: 选择“无人机自动前出侦测”
|
||||
MainUI -> AutoUI: 跳转到自动前出侦测界面
|
||||
AutoUI --> User: 显示自动侦测信息
|
||||
|
||||
User -> AutoUI: 切换手动模式
|
||||
AutoUI -> ManualUI: 跳转到手动操纵无人机界面
|
||||
ManualUI --> User: 显示手动操纵画面
|
||||
|
||||
User -> ManualUI: 查看战场地图
|
||||
ManualUI -> MapUI: 跳转到战场地图标注界面
|
||||
MapUI --> User: 显示无人机位置与声源定位结果
|
||||
|
||||
User -> MapUI: 进入系统设置
|
||||
MapUI -> SettingUI: 打开系统设置界面
|
||||
SettingUI --> User: 显示参数配置功能
|
||||
|
||||
@enduml
|
||||
@ -0,0 +1,54 @@
|
||||
@startuml
|
||||
title ER Diagram for "基于多模态融合的无人机战场隐蔽威胁定位系统"
|
||||
|
||||
entity "T_Drone" as Drone {
|
||||
-- Fields --
|
||||
* id : VARCHAR(50) <<PK>>
|
||||
status : ENUM
|
||||
battery_level : INT
|
||||
last_communication : TIMESTAMP
|
||||
}
|
||||
|
||||
entity "T_AcousticData" as Acoustic {
|
||||
-- Fields --
|
||||
* id : INT <<PK>>
|
||||
drone_id : VARCHAR(50)
|
||||
timestamp : TIMESTAMP
|
||||
location : GEOMETRY // 声源定位信息(方位角+距离)
|
||||
features : BLOB // 声学特征数据
|
||||
is_gunshot : BOOL
|
||||
}
|
||||
|
||||
entity "T_VisualData" as Visual {
|
||||
-- Fields --
|
||||
* id : INT <<PK>>
|
||||
drone_id : VARCHAR(50)
|
||||
timestamp : TIMESTAMP
|
||||
image_data : BLOB // 图像数据
|
||||
threats : BLOB // 检测到的威胁信息
|
||||
}
|
||||
|
||||
entity "T_Threat" as Threat {
|
||||
-- Fields --
|
||||
* id : INT <<PK>>
|
||||
location : GEOMETRY
|
||||
type : ENUM
|
||||
confidence : FLOAT
|
||||
discovery_time : TIMESTAMP
|
||||
}
|
||||
|
||||
entity "T_CommandCenter" as CmdCenter {
|
||||
-- Fields --
|
||||
* id : VARCHAR(50) <<PK>>
|
||||
connected_drones : VARCHAR
|
||||
last_update : TIMESTAMP
|
||||
}
|
||||
|
||||
' 关系定义:一架无人机可以采集多条声学或视觉数据
|
||||
Drone ||--o{ Acoustic : "drone_id"
|
||||
Drone ||--o{ Visual : "drone_id"
|
||||
|
||||
' 指挥中心与无人机的关系(指挥中心可连接多架无人机)
|
||||
CmdCenter ||--o{ Drone : "connected_drones"
|
||||
|
||||
@enduml
|
||||
Binary file not shown.
@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>主界面</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
.function-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10px;
|
||||
justify-content: center;
|
||||
gap: 26px;
|
||||
}
|
||||
.function-module {
|
||||
background: #ffffff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 10px;
|
||||
width: 200px;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
.function-module:hover {
|
||||
box-shadow: 0 0 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
.function-icon {
|
||||
font-size: 40px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.function-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
footer {
|
||||
margin-top: 30px;
|
||||
}
|
||||
.help-btn {
|
||||
background: #0078d4;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
.help-btn:hover {
|
||||
background: #005ea5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>“基于多模态融合的无人机战场隐蔽威胁定位系统”主界面</h1>
|
||||
</header>
|
||||
|
||||
<div class="function-container">
|
||||
<div class="function-module" onclick="goToAutoDetection()">
|
||||
<div class="function-icon">🚁</div>
|
||||
<div>无人机自动前出侦测</div>
|
||||
<div class="function-desc">快速侦测并定位战场隐蔽威胁</div>
|
||||
</div>
|
||||
<div class="function-module" onclick="goToManualUAV()">
|
||||
<div class="function-icon">🎮</div>
|
||||
<div>手动操纵无人机</div>
|
||||
<div class="function-desc">自主飞行操控,实时掌握画面</div>
|
||||
</div>
|
||||
<div class="function-module" onclick="goToMapMarkup()">
|
||||
<div class="function-icon">🗺️</div>
|
||||
<div>战场地图标注</div>
|
||||
<div class="function-desc">查看无人机位置及声源定位信息</div>
|
||||
</div>
|
||||
<div class="function-module" onclick="goToSettings()">
|
||||
<div class="function-icon">⚙️</div>
|
||||
<div>系统设置</div>
|
||||
<div class="function-desc">灵活配置系统参数</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<a class="help-btn" href="help.html">帮助</a>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function goToAutoDetection() {
|
||||
// 跳转到“无人机自动前出侦测”模块
|
||||
alert('进入无人机自动前出侦测界面');
|
||||
}
|
||||
function goToManualUAV() {
|
||||
// 跳转到“手动操纵无人机”模块
|
||||
alert('进入手动操纵无人机界面');
|
||||
}
|
||||
function goToMapMarkup() {
|
||||
// 跳转到“战场地图标注”模块
|
||||
alert('进入战场地图标注界面');
|
||||
}
|
||||
function goToSettings() {
|
||||
// 跳转到“系统设置”界面
|
||||
alert('进入系统设置界面');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,177 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>手动操纵无人机界面</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
header, footer {
|
||||
background-color: #f1f1f1;
|
||||
padding: 10px;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
.video-section {
|
||||
flex: 2;
|
||||
border-right: 1px solid #ddd;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.control-section {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.param-list {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.control-panel {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
.control-panel h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
/* 轮盘容器 */
|
||||
.joystick-wrapper {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin: 0 auto;
|
||||
background: #eee;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* 轮盘主体 */
|
||||
.joystick-base {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: #ccc;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
/* 用四个按钮模拟轮盘点击区域 */
|
||||
.joystick-base button {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
background: #999;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.joystick-base button:hover {
|
||||
background: #666;
|
||||
}
|
||||
.btn-up {
|
||||
top: 0;
|
||||
left: calc(50% - 15px);
|
||||
}
|
||||
.btn-down {
|
||||
bottom: 0;
|
||||
left: calc(50% - 15px);
|
||||
}
|
||||
.btn-left {
|
||||
left: 0;
|
||||
top: calc(50% - 15px);
|
||||
}
|
||||
.btn-right {
|
||||
right: 0;
|
||||
top: calc(50% - 15px);
|
||||
}
|
||||
.speed-control {
|
||||
text-align: center;
|
||||
}
|
||||
.status-bar {
|
||||
background: #fafafa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
}
|
||||
button {
|
||||
margin: 0 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<button onclick="returnHome()">一键返航</button>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="video-section">
|
||||
<!-- 实时视觉画面或占位图 -->
|
||||
<img src="https://via.placeholder.com/640x360?text=Real-Time+UAV+Feed" alt="实时画面" />
|
||||
</section>
|
||||
|
||||
<section class="control-section">
|
||||
<div>
|
||||
<div class="param-list">
|
||||
<h3>声源定位参数</h3>
|
||||
<p>方位角: <span id="azimuth">N/A</span></p>
|
||||
<p>距离: <span id="distance">N/A</span></p>
|
||||
</div>
|
||||
<div class="control-panel">
|
||||
<h3>无人机控制面板</h3>
|
||||
<div class="joystick-wrapper">
|
||||
<div class="joystick-base">
|
||||
<button class="btn-up" onclick="move('up')">↑</button>
|
||||
<button class="btn-left" onclick="move('left')">←</button>
|
||||
<button class="btn-right" onclick="move('right')">→</button>
|
||||
<button class="btn-down" onclick="move('down')">↓</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="speed-control">
|
||||
<label for="speedSlider">速度调节</label>
|
||||
<input type="range" id="speedSlider" min="0" max="100" value="50" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-bar">
|
||||
<p>电池电量: <span id="battery">100%</span></p>
|
||||
<p>信号强度: <span id="signal">良好</span></p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
FalconSense UAV System © 2025
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function returnHome() {
|
||||
alert("启动一键返航功能...");
|
||||
}
|
||||
function move(direction) {
|
||||
alert("无人机向 " + direction + " 移动");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>无人机自动前出侦测界面</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
header, footer {
|
||||
background-color: #f1f1f1;
|
||||
padding: 10px;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
.video-section {
|
||||
flex: 2;
|
||||
border-right: 1px solid #ddd;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.params-section {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.param-list {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
.status-bar {
|
||||
background: #fafafa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
}
|
||||
button {
|
||||
margin: 0 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<button onclick="startDetection()">开始侦测</button>
|
||||
<button onclick="pauseDetection()">暂停侦测</button>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="video-section">
|
||||
<!-- 实时视觉画面,可使用视频流或占位图 -->
|
||||
<img src="https://via.placeholder.com/640x360?text=Real-Time+UAV+Feed" alt="实时画面" />
|
||||
</section>
|
||||
|
||||
<section class="params-section">
|
||||
<div class="param-list">
|
||||
<h3>声源定位参数</h3>
|
||||
<p>方位角: <span id="azimuth">N/A</span></p>
|
||||
<p>距离: <span id="distance">N/A</span></p>
|
||||
</div>
|
||||
<div class="status-bar">
|
||||
<p>电池电量: <span id="battery">100%</span></p>
|
||||
<p>信号强度: <span id="signal">良好</span></p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
FalconSense UAV System © 2025
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function startDetection() {
|
||||
alert("开始侦测");
|
||||
}
|
||||
function pauseDetection() {
|
||||
alert("暂停侦测");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,184 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>系统设置界面</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
header, footer {
|
||||
background-color: #f1f1f1;
|
||||
padding: 10px;
|
||||
}
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
background: #fafafa;
|
||||
}
|
||||
.settings-panel {
|
||||
width: 70%;
|
||||
margin: 20px auto;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.setting-section {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.setting-section h3 {
|
||||
margin-top: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.setting-item label {
|
||||
width: 120px;
|
||||
}
|
||||
.setting-item input[type="text"],
|
||||
.setting-item input[type="number"],
|
||||
.setting-item input[type="range"],
|
||||
.setting-item select {
|
||||
flex: 1;
|
||||
padding: 4px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.setting-hint {
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
}
|
||||
.footer-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.footer-buttons button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.save-button {
|
||||
background: #0078d4;
|
||||
color: #fff;
|
||||
}
|
||||
.save-button:hover {
|
||||
background: #005ea5;
|
||||
}
|
||||
.reset-button {
|
||||
background: #888;
|
||||
color: #fff;
|
||||
}
|
||||
.reset-button:hover {
|
||||
background: #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>系统设置</h2>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="settings-panel">
|
||||
<!-- 无人机设置 -->
|
||||
<div class="setting-section">
|
||||
<h3>无人机设置</h3>
|
||||
<div class="setting-item">
|
||||
<label for="flightHeight">飞行高度</label>
|
||||
<input type="number" id="flightHeight" placeholder="单位: 米" />
|
||||
<span class="setting-hint">推荐值: 50 ~ 150</span>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="flightSpeed">飞行速度限制</label>
|
||||
<input type="number" id="flightSpeed" placeholder="单位: 米/秒" />
|
||||
<span class="setting-hint">推荐值: 5 ~ 15</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 声学设置 -->
|
||||
<div class="setting-section">
|
||||
<h3>声学设置</h3>
|
||||
<div class="setting-item">
|
||||
<label for="micSensitivity">麦克风灵敏度</label>
|
||||
<input type="range" id="micSensitivity" min="0" max="100" value="50" />
|
||||
<span class="setting-hint">推荐值: 40 ~ 60</span>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="noiseThreshold">声源过滤阈值</label>
|
||||
<input type="number" id="noiseThreshold" placeholder="单位: dB" />
|
||||
<span class="setting-hint">推荐值: 10 ~ 20</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视觉设置 -->
|
||||
<div class="setting-section">
|
||||
<h3>视觉设置</h3>
|
||||
<div class="setting-item">
|
||||
<label for="cameraResolution">摄像头分辨率</label>
|
||||
<select id="cameraResolution">
|
||||
<option>1280x720</option>
|
||||
<option>1920x1080</option>
|
||||
<option>2560x1440</option>
|
||||
</select>
|
||||
<span class="setting-hint">推荐: 1920x1080</span>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="frameRate">图像传输频率</label>
|
||||
<input type="number" id="frameRate" placeholder="单位: FPS" />
|
||||
<span class="setting-hint">推荐值: 15 ~ 30</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通信设置 -->
|
||||
<div class="setting-section">
|
||||
<h3>通信设置</h3>
|
||||
<div class="setting-item">
|
||||
<label for="networkConfig">网络连接参数</label>
|
||||
<input type="text" id="networkConfig" placeholder="如: 192.168.1.1:8080" />
|
||||
<span class="setting-hint">输入IP地址与端口信息</span>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="dataRate">数据传输速率</label>
|
||||
<input type="number" id="dataRate" placeholder="单位: Kbps" />
|
||||
<span class="setting-hint">推荐值: 500 ~ 2000</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-buttons">
|
||||
<button class="save-button" onclick="saveSettings()">保存设置</button>
|
||||
<button class="reset-button" onclick="resetSettings()">恢复默认</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
FalconSense UAV System © 2025
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function saveSettings() {
|
||||
alert("设置已保存");
|
||||
}
|
||||
function resetSettings() {
|
||||
alert("已恢复默认设置");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,35 @@
|
||||
@startuml
|
||||
title 分布式部署架构
|
||||
|
||||
node "指挥中心" <<device>> {
|
||||
node "指挥终端" <<device>> {
|
||||
artifact "Windows/Ubuntu OS"
|
||||
artifact "主界面、自动侦测等前端界面" as GUI
|
||||
}
|
||||
|
||||
node "计算节点" <<device>> {
|
||||
artifact "Ubuntu OS"
|
||||
artifact "定位与建图子系统"
|
||||
artifact "MySQL 数据库管理系统"
|
||||
}
|
||||
|
||||
node "数据库服务器" <<device>> {
|
||||
artifact "专用数据库服务器环境"
|
||||
artifact "MySQL DB"
|
||||
}
|
||||
}
|
||||
|
||||
node "战场环境" <<device>> {
|
||||
node "无人机平台" <<device>> {
|
||||
artifact "嵌入式Linux OS"
|
||||
artifact "麦克风阵列、摄像头、声卡、GPS等"
|
||||
artifact "声源定位与检测程序"
|
||||
}
|
||||
}
|
||||
|
||||
' 连接关系
|
||||
"无人机平台" -[#blue]-> "指挥终端" : WebSocket (4G/5G/军事频段)\n声学+视觉数据传输
|
||||
"指挥终端" -[#red]-> "计算节点" : HTTPS RESTful API
|
||||
"计算节点" -[#teal]-> "数据库服务器" : MySQL通信
|
||||
|
||||
@enduml
|
||||
@ -0,0 +1,69 @@
|
||||
# Details
|
||||
|
||||
Date : 2025-06-10 08:55:17
|
||||
|
||||
Directory e:\\pycharm_projects\\AudioClassification-Pytorch-master
|
||||
|
||||
Total : 54 files, 6851 codes, 838 comments, 1201 blanks, all 8890 lines
|
||||
|
||||
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
|
||||
|
||||
## Files
|
||||
| filename | language | code | comment | blank | total |
|
||||
| :--- | :--- | ---: | ---: | ---: | ---: |
|
||||
| [README.md](/README.md) | Markdown | 302 | 0 | 72 | 374 |
|
||||
| [README\_en.md](/README_en.md) | Markdown | 231 | 0 | 45 | 276 |
|
||||
| [audio-classification-platform/README.md](/audio-classification-platform/README.md) | Markdown | 96 | 0 | 41 | 137 |
|
||||
| [audio-classification-platform/backend/app.py](/audio-classification-platform/backend/app.py) | Python | 255 | 35 | 61 | 351 |
|
||||
| [audio-classification-platform/backend/config.py](/audio-classification-platform/backend/config.py) | Python | 22 | 8 | 10 | 40 |
|
||||
| [audio-classification-platform/backend/requirements.txt](/audio-classification-platform/backend/requirements.txt) | pip requirements | 7 | 0 | 1 | 8 |
|
||||
| [audio-classification-platform/frontend/index.html](/audio-classification-platform/frontend/index.html) | HTML | 25 | 0 | 2 | 27 |
|
||||
| [audio-classification-platform/frontend/package.json](/audio-classification-platform/frontend/package.json) | JSON | 24 | 0 | 1 | 25 |
|
||||
| [audio-classification-platform/frontend/src/App.vue](/audio-classification-platform/frontend/src/App.vue) | Vue | 88 | 5 | 14 | 107 |
|
||||
| [audio-classification-platform/frontend/src/components/AudioRecorder.vue](/audio-classification-platform/frontend/src/components/AudioRecorder.vue) | Vue | 551 | 38 | 102 | 691 |
|
||||
| [audio-classification-platform/frontend/src/components/AudioRecorder\_new.vue](/audio-classification-platform/frontend/src/components/AudioRecorder_new.vue) | Vue | 811 | 48 | 162 | 1,021 |
|
||||
| [audio-classification-platform/frontend/src/components/AudioUpload.vue](/audio-classification-platform/frontend/src/components/AudioUpload.vue) | Vue | 580 | 28 | 107 | 715 |
|
||||
| [audio-classification-platform/frontend/src/components/HistoryList.vue](/audio-classification-platform/frontend/src/components/HistoryList.vue) | Vue | 513 | 25 | 79 | 617 |
|
||||
| [audio-classification-platform/frontend/src/components/PredictionResult.vue](/audio-classification-platform/frontend/src/components/PredictionResult.vue) | Vue | 803 | 22 | 117 | 942 |
|
||||
| [audio-classification-platform/frontend/src/main.js](/audio-classification-platform/frontend/src/main.js) | JavaScript | 13 | 1 | 5 | 19 |
|
||||
| [audio-classification-platform/frontend/src/router/index.js](/audio-classification-platform/frontend/src/router/index.js) | JavaScript | 14 | 0 | 4 | 18 |
|
||||
| [audio-classification-platform/frontend/src/utils/api.js](/audio-classification-platform/frontend/src/utils/api.js) | JavaScript | 156 | 25 | 33 | 214 |
|
||||
| [audio-classification-platform/frontend/src/views/HomePage.vue](/audio-classification-platform/frontend/src/views/HomePage.vue) | Vue | 692 | 40 | 110 | 842 |
|
||||
| [audio-classification-platform/frontend/vite.config.js](/audio-classification-platform/frontend/vite.config.js) | JavaScript | 32 | 0 | 2 | 34 |
|
||||
| [audio-classification-platform/start.bat](/audio-classification-platform/start.bat) | Batch | 20 | 0 | 5 | 25 |
|
||||
| [audio-classification-platform/start.sh](/audio-classification-platform/start.sh) | Shell Script | 26 | 3 | 8 | 37 |
|
||||
| [configs/augmentation.yml](/configs/augmentation.yml) | YAML | 21 | 21 | 5 | 47 |
|
||||
| [configs/cam++.yml](/configs/cam++.yml) | YAML | 43 | 33 | 5 | 81 |
|
||||
| [configs/ecapa\_tdnn.yml](/configs/ecapa_tdnn.yml) | YAML | 43 | 33 | 5 | 81 |
|
||||
| [configs/eres2net.yml](/configs/eres2net.yml) | YAML | 43 | 33 | 5 | 81 |
|
||||
| [configs/panns.yml](/configs/panns.yml) | YAML | 43 | 33 | 5 | 81 |
|
||||
| [configs/res2net.yml](/configs/res2net.yml) | YAML | 43 | 33 | 5 | 81 |
|
||||
| [configs/resnet\_se.yml](/configs/resnet_se.yml) | YAML | 43 | 33 | 5 | 81 |
|
||||
| [configs/tdnn.yml](/configs/tdnn.yml) | YAML | 43 | 33 | 5 | 81 |
|
||||
| [create\_data.py](/create_data.py) | Python | 75 | 9 | 16 | 100 |
|
||||
| [eval.py](/eval.py) | Python | 20 | 2 | 5 | 27 |
|
||||
| [extract\_features.py](/extract_features.py) | Python | 13 | 2 | 5 | 20 |
|
||||
| [infer.py](/infer.py) | Python | 17 | 1 | 6 | 24 |
|
||||
| [infer\_record.py](/infer_record.py) | Python | 40 | 7 | 12 | 59 |
|
||||
| [macls/\_\_init\_\_.py](/macls/__init__.py) | Python | 1 | 0 | 1 | 2 |
|
||||
| [macls/data\_utils/\_\_init\_\_.py](/macls/data_utils/__init__.py) | Python | 0 | 0 | 1 | 1 |
|
||||
| [macls/data\_utils/collate\_fn.py](/macls/data_utils/collate_fn.py) | Python | 17 | 4 | 3 | 24 |
|
||||
| [macls/data\_utils/featurizer.py](/macls/data_utils/featurizer.py) | Python | 88 | 36 | 9 | 133 |
|
||||
| [macls/data\_utils/reader.py](/macls/data_utils/reader.py) | Python | 114 | 33 | 11 | 158 |
|
||||
| [macls/metric/\_\_init\_\_.py](/macls/metric/__init__.py) | Python | 0 | 0 | 1 | 1 |
|
||||
| [macls/metric/metrics.py](/macls/metric/metrics.py) | Python | 9 | 1 | 3 | 13 |
|
||||
| [macls/optimizer/\_\_init\_\_.py](/macls/optimizer/__init__.py) | Python | 26 | 0 | 7 | 33 |
|
||||
| [macls/optimizer/scheduler.py](/macls/optimizer/scheduler.py) | Python | 42 | 0 | 7 | 49 |
|
||||
| [macls/predict.py](/macls/predict.py) | Python | 124 | 47 | 7 | 178 |
|
||||
| [macls/trainer.py](/macls/trainer.py) | Python | 338 | 99 | 20 | 457 |
|
||||
| [macls/utils/\_\_init\_\_.py](/macls/utils/__init__.py) | Python | 0 | 0 | 1 | 1 |
|
||||
| [macls/utils/checkpoint.py](/macls/utils/checkpoint.py) | Python | 113 | 40 | 10 | 163 |
|
||||
| [macls/utils/record.py](/macls/utils/record.py) | Python | 18 | 8 | 6 | 32 |
|
||||
| [macls/utils/utils.py](/macls/utils/utils.py) | Python | 99 | 16 | 17 | 132 |
|
||||
| [record\_audio.py](/record_audio.py) | Python | 10 | 0 | 5 | 15 |
|
||||
| [requirements.txt](/requirements.txt) | pip requirements | 17 | 0 | 1 | 18 |
|
||||
| [setup.py](/setup.py) | Python | 43 | 1 | 11 | 55 |
|
||||
| [tools/download\_language\_data.sh](/tools/download_language_data.sh) | Shell Script | 19 | 1 | 10 | 30 |
|
||||
| [train.py](/train.py) | Python | 25 | 1 | 5 | 31 |
|
||||
|
||||
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
|
||||
@ -0,0 +1,15 @@
|
||||
# Diff Details
|
||||
|
||||
Date : 2025-06-10 08:55:17
|
||||
|
||||
Directory e:\\pycharm_projects\\AudioClassification-Pytorch-master
|
||||
|
||||
Total : 0 files, 0 codes, 0 comments, 0 blanks, all 0 lines
|
||||
|
||||
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details
|
||||
|
||||
## Files
|
||||
| filename | language | code | comment | blank | total |
|
||||
| :--- | :--- | ---: | ---: | ---: | ---: |
|
||||
|
||||
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Diff Summary
|
||||
|
||||
Date : 2025-06-10 08:55:17
|
||||
|
||||
Directory e:\\pycharm_projects\\AudioClassification-Pytorch-master
|
||||
|
||||
Total : 0 files, 0 codes, 0 comments, 0 blanks, all 0 lines
|
||||
|
||||
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)
|
||||
|
||||
## Languages
|
||||
| language | files | code | comment | blank | total |
|
||||
| :--- | ---: | ---: | ---: | ---: | ---: |
|
||||
|
||||
## Directories
|
||||
| path | files | code | comment | blank | total |
|
||||
| :--- | ---: | ---: | ---: | ---: | ---: |
|
||||
|
||||
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)
|
||||
@ -0,0 +1,22 @@
|
||||
Date : 2025-06-10 08:55:17
|
||||
Directory : e:\pycharm_projects\AudioClassification-Pytorch-master
|
||||
Total : 0 files, 0 codes, 0 comments, 0 blanks, all 0 lines
|
||||
|
||||
Languages
|
||||
+----------+------------+------------+------------+------------+------------+
|
||||
| language | files | code | comment | blank | total |
|
||||
+----------+------------+------------+------------+------------+------------+
|
||||
+----------+------------+------------+------------+------------+------------+
|
||||
|
||||
Directories
|
||||
+------+------------+------------+------------+------------+------------+
|
||||
| path | files | code | comment | blank | total |
|
||||
+------+------------+------------+------------+------------+------------+
|
||||
+------+------------+------------+------------+------------+------------+
|
||||
|
||||
Files
|
||||
+----------+----------+------------+------------+------------+------------+
|
||||
| filename | language | code | comment | blank | total |
|
||||
+----------+----------+------------+------------+------------+------------+
|
||||
| Total | | 0 | 0 | 0 | 0 |
|
||||
+----------+----------+------------+------------+------------+------------+
|
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,50 @@
|
||||
# Summary
|
||||
|
||||
Date : 2025-06-10 08:55:17
|
||||
|
||||
Directory e:\\pycharm_projects\\AudioClassification-Pytorch-master
|
||||
|
||||
Total : 54 files, 6851 codes, 838 comments, 1201 blanks, all 8890 lines
|
||||
|
||||
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
|
||||
|
||||
## Languages
|
||||
| language | files | code | comment | blank | total |
|
||||
| :--- | ---: | ---: | ---: | ---: | ---: |
|
||||
| Vue | 7 | 4,038 | 206 | 691 | 4,935 |
|
||||
| Python | 25 | 1,509 | 350 | 240 | 2,099 |
|
||||
| Markdown | 3 | 629 | 0 | 158 | 787 |
|
||||
| YAML | 8 | 322 | 252 | 40 | 614 |
|
||||
| JavaScript | 4 | 215 | 26 | 44 | 285 |
|
||||
| Shell Script | 2 | 45 | 4 | 18 | 67 |
|
||||
| HTML | 1 | 25 | 0 | 2 | 27 |
|
||||
| pip requirements | 2 | 24 | 0 | 2 | 26 |
|
||||
| JSON | 1 | 24 | 0 | 1 | 25 |
|
||||
| Batch | 1 | 20 | 0 | 5 | 25 |
|
||||
|
||||
## Directories
|
||||
| path | files | code | comment | blank | total |
|
||||
| :--- | ---: | ---: | ---: | ---: | ---: |
|
||||
| . | 54 | 6,851 | 838 | 1,201 | 8,890 |
|
||||
| . (Files) | 11 | 793 | 23 | 183 | 999 |
|
||||
| audio-classification-platform | 19 | 4,728 | 278 | 864 | 5,870 |
|
||||
| audio-classification-platform (Files) | 3 | 142 | 3 | 54 | 199 |
|
||||
| audio-classification-platform\\backend | 3 | 284 | 43 | 72 | 399 |
|
||||
| audio-classification-platform\\frontend | 13 | 4,302 | 232 | 738 | 5,272 |
|
||||
| audio-classification-platform\\frontend (Files) | 3 | 81 | 0 | 5 | 86 |
|
||||
| audio-classification-platform\\frontend\\src | 10 | 4,221 | 232 | 733 | 5,186 |
|
||||
| audio-classification-platform\\frontend\\src (Files) | 2 | 101 | 6 | 19 | 126 |
|
||||
| audio-classification-platform\\frontend\\src\\components | 5 | 3,258 | 161 | 567 | 3,986 |
|
||||
| audio-classification-platform\\frontend\\src\\router | 1 | 14 | 0 | 4 | 18 |
|
||||
| audio-classification-platform\\frontend\\src\\utils | 1 | 156 | 25 | 33 | 214 |
|
||||
| audio-classification-platform\\frontend\\src\\views | 1 | 692 | 40 | 110 | 842 |
|
||||
| configs | 8 | 322 | 252 | 40 | 614 |
|
||||
| macls | 15 | 989 | 284 | 104 | 1,377 |
|
||||
| macls (Files) | 3 | 463 | 146 | 28 | 637 |
|
||||
| macls\\data_utils | 4 | 219 | 73 | 24 | 316 |
|
||||
| macls\\metric | 2 | 9 | 1 | 4 | 14 |
|
||||
| macls\\optimizer | 2 | 68 | 0 | 14 | 82 |
|
||||
| macls\\utils | 4 | 230 | 64 | 34 | 328 |
|
||||
| tools | 1 | 19 | 1 | 10 | 30 |
|
||||
|
||||
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
|
||||
@ -0,0 +1,107 @@
|
||||
Date : 2025-06-10 08:55:17
|
||||
Directory : e:\pycharm_projects\AudioClassification-Pytorch-master
|
||||
Total : 54 files, 6851 codes, 838 comments, 1201 blanks, all 8890 lines
|
||||
|
||||
Languages
|
||||
+------------------+------------+------------+------------+------------+------------+
|
||||
| language | files | code | comment | blank | total |
|
||||
+------------------+------------+------------+------------+------------+------------+
|
||||
| Vue | 7 | 4,038 | 206 | 691 | 4,935 |
|
||||
| Python | 25 | 1,509 | 350 | 240 | 2,099 |
|
||||
| Markdown | 3 | 629 | 0 | 158 | 787 |
|
||||
| YAML | 8 | 322 | 252 | 40 | 614 |
|
||||
| JavaScript | 4 | 215 | 26 | 44 | 285 |
|
||||
| Shell Script | 2 | 45 | 4 | 18 | 67 |
|
||||
| HTML | 1 | 25 | 0 | 2 | 27 |
|
||||
| pip requirements | 2 | 24 | 0 | 2 | 26 |
|
||||
| JSON | 1 | 24 | 0 | 1 | 25 |
|
||||
| Batch | 1 | 20 | 0 | 5 | 25 |
|
||||
+------------------+------------+------------+------------+------------+------------+
|
||||
|
||||
Directories
|
||||
+------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
|
||||
| path | files | code | comment | blank | total |
|
||||
+------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
|
||||
| . | 54 | 6,851 | 838 | 1,201 | 8,890 |
|
||||
| . (Files) | 11 | 793 | 23 | 183 | 999 |
|
||||
| audio-classification-platform | 19 | 4,728 | 278 | 864 | 5,870 |
|
||||
| audio-classification-platform (Files) | 3 | 142 | 3 | 54 | 199 |
|
||||
| audio-classification-platform\backend | 3 | 284 | 43 | 72 | 399 |
|
||||
| audio-classification-platform\frontend | 13 | 4,302 | 232 | 738 | 5,272 |
|
||||
| audio-classification-platform\frontend (Files) | 3 | 81 | 0 | 5 | 86 |
|
||||
| audio-classification-platform\frontend\src | 10 | 4,221 | 232 | 733 | 5,186 |
|
||||
| audio-classification-platform\frontend\src (Files) | 2 | 101 | 6 | 19 | 126 |
|
||||
| audio-classification-platform\frontend\src\components | 5 | 3,258 | 161 | 567 | 3,986 |
|
||||
| audio-classification-platform\frontend\src\router | 1 | 14 | 0 | 4 | 18 |
|
||||
| audio-classification-platform\frontend\src\utils | 1 | 156 | 25 | 33 | 214 |
|
||||
| audio-classification-platform\frontend\src\views | 1 | 692 | 40 | 110 | 842 |
|
||||
| configs | 8 | 322 | 252 | 40 | 614 |
|
||||
| macls | 15 | 989 | 284 | 104 | 1,377 |
|
||||
| macls (Files) | 3 | 463 | 146 | 28 | 637 |
|
||||
| macls\data_utils | 4 | 219 | 73 | 24 | 316 |
|
||||
| macls\metric | 2 | 9 | 1 | 4 | 14 |
|
||||
| macls\optimizer | 2 | 68 | 0 | 14 | 82 |
|
||||
| macls\utils | 4 | 230 | 64 | 34 | 328 |
|
||||
| tools | 1 | 19 | 1 | 10 | 30 |
|
||||
+------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
|
||||
|
||||
Files
|
||||
+------------------------------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
|
||||
| filename | language | code | comment | blank | total |
|
||||
+------------------------------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\README.md | Markdown | 302 | 0 | 72 | 374 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\README_en.md | Markdown | 231 | 0 | 45 | 276 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\README.md | Markdown | 96 | 0 | 41 | 137 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\backend\app.py | Python | 255 | 35 | 61 | 351 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\backend\config.py | Python | 22 | 8 | 10 | 40 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\backend\requirements.txt | pip requirements | 7 | 0 | 1 | 8 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\index.html | HTML | 25 | 0 | 2 | 27 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\package.json | JSON | 24 | 0 | 1 | 25 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\App.vue | Vue | 88 | 5 | 14 | 107 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\AudioRecorder.vue | Vue | 551 | 38 | 102 | 691 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\AudioRecorder_new.vue | Vue | 811 | 48 | 162 | 1,021 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\AudioUpload.vue | Vue | 580 | 28 | 107 | 715 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\HistoryList.vue | Vue | 513 | 25 | 79 | 617 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\components\PredictionResult.vue | Vue | 803 | 22 | 117 | 942 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\main.js | JavaScript | 13 | 1 | 5 | 19 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\router\index.js | JavaScript | 14 | 0 | 4 | 18 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\utils\api.js | JavaScript | 156 | 25 | 33 | 214 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\src\views\HomePage.vue | Vue | 692 | 40 | 110 | 842 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\frontend\vite.config.js | JavaScript | 32 | 0 | 2 | 34 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\start.bat | Batch | 20 | 0 | 5 | 25 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\audio-classification-platform\start.sh | Shell Script | 26 | 3 | 8 | 37 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\augmentation.yml | YAML | 21 | 21 | 5 | 47 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\cam++.yml | YAML | 43 | 33 | 5 | 81 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\ecapa_tdnn.yml | YAML | 43 | 33 | 5 | 81 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\eres2net.yml | YAML | 43 | 33 | 5 | 81 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\panns.yml | YAML | 43 | 33 | 5 | 81 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\res2net.yml | YAML | 43 | 33 | 5 | 81 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\resnet_se.yml | YAML | 43 | 33 | 5 | 81 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\configs\tdnn.yml | YAML | 43 | 33 | 5 | 81 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\create_data.py | Python | 75 | 9 | 16 | 100 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\eval.py | Python | 20 | 2 | 5 | 27 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\extract_features.py | Python | 13 | 2 | 5 | 20 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\infer.py | Python | 17 | 1 | 6 | 24 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\infer_record.py | Python | 40 | 7 | 12 | 59 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\__init__.py | Python | 1 | 0 | 1 | 2 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\__init__.py | Python | 0 | 0 | 1 | 1 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\collate_fn.py | Python | 17 | 4 | 3 | 24 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\featurizer.py | Python | 88 | 36 | 9 | 133 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\data_utils\reader.py | Python | 114 | 33 | 11 | 158 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\metric\__init__.py | Python | 0 | 0 | 1 | 1 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\metric\metrics.py | Python | 9 | 1 | 3 | 13 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\optimizer\__init__.py | Python | 26 | 0 | 7 | 33 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\optimizer\scheduler.py | Python | 42 | 0 | 7 | 49 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\predict.py | Python | 124 | 47 | 7 | 178 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\trainer.py | Python | 338 | 99 | 20 | 457 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\__init__.py | Python | 0 | 0 | 1 | 1 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\checkpoint.py | Python | 113 | 40 | 10 | 163 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\record.py | Python | 18 | 8 | 6 | 32 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\macls\utils\utils.py | Python | 99 | 16 | 17 | 132 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\record_audio.py | Python | 10 | 0 | 5 | 15 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\requirements.txt | pip requirements | 17 | 0 | 1 | 18 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\setup.py | Python | 43 | 1 | 11 | 55 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\tools\download_language_data.sh | Shell Script | 19 | 1 | 10 | 30 |
|
||||
| e:\pycharm_projects\AudioClassification-Pytorch-master\train.py | Python | 25 | 1 | 5 | 31 |
|
||||
| Total | | 6,851 | 838 | 1,201 | 8,890 |
|
||||
+------------------------------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
|
||||
@ -0,0 +1,296 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
macls.egg-info/
|
||||
|
||||
# PyInstaller
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django/Flask stuff
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
Pipfile.lock
|
||||
|
||||
# PEP 582
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# Node.js dependencies
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
package-lock.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
.env.local
|
||||
.env.production
|
||||
|
||||
# parcel-bundler cache
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
|
||||
# Gatsby files
|
||||
# Comment in the public line in if your project uses Gatsby
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# Audio Classification specific ignores
|
||||
# Model files (usually large)
|
||||
*.pth
|
||||
*.pt
|
||||
*.h5
|
||||
*.ckpt
|
||||
*.pb
|
||||
*.onnx
|
||||
*.pkl
|
||||
*.joblib
|
||||
|
||||
# Dataset directories (usually large audio files)
|
||||
dataset/
|
||||
dataset/*/audio/
|
||||
dataset/*/wav/
|
||||
dataset/*/mp3/
|
||||
dataset/*/flac/
|
||||
# Uncomment if you want to ignore all audio files
|
||||
# *.wav
|
||||
# *.mp3
|
||||
# *.flac
|
||||
# *.ogg
|
||||
# *.m4a
|
||||
# *.aac
|
||||
|
||||
# Training artifacts and logs
|
||||
log/
|
||||
logs/
|
||||
output/
|
||||
outputs/
|
||||
uploads/
|
||||
results/
|
||||
checkpoints/
|
||||
models/
|
||||
pretrained_models/
|
||||
feature_models/
|
||||
runs/
|
||||
wandb/
|
||||
mlruns/
|
||||
.mlflow/
|
||||
|
||||
# Temporary files
|
||||
temp/
|
||||
tmp/
|
||||
*.tmp
|
||||
test*.py
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Audio processing temporary files
|
||||
*.spec
|
||||
*.mfcc
|
||||
*.mel
|
||||
|
||||
# Frontend build files
|
||||
audio-classification-platform/frontend/dist/
|
||||
audio-classification-platform/frontend/build/
|
||||
audio-classification-platform/frontend/.vite/
|
||||
|
||||
# Uploaded files directory
|
||||
audio-classification-platform/backend/uploads/
|
||||
|
||||
# Local development configuration
|
||||
audio-classification-platform/backend/.env
|
||||
audio-classification-platform/frontend/.env.local
|
||||
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@ -0,0 +1,39 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 配置
|
||||
class Config:
|
||||
# 模型配置
|
||||
MODEL_CONFIG_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../configs/cam++.yml'))
|
||||
MODEL_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../models/CAMPPlus_Fbank/best_model/'))
|
||||
LABEL_FILE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../dataset/label_list.txt'))
|
||||
|
||||
# Flask配置
|
||||
SECRET_KEY = 'your-secret-key-here'
|
||||
MAX_CONTENT_LENGTH = 50 * 1024 * 1024 # 50MB
|
||||
|
||||
# 文件上传配置
|
||||
UPLOAD_FOLDER = 'uploads'
|
||||
ALLOWED_EXTENSIONS = {'wav', 'mp3', 'flac', 'm4a', 'ogg', 'aac'}
|
||||
|
||||
# 音频处理配置
|
||||
TARGET_SAMPLE_RATE = 16000
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL = 'INFO'
|
||||
|
||||
# GPU配置
|
||||
USE_GPU = True
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
DEBUG = True
|
||||
|
||||
class ProductionConfig(Config):
|
||||
DEBUG = False
|
||||
|
||||
# 根据环境变量选择配置
|
||||
config = {
|
||||
'development': DevelopmentConfig,
|
||||
'production': ProductionConfig,
|
||||
'default': DevelopmentConfig
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
Flask==2.3.3
|
||||
Flask-CORS==4.0.0
|
||||
librosa==0.10.1
|
||||
soundfile==0.12.1
|
||||
numpy==1.24.3
|
||||
loguru==0.7.2
|
||||
Werkzeug==2.3.7
|
||||
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>声纹识别系统</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "audio-classification-frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"serve": "vite preview"
|
||||
}, "dependencies": {
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4",
|
||||
"axios": "^1.5.0",
|
||||
"element-plus": "^2.3.9",
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"echarts": "^5.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.3.4",
|
||||
"vite": "^4.4.9",
|
||||
"unplugin-vue-components": "^0.25.2",
|
||||
"unplugin-auto-import": "^0.16.6"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 主应用组件
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
|
||||
font-family: 'Inter', 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||
color: #333;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 全局美化样式 */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* 自定义滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 全局动画 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInLeft {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
#app {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,690 @@
|
||||
<template>
|
||||
<div class="audio-recorder">
|
||||
<div class="recorder-content">
|
||||
<!-- 录音状态显示 -->
|
||||
<div class="recorder-status">
|
||||
<div class="status-indicator" :class="{
|
||||
active: isRecording,
|
||||
ready: isReady,
|
||||
processing: isProcessing
|
||||
}">
|
||||
<div class="status-bg"></div>
|
||||
<transition name="icon-switch" mode="out-in">
|
||||
<el-icon class="status-icon processing" v-if="isProcessing" key="processing">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<el-icon class="status-icon recording" v-else-if="isRecording" key="recording">
|
||||
<Microphone />
|
||||
</el-icon>
|
||||
<el-icon class="status-icon ready" v-else-if="isReady" key="ready">
|
||||
<Microphone />
|
||||
</el-icon>
|
||||
<el-icon class="status-icon default" v-else key="default">
|
||||
<MicrophoneOne />
|
||||
</el-icon>
|
||||
</transition>
|
||||
|
||||
<!-- 录音波纹效果 -->
|
||||
<div v-if="isRecording" class="recording-waves">
|
||||
<div class="wave wave-1"></div>
|
||||
<div class="wave wave-2"></div>
|
||||
<div class="wave wave-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status-text">
|
||||
<h3 class="status-title">{{ statusTitle }}</h3>
|
||||
<p class="primary-text">{{ statusText }}</p>
|
||||
<transition name="slide-down">
|
||||
<p class="secondary-text" v-if="recordingTime > 0">
|
||||
<el-icon><Timer /></el-icon>
|
||||
录音时长: {{ formatTime(recordingTime) }}
|
||||
</p>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 录音控制按钮 -->
|
||||
<div class="recorder-controls">
|
||||
<transition name="button-switch" mode="out-in">
|
||||
<el-button
|
||||
v-if="!isRecording && !isProcessing"
|
||||
type="primary"
|
||||
size="large"
|
||||
:disabled="disabled || !isReady"
|
||||
@click="startRecording"
|
||||
class="record-button"
|
||||
key="start"
|
||||
>
|
||||
<template #icon>
|
||||
<el-icon><Microphone /></el-icon>
|
||||
</template>
|
||||
<span>开始录音</span>
|
||||
<div class="button-glow"></div>
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-else-if="isRecording"
|
||||
type="danger"
|
||||
size="large"
|
||||
@click="stopRecording"
|
||||
class="stop-button"
|
||||
key="stop"
|
||||
>
|
||||
<template #icon>
|
||||
<el-icon><VideoPause /></el-icon>
|
||||
</template>
|
||||
<span>停止录音</span>
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-else
|
||||
size="large"
|
||||
loading
|
||||
class="processing-button"
|
||||
key="processing"
|
||||
>
|
||||
<span>正在处理...</span>
|
||||
</el-button>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<!-- 录音设置 -->
|
||||
<div class="recorder-settings">
|
||||
<div class="settings-header">
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span>录音设置</span>
|
||||
</div>
|
||||
<el-form :model="settings" label-width="80px" size="small" class="settings-form">
|
||||
<el-form-item label="录音时长">
|
||||
<el-select v-model="settings.duration" :disabled="isRecording" class="duration-select">
|
||||
<el-option label="3秒" :value="3">
|
||||
<div class="option-content">
|
||||
<span>3秒</span>
|
||||
<el-tag size="small" type="info">快速</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
<el-option label="5秒" :value="5">
|
||||
<div class="option-content">
|
||||
<span>5秒</span>
|
||||
<el-tag size="small" type="success">推荐</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
<el-option label="10秒" :value="10">
|
||||
<div class="option-content">
|
||||
<span>10秒</span>
|
||||
<el-tag size="small" type="warning">详细</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
<el-option label="15秒" :value="15" />
|
||||
<el-option label="30秒" :value="30" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="自动停止">
|
||||
<el-switch
|
||||
v-model="settings.autoStop"
|
||||
:disabled="isRecording"
|
||||
active-color="#52c41a"
|
||||
inactive-color="rgba(255, 255, 255, 0.3)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 音频可视化 -->
|
||||
<transition name="fade">
|
||||
<div v-if="isRecording" class="audio-visualizer">
|
||||
<div class="visualizer-header">
|
||||
<el-icon><DataAnalysis /></el-icon>
|
||||
<span>实时音频波形</span>
|
||||
</div>
|
||||
<canvas ref="canvasRef" class="visualizer-canvas"></canvas>
|
||||
<div class="visualizer-info">
|
||||
<div class="info-item">
|
||||
<span class="label">音量:</span>
|
||||
<div class="volume-bar">
|
||||
<div class="volume-fill" :style="{ width: `${volume}%` }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 录音预览 -->
|
||||
<transition name="slide-up">
|
||||
<div v-if="recordedAudio" class="audio-preview">
|
||||
<div class="preview-header">
|
||||
<div class="preview-title">
|
||||
<el-icon class="preview-icon"><Headphone /></el-icon>
|
||||
<span>录音预览</span>
|
||||
</div>
|
||||
<div class="preview-actions">
|
||||
<el-button type="text" size="small" @click="playRecording" class="action-button">
|
||||
<el-icon><VideoPlay /></el-icon>
|
||||
播放
|
||||
</el-button>
|
||||
<el-button type="text" size="small" @click="clearRecording" class="action-button danger">
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="audio-player-container">
|
||||
<audio
|
||||
ref="audioPlayerRef"
|
||||
controls
|
||||
:src="recordedAudio.url"
|
||||
class="audio-player"
|
||||
>
|
||||
您的浏览器不支持音频播放
|
||||
</audio>
|
||||
</div>
|
||||
<div class="preview-info">
|
||||
<div class="info-item">
|
||||
<el-icon><Timer /></el-icon>
|
||||
<span>时长: {{ formatTime(recordedAudio.duration) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<el-icon><DataBoard /></el-icon>
|
||||
<span>大小: {{ formatFileSize(recordedAudio.size) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="submitRecording"
|
||||
:loading="isSubmitting"
|
||||
class="submit-button"
|
||||
>
|
||||
识别录音
|
||||
</el-button>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { apiService } from '../utils/api'
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(['record-success', 'record-error'])
|
||||
|
||||
// 响应式数据
|
||||
const isRecording = ref(false)
|
||||
const isReady = ref(false)
|
||||
const isSubmitting = ref(false)
|
||||
const isProcessing = ref(false)
|
||||
const recordingTime = ref(0)
|
||||
const recordedAudio = ref(null)
|
||||
const canvasRef = ref()
|
||||
const audioPlayerRef = ref()
|
||||
const volume = ref(0)
|
||||
|
||||
// 录音相关变量
|
||||
let mediaRecorder = null
|
||||
let audioChunks = []
|
||||
let recordingTimer = null
|
||||
let audioContext = null
|
||||
let analyser = null
|
||||
let microphone = null
|
||||
let animationId = null
|
||||
|
||||
// 录音设置
|
||||
const settings = ref({
|
||||
duration: 5, // 默认5秒
|
||||
autoStop: true
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const statusTitle = computed(() => {
|
||||
if (isProcessing.value) return '处理中'
|
||||
if (isRecording.value) return '录音中'
|
||||
if (isReady.value) return '就绪'
|
||||
return '准备中'
|
||||
})
|
||||
|
||||
const statusText = computed(() => {
|
||||
if (isProcessing.value) return '正在处理录音数据...'
|
||||
if (!isReady.value) return '正在初始化麦克风...'
|
||||
if (isRecording.value) return '正在录音中,请对着麦克风说话'
|
||||
return '点击开始录音按钮进行音频识别'
|
||||
})
|
||||
|
||||
// 初始化录音
|
||||
const initRecorder = async () => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
sampleRate: 16000,
|
||||
channelCount: 1,
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true
|
||||
}
|
||||
})
|
||||
|
||||
// 创建MediaRecorder
|
||||
mediaRecorder = new MediaRecorder(stream, {
|
||||
mimeType: 'audio/webm;codecs=opus'
|
||||
})
|
||||
|
||||
// 设置音频上下文用于可视化
|
||||
audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
||||
analyser = audioContext.createAnalyser()
|
||||
microphone = audioContext.createMediaStreamSource(stream)
|
||||
microphone.connect(analyser)
|
||||
|
||||
analyser.fftSize = 256
|
||||
|
||||
// 事件监听
|
||||
mediaRecorder.ondataavailable = (event) => {
|
||||
if (event.data.size > 0) {
|
||||
audioChunks.push(event.data)
|
||||
}
|
||||
}
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' })
|
||||
createAudioPreview(audioBlob)
|
||||
audioChunks = []
|
||||
}
|
||||
|
||||
isReady.value = true
|
||||
ElMessage.success('麦克风初始化成功')
|
||||
|
||||
} catch (error) {
|
||||
console.error('麦克风初始化失败:', error)
|
||||
ElMessage.error('无法访问麦克风,请检查权限设置')
|
||||
}
|
||||
}
|
||||
|
||||
// 开始录音
|
||||
const startRecording = () => {
|
||||
if (!mediaRecorder || mediaRecorder.state !== 'inactive') return
|
||||
|
||||
audioChunks = []
|
||||
recordingTime.value = 0
|
||||
isRecording.value = true
|
||||
|
||||
mediaRecorder.start()
|
||||
|
||||
// 开始计时
|
||||
recordingTimer = setInterval(() => {
|
||||
recordingTime.value++
|
||||
|
||||
// 自动停止
|
||||
if (settings.value.autoStop && recordingTime.value >= settings.value.duration) {
|
||||
stopRecording()
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
// 开始音频可视化
|
||||
startVisualization()
|
||||
|
||||
ElMessage.info('开始录音')
|
||||
}
|
||||
|
||||
// 停止录音
|
||||
const stopRecording = () => {
|
||||
if (!mediaRecorder || mediaRecorder.state !== 'recording') return
|
||||
|
||||
isRecording.value = false
|
||||
mediaRecorder.stop()
|
||||
|
||||
// 清除计时器
|
||||
if (recordingTimer) {
|
||||
clearInterval(recordingTimer)
|
||||
recordingTimer = null
|
||||
}
|
||||
|
||||
// 停止可视化
|
||||
stopVisualization()
|
||||
|
||||
ElMessage.success('录音完成')
|
||||
}
|
||||
|
||||
// 创建音频预览
|
||||
const createAudioPreview = (audioBlob) => {
|
||||
const url = URL.createObjectURL(audioBlob)
|
||||
recordedAudio.value = {
|
||||
blob: audioBlob,
|
||||
url: url,
|
||||
duration: recordingTime.value,
|
||||
size: audioBlob.size
|
||||
}
|
||||
}
|
||||
|
||||
// 播放录音
|
||||
const playRecording = () => {
|
||||
if (audioPlayerRef.value) {
|
||||
audioPlayerRef.value.play()
|
||||
}
|
||||
}
|
||||
|
||||
// 清除录音
|
||||
const clearRecording = () => {
|
||||
if (recordedAudio.value) {
|
||||
URL.revokeObjectURL(recordedAudio.value.url)
|
||||
recordedAudio.value = null
|
||||
}
|
||||
recordingTime.value = 0
|
||||
}
|
||||
|
||||
// 提交录音进行识别
|
||||
const submitRecording = async () => {
|
||||
if (!recordedAudio.value) return
|
||||
|
||||
isSubmitting.value = true
|
||||
|
||||
try {
|
||||
// 使用Web Audio API解码音频数据
|
||||
const arrayBuffer = await recordedAudio.value.blob.arrayBuffer()
|
||||
|
||||
// 创建临时音频上下文用于解码
|
||||
const tempAudioContext = new (window.AudioContext || window.webkitAudioContext)()
|
||||
const audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer)
|
||||
|
||||
// 获取第一个声道的音频数据
|
||||
const channelData = audioBuffer.getChannelData(0)
|
||||
const audioData = Array.from(channelData)
|
||||
|
||||
// 关闭临时音频上下文
|
||||
await tempAudioContext.close()
|
||||
|
||||
const response = await apiService.predictAudioData({
|
||||
audio_data: audioData,
|
||||
sample_rate: audioBuffer.sampleRate
|
||||
})
|
||||
|
||||
if (response.data.status === 'success') {
|
||||
emit('record-success', response.data.result)
|
||||
clearRecording()
|
||||
} else {
|
||||
throw new Error(response.data.message)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
emit('record-error', error.message)
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 开始音频可视化
|
||||
const startVisualization = () => {
|
||||
if (!canvasRef.value || !analyser) return
|
||||
|
||||
const canvas = canvasRef.value
|
||||
const ctx = canvas.getContext('2d')
|
||||
const bufferLength = analyser.frequencyBinCount
|
||||
const dataArray = new Uint8Array(bufferLength)
|
||||
|
||||
const draw = () => {
|
||||
if (!isRecording.value) return
|
||||
|
||||
animationId = requestAnimationFrame(draw)
|
||||
|
||||
analyser.getByteFrequencyData(dataArray)
|
||||
|
||||
ctx.fillStyle = 'rgb(255, 255, 255)'
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
const barWidth = (canvas.width / bufferLength) * 2.5
|
||||
let barHeight
|
||||
let x = 0
|
||||
|
||||
for (let i = 0; i < bufferLength; i++) {
|
||||
barHeight = dataArray[i] / 255 * canvas.height
|
||||
|
||||
ctx.fillStyle = `rgb(${barHeight + 100}, 102, 234)`
|
||||
ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight)
|
||||
|
||||
x += barWidth + 1
|
||||
}
|
||||
}
|
||||
|
||||
draw()
|
||||
}
|
||||
|
||||
// 停止音频可视化
|
||||
const stopVisualization = () => {
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId)
|
||||
animationId = null
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (seconds) => {
|
||||
const mins = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// 格式化文件大小
|
||||
const formatFileSize = (bytes) => {
|
||||
if (bytes === 0) return '0 B'
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
initRecorder()
|
||||
})
|
||||
|
||||
// 组件卸载
|
||||
onUnmounted(() => {
|
||||
if (recordingTimer) {
|
||||
clearInterval(recordingTimer)
|
||||
}
|
||||
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId)
|
||||
}
|
||||
|
||||
if (mediaRecorder && mediaRecorder.stream) {
|
||||
mediaRecorder.stream.getTracks().forEach(track => track.stop())
|
||||
}
|
||||
|
||||
if (audioContext && audioContext.state !== 'closed') {
|
||||
audioContext.close()
|
||||
}
|
||||
|
||||
clearRecording()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.audio-recorder {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.recorder-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.recorder-status {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #e4e7ed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 15px;
|
||||
transition: all 0.3s ease;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.status-indicator.ready {
|
||||
border-color: #67c23a;
|
||||
background: #f0f9ff;
|
||||
}
|
||||
|
||||
.status-indicator.active {
|
||||
border-color: #e6a23c;
|
||||
background: #fef0e6;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 0 rgba(230, 162, 60, 0.7);
|
||||
}
|
||||
70% {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 0 0 10px rgba(230, 162, 60, 0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 0 rgba(230, 162, 60, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 2.5rem;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.status-indicator.ready .status-icon {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.status-indicator.active .status-icon {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.primary-text {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.secondary-text {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.recorder-controls {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.record-button,
|
||||
.stop-button {
|
||||
min-width: 120px;
|
||||
height: 45px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.recorder-settings {
|
||||
margin-bottom: 25px;
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.audio-visualizer {
|
||||
margin-bottom: 25px;
|
||||
padding: 15px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.visualizer-canvas {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.audio-preview {
|
||||
margin-top: 25px;
|
||||
padding: 20px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.preview-header span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.preview-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.audio-player {
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.preview-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 480px) {
|
||||
.status-indicator {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.record-button,
|
||||
.stop-button {
|
||||
min-width: 100px;
|
||||
height: 40px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,941 @@
|
||||
<template>
|
||||
<div class="prediction-result" v-if="result">
|
||||
<!-- 主要预测结果卡片 -->
|
||||
<div class="main-result-card">
|
||||
<div class="result-header">
|
||||
<div class="result-icon">
|
||||
<el-icon><TrophyBase /></el-icon>
|
||||
</div>
|
||||
<div class="result-title">
|
||||
<h3>识别结果</h3>
|
||||
<p>深度学习模型分析完成</p>
|
||||
</div>
|
||||
<div class="result-actions">
|
||||
<el-button type="primary" size="small" @click="exportResult" class="action-btn">
|
||||
<template #icon><el-icon><Download /></el-icon></template>
|
||||
导出
|
||||
</el-button>
|
||||
<el-button size="small" @click="shareResult" class="action-btn">
|
||||
<template #icon><el-icon><Share /></el-icon></template>
|
||||
分享
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主要预测结果展示 -->
|
||||
<div class="main-prediction">
|
||||
<div class="prediction-badge">
|
||||
<div class="badge-icon">
|
||||
<el-icon><Star /></el-icon>
|
||||
</div>
|
||||
<div class="badge-content">
|
||||
<div class="predicted-class">{{ result.predicted_class || result.label }}</div>
|
||||
<div class="confidence-score">
|
||||
置信度: <span class="confidence-value">{{ ((result.confidence || result.score) * 100).toFixed(2) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="confidence-ring">
|
||||
<svg class="ring-svg" viewBox="0 0 100 100">
|
||||
<circle
|
||||
class="ring-background"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
fill="none"
|
||||
stroke="rgba(255, 255, 255, 0.2)"
|
||||
stroke-width="8"
|
||||
/>
|
||||
<circle
|
||||
class="ring-progress"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
fill="none"
|
||||
stroke="url(#gradient)"
|
||||
stroke-width="8"
|
||||
stroke-linecap="round"
|
||||
:stroke-dasharray="circumference"
|
||||
:stroke-dashoffset="strokeDashoffset"
|
||||
transform="rotate(-90 50 50)"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#52c41a"/>
|
||||
<stop offset="100%" style="stop-color:#73d13d"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<div class="ring-text">{{ Math.round((result.confidence || result.score) * 100) }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详细信息卡片 -->
|
||||
<div class="details-card">
|
||||
<div class="details-header">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>详细信息</span>
|
||||
</div>
|
||||
<div class="details-grid">
|
||||
<div class="detail-item">
|
||||
<div class="detail-icon">
|
||||
<el-icon><Flag /></el-icon>
|
||||
</div>
|
||||
<div class="detail-content">
|
||||
<div class="detail-label">预测类别</div>
|
||||
<div class="detail-value">{{ result.predicted_class || result.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<div class="detail-icon">
|
||||
<el-icon><DataAnalysis /></el-icon>
|
||||
</div>
|
||||
<div class="detail-content">
|
||||
<div class="detail-label">置信度</div>
|
||||
<div class="detail-value">{{ ((result.confidence || result.score) * 100).toFixed(2) }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<div class="detail-icon">
|
||||
<el-icon><Timer /></el-icon>
|
||||
</div>
|
||||
<div class="detail-content">
|
||||
<div class="detail-label">预测时间</div>
|
||||
<div class="detail-value">{{ formatTime(result.timestamp) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<div class="detail-icon">
|
||||
<el-icon><Headphone /></el-icon>
|
||||
</div>
|
||||
<div class="detail-content">
|
||||
<div class="detail-label">音频时长</div>
|
||||
<div class="detail-value">{{ formatDuration(result.audio_info?.duration) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 概率分布图表 -->
|
||||
<div v-if="result.all_probabilities" class="probability-card">
|
||||
<div class="probability-header">
|
||||
<el-icon><PieChart /></el-icon>
|
||||
<span>所有类别概率分布</span>
|
||||
<el-button type="text" size="small" @click="toggleChartView" class="toggle-view">
|
||||
<el-icon><Switch /></el-icon>
|
||||
{{ showChart ? '列表视图' : '图表视图' }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 图表视图 -->
|
||||
<transition name="fade">
|
||||
<div v-if="showChart" class="chart-container">
|
||||
<div ref="chartContainer" class="echarts-chart"></div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 列表视图 -->
|
||||
<transition name="fade">
|
||||
<div v-if="!showChart" class="probability-list">
|
||||
<div
|
||||
v-for="(prob, className) in sortedProbabilities"
|
||||
:key="className"
|
||||
class="probability-item"
|
||||
:class="{ active: className === (result.predicted_class || result.label) }"
|
||||
>
|
||||
<div class="prob-info">
|
||||
<div class="class-name">
|
||||
<span class="name-text">{{ className }}</span>
|
||||
<el-tag v-if="className === (result.predicted_class || result.label)"
|
||||
size="small" type="success" class="winner-tag">
|
||||
<el-icon><Trophy /></el-icon>
|
||||
最高
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="prob-value">{{ (prob * 100).toFixed(2) }}%</div>
|
||||
</div> <div class="prob-bar-container">
|
||||
<div class="prob-bar">
|
||||
<div
|
||||
class="prob-fill"
|
||||
:style="{
|
||||
width: `${prob * 100}%`,
|
||||
background: getProgressColor(prob, className === (result.predicted_class || result.label))
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="no-result">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<el-icon><Document /></el-icon>
|
||||
</div>
|
||||
<h3>暂无预测结果</h3>
|
||||
<p>请上传音频文件或录制音频进行识别</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted, nextTick, onBeforeUnmount } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
const props = defineProps({
|
||||
result: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
// 响应式数据
|
||||
const chartContainer = ref(null)
|
||||
const showChart = ref(false)
|
||||
let chartInstance = null
|
||||
|
||||
// 计算属性
|
||||
const circumference = computed(() => 2 * Math.PI * 40) // r=40的圆周长
|
||||
|
||||
const strokeDashoffset = computed(() => {
|
||||
if (!props.result) return circumference.value
|
||||
const confidence = props.result.confidence || props.result.score || 0
|
||||
return circumference.value - (confidence * circumference.value)
|
||||
})
|
||||
|
||||
const sortedProbabilities = computed(() => {
|
||||
if (!props.result?.all_probabilities) return {}
|
||||
|
||||
const entries = Object.entries(props.result.all_probabilities)
|
||||
entries.sort((a, b) => b[1] - a[1]) // 按概率降序排列
|
||||
|
||||
return Object.fromEntries(entries)
|
||||
})
|
||||
|
||||
// 格式化函数
|
||||
const formatTime = (timestamp) => {
|
||||
if (!timestamp) return 'N/A'
|
||||
try {
|
||||
if (typeof timestamp === 'string') {
|
||||
return timestamp
|
||||
}
|
||||
return new Date(timestamp).toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('时间格式化错误:', error)
|
||||
return 'N/A'
|
||||
}
|
||||
}
|
||||
|
||||
const formatDuration = (seconds) => {
|
||||
if (!seconds && seconds !== 0) return 'N/A'
|
||||
|
||||
try {
|
||||
const duration = parseFloat(seconds)
|
||||
if (isNaN(duration)) return 'N/A'
|
||||
|
||||
if (duration < 60) {
|
||||
return `${duration.toFixed(1)}秒`
|
||||
}
|
||||
|
||||
const minutes = Math.floor(duration / 60)
|
||||
const remainingSeconds = (duration % 60).toFixed(1)
|
||||
return `${minutes}分${remainingSeconds}秒`
|
||||
} catch (error) {
|
||||
console.error('时长格式化错误:', error)
|
||||
return 'N/A'
|
||||
}
|
||||
}
|
||||
|
||||
const getProgressColor = (prob, isWinner = false) => {
|
||||
if (isWinner) {
|
||||
return 'linear-gradient(135deg, #52c41a, #73d13d)'
|
||||
}
|
||||
if (prob > 0.7) return 'linear-gradient(135deg, #52c41a, #73d13d)'
|
||||
if (prob > 0.4) return 'linear-gradient(135deg, #fadb14, #ffec3d)'
|
||||
if (prob > 0.2) return 'linear-gradient(135deg, #fa8c16, #ffa940)'
|
||||
return 'linear-gradient(135deg, #ff4d4f, #ff7875)'
|
||||
}
|
||||
|
||||
const initChart = () => {
|
||||
if (!props.result?.all_probabilities || !chartContainer.value) return
|
||||
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose()
|
||||
}
|
||||
|
||||
chartInstance = echarts.init(chartContainer.value)
|
||||
|
||||
const data = Object.entries(props.result.all_probabilities)
|
||||
.map(([name, value]) => ({ name, value: (value * 100).toFixed(2) }))
|
||||
.sort((a, b) => b.value - a.value)
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
text: '类别置信度分布',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c}%'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '置信度',
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
data: data,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
chartInstance.setOption(option)
|
||||
}
|
||||
|
||||
const exportResult = () => {
|
||||
try {
|
||||
const exportData = {
|
||||
predicted_class: props.result.predicted_class,
|
||||
confidence: props.result.confidence,
|
||||
timestamp: props.result.timestamp,
|
||||
all_probabilities: props.result.all_probabilities
|
||||
}
|
||||
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `prediction_result_${Date.now()}.json`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
ElMessage.success('结果导出成功')
|
||||
} catch (error) {
|
||||
ElMessage.error('导出失败: ' + error.message)
|
||||
}
|
||||
}
|
||||
|
||||
const shareResult = () => {
|
||||
const shareText = `音频分类结果: ${props.result.predicted_class} (置信度: ${(props.result.confidence * 100).toFixed(2)}%)`
|
||||
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: '音频分类结果',
|
||||
text: shareText
|
||||
})
|
||||
} else {
|
||||
navigator.clipboard.writeText(shareText).then(() => {
|
||||
ElMessage.success('结果已复制到剪贴板')
|
||||
}).catch(() => {
|
||||
ElMessage.error('复制失败')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.result, () => {
|
||||
if (props.result) {
|
||||
nextTick(() => {
|
||||
initChart()
|
||||
})
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
if (props.result) {
|
||||
nextTick(() => {
|
||||
initChart()
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 预测结果主容器 */
|
||||
.prediction-result {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
/* 主要结果卡片 */
|
||||
.main-result-card {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-result-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.5s ease;
|
||||
}
|
||||
|
||||
.main-result-card:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.main-result-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 结果头部 */
|
||||
.result-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: linear-gradient(135deg, #52c41a, #73d13d);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
box-shadow: 0 8px 25px rgba(82, 196, 26, 0.3);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.result-title h3 {
|
||||
color: white;
|
||||
font-size: 1.8rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 5px 0;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.result-title p {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.result-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
||||
color: white !important;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.2) !important;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 主要预测结果 */
|
||||
.main-prediction {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.prediction-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 25px;
|
||||
padding: 25px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 20px;
|
||||
backdrop-filter: blur(15px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.badge-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: linear-gradient(135deg, #fadb14, #ffec3d);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1.8rem;
|
||||
box-shadow: 0 8px 25px rgba(250, 219, 20, 0.4);
|
||||
}
|
||||
|
||||
.badge-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.predicted-class {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
margin-bottom: 8px;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.confidence-score {
|
||||
font-size: 1.2rem;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.confidence-value {
|
||||
font-weight: 700;
|
||||
color: #52c41a;
|
||||
text-shadow: 0 0 10px rgba(82, 196, 26, 0.5);
|
||||
}
|
||||
|
||||
/* 置信度环形图 */
|
||||
.confidence-ring {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.ring-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.ring-background {
|
||||
stroke: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.ring-progress {
|
||||
stroke: url(#gradient);
|
||||
stroke-linecap: round;
|
||||
transition: stroke-dashoffset 1s ease-in-out;
|
||||
}
|
||||
|
||||
.ring-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 详细信息卡片 */
|
||||
.details-card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 25px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.details-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.details-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
color: white;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.details-header .el-icon {
|
||||
color: #409eff;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.detail-item:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.detail-icon {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
background: linear-gradient(135deg, #409eff, #52c41a);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1.2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 概率分布卡片 */
|
||||
.probability-card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 25px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.probability-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.probability-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 25px;
|
||||
color: white;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.probability-header .el-icon {
|
||||
color: #fa8c16;
|
||||
font-size: 1.4rem;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.toggle-view {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
||||
color: white !important;
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.toggle-view:hover {
|
||||
background: rgba(255, 255, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
/* 图表容器 */
|
||||
.chart-container {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.echarts-chart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
/* 概率列表 */
|
||||
.probability-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.probability-item {
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.probability-item:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.probability-item.active {
|
||||
background: rgba(82, 196, 26, 0.2);
|
||||
border-color: rgba(82, 196, 26, 0.5);
|
||||
box-shadow: 0 0 20px rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
|
||||
.prob-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.class-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.name-text {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.winner-tag {
|
||||
background: rgba(82, 196, 26, 0.2) !important;
|
||||
border-color: rgba(82, 196, 26, 0.5) !important;
|
||||
color: #52c41a !important;
|
||||
}
|
||||
|
||||
.prob-value {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.prob-bar-container {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.prob-bar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.prob-fill {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: linear-gradient(90deg, #52c41a, #73d13d);
|
||||
position: relative;
|
||||
animation: progressFill 1s ease-out;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.no-result {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 4rem;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-bottom: 20px;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 1.1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 动画 */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes progressFill {
|
||||
from {
|
||||
width: 0%;
|
||||
}
|
||||
to {
|
||||
width: var(--target-width, 100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* 过渡动画 */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.prediction-result {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.main-result-card {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.prediction-badge {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.details-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.prob-info {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.echarts-chart {
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.main-result-card,
|
||||
.details-card,
|
||||
.probability-card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.predicted-class {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.confidence-ring {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.probability-item {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,18 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
// 注册所有图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
|
||||
app.use(ElementPlus)
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
@ -0,0 +1,17 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HomePage from '../views/HomePage.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: HomePage
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
@ -0,0 +1,33 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
port: 3000,
|
||||
host: '0.0.0.0',
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:5000',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
chunkSizeWarningLimit: 1000
|
||||
}
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
||||
@echo off
|
||||
echo ================================
|
||||
echo 声纹识别系统启动脚本
|
||||
echo ================================
|
||||
echo.
|
||||
|
||||
echo 正在启动后端服务...
|
||||
cd /d "%~dp0backend"
|
||||
start "后端服务" cmd /k "python app.py"
|
||||
|
||||
echo 等待后端服务启动...
|
||||
timeout /t 3 /nobreak >nul
|
||||
|
||||
echo 正在启动前端服务...
|
||||
cd /d "%~dp0frontend"
|
||||
start "前端服务" cmd /k "npm run dev"
|
||||
|
||||
echo.
|
||||
echo 启动完成!
|
||||
echo 后端服务: http://localhost:5000
|
||||
echo 前端服务: http://localhost:3000
|
||||
echo.
|
||||
echo 请等待服务完全启动后访问前端地址
|
||||
pause
|
||||
@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "================================"
|
||||
echo " 声纹识别系统启动脚本"
|
||||
echo "================================"
|
||||
echo
|
||||
|
||||
# 获取脚本所在目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo "正在启动后端服务..."
|
||||
cd "$SCRIPT_DIR/backend"
|
||||
python app.py &
|
||||
BACKEND_PID=$!
|
||||
|
||||
echo "等待后端服务启动..."
|
||||
sleep 3
|
||||
|
||||
echo "正在启动前端服务..."
|
||||
cd "$SCRIPT_DIR/frontend"
|
||||
npm run dev &
|
||||
FRONTEND_PID=$!
|
||||
|
||||
echo
|
||||
echo "启动完成!"
|
||||
echo "后端服务: http://localhost:5000"
|
||||
echo "前端服务: http://localhost:3000"
|
||||
echo
|
||||
echo "后端进程 PID: $BACKEND_PID"
|
||||
echo "前端进程 PID: $FRONTEND_PID"
|
||||
echo
|
||||
echo "按 Ctrl+C 停止所有服务"
|
||||
|
||||
# 等待用户中断
|
||||
trap 'echo "正在停止服务..."; kill $BACKEND_PID $FRONTEND_PID; exit' INT
|
||||
wait
|
||||
@ -0,0 +1,46 @@
|
||||
# 语速增强
|
||||
speed:
|
||||
# 增强概率
|
||||
prob: 1.0
|
||||
|
||||
# 音量增强
|
||||
volume:
|
||||
# 增强概率
|
||||
prob: 0.0
|
||||
# 最小增益
|
||||
min_gain_dBFS: -15
|
||||
# 最大增益
|
||||
max_gain_dBFS: 15
|
||||
|
||||
# 噪声增强
|
||||
noise:
|
||||
# 增强概率
|
||||
prob: 0.5
|
||||
# 噪声增强的噪声文件夹
|
||||
noise_dir: 'dataset/noise'
|
||||
# 针对噪声的最小音量增益
|
||||
min_snr_dB: 10
|
||||
# 针对噪声的最大音量增益
|
||||
max_snr_dB: 50
|
||||
|
||||
# 混响增强
|
||||
reverb:
|
||||
# 增强概率
|
||||
prob: 0.5
|
||||
# 混响增强的混响文件夹
|
||||
reverb_dir: 'dataset/reverb'
|
||||
|
||||
# Spec增强
|
||||
spec_aug:
|
||||
# 增强概率
|
||||
prob: 0.5
|
||||
# 频域掩蔽的比例
|
||||
freq_mask_ratio: 0.1
|
||||
# 频域掩蔽次数
|
||||
n_freq_masks: 1
|
||||
# 频域掩蔽的比例
|
||||
time_mask_ratio: 0.05
|
||||
# 频域掩蔽次数
|
||||
n_time_masks: 1
|
||||
# 最大时间扭曲
|
||||
max_time_warp: 0
|
||||
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 115 KiB |
@ -0,0 +1,19 @@
|
||||
import argparse
|
||||
import functools
|
||||
|
||||
from macls.trainer import MAClsTrainer
|
||||
from macls.utils.utils import add_arguments, print_arguments
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
add_arg = functools.partial(add_arguments, argparser=parser)
|
||||
add_arg('configs', str, 'configs/cam++.yml', '配置文件')
|
||||
add_arg('save_dir', str, 'dataset/features', '保存特征的路径')
|
||||
add_arg('max_duration', int, 100, '提取特征的最大时长,避免过长显存不足,单位秒')
|
||||
args = parser.parse_args()
|
||||
print_arguments(args=args)
|
||||
|
||||
# 获取训练器
|
||||
trainer = MAClsTrainer(configs=args.configs)
|
||||
|
||||
# 提取特征保存文件
|
||||
trainer.extract_features(save_dir=args.save_dir, max_duration=args.max_duration)
|
||||
@ -0,0 +1,23 @@
|
||||
import argparse
|
||||
import functools
|
||||
|
||||
from macls.predict import MAClsPredictor
|
||||
from macls.utils.utils import add_arguments, print_arguments
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
add_arg = functools.partial(add_arguments, argparser=parser)
|
||||
add_arg('configs', str, 'configs/cam++.yml', '配置文件')
|
||||
add_arg('use_gpu', bool, True, '是否使用GPU预测')
|
||||
add_arg('audio_path', str, 'dataset/UrbanSound8K/audio/fold5/156634-5-2-5.wav', '音频路径')
|
||||
add_arg('model_path', str, 'models/CAMPPlus_Fbank/best_model/', '导出的预测模型文件路径')
|
||||
args = parser.parse_args()
|
||||
print_arguments(args=args)
|
||||
|
||||
# 获取识别器
|
||||
predictor = MAClsPredictor(configs=args.configs,
|
||||
model_path=args.model_path,
|
||||
use_gpu=args.use_gpu)
|
||||
|
||||
label, score = predictor.predict(audio_data=args.audio_path)
|
||||
|
||||
print(f'音频:{args.audio_path} 的预测结果标签为:{label},得分:{score}')
|
||||
@ -0,0 +1,58 @@
|
||||
import argparse
|
||||
import functools
|
||||
import threading
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
import soundcard as sc
|
||||
|
||||
from macls.predict import MAClsPredictor
|
||||
from macls.utils.utils import add_arguments, print_arguments
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
add_arg = functools.partial(add_arguments, argparser=parser)
|
||||
add_arg('configs', str, 'configs/cam++.yml', '配置文件')
|
||||
add_arg('use_gpu', bool, True, '是否使用GPU预测')
|
||||
add_arg('record_seconds', float, 3, '录音长度')
|
||||
add_arg('model_path', str, 'models/CAMPPlus_Fbank/best_model/', '导出的预测模型文件路径')
|
||||
args = parser.parse_args()
|
||||
print_arguments(args=args)
|
||||
|
||||
# 获取识别器
|
||||
predictor = MAClsPredictor(configs=args.configs,
|
||||
model_path=args.model_path,
|
||||
use_gpu=args.use_gpu)
|
||||
|
||||
all_data = []
|
||||
# 获取默认麦克风
|
||||
default_mic = sc.default_microphone()
|
||||
# 录音采样率
|
||||
samplerate = 16000
|
||||
# 录音块大小
|
||||
numframes = 1024
|
||||
# 模型输入长度
|
||||
infer_len = int(samplerate * args.record_seconds / numframes)
|
||||
|
||||
|
||||
def infer_thread():
|
||||
global all_data
|
||||
s = time.time()
|
||||
while True:
|
||||
if len(all_data) < infer_len: continue
|
||||
# 截取最新的音频数据
|
||||
seg_data = all_data[-infer_len:]
|
||||
d = np.concatenate(seg_data)
|
||||
# 删除旧的音频数据
|
||||
del all_data[:len(all_data) - infer_len]
|
||||
label, score = predictor.predict(audio_data=d, sample_rate=samplerate)
|
||||
print(f'{int(time.time() - s)}s 预测结果标签为:{label},得分:{score}')
|
||||
|
||||
|
||||
thread = threading.Thread(target=infer_thread, args=())
|
||||
thread.start()
|
||||
|
||||
|
||||
with default_mic.recorder(samplerate=samplerate, channels=1) as mic:
|
||||
while True:
|
||||
data = mic.record(numframes=numframes)
|
||||
all_data.append(data)
|
||||
@ -0,0 +1 @@
|
||||
__version__ = "1.0.6"
|
||||
@ -0,0 +1,132 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
import torchaudio.compliance.kaldi as Kaldi
|
||||
from torch import nn
|
||||
from torchaudio.transforms import MelSpectrogram, Spectrogram, MFCC
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class AudioFeaturizer(nn.Module):
|
||||
"""音频特征器
|
||||
|
||||
:param feature_method: 所使用的预处理方法
|
||||
:type feature_method: str
|
||||
:param use_hf_model: 是否使用HF上的Wav2Vec2类似模型提取音频特征
|
||||
:type use_hf_model: bool
|
||||
:param method_args: 预处理方法的参数
|
||||
:type method_args: dict
|
||||
"""
|
||||
|
||||
def __init__(self, feature_method='MelSpectrogram', use_hf_model=False, method_args={}):
|
||||
super().__init__()
|
||||
self._method_args = method_args
|
||||
self._feature_method = feature_method
|
||||
self.use_hf_model = use_hf_model
|
||||
if self.use_hf_model:
|
||||
from transformers import AutoModel, AutoFeatureExtractor
|
||||
# 判断是否使用GPU提取特征
|
||||
use_gpu = torch.cuda.is_available() and method_args.get('use_gpu', True)
|
||||
self.device = torch.device("cuda") if use_gpu else torch.device("cpu")
|
||||
# 加载Wav2Vec2类似模型
|
||||
self.processor = AutoFeatureExtractor.from_pretrained(feature_method)
|
||||
self.feature_model = AutoModel.from_pretrained(feature_method).to(self.device)
|
||||
logger.info(f'使用模型【{feature_method}】提取特征,使用【{self.device}】设备提取')
|
||||
# 获取模型的输出通道数
|
||||
inputs = self.processor(np.ones(16000 * 1, dtype=np.float32), sampling_rate=16000,
|
||||
return_tensors="pt").to(self.device)
|
||||
with torch.no_grad():
|
||||
outputs = self.feature_model(**inputs)
|
||||
self.output_channels = outputs.extract_features.shape[2]
|
||||
else:
|
||||
if feature_method == 'MelSpectrogram':
|
||||
self.feat_fun = MelSpectrogram(**method_args)
|
||||
elif feature_method == 'Spectrogram':
|
||||
self.feat_fun = Spectrogram(**method_args)
|
||||
elif feature_method == 'MFCC':
|
||||
self.feat_fun = MFCC(**method_args)
|
||||
elif feature_method == 'Fbank':
|
||||
self.feat_fun = KaldiFbank(**method_args)
|
||||
else:
|
||||
raise Exception(f'预处理方法 {self._feature_method} 不存在!')
|
||||
logger.info(f'使用【{feature_method}】提取特征')
|
||||
|
||||
def forward(self, waveforms, input_lens_ratio=None):
|
||||
"""从AudioSegment中提取音频特征
|
||||
|
||||
:param waveforms: Audio segment to extract features from.
|
||||
:type waveforms: AudioSegment
|
||||
:param input_lens_ratio: input length ratio
|
||||
:type input_lens_ratio: tensor
|
||||
:return: Spectrogram audio feature in 2darray.
|
||||
:rtype: ndarray
|
||||
"""
|
||||
if len(waveforms.shape) == 1:
|
||||
waveforms = waveforms.unsqueeze(0)
|
||||
if self.use_hf_model:
|
||||
# 使用HF上的Wav2Vec2类似模型提取音频特征
|
||||
if isinstance(waveforms, torch.Tensor):
|
||||
waveforms = waveforms.numpy()
|
||||
inputs = self.processor(waveforms, sampling_rate=16000,
|
||||
return_tensors="pt").to(self.device)
|
||||
with torch.no_grad():
|
||||
outputs = self.feature_model(**inputs)
|
||||
feature = outputs.extract_features.cpu().detach()
|
||||
else:
|
||||
# 使用普通方法提取音频特征
|
||||
feature = self.feat_fun(waveforms)
|
||||
feature = feature.transpose(2, 1)
|
||||
# 归一化
|
||||
feature = feature - feature.mean(1, keepdim=True)
|
||||
if input_lens_ratio is not None:
|
||||
# 对掩码比例进行扩展
|
||||
input_lens = (input_lens_ratio * feature.shape[1])
|
||||
mask_lens = torch.round(input_lens).long()
|
||||
mask_lens = mask_lens.unsqueeze(1)
|
||||
# 生成掩码张量
|
||||
idxs = torch.arange(feature.shape[1], device=feature.device).repeat(feature.shape[0], 1)
|
||||
mask = idxs < mask_lens
|
||||
mask = mask.unsqueeze(-1)
|
||||
# 对特征进行掩码操作
|
||||
feature = torch.where(mask, feature, torch.zeros_like(feature))
|
||||
return feature
|
||||
|
||||
@property
|
||||
def feature_dim(self):
|
||||
"""返回特征大小
|
||||
|
||||
:return: 特征大小
|
||||
:rtype: int
|
||||
"""
|
||||
if self.use_hf_model:
|
||||
return self.output_channels
|
||||
if self._feature_method == 'MelSpectrogram':
|
||||
return self._method_args.get('n_mels', 128)
|
||||
elif self._feature_method == 'Spectrogram':
|
||||
return self._method_args.get('n_fft', 400) // 2 + 1
|
||||
elif self._feature_method == 'MFCC':
|
||||
return self._method_args.get('n_mfcc', 40)
|
||||
elif self._feature_method == 'Fbank':
|
||||
return self._method_args.get('num_mel_bins', 23)
|
||||
else:
|
||||
raise Exception('没有{}预处理方法'.format(self._feature_method))
|
||||
|
||||
|
||||
class KaldiFbank(nn.Module):
|
||||
def __init__(self, **kwargs):
|
||||
super(KaldiFbank, self).__init__()
|
||||
self.kwargs = kwargs
|
||||
|
||||
def forward(self, waveforms):
|
||||
"""
|
||||
:param waveforms: [Batch, Length]
|
||||
:return: [Batch, Feature, Length]
|
||||
"""
|
||||
log_fbanks = []
|
||||
for waveform in waveforms:
|
||||
if len(waveform.shape) == 1:
|
||||
waveform = waveform.unsqueeze(0)
|
||||
log_fbank = Kaldi.fbank(waveform, **self.kwargs)
|
||||
log_fbank = log_fbank.transpose(0, 1)
|
||||
log_fbanks.append(log_fbank)
|
||||
log_fbank = torch.stack(log_fbanks)
|
||||
return log_fbank
|
||||
@ -0,0 +1,12 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
# 计算准确率
|
||||
def accuracy(output, label):
|
||||
output = torch.nn.functional.softmax(output, dim=-1)
|
||||
output = output.data.cpu().numpy()
|
||||
output = np.argmax(output, axis=1)
|
||||
label = label.data.cpu().numpy()
|
||||
acc = np.mean((output == label).astype(int))
|
||||
return acc
|
||||
@ -0,0 +1,32 @@
|
||||
import importlib
|
||||
|
||||
from loguru import logger
|
||||
from torch.optim import *
|
||||
from .scheduler import WarmupCosineSchedulerLR
|
||||
from torch.optim.lr_scheduler import *
|
||||
|
||||
__all__ = ['build_optimizer', 'build_lr_scheduler']
|
||||
|
||||
|
||||
def build_optimizer(params, configs):
|
||||
use_optimizer = configs.optimizer_conf.get('optimizer', 'Adam')
|
||||
optimizer_args = configs.optimizer_conf.get('optimizer_args', {})
|
||||
optim = importlib.import_module(__name__)
|
||||
optimizer = getattr(optim, use_optimizer)(params=params, **optimizer_args)
|
||||
logger.info(f'成功创建优化方法:{use_optimizer},参数为:{optimizer_args}')
|
||||
return optimizer
|
||||
|
||||
|
||||
def build_lr_scheduler(optimizer, step_per_epoch, configs):
|
||||
use_scheduler = configs.optimizer_conf.get('scheduler', 'WarmupCosineSchedulerLR')
|
||||
scheduler_args = configs.optimizer_conf.get('scheduler_args', {})
|
||||
if configs.optimizer_conf.scheduler == 'CosineAnnealingLR' and 'T_max' not in scheduler_args:
|
||||
scheduler_args.T_max = int(configs.train_conf.max_epoch * 1.2) * step_per_epoch
|
||||
if configs.optimizer_conf.scheduler == 'WarmupCosineSchedulerLR' and 'fix_epoch' not in scheduler_args:
|
||||
scheduler_args.fix_epoch = configs.train_conf.max_epoch
|
||||
if configs.optimizer_conf.scheduler == 'WarmupCosineSchedulerLR' and 'step_per_epoch' not in scheduler_args:
|
||||
scheduler_args.step_per_epoch = step_per_epoch
|
||||
optim = importlib.import_module(__name__)
|
||||
scheduler = getattr(optim, use_scheduler)(optimizer=optimizer, **scheduler_args)
|
||||
logger.info(f'成功创建学习率衰减:{use_scheduler},参数为:{scheduler_args}')
|
||||
return scheduler
|
||||
@ -0,0 +1,48 @@
|
||||
import math
|
||||
from typing import List
|
||||
|
||||
|
||||
class WarmupCosineSchedulerLR:
|
||||
def __init__(
|
||||
self,
|
||||
optimizer,
|
||||
min_lr,
|
||||
max_lr,
|
||||
warmup_epoch,
|
||||
fix_epoch,
|
||||
step_per_epoch
|
||||
):
|
||||
self.optimizer = optimizer
|
||||
assert min_lr <= max_lr
|
||||
self.min_lr = min_lr
|
||||
self.max_lr = max_lr
|
||||
self.warmup_step = warmup_epoch * step_per_epoch
|
||||
self.fix_step = fix_epoch * step_per_epoch
|
||||
self.current_step = 0.0
|
||||
|
||||
def set_lr(self, ):
|
||||
new_lr = self.clr(self.current_step)
|
||||
for param_group in self.optimizer.param_groups:
|
||||
param_group['lr'] = new_lr
|
||||
return new_lr
|
||||
|
||||
def step(self, step=None):
|
||||
if step is not None:
|
||||
self.current_step = step
|
||||
new_lr = self.set_lr()
|
||||
self.current_step += 1
|
||||
return new_lr
|
||||
|
||||
def clr(self, step):
|
||||
if step < self.warmup_step:
|
||||
return self.min_lr + (self.max_lr - self.min_lr) * \
|
||||
(step / self.warmup_step)
|
||||
elif self.warmup_step <= step < self.fix_step:
|
||||
return self.min_lr + 0.5 * (self.max_lr - self.min_lr) * \
|
||||
(1 + math.cos(math.pi * (step - self.warmup_step) /
|
||||
(self.fix_step - self.warmup_step)))
|
||||
else:
|
||||
return self.min_lr
|
||||
|
||||
def get_last_lr(self) -> List[float]:
|
||||
return [self.clr(self.current_step)]
|
||||
@ -0,0 +1,162 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import torch
|
||||
from loguru import logger
|
||||
from macls import __version__
|
||||
|
||||
|
||||
def load_pretrained(model, pretrained_model, use_gpu=True):
|
||||
"""加载预训练模型
|
||||
|
||||
:param model: 使用的模型
|
||||
:param pretrained_model: 预训练模型路径
|
||||
:param use_gpu: 模型是否使用GPU
|
||||
:return: 加载的模型
|
||||
"""
|
||||
# 加载预训练模型
|
||||
if pretrained_model is None: return model
|
||||
if os.path.isdir(pretrained_model):
|
||||
pretrained_model = os.path.join(pretrained_model, 'model.pth')
|
||||
assert os.path.exists(pretrained_model), f"{pretrained_model} 模型不存在!"
|
||||
if isinstance(model, torch.nn.parallel.DistributedDataParallel):
|
||||
model_dict = model.module.state_dict()
|
||||
else:
|
||||
model_dict = model.state_dict()
|
||||
if torch.cuda.is_available() and use_gpu:
|
||||
model_state_dict = torch.load(pretrained_model, weights_only=False)
|
||||
else:
|
||||
model_state_dict = torch.load(pretrained_model, weights_only=False, map_location='cpu')
|
||||
# 过滤不存在的参数
|
||||
for name, weight in model_dict.items():
|
||||
if name in model_state_dict.keys():
|
||||
if list(weight.shape) != list(model_state_dict[name].shape):
|
||||
logger.warning(f'{name} not used, shape {list(model_state_dict[name].shape)} '
|
||||
f'unmatched with {list(weight.shape)} in model.')
|
||||
model_state_dict.pop(name, None)
|
||||
# 加载权重
|
||||
if isinstance(model, torch.nn.parallel.DistributedDataParallel):
|
||||
missing_keys, unexpected_keys = model.module.load_state_dict(model_state_dict, strict=False)
|
||||
else:
|
||||
missing_keys, unexpected_keys = model.load_state_dict(model_state_dict, strict=False)
|
||||
if len(unexpected_keys) > 0:
|
||||
logger.warning('Unexpected key(s) in state_dict: {}. '
|
||||
.format(', '.join('"{}"'.format(k) for k in unexpected_keys)))
|
||||
if len(missing_keys) > 0:
|
||||
logger.warning('Missing key(s) in state_dict: {}. '
|
||||
.format(', '.join('"{}"'.format(k) for k in missing_keys)))
|
||||
logger.info('成功加载预训练模型:{}'.format(pretrained_model))
|
||||
return model
|
||||
|
||||
|
||||
def load_checkpoint(configs, model, optimizer, amp_scaler, scheduler,
|
||||
step_epoch, save_model_path, resume_model):
|
||||
"""加载模型
|
||||
|
||||
:param configs: 配置信息
|
||||
:param model: 使用的模型
|
||||
:param optimizer: 使用的优化方法
|
||||
:param amp_scaler: 使用的自动混合精度
|
||||
:param scheduler: 使用的学习率调整策略
|
||||
:param step_epoch: 每个epoch的step数量
|
||||
:param save_model_path: 模型保存路径
|
||||
:param resume_model: 恢复训练的模型路径
|
||||
"""
|
||||
last_epoch1 = 0
|
||||
accuracy1 = 0.
|
||||
|
||||
def load_model(model_path):
|
||||
assert os.path.exists(os.path.join(model_path, 'model.pth')), "模型参数文件不存在!"
|
||||
assert os.path.exists(os.path.join(model_path, 'optimizer.pth')), "优化方法参数文件不存在!"
|
||||
state_dict = torch.load(os.path.join(model_path, 'model.pth'), weights_only=False)
|
||||
if isinstance(model, torch.nn.parallel.DistributedDataParallel):
|
||||
model.module.load_state_dict(state_dict)
|
||||
else:
|
||||
model.load_state_dict(state_dict)
|
||||
optimizer.load_state_dict(torch.load(os.path.join(model_path, 'optimizer.pth'), weights_only=False))
|
||||
# 自动混合精度参数
|
||||
if amp_scaler is not None and os.path.exists(os.path.join(model_path, 'scaler.pth')):
|
||||
amp_scaler.load_state_dict(torch.load(os.path.join(model_path, 'scaler.pth')), weights_only=False)
|
||||
with open(os.path.join(model_path, 'model.state'), 'r', encoding='utf-8') as f:
|
||||
json_data = json.load(f)
|
||||
last_epoch = json_data['last_epoch']
|
||||
accuracy = json_data['accuracy']
|
||||
logger.info('成功恢复模型参数和优化方法参数:{}'.format(model_path))
|
||||
optimizer.step()
|
||||
[scheduler.step() for _ in range(last_epoch * step_epoch)]
|
||||
return last_epoch, accuracy
|
||||
|
||||
# 获取最后一个保存的模型
|
||||
save_feature_method = configs.preprocess_conf.feature_method
|
||||
if configs.preprocess_conf.get('use_hf_model', False):
|
||||
save_feature_method = save_feature_method[:-1] if save_feature_method[-1] == '/' else save_feature_method
|
||||
save_feature_method = os.path.basename(save_feature_method)
|
||||
last_model_dir = os.path.join(save_model_path,
|
||||
f'{configs.model_conf.model}_{save_feature_method}',
|
||||
'last_model')
|
||||
if resume_model is not None or (os.path.exists(os.path.join(last_model_dir, 'model.pth'))
|
||||
and os.path.exists(os.path.join(last_model_dir, 'optimizer.pth'))):
|
||||
if resume_model is not None:
|
||||
last_epoch1, accuracy1 = load_model(resume_model)
|
||||
else:
|
||||
try:
|
||||
# 自动获取最新保存的模型
|
||||
last_epoch1, accuracy1 = load_model(last_model_dir)
|
||||
except Exception as e:
|
||||
logger.warning(f'尝试自动恢复最新模型失败,错误信息:{e}')
|
||||
return model, optimizer, amp_scaler, scheduler, last_epoch1, accuracy1
|
||||
|
||||
|
||||
# 保存模型
|
||||
def save_checkpoint(configs, model, optimizer, amp_scaler, save_model_path, epoch_id,
|
||||
accuracy=0., best_model=False):
|
||||
"""保存模型
|
||||
|
||||
:param configs: 配置信息
|
||||
:param model: 使用的模型
|
||||
:param optimizer: 使用的优化方法
|
||||
:param amp_scaler: 使用的自动混合精度
|
||||
:param save_model_path: 模型保存路径
|
||||
:param epoch_id: 当前epoch
|
||||
:param accuracy: 当前准确率
|
||||
:param best_model: 是否为最佳模型
|
||||
"""
|
||||
if isinstance(model, torch.nn.parallel.DistributedDataParallel):
|
||||
state_dict = model.module.state_dict()
|
||||
else:
|
||||
state_dict = model.state_dict()
|
||||
# 保存模型的路径
|
||||
save_feature_method = configs.preprocess_conf.feature_method
|
||||
if configs.preprocess_conf.get('use_hf_model', False):
|
||||
save_feature_method = save_feature_method[:-1] if save_feature_method[-1] == '/' else save_feature_method
|
||||
save_feature_method = os.path.basename(save_feature_method)
|
||||
if best_model:
|
||||
model_path = os.path.join(save_model_path,
|
||||
f'{configs.model_conf.model}_{save_feature_method}', 'best_model')
|
||||
else:
|
||||
model_path = os.path.join(save_model_path,
|
||||
f'{configs.model_conf.model}_{save_feature_method}', 'epoch_{}'.format(epoch_id))
|
||||
os.makedirs(model_path, exist_ok=True)
|
||||
# 保存模型参数
|
||||
torch.save(optimizer.state_dict(), os.path.join(model_path, 'optimizer.pth'))
|
||||
torch.save(state_dict, os.path.join(model_path, 'model.pth'))
|
||||
# 自动混合精度参数
|
||||
if amp_scaler is not None:
|
||||
torch.save(amp_scaler.state_dict(), os.path.join(model_path, 'scaler.pth'))
|
||||
with open(os.path.join(model_path, 'model.state'), 'w', encoding='utf-8') as f:
|
||||
data = {"last_epoch": epoch_id, "accuracy": accuracy, "version": __version__,
|
||||
"model": configs.model_conf.model, "feature_method": save_feature_method}
|
||||
f.write(json.dumps(data, indent=4, ensure_ascii=False))
|
||||
if not best_model:
|
||||
last_model_path = os.path.join(save_model_path,
|
||||
f'{configs.model_conf.model}_{save_feature_method}', 'last_model')
|
||||
shutil.rmtree(last_model_path, ignore_errors=True)
|
||||
shutil.copytree(model_path, last_model_path)
|
||||
# 删除旧的模型
|
||||
old_model_path = os.path.join(save_model_path,
|
||||
f'{configs.model_conf.model}_{save_feature_method}',
|
||||
'epoch_{}'.format(epoch_id - 3))
|
||||
if os.path.exists(old_model_path):
|
||||
shutil.rmtree(old_model_path)
|
||||
logger.info('已保存模型:{}'.format(model_path))
|
||||
@ -0,0 +1,14 @@
|
||||
import time
|
||||
|
||||
from macls.utils.record import RecordAudio
|
||||
|
||||
s = input('请输入你计划录音多少秒:')
|
||||
record_seconds = int(s)
|
||||
save_path = "dataset/save_audio/%s.wav" % str(int(time.time()*1000))
|
||||
|
||||
record_audio = RecordAudio()
|
||||
input(f"按下回车键开机录音,录音{record_seconds}秒中:")
|
||||
record_audio.record(record_seconds=record_seconds,
|
||||
save_path=save_path)
|
||||
|
||||
print('文件保存在:%s' % save_path)
|
||||
@ -0,0 +1,17 @@
|
||||
numpy>=1.19.2
|
||||
scipy>=1.6.3
|
||||
librosa>=0.9.1
|
||||
soundfile>=0.12.1
|
||||
soundcard>=0.4.2
|
||||
resampy>=0.2.2
|
||||
numba>=0.53.0
|
||||
pydub~=0.25.1
|
||||
matplotlib>=3.5.2
|
||||
pillow>=10.3.0
|
||||
tqdm>=4.66.3
|
||||
visualdl==2.5.3
|
||||
pyyaml>=5.4.1
|
||||
scikit-learn>=1.0.2
|
||||
torchinfo>=1.7.2
|
||||
loguru>=0.7.2
|
||||
yeaudio>=0.0.7
|
||||
@ -0,0 +1,54 @@
|
||||
import shutil
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
import macls
|
||||
|
||||
VERSION = macls.__version__
|
||||
|
||||
# 复制配置文件到项目目录下
|
||||
shutil.rmtree('./macls/configs/', ignore_errors=True)
|
||||
shutil.copytree('./configs/', './macls/configs/')
|
||||
|
||||
|
||||
def readme():
|
||||
with open('README.md', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
return content
|
||||
|
||||
|
||||
def parse_requirements():
|
||||
with open('./requirements.txt', encoding="utf-8") as f:
|
||||
requirements = f.readlines()
|
||||
return requirements
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name='macls',
|
||||
packages=find_packages(),
|
||||
package_data={'': ['configs/*']},
|
||||
author='yeyupiaoling',
|
||||
version=VERSION,
|
||||
install_requires=parse_requirements(),
|
||||
description='Audio Classification toolkit on Pytorch',
|
||||
long_description=readme(),
|
||||
long_description_content_type='text/markdown',
|
||||
url='https://github.com/yeyupiaoling/AudioClassification-Pytorch',
|
||||
download_url='https://github.com/yeyupiaoling/AudioClassification-Pytorch.git',
|
||||
keywords=['audio', 'pytorch'],
|
||||
classifiers=[
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Operating System :: OS Independent',
|
||||
'Natural Language :: Chinese (Simplified)',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9', 'Topic :: Utilities'
|
||||
],
|
||||
license='Apache License 2.0',
|
||||
ext_modules=[])
|
||||
shutil.rmtree('./macls/configs/', ignore_errors=True)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue