简单的音频比较
频谱算法
import os
import numpy as np
import matplotlib.pyplot as plt
from pydub import AudioSegment
from imagehash import phash
from PIL import Image
import io
import random
import time
def audio_to_spectrogram(audio_path, output_path=None, return_bytes=False):
"""
将音频文件转换为频谱图
:param audio_path: 音频文件的路径
:param output_path: 频谱图的输出路径,如果为None则不保存到文件
:param return_bytes: 是否返回字节形式的频谱图
:return: 如果return_bytes为True,返回内存中的图像字节;否则返回None
"""
audio = AudioSegment.from_file(audio_path)
samples = np.array(audio.get_array_of_samples())
if audio.channels == 2:
samples = samples.reshape((-1, 2))
samples = samples.mean(axis=1)
print(f'处理音频: {audio_path}')
plt.figure(figsize=(10, 4))
plt.specgram(samples, Fs=audio.frame_rate, NFFT=1024, noverlap=512)
plt.axis('off')
# 保存到内存
if return_bytes or not output_path:
buf = io.BytesIO()
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0)
buf.seek(0)
# 如果同时需要保存到文件
if output_path:
plt.savefig(output_path, bbox_inches='tight', pad_inches=0)
print(f'频谱图已保存至: {output_path}')
plt.close()
return buf.getvalue() if return_bytes else buf
# 只保存到文件
if output_path:
plt.savefig(output_path, bbox_inches='tight', pad_inches=0)
print(f'频谱图已保存至: {output_path}')
plt.close()
return None
def get_audio_perceptual_hash(audio_path):
"""
获取音频的感知哈希值
:param audio_path: 音频文件的路径
:return: 音频的感知哈希值
"""
# 直接获取内存中的频谱图数据
spectrogram_buffer = audio_to_spectrogram(audio_path, return_bytes=False)
# 从内存数据创建PIL图像对象
image = Image.open(spectrogram_buffer)
# 计算感知哈希
hash_value = phash(image)
return hash_value
def compare_audio_similarity(audio_path1, audio_path2):
"""
对比两个音频文件的相似性
:param audio_path1: 第一个音频文件的路径
:param audio_path2: 第二个音频文件的路径
:return: 汉明距离,值越小表示越相似
"""
hash1 = get_audio_perceptual_hash(audio_path1)
hash2 = get_audio_perceptual_hash(audio_path2)
hamming_distance = hash1 - hash2
return hamming_distance
if __name__ == "__main__":
audio_path1 = "audio1.wav"
audio_path2 = "audio2.wav"
print(compare_audio_similarity(audio_path1, audio_path2))
dtw比较
import librosa
import numpy as np
from librosa.sequence import dtw
import matplotlib.pyplot as plt
import matplotlib
# 设置matplotlib支持中文显示
matplotlib.rcParams['font.sans-serif'] = ['SimHei'] # 用黑体显示中文
matplotlib.rcParams['axes.unicode_minus'] = False # 正常显示负号
from pathlib import Path
import time
import logging
logger = logging.getLogger(__name__)
def compare_audio_dtw(audio1_path, audio2_path, chunk_duration=1.0, visualize=False, save_dir=None):
"""
使用DTW算法分析audio1的每个片段在audio2中的位置和相似度
参数:
audio1_path: 第一个音频文件路径(会被分段)
audio2_path: 第二个音频文件路径(完整搜索空间)
chunk_duration: 每个分段的时长(秒)
visualize: 是否生成MFCC可视化图像
save_dir: 可视化图像保存目录,如果为None则为'images'
返回:
list: 包含每个片段的匹配信息的列表,每项为(片段索引, 匹配时间点, 相似度得分)
"""
start_time = time.time()
# 创建可视化保存目录
if visualize:
save_dir = save_dir or 'images'
img_dir = Path(save_dir)
if not img_dir.exists():
img_dir.mkdir(parents=True, exist_ok=True)
# === 加载音频 ===
logger.info(f"加载音频文件: {audio1_path}, {audio2_path}")
y1, sr = librosa.load(audio1_path, sr=None, res_type='kaiser_fast', offset=0.0, duration=None, mono=True)
y2, _ = librosa.load(audio2_path, sr=sr, res_type='kaiser_fast', mono=True) # 保持同采样率
logger.info(f"{audio1_path} 采样率: {sr}")
logger.info(f"{audio2_path} 采样率: {sr}")
# === 参数设置 ===
chunk_samples = int(chunk_duration * sr)
num_chunks = len(y1) // chunk_samples
logger.info(f"音频1 总共 {num_chunks} 段,每段 {chunk_duration} 秒")
# 结果列表
results = []
# === 对每段做 DTW 匹配 audio2 ===
for i in range(num_chunks):
chunk = y1[i * chunk_samples : (i + 1) * chunk_samples]
mfcc_chunk = librosa.feature.mfcc(y=chunk, sr=sr, n_mfcc=13)
mfcc2 = librosa.feature.mfcc(y=y2, sr=sr, n_mfcc=13)
# 可视化 mfcc_chunk 和 mfcc2
if visualize:
plt.figure(figsize=(12, 6))
# 显示第一个音频片段的MFCC
plt.subplot(1, 2, 1)
plt.imshow(mfcc_chunk, aspect='auto', origin='lower')
plt.title(f'MFCC - audio1 segment {i}')
plt.ylabel('MFCC coefficients')
plt.xlabel('Frames')
plt.colorbar(format='%+2.0f dB')
# 显示第二个音频文件的MFCC
plt.subplot(1, 2, 2)
plt.imshow(mfcc2, aspect='auto', origin='lower')
plt.title('MFCC - audio2 full file')
plt.ylabel('MFCC coefficients')
plt.xlabel('Frames')
plt.colorbar(format='%+2.0f dB')
plt.tight_layout()
plt.savefig(f'{img_dir}/mfcc_comparison_{i}.png') # 保存图像到文件
plt.close() # 关闭图形以避免内存问题
# 计算DTW
D, wp = dtw(mfcc_chunk, mfcc2, subseq=True, metric='euclidean')
# 找到最佳匹配位置
best_idx = np.argmin(D[-1])
best_time = librosa.frames_to_time(best_idx, sr=sr)
# 获取最小代价和归一化相似度
min_cost = D[-1, best_idx]
max_possible_cost = np.max(D)
similarity_score = 1 - (min_cost / max_possible_cost) if max_possible_cost > 0 else 1
# 添加到结果列表
results.append((i, best_time, similarity_score))
logger.info(f"第{i}段 匹配位置: {best_time:.2f} 秒,相似度: {similarity_score:.2f}")
# 计算耗时
elapsed_time = time.time() - start_time
logger.info(f"分析完成,耗时: {elapsed_time:.2f} 秒")
return results
def format_results(results):
"""格式化结果为易读的字符串"""
output = []
for idx, time_pos, score in results:
output.append(f"片段 {idx}: 在 {time_pos:.2f} 秒处匹配,相似度 {score:.2f}")
return "\n".join(output)
if __name__ == "__main__":
# 设置基本日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 示例用法
audio1_path = "audio1.wav"
audio2_path = "audio2.wav"
results = compare_audio_dtw(audio1_path, audio2_path, visualize=True)
print("\n匹配结果:")
print(format_results(results))