You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Kotlin语音发音准确率对比结果异常问题求助

问题:MFCC+DTW计算发音相似度时,无关音频相似度偏高(70%-90%)

我正在开发一款对比用户发音与设备音频准确率的应用,采用MFCC提取特征+DTW算法对比的方案,但发现即使录制和设备音频完全不同的句子,相似度始终维持在70%-90%区间。尝试过切换CosineDistance、EuclideanDistance,也对MFCC做了归一化处理,问题依然存在。

以下是我用于检查相似度百分比的类:

class SimilarityChecker {
    private val sampleRate = 16000
    private val nMFCC = 13
    private val bufferSize = 512
    private val nFFT = 2048
    private val hopLength = 512
    private val nMels = 128

    fun percentage(file1Path: String, file2Path: String): Double
    {
        val mfcc1 = extractMFCCs(file1Path)
        val mfcc2 = extractMFCCs(file2Path)
        
        val percentage = calculateDTWDistance(mfcc1 , mfcc2)

        return (percentage * 100.0).roundToInt() / 100.0
    }

    private fun extractMFCCs(@NonNull path: String): MutableList<MutableList<Float>> {
        Log.i("path from Main:", path)
        var fileList = JLibrosa().loadAndRead(path, sampleRate, -1)

        val MFCC = JLibrosa().generateMFCCFeatures(fileList, sampleRate, this.nMFCC, nFFT, nMels, hopLength)
            .map { arr -> arr.toList() }

        return MFCC.map { d -> d.toMutableList() }.toMutableList()
    }

    private fun calculateDTWDistance(
        referenceFeatures: MutableList<MutableList<Float>>,
        studentFeatures: MutableList<MutableList<Float>>
    ) : Double {
        val dtw = DTW()
        val ldTW = io.github.cawfree.dtw.DTW()

        var distances: MutableList<Double> = mutableListOf()
        for (i in referenceFeatures.indices) {
            distances.add(
                FastDTW.compare(
                    TimeSeriesBase(
                        referenceFeatures[i].mapIndexed { index, value ->
                            TimeSeriesItem(
                                index.toDouble(),
                                TimeSeriesPoint(doubleArrayOf(value.toDouble()))
                            )
                        }
                    ),
                    TimeSeriesBase(
                        studentFeatures[i].mapIndexed { index, value ->
                            TimeSeriesItem(
                                index.toDouble(),
                                TimeSeriesPoint(doubleArrayOf(value.toDouble()))
                            )
                        }
                    ),
                    10, Distances.EUCLIDEAN_DISTANCE
                ).distance
            )
        }

        val distance = distances.average()

        return if (distances.max() == 0.0) 100.0 else ((distances.max() - distance) / distances.max()) * 100
    }
}

问题根源分析

  1. DTW使用逻辑错误
    代码中对每个MFCC维度(比如第1到第13个系数)单独执行DTW,再取平均距离转换为相似度。但MFCC的正确结构是:每一行是一个时间帧的13维特征向量,应该把整个MFCC矩阵作为多维时间序列执行DTW,而非拆分维度单独计算,否则会丢失特征间的关联信息,导致区分度大幅下降。

  2. 相似度转换公式不合理
    ((distances.max() - distance)/distances.max())*100 的计算逻辑依赖单个维度的最大距离,这个值不具备全局可比性,会导致无关音频的距离被错误映射到高相似度区间。

  3. 缺少关键预处理步骤
    未对MFCC执行均值方差归一化(CMVN),无法消除不同录制环境(如麦克风差异、背景噪音)带来的特征偏移,导致无关音频的特征分布重叠度高。


修正方案

1. 正确处理多维MFCC时间序列

将整个MFCC的帧序列(每个帧是13维向量)传入DTW,计算全局的DTW距离。

2. 优化相似度映射逻辑

DTW距离越小,序列相似度越高,需将距离映射到0-100区间:

  • 先通过离线统计确定无关音频的DTW距离阈值(比如取大量无关样本的平均距离+2倍标准差)
  • 相似度 = max(0, 100 - (当前DTW距离 / 阈值距离) * 100)

3. 加入MFCC预处理(CMVN)

对每个MFCC维度做均值方差归一化,消除环境干扰。


修正后的代码示例

class SimilarityChecker {
    private val sampleRate = 16000
    private val nMFCC = 13
    private val nFFT = 2048
    private val hopLength = 512
    private val nMels = 128
    // 离线统计得到的无关音频DTW距离阈值(根据实际样本调整)
    private val maxDtwThreshold = 150.0

    fun percentage(file1Path: String, file2Path: String): Double {
        val mfcc1 = extractAndPreprocessMFCCs(file1Path)
        val mfcc2 = extractAndPreprocessMFCCs(file2Path)

        val dtwDistance = calculateMultiDTWDistance(mfcc1, mfcc2)
        // 映射为0-100的相似度
        val similarity = if (dtwDistance >= maxDtwThreshold) 0.0 else (100.0 - (dtwDistance / maxDtwThreshold) * 100.0)
        return similarity.roundToInt() / 100.0
    }

    private fun extractAndPreprocessMFCCs(path: String): List<DoubleArray> {
        Log.i("path from Main:", path)
        val audioData = JLibrosa().loadAndRead(path, sampleRate, -1)
        // 提取MFCC特征
        val mfccFeatures = JLibrosa().generateMFCCFeatures(audioData, sampleRate, nMFCC, nFFT, nMels, hopLength)
        val mfccList = mfccFeatures.map { it.toDoubleArray() }.toList()
        // 执行CMVN归一化
        return applyCMVN(mfccList)
    }

    private fun applyCMVN(mfccList: List<DoubleArray>): List<DoubleArray> {
        val numFeatures = mfccList.firstOrNull()?.size ?: return mfccList
        val means = DoubleArray(numFeatures) { 0.0 }
        val stds = DoubleArray(numFeatures) { 0.0 }

        // 计算每个维度的均值
        mfccList.forEach { frame ->
            frame.forEachIndexed { idx, value -> means[idx] += value }
        }
        means.forEachIndexed { idx, sum -> means[idx] = sum / mfccList.size }

        // 计算每个维度的标准差
        mfccList.forEach { frame ->
            frame.forEachIndexed { idx, value -> stds[idx] += Math.pow(value - means[idx], 2.0) }
        }
        stds.forEachIndexed { idx, sum ->
            stds[idx] = Math.sqrt(sum / mfccList.size)
            // 避免除以0
            if (stds[idx] < 1e-6) stds[idx] = 1.0
        }

        // 归一化每个帧
        return mfccList.map { frame ->
            frame.mapIndexed { idx, value -> (value - means[idx]) / stds[idx] }.toDoubleArray()
        }
    }

    private fun calculateMultiDTWDistance(reference: List<DoubleArray>, student: List<DoubleArray>): Double {
        // 构造多维时间序列
        val refTimeSeries = TimeSeriesBase(
            reference.mapIndexed { timeStep, features ->
                TimeSeriesItem(timeStep.toDouble(), TimeSeriesPoint(features))
            }
        )
        val studentTimeSeries = TimeSeriesBase(
            student.mapIndexed { timeStep, features ->
                TimeSeriesItem(timeStep.toDouble(), TimeSeriesPoint(features))
            }
        )
        // 计算全局DTW距离
        return FastDTW.compare(refTimeSeries, studentTimeSeries, 10, Distances.EUCLIDEAN_DISTANCE).distance
    }
}

额外优化建议

  • 扩展特征维度:在MFCC基础上计算一阶差分(delta)和二阶差分(delta-delta),将特征维度扩展到39维,提升区分度。
  • 加入DTW窗口约束:使用Sakoe-Chiba窗口限制DTW路径搜索范围,减少无关帧的匹配。
  • 动态校准阈值:定期用新的样本更新maxDtwThreshold,确保相似度计算的准确性。

内容的提问来源于stack exchange,提问作者Cooper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 19:44:54