#!/usr/bin/env python3 """K-means 聚类自动分组声纹,每组抽 1 个样本供试听确认""" import os, sys, json, warnings, shutil import numpy as np import librosa from sklearn.cluster import KMeans warnings.filterwarnings('ignore') SEARCH_DIR = r"D:\Project\Code\Uni\Cyrene-Voice-Model\data\cleaned" SAMPLE_DIR = r"D:\Project\Code\Uni\Cyrene-Voice-Model\data\voice_samples" N_CLUSTERS = 8 # 预期 3-5 个女声 + 若干男声/杂音组 def extract_features(wav_path): try: y, sr = librosa.load(wav_path, sr=22050, mono=True) if len(y) < sr * 0.3: return None f0, _, _ = librosa.pyin(y, fmin=80, fmax=600, sr=sr) f0 = f0[~np.isnan(f0)] if len(f0) < 10: return None mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13) mfcc_d = librosa.feature.delta(mfcc) feat = np.concatenate([ [np.mean(f0), np.std(f0), np.percentile(f0, 25), np.percentile(f0, 50), np.percentile(f0, 75)], np.mean(mfcc, axis=1), np.std(mfcc, axis=1), np.mean(mfcc_d, axis=1), np.std(mfcc_d, axis=1), ]) return feat.astype(np.float32) except: return None print("提取声纹特征...") wav_files = [] features = [] for root, dirs, files in os.walk(SEARCH_DIR): for f in files: if f.endswith('.wav') and 'VoBanks' in root: path = os.path.join(root, f) feat = extract_features(path) if feat is not None: wav_files.append(path) features.append(feat) features = np.array(features) print(f" 有效文件: {len(features)}") # K-means 聚类 print(f"\nK-means 聚类 (k={N_CLUSTERS})...") kmeans = KMeans(n_clusters=N_CLUSTERS, random_state=42, n_init=10) labels = kmeans.fit_predict(features) # 统计每组 clusters = {} for i, (label, path) in enumerate(zip(labels, wav_files)): if label not in clusters: clusters[label] = [] clusters[label].append((path, features[i])) # 每组选最接近中心的样本 print(f"\n=== 聚类结果 ===\n") for label in sorted(clusters.keys()): group = clusters[label] center = kmeans.cluster_centers_[label] # 找离中心最近的 best_idx = min(range(len(group)), key=lambda i: np.linalg.norm(group[i][1] - center)) best_path = group[best_idx][0] # 统计音高 pitches = [g[1][0] for g in group] # mean pitch avg_pitch = np.mean(pitches) voice_type = "男" if avg_pitch < 170 else "女" print(f" Group {label+1}: {len(group):4d} files, pitch={avg_pitch:.0f}Hz ({voice_type}), " f"sample: {os.path.basename(best_path)}") # 复制每组样本到样本目录 os.makedirs(SAMPLE_DIR, exist_ok=True) for label in sorted(clusters.keys()): group = clusters[label] center = kmeans.cluster_centers_[label] best_idx = min(range(len(group)), key=lambda i: np.linalg.norm(group[i][1] - center)) src = group[best_idx][0] dst = os.path.join(SAMPLE_DIR, f"group_{label+1:02d}_{os.path.basename(src)}") shutil.copy2(src, dst) print(f"\n每组样本已复制到: {SAMPLE_DIR}") print("试听每个 group_*.wav,找到昔涟的组,告诉我编号。")