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

如何从WAV文件提取特征片段识别葡萄牙语数字并对比

问题描述

我通过HTTP请求获取到由谷歌语音朗读的6位葡萄牙语数字音频WAV文件,数字间以静音分隔。本地已存储0-9每个数字的WAV音频,需从本地文件提取代表性原始数据片段,与请求得到的音频分片对比。现有Python代码通过二进制片段匹配识别数字,但不知如何选择本地WAV的代表性片段;尝试用pydub的split_on_silence拆分音频并导出、生成MD5哈希对比均失败,求解决方案。

现有代码
def discaptcher(file_content):
    # legacy code, other audio
    footprints_to_numbers = {
        b"\x72\x00\x77\x00\x7a\x00\x4a\x00\x58\x00\x7a\x00\x55\x00\x53\x00": "0",  # noqa
        b"\x86\x00\x7f\x00\x8c\x00\x7f\x00\x71\x00\x62\x00\x52\x00\x52\x00": "1",  # noqa
        b"\x15\x00\x1e\x00\x1b\x00\x05\x00\x16\x00\x1b\x00\x20\x00\x17\x00": "2",  # noqa
        b"\x8e\xff\x85\xff\x8c\xff\x85\xff\x79\xff\xb6\xff\xbd\xff\xaa\xff": "3",  # noqa
        b"\xfd\xff\x0b\x00\x0f\x00\xf6\xff\xd7\xff\xcf\xff\xbb\xff\xb2\xff": "4",  # noqa
        b"\x87\x00\x85\x00\x7c\x00\x77\x00\x7b\x00\x81\x00\x79\x00\x7f\x00": "5",  # noqa
        b"\x65\xff\x87\xff\xb1\xff\xa3\xff\x6a\xff\x20\xff\x26\xff\x2c\xff": "6",  # noqa
        b"\xb9\xfe\xbb\xfe\xae\xfe\xa6\xfe\xa1\xfe\xb8\xfe\xd2\xfe\xfb\xfe": "7",  # noqa
        b"\x5b\xff\x66\xff\x90\xff\x85\xff\x76\xff\x75\xff\x79\xff\x75\xff": "8",  # noqa
        b"\x16\x01\x1e\x01\x29\x01\x13\x01\x0b\x01\x05\x01\x06\x01\xf8\x00": "9",
    }  # noqa

    captcha_dict = {}

    captcha = ""

    for snd, lt in footprints_to_numbers.items():
        pos = [m.start() for m in regex.finditer(regex.escape(snd), file_content)]

        for i in pos:
            captcha_dict[i] = lt

    for key in sorted(captcha_dict.keys()):
        captcha += captcha_dict[key]

    return captcha
解决方案

一、修复split_on_silence音频拆分问题

split_on_silence失败大多是阈值或格式不匹配导致,调整参数并统一音频格式:

from pydub import AudioSegment
from pydub.silence import split_on_silence

# 加载目标音频并统一格式(和本地音频保持一致,比如单声道、16000Hz采样率、16bit位深)
target_audio = AudioSegment.from_wav("captcha.wav")
target_audio = target_audio.set_frame_rate(16000).set_channels(1).set_sample_width(2)

# 调整拆分参数:根据实际音频的静音长度和音量设置阈值
chunks = split_on_silence(
    target_audio,
    min_silence_len=80,  # 静音段最小时长(ms),按需调整
    silence_thresh=-35,  # 静音音量阈值(dBFS),数值越小越敏感
    keep_silence=40  # 保留片段前后少量静音,避免切到有效音频
)

# 导出拆分后的单个数字片段
for idx, chunk in enumerate(chunks):
    chunk.export(f"split_digit_{idx}.wav", format="wav")

二、提取本地音频的代表性片段

放弃固定二进制片段,改用更稳定的中间段采样或特征提取:

1. 中间稳定片段采样

跳过音频开头结尾的过渡音,取中间100-200ms的原始数据作为特征:

def get_representative_chunk(audio_path, duration_ms=150):
    audio = AudioSegment.from_wav(audio_path)
    audio = audio.set_frame_rate(16000).set_channels(1).set_sample_width(2)
    # 计算中间位置,避免开头结尾的杂音
    start_ms = (len(audio) - duration_ms) // 2
    end_ms = start_ms + duration_ms
    return audio[start_ms:end_ms].raw_data

# 预生成本地0-9数字的特征片段
local_digit_features = {}
for num in range(10):
    local_digit_features[str(num)] = get_representative_chunk(f"local_{num}.wav")

2. MFCC特征匹配(更高容错性)

用音频特征替代二进制匹配,避免格式差异导致的失败:

import librosa
import numpy as np

def get_mfcc_feature(audio_path):
    y, sr = librosa.load(audio_path, sr=16000)
    mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
    # 取特征均值作为数字的代表特征
    return np.mean(mfcc, axis=1)

# 预生成本地数字的MFCC特征
local_mfcc_features = {}
for num in range(10):
    local_mfcc_features[str(num)] = get_mfcc_feature(f"local_{num}.wav")

# 对比片段与本地特征的相似度
def match_digit(chunk_path):
    chunk_feature = get_mfcc_feature(chunk_path)
    min_distance = float("inf")
    matched_num = ""
    for num, feat in local_mfcc_features.items():
        # 计算欧氏距离,越小越相似
        distance = np.linalg.norm(chunk_feature - feat)
        if distance < min_distance:
            min_distance = distance
            matched_num = num
    return matched_num

三、替代MD5哈希的匹配方案

MD5要求完全匹配容错性为0,改用滑动窗口+差异容忍匹配:

def sliding_window_match(target_data, local_data, tolerance=2, max_diff_ratio=0.1):
    target_len = len(target_data)
    local_len = len(local_data)
    if target_len < local_len:
        return False
    # 滑动窗口遍历目标数据
    for i in range(target_len - local_len + 1):
        window = target_data[i:i+local_len]
        # 统计差异字节数(允许小范围内的数值差异)
        diff_count = sum(1 for a, b in zip(window, local_data) if abs(a - b) > tolerance)
        if diff_count < len(local_data) * max_diff_ratio:
            return True
    return False

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:51:08