简单的音频比较
频谱算法
import os
import numpy as np
import matplotlib.pyplot as plt
from pydub import AudioSegment
from imagehash import phash
from PIL import Image
import ioimport random
import timedef 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 Nonedef 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_valuedef 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 - hash2return hamming_distanceif __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 logginglogger = 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_sampleslogger.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 和 mfcc2if visualize:plt.figure(figsize=(12, 6))# 显示第一个音频片段的MFCCplt.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')# 显示第二个音频文件的MFCCplt.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() # 关闭图形以避免内存问题# 计算DTWD, 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_timelogger.info(f"分析完成,耗时: {elapsed_time:.2f} 秒")return resultsdef 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))