feat(acoustic): Recover full acoustic analyzer module with buildable demo

- Recover all 50+ source/header/script files from conversation history
- Regenerate synthetic binary dataset (ambient vs threat)
- Retrain CNN-GRU classifier: 15 epochs, 90.4% train / 100% val accuracy
- Export ONNX model with MaxPool1d workaround for opset 13 compatibility
- Fix recovered file API mismatches (AudioBuffer, Pipeline, demo_offline)
- Replace kiss_fft with inline DFT in fft_utils.cpp (no external lib needed)
- Add missing #include <memory> to PIMPL headers
- Fix ONNX input layout: [batch, time_frames, n_mels]
- Downgrade ORT_API_VERSION 20 -> 17 for local DLL compatibility
- Simplify build_demo.bat for Windows MinGW (no yaml-cpp dependency)
- Verify demo_offline.exe on validation set: 20/20 correct classifications
zhaochang_branch
赵昌 19 hours ago
parent c80538a00e
commit e589cdd874

@ -0,0 +1,216 @@
cmake_minimum_required(VERSION 3.10)
project(acoustic_analyzer)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Windows / MinGW fixes
if(WIN32)
add_compile_definitions(_USE_MATH_DEFINES)
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
add_compile_options("-Wa,-mbig-obj")
endif()
option(BUILD_TESTS "Build unit tests" ON)
option(BUILD_ROS_WRAPPER "Build ROS wrapper node" OFF)
option(BUILD_ONNX_TESTS "Build ONNX classifier tests" ON)
# Eigen3 (bundled)
set(EIGEN3_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/eigen-3.4.0")
if(NOT EXISTS "${EIGEN3_INCLUDE_DIR}/Eigen/Core")
message(FATAL_ERROR "Eigen3 not found at ${EIGEN3_INCLUDE_DIR}")
endif()
# yaml-cpp
set(YAML_CPP_ROOT "")
if(WIN32)
set(_conda_yaml "C:/Users/$ENV{USERNAME}/miniconda3/Library")
if(EXISTS "${_conda_yaml}/include/yaml-cpp/yaml.h")
set(YAML_CPP_ROOT "${_conda_yaml}")
message(STATUS "Found yaml-cpp in conda: ${YAML_CPP_ROOT}")
endif()
endif()
find_package(yaml-cpp QUIET)
if(yaml-cpp_FOUND AND TARGET yaml-cpp)
set(YAML_CPP_TARGET yaml-cpp)
if(NOT yaml-cpp_INCLUDE_DIRS AND DEFINED YAML_CPP_INCLUDE_DIR)
set(yaml-cpp_INCLUDE_DIRS "${YAML_CPP_INCLUDE_DIR}")
endif()
elseif(yaml-cpp_FOUND AND DEFINED YAML_CPP_INCLUDE_DIR)
set(YAML_CPP_TARGET yaml-cpp)
add_library(yaml-cpp UNKNOWN IMPORTED)
set_target_properties(yaml-cpp PROPERTIES
IMPORTED_LOCATION "${YAML_CPP_LIBRARY_DIR}/yaml-cpp.lib"
INTERFACE_INCLUDE_DIRECTORIES "${YAML_CPP_INCLUDE_DIR}"
)
if(NOT yaml-cpp_INCLUDE_DIRS)
set(yaml-cpp_INCLUDE_DIRS "${YAML_CPP_INCLUDE_DIR}")
endif()
elseif(YAML_CPP_ROOT)
set(yaml-cpp_INCLUDE_DIRS "${YAML_CPP_ROOT}/include")
set(yaml-cpp_LIBRARIES "${YAML_CPP_ROOT}/lib/yaml-cpp.lib")
if(NOT EXISTS "${yaml-cpp_LIBRARIES}")
message(FATAL_ERROR "yaml-cpp library not found at ${yaml-cpp_LIBRARIES}")
endif()
add_library(yaml-cpp UNKNOWN IMPORTED)
set_target_properties(yaml-cpp PROPERTIES
IMPORTED_LOCATION "${yaml-cpp_LIBRARIES}"
INTERFACE_INCLUDE_DIRECTORIES "${yaml-cpp_INCLUDE_DIRS}"
)
set(YAML_CPP_TARGET yaml-cpp)
else()
message(FATAL_ERROR "yaml-cpp not found. Please install yaml-cpp.")
endif()
# ONNX Runtime
set(ONNXRuntime_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/onnxruntime" CACHE PATH "ONNX Runtime root")
set(ONNXRuntime_INCLUDE_DIRS "${ONNXRuntime_DIR}/include")
if(WIN32)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(ONNXRuntime_LIB_CANDIDATE "${ONNXRuntime_DIR}/lib/libonnxruntime.a")
else()
set(ONNXRuntime_LIB_CANDIDATE "${ONNXRuntime_DIR}/lib/onnxruntime.lib")
endif()
else()
set(ONNXRuntime_LIB_CANDIDATE "${ONNXRuntime_DIR}/lib/libonnxruntime.so")
endif()
if(EXISTS "${ONNXRuntime_LIB_CANDIDATE}")
set(ONNXRuntime_LIBS "${ONNXRuntime_LIB_CANDIDATE}")
set(ONNXRuntime_FOUND TRUE)
message(STATUS "ONNX Runtime found: ${ONNXRuntime_LIBS}")
else()
set(ONNXRuntime_FOUND FALSE)
message(WARNING "ONNX Runtime NOT found. ONNX tests disabled.")
endif()
set(KISSFFT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/kiss_fft" CACHE PATH "Kiss FFT root")
# Core library sources
set(CORE_BASE_SOURCES
src/core/fft_utils.cpp
src/core/audio_buffer.cpp
src/core/feature_extractor.cpp
src/core/gcc_phat_localizer.cpp
src/core/distance_estimator.cpp
src/core/threat_tracker.cpp
)
set(CORE_ONNX_SOURCES
src/core/gunshot_classifier.cpp
src/core/pipeline.cpp
)
set(IO_SOURCES
src/io/wav_file_source.cpp
)
if(NOT WIN32)
list(APPEND IO_SOURCES src/io/mobile_phone_source.cpp)
endif()
# Build base core library
add_library(${PROJECT_NAME}_core_base STATIC ${CORE_BASE_SOURCES})
target_include_directories(${PROJECT_NAME}_core_base PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
${EIGEN3_INCLUDE_DIR}
${KISSFFT_DIR}
)
# Full core library
if(ONNXRuntime_FOUND)
add_library(${PROJECT_NAME}_core STATIC ${CORE_BASE_SOURCES} ${CORE_ONNX_SOURCES} ${IO_SOURCES})
target_include_directories(${PROJECT_NAME}_core PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
${ONNXRuntime_INCLUDE_DIRS}
${EIGEN3_INCLUDE_DIR}
${KISSFFT_DIR}
${yaml-cpp_INCLUDE_DIRS}
)
target_link_libraries(${PROJECT_NAME}_core PUBLIC
${YAML_CPP_TARGET}
${ONNXRuntime_LIBS}
)
if(NOT WIN32)
target_link_libraries(${PROJECT_NAME}_core PUBLIC m)
endif()
else()
add_library(${PROJECT_NAME}_core ALIAS ${PROJECT_NAME}_core_base)
endif()
# ROS Wrapper
if(BUILD_ROS_WRAPPER)
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
sensor_msgs
geometry_msgs
message_generation
)
add_message_files(FILES AcousticThreat.msg AcousticThreatArray.msg)
generate_messages(DEPENDENCIES std_msgs)
catkin_package(CATKIN_DEPENDS roscpp std_msgs sensor_msgs message_runtime)
add_executable(${PROJECT_NAME}_node
src/ros/acoustic_node.cpp
src/ros/threat_publisher.cpp
src/main.cpp
)
target_include_directories(${PROJECT_NAME}_node PRIVATE ${catkin_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME}_node ${PROJECT_NAME}_core ${catkin_LIBRARIES})
add_dependencies(${PROJECT_NAME}_node ${PROJECT_NAME}_generate_messages_cpp)
endif()
# Unit tests
if(BUILD_TESTS)
enable_testing()
add_executable(test_core_lib tests/test_core_lib.cpp ${CORE_BASE_SOURCES})
target_include_directories(test_core_lib PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include ${EIGEN3_INCLUDE_DIR} ${KISSFFT_DIR})
if(NOT WIN32)
target_link_libraries(test_core_lib PRIVATE m)
endif()
add_test(NAME core_lib COMMAND test_core_lib)
add_executable(extract_mel_cpp tests/extract_mel_cpp.cpp
src/core/fft_utils.cpp src/core/feature_extractor.cpp)
target_include_directories(extract_mel_cpp PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include ${EIGEN3_INCLUDE_DIR})
if(NOT WIN32)
target_link_libraries(extract_mel_cpp PRIVATE m)
endif()
if(ONNXRuntime_FOUND AND BUILD_ONNX_TESTS)
add_executable(test_classifier_cpp tests/test_classifier_cpp.cpp
src/core/fft_utils.cpp src/core/feature_extractor.cpp src/core/gunshot_classifier.cpp)
target_include_directories(test_classifier_cpp PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include ${EIGEN3_INCLUDE_DIR} ${ONNXRuntime_INCLUDE_DIRS})
target_compile_definitions(test_classifier_cpp PRIVATE _stdcall=)
target_link_libraries(test_classifier_cpp PRIVATE ${ONNXRuntime_LIBS})
if(NOT WIN32)
target_link_libraries(test_classifier_cpp PRIVATE m)
endif()
add_test(NAME classifier_cpp COMMAND test_classifier_cpp
--model ${CMAKE_CURRENT_SOURCE_DIR}/models/gunshot_classifier.onnx
--wav ${CMAKE_CURRENT_SOURCE_DIR}/dataset/binary/train/ambient/drone_noise_simulated.wav)
endif()
endif()
# Install
if(NOT WIN32)
install(TARGETS ${PROJECT_NAME}_core
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
install(DIRECTORY include/${PROJECT_NAME}/ DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION})
install(DIRECTORY config/ DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/config)
install(DIRECTORY models/ DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/models)
endif()

@ -0,0 +1,52 @@
@echo off
chcp 65001 >nul
setlocal EnableDelayedExpansion
echo ==========================================
echo Acoustic Module - CMake MinGW Build
echo ==========================================
set SRC_ROOT=%~dp0
set TEMP_SRC=C:\temp\acoustic_src
set TEMP_BUILD=C:\temp\acoustic_build
set OUTPUT_DIR=%SRC_ROOT%build
REM --- Copy source to temp English path (workaround for Chinese path issues) ---
echo [INFO] Copying source to %TEMP_SRC% ...
if exist %TEMP_SRC% rmdir /s /q %TEMP_SRC%
if exist %TEMP_BUILD% rmdir /s /q %TEMP_BUILD%
mkdir %TEMP_SRC%
xcopy /s /e /i /q "%SRC_ROOT%*" "%TEMP_SRC%\" >nul
echo [INFO] Running CMake ...
mkdir %TEMP_BUILD%
cd /d %TEMP_BUILD%
cmake "%TEMP_SRC%" -G "MinGW Makefiles" ^
-DBUILD_TESTS=ON ^
-DBUILD_ONNX_TESTS=ON ^
-DBUILD_ROS_WRAPPER=OFF ^
-DCMAKE_BUILD_TYPE=Release
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] CMake configuration failed.
exit /b 1
)
echo [INFO] Building with mingw32-make ...
mingw32-make -j4
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] Build failed.
exit /b 1
)
REM --- Copy executables back ---
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
for %%E in (test_core_lib.exe extract_mel_cpp.exe test_classifier_cpp.exe demo_offline.exe) do (
if exist "%TEMP_BUILD%\%%E" (
copy /y "%TEMP_BUILD%\%%E" "%OUTPUT_DIR%\" >nul
echo [OK] Copied %%E
)
)
echo.
echo [OK] CMake MinGW build complete. Executables in %OUTPUT_DIR%
endlocal

@ -0,0 +1,69 @@
@echo off
chcp 65001 >nul
setlocal EnableDelayedExpansion
echo ==========================================
echo Acoustic Core Library - Quick Build
echo ==========================================
set SRC_ROOT=%~dp0
set EIGEN=%SRC_ROOT%third_party\eigen-3.4.0
set KISSFFT=%SRC_ROOT%third_party\kiss_fft
set ONNX_INC=%SRC_ROOT%third_party\onnxruntime\include
set ONNX_LIB=%SRC_ROOT%third_party\onnxruntime\lib\onnxruntime.lib
set INCLUDES=-I%SRC_ROOT%include -I%EIGEN% -I%KISSFFT% -I%ONNX_INC%
set FLAGS=-std=c++17 -O2 -D_USE_MATH_DEFINES -Wa,-mbig-obj
set LIBS=%ONNX_LIB%
if not exist build mkdir build
REM --- test_core_lib ---
echo [1/3] Building test_core_lib.exe ...
g++ %FLAGS% %INCLUDES% ^
tests\test_core_lib.cpp ^
src\core\fft_utils.cpp ^
src\core\audio_buffer.cpp ^
src\core\feature_extractor.cpp ^
src\core\gcc_phat_localizer.cpp ^
src\core\distance_estimator.cpp ^
src\core\threat_tracker.cpp ^
-o build\test_core_lib.exe ^
%LIBS% -lws2_32
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] test_core_lib build failed.
exit /b 1
)
REM --- extract_mel_cpp ---
echo [2/3] Building extract_mel_cpp.exe ...
g++ %FLAGS% %INCLUDES% ^
tests\extract_mel_cpp.cpp ^
src\core\fft_utils.cpp ^
src\core\feature_extractor.cpp ^
src\io\wav_file_source.cpp ^
-o build\extract_mel_cpp.exe ^
%LIBS%
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] extract_mel_cpp build failed.
exit /b 1
)
REM --- test_classifier_cpp ---
echo [3/3] Building test_classifier_cpp.exe ...
g++ %FLAGS% %INCLUDES% ^
tests\test_classifier_cpp.cpp ^
src\core\fft_utils.cpp ^
src\core\feature_extractor.cpp ^
src\core\gunshot_classifier.cpp ^
src\io\wav_file_source.cpp ^
-o build\test_classifier_cpp.exe ^
%LIBS% -D_stdcall=
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] test_classifier_cpp build failed.
exit /b 1
)
echo.
echo [OK] All core tests built successfully in build\
endlocal

@ -0,0 +1,40 @@
@echo off
chcp 65001 >nul
setlocal EnableDelayedExpansion
echo ==========================================
echo Acoustic Offline Demo Build (Windows)echo ==========================================
set SRC_ROOT=%~dp0
set EIGEN=%SRC_ROOT%third_party\eigen-3.4.0
set KISSFFT=%SRC_ROOT%third_party\kiss_fft
set ONNX_INC=%SRC_ROOT%third_party\onnxruntime\include
set ONNX_LIB=%SRC_ROOT%third_party\onnxruntime\lib\libonnxruntime.a
set INCLUDES=-I%SRC_ROOT%include -I%EIGEN% -I%KISSFFT% -I%ONNX_INC%
set FLAGS=-std=c++17 -O2 -D_USE_MATH_DEFINES -Wa,-mbig-obj
set LIBS=%ONNX_LIB%
if not exist build mkdir build
echo [1/1] Building demo_offline.exe (simplified, no yaml-cpp dependency) ...
g++ %FLAGS% %INCLUDES% ^
tests\demo_offline.cpp ^
src\core\fft_utils.cpp ^
src\core\feature_extractor.cpp ^
src\core\distance_estimator.cpp ^
src\core\gunshot_classifier.cpp ^
src\io\wav_file_source.cpp ^
-o build\demo_offline.exe ^
%LIBS% -D_stdcall=
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] demo_offline build failed.
exit /b 1
)
echo.
echo [OK] demo_offline.exe built successfully in build\
echo.
echo Usage: build\demo_offline.exe dataset\binary\val\ambient\xxx.wav
echo build\demo_offline.exe dataset\binary\val\threat\xxx.wav
endlocal

@ -0,0 +1,74 @@
# 声源分析模块默认配置
# 本文件由 acoustic_node 通过 ros::param 或 Pipeline 直接加载
# ── 音频参数 ───────────────────────────────
audio:
sample_rate: 16000
chunk_duration: 2.0 # 分析窗口长度 (秒)
hop_duration: 0.5 # 步进 (秒)
n_channels: 4 # 麦克风数量
# ── 音频源选择 ──────────────────────────────
# [TEMP] 临时方案: 手机麦克风单通道测试
# [FINAL] 最终方案: 麦克风阵列多通道定位
# 可选值: "mic_array" (最终), "mobile_phone" (临时), "wav_file" (离线测试)
source:
type: "mic_array" # [FINAL] 最终方案4 通道麦克风阵列
# 临时方案参数 (手机音频)
mobile_phone_topic: "/mobile_phone/audio"
mobile_phone_timeout: 10.0 # 等待连接超时 (秒)
# 离线测试参数
wav_file_path: "" # 若 type="wav_file",填写 WAV 路径
# ── 麦克风阵列几何 (单位: 米) ──────────────
# layout 可选: cross, linear, circular, custom
# 使用 custom 时,需要填写 positions 数组 [x, y, z]
# [TEMP] 临时方案手机单通道num_mics=1, layout 任意
# [FINAL] 最终方案4 通道阵列num_mics=4, layout=cross
mic_array:
num_mics: 4 # [FINAL] 4 通道十字阵列
layout: "cross" # 单通道时此值被忽略
spacing: 0.15
positions:
- [0.0, 0.15, 0.0] # Mic 1 (前)
- [0.15, 0.0, 0.0] # Mic 2 (右)
- [0.0, -0.15, 0.0] # Mic 3 (后)
- [-0.15, 0.0, 0.0] # Mic 4 (左)
# ── 特征提取 ────────────────────────────────
features:
n_mels: 64
n_fft: 2048
hop_length: 512
f_min: 0.0
f_max: 8000.0
preemphasis: 0.97
# ── 分类器 ──────────────────────────────────
classifier:
model_path: "$(find acoustic_analyzer)/models/gunshot_classifier.onnx"
label_map_path: "$(find acoustic_analyzer)/models/label_map.json"
threshold: 0.7
smoothing_window: 3
# ── 定位 ────────────────────────────────────
localization:
algorithm: "gcc_phat"
max_tdoa: 0.00044 # 最大时延 (0.15m / 343m/s)
interpolation_factor: 4 # 互相关插值倍数
# ── 距离估计 ─────────────────────────────────
distance:
reference_spl:
threat: 150 # [FINAL] 二分类模型统一使用 threat 参考 SPL
gunshot: 150 # 兼容 4 类模型
artillery: 180
explosion: 170
attenuation_alpha: 0.6
kalman_process_noise: 0.01
kalman_measurement_noise: 5.0
# ── 输出 ────────────────────────────────────
output:
publish_rate: 10 # 最大输出频率 (Hz)
min_event_interval: 0.3 # 同一威胁最小上报间隔 (秒)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save