<think>这个错误是由于在可视化函数 `visualize_differences` 中,使用 `plt.boxplot` 时,传入的数据与 `positions` 参数长度不匹配导致的。
具体来说,`plt.boxplot(solid_features.T, positions=np.arange(features.shape[1])-0.15, widths=0.3)` 这行代码中:
- `solid_features.T` 的维度是 (特征数, 样本数),即每一行是一个特征的所有样本数据。
- `positions` 参数指定了每个箱线图的位置,其长度应该与 `solid_features.T` 的行数(即特征数)相同。
但是,在错误中提示:`List of boxplot statistics and `positions` values must have same the length`,这意味着 `solid_features.T` 的行数与 `positions` 数组的长度不一致。
从错误信息中可以看出,`features.shape[1]` 是特征的数量(即9个特征),而 `solid_features.T` 的行数应该也是特征数(9行)。所以理论上应该是匹配的。
然而,我们注意到在代码中,`solid_features` 是从 `features` 中根据标签提取的,而 `features` 的维度是 (样本数, 特征数)。因此,`solid_features` 的维度是 (无孔样本数, 特征数)。那么 `solid_features.T` 的维度就是 (特征数, 无孔样本数)。所以行数等于特征数,即9行。
那么为什么会报长度不一致呢?可能的原因是在提取 `solid_features` 时,如果某一类样本数为0,那么 `solid_features` 就为空,从而 `solid_features.T` 的行数为0,而 `positions` 的长度为9,导致不一致。
从输出中我们看到,无孔硬币加载了35个,有孔硬币加载了35个,所以每类都有样本,因此不应该出现这种情况。
另一种可能的错误是:在 `visualize_differences` 函数中,我们使用了 `features` 的原始维度(即样本数×特征数)来创建 `solid_features` 和 `hole_features`。但是,在调用 `visualize_differences` 之前,我们对特征进行了标准化(`scaled_features = scaler.fit_transform(features)`),然后传入的是 `scaled_features`,这是一个二维数组,形状为 (70, 9)。然后我们根据标签将其分为两组:
solid_features = features[labels == 0] # 形状 (35, 9)
hole_features = features[labels == 1] # 形状 (35, 9)
然后,在绘制箱线图时,我们分别对这两组数据绘制每个特征的箱线图。我们使用 `solid_features.T` 来将数据转为 (9, 35),这样就有9个特征,每个特征是一个长度为35的数组(即35个样本)。
然后,我们使用 `plt.boxplot` 绘制这9个特征的数据,并指定 `positions` 为0到8(特征索引)减去0.15和加上0.15(以便并排显示)。
所以,问题可能出在 `solid_features` 或 `hole_features` 为空,但我们的数据中每类都有35个样本,所以不应该为空。
另一个可能的问题是:在 `visualize_differences` 函数中,我们使用了 `features` 的原始变量名,但是在函数内部,我们将传入的数组命名为 `features`,但在绘制箱线图时,我们使用了:
solid_features = features[labels == 0]
hole_features = features[labels == 1]
注意:这里的 `features` 实际上是经过标准化的 `scaled_features`,所以没有问题。
但是,我们注意到在绘制箱线图时,我们调用了:
plt.boxplot(solid_features.T, positions=np.arange(features.shape[1])-0.15, widths=0.3)
plt.boxplot(hole_features.T, positions=np.arange(features.shape[1])+0.15, widths=0.3)
这里的 `features.shape[1]` 是特征的数量(9),所以 `positions` 是一个长度为9的数组。
而 `solid_features.T` 有9行,所以应该没有问题。
但是,错误提示长度不一致,所以我们需要检查一下实际运行中 `solid_features.T` 的形状和 `features.shape[1]` 的值。
我们可以添加打印语句来调试:
```python
print("solid_features shape:", solid_features.shape) # 应该是 (35,9)
print("solid_features.T shape:", solid_features.T.shape) # 应该是 (9,35)
print("positions shape:", np.arange(features.shape[1]).shape) # 应该是 (9,)
```
建议在 `visualize_differences` 函数中加入上述打印语句,以确认形状。
然而,从错误信息来看,问题发生在 `plt.boxplot` 函数中,所以可能是 `plt.boxplot` 对输入数据的要求问题。
另一种可能性:`solid_features` 中可能包含NaN或Inf值?这会导致实际有效的数据行数减少。
因此,我们可以在函数开始时检查数据中是否有NaN或Inf。
由于时间关系,我们直接修改代码,确保在绘制箱线图时,我们传入的数据是有效的,并且长度匹配。
我们也可以尝试将 `solid_features.T` 转换为一个列表,其中每个元素是一个特征的所有样本值(即一个一维数组)。因为 `boxplot` 要求传入一个由多个数据集组成的序列(每个数据集是一个特征的所有样本值)。
另外,注意 `positions` 参数的长度必须与数据集的数量(即特征数)相同。
我们修改一下代码,确保在传入 `boxplot` 之前,每个特征的数据是一个一维数组,并且没有NaN值。
我们可以添加一个检查:
```python
# 在visualize_differences函数中,绘制箱线图之前
# 确保没有NaN
if np.any(np.isnan(solid_features)) or np.any(np.isinf(solid_features)):
print("Warning: solid_features contains NaN or Inf")
# 用0替换NaN和Inf
solid_features = np.nan_to_num(solid_features)
if np.any(np.isnan(hole_features)) or np.any(np.isinf(hole_features)):
print("Warning: hole_features contains NaN or Inf")
hole_features = np.nan_to_num(hole_features)
```
同时,我们确保传入 `boxplot` 的数据是一个列表,每个元素是一个特征的数据:
```python
# 修改为:
# 将每个特征的数据提取出来,组成列表
solid_data = [solid_features[:, i] for i in range(solid_features.shape[1])]
hole_data = [hole_features[:, i] for i in range(hole_features.shape[1])]
# 然后绘制
plt.boxplot(solid_data, positions=np.arange(len(solid_data))-0.15, widths=0.3)
plt.boxplot(hole_data, positions=np.arange(len(hole_data))+0.15, widths=0.3)
```
这样,我们明确地将每个特征的数据作为一个列表传入,并且 `positions` 的长度与 `solid_data` 的长度(特征数)相同。
修改后的 `visualize_differences` 函数中的相关部分如下:
```python
def visualize_differences(features, labels):
"""可视化两类硬币的声音特征差异"""
plt.figure(figsize=(15, 10))
# 准备数据
solid_features = features[labels == 0]
hole_features = features[labels == 1]
feature_names = [
'Mean Amp', 'Std Dev', 'Zero Crossing',
'Spectral Centroid', 'Spectral Bandwidth', 'Spectral Rolloff',
'MFCC1', 'MFCC2', 'MFCC3'
]
# 检查NaN或Inf
solid_features = np.nan_to_num(solid_features)
hole_features = np.nan_to_num(hole_features)
# 将每个特征的数据转换为列表形式
solid_data = [solid_features[:, i] for i in range(solid_features.shape[1])]
hole_data = [hole_features[:, i] for i in range(hole_features.shape[1])]
# 特征分布对比
plt.subplot(2, 2, 1)
# 绘制特征范围线
for i in range(features.shape[1]):
plt.plot([i, i],
[np.min(features[:, i]), np.max(features[:, i])],
'k--', alpha=0.3)
# 绘制箱线图
plt.boxplot(solid_data, positions=np.arange(len(solid_data))-0.15, widths=0.3)
plt.boxplot(hole_data, positions=np.arange(len(hole_data))+0.15, widths=0.3)
plt.xticks(range(len(feature_names)), feature_names, rotation=45)
plt.title('Feature Distribution Comparison')
plt.legend(['Range', 'Solid Coins', 'Coins with Holes'])
# ... 其余部分保持不变 ...
```
这样修改后,应该可以解决长度不一致的问题。
另外,注意在绘制箱线图之前,我们使用了 `np.nan_to_num` 来替换NaN和Inf,确保数据清洗。
请尝试使用修改后的代码,并再次运行。如果问题仍然存在,请检查打印的特征数量是否与 `feature_names` 的长度(9)一致。
此外,从您的输出中看到采样率为8000Hz,而之前的模拟数据是44100Hz,所以特征值可能会有所不同,但不会导致这个错误。
请将 `visualize_differences` 函数按照上述修改后再次运行。
</think>这个错误是由于在 `visualize_differences` 函数中,特征数量与位置参数不匹配导致的。让我提供一个修复后的完整代码:
```python
import numpy as np
import matplotlib.pyplot as plt
from scipy import fft
import librosa
import librosa.display
from sklearn.preprocessing import StandardScaler
import soundfile as sf
import os
# 1. 数据加载函数(修复采样率问题)
def load_audio_data(solid_folder, hole_folder, max_duration=0.5):
sounds = []
labels = []
sample_rates = [] # 收集所有采样率
# 加载无孔硬币音频
print(f"加载无孔硬币音频: {solid_folder}")
for filename in os.listdir(solid_folder):
if filename.lower().endswith(('.wav', '.mp3', '.flac')):
filepath = os.path.join(solid_folder, filename)
try:
audio, sr = librosa.load(filepath, sr=None, mono=True, duration=max_duration)
sounds.append(audio)
labels.append(0)
sample_rates.append(sr)
print(f"✓ 加载成功: {filename} (采样率: {sr} Hz)")
except Exception as e:
print(f"× 加载失败: {filename} - {str(e)}")
# 加载有孔硬币音频
print(f"\n加载有孔硬币音频: {hole_folder}")
for filename in os.listdir(hole_folder):
if filename.lower().endswith(('.wav', '.mp3', '.flac')):
filepath = os.path.join(hole_folder, filename)
try:
audio, sr = librosa.load(filepath, sr=None, mono=True, duration=max_duration)
sounds.append(audio)
labels.append(1)
sample_rates.append(sr)
print(f"✓ 加载成功: {filename} (采样率: {sr} Hz)")
except Exception as e:
print(f"× 加载失败: {filename} - {str(e)}")
# 找出最常见的采样率
sample_rate = max(set(sample_rates), key=sample_rates.count)
print(f"\n总计加载: {len(sounds)}个音频文件")
print(f"使用采样率: {sample_rate} Hz (最常见的采样率)")
# 重新采样所有音频到最常见的采样率
resampled_sounds = []
for i in range(len(sounds)):
if sample_rates[i] != sample_rate:
audio = librosa.resample(sounds[i], orig_sr=sample_rates[i], target_sr=sample_rate)
print(f"重新采样文件 {i}: {sample_rates[i]} -> {sample_rate} Hz")
resampled_sounds.append(audio)
else:
resampled_sounds.append(sounds[i])
return np.array(resampled_sounds), np.array(labels), sample_rate
# 2. 特征提取函数(修复异常处理)
def extract_features(sounds, sample_rate):
features = []
for sound in sounds:
# 确保音频长度大于0
if len(sound) == 0:
features.append([0]*9) # 使用默认特征值
continue
# 时域特征
mean = np.mean(sound)
std = np.std(sound)
zcr = np.sum(librosa.zero_crossings(sound))
# 频域特征
fft_result = np.abs(fft.fft(sound)[:len(sound)//2])
try:
spectral_centroid = librosa.feature.spectral_centroid(y=sound, sr=sample_rate)[0, 0]
spectral_bandwidth = librosa.feature.spectral_bandwidth(y=sound, sr=sample_rate)[0, 0]
spectral_rolloff = librosa.feature.spectral_rolloff(y=sound, sr=sample_rate)[0, 0]
except:
# 处理计算失败的情况
spectral_centroid = 0
spectral_bandwidth = 0
spectral_rolloff = 0
# MFCC特征
try:
mfccs = librosa.feature.mfcc(y=sound, sr=sample_rate, n_mfcc=3)
mfcc_mean = np.mean(mfccs, axis=1)
except:
mfcc_mean = [0, 0, 0]
# 组合特征
feature_vec = [
mean, std, zcr,
spectral_centroid, spectral_bandwidth, spectral_rolloff,
*mfcc_mean
]
features.append(feature_vec)
return np.array(features)
# 3. 修复的可视化函数(解决位置参数问题)
def visualize_differences(features, labels):
"""可视化两类硬币的声音特征差异"""
plt.figure(figsize=(15, 10))
# 准备数据
solid_features = features[labels == 0]
hole_features = features[labels == 1]
# 检查是否有足够的数据
if solid_features.size == 0 or hole_features.size == 0:
print("警告:缺少某一类别的数据")
return [0, 1, 2] # 返回默认特征索引
feature_names = [
'Mean Amp', 'Std Dev', 'Zero Crossing',
'Spectral Centroid', 'Spectral Bandwidth', 'Spectral Rolloff',
'MFCC1', 'MFCC2', 'MFCC3'
]
num_features = features.shape[1]
# 特征分布对比
plt.subplot(2, 2, 1)
# 将特征数据转换为适合箱线图的格式
solid_data = [solid_features[:, i] for i in range(num_features)]
hole_data = [hole_features[:, i] for i in range(num_features)]
# 使用正确的positions参数
positions = np.arange(num_features)
plt.boxplot(
solid_data,
positions=positions - 0.15,
widths=0.3,
patch_artist=True,
boxprops=dict(facecolor='blue', alpha=0.5),
showfliers=False
)
plt.boxplot(
hole_data,
positions=positions + 0.15,
widths=0.3,
patch_artist=True,
boxprops=dict(facecolor='red', alpha=0.5),
showfliers=False
)
plt.xticks(positions, feature_names, rotation=45)
plt.title('特征分布对比')
plt.legend([plt.Rectangle((0,0),1,1, fc='blue', alpha=0.5),
plt.Rectangle((0,0),1,1, fc='red', alpha=0.5)],
['无孔硬币', '有孔硬币'])
# 频谱对比
plt.subplot(2, 2, 2)
try:
solid_avg_spectrum = np.mean(np.abs(fft.fft(solid_features, axis=1)), axis=0)[:solid_features.shape[1]//2]
hole_avg_spectrum = np.mean(np.abs(fft.fft(hole_features, axis=1)), axis=0)[:hole_features.shape[1]//2]
# 确保频谱长度一致
min_len = min(len(solid_avg_spectrum), len(hole_avg_spectrum))
solid_avg_spectrum = solid_avg_spectrum[:min_len]
hole_avg_spectrum = hole_avg_spectrum[:min_len]
freqs = np.linspace(0, 8000/2, min_len) # 使用8000Hz作为最大频率
plt.plot(freqs, solid_avg_spectrum, label='无孔硬币')
plt.plot(freqs, hole_avg_spectrum, label='有孔硬币')
plt.xlabel('频率 (Hz)')
plt.ylabel('振幅')
plt.title('平均频谱对比')
plt.legend()
plt.xlim(0, 4000) # 限制到4kHz
except Exception as e:
print(f"频谱对比失败: {str(e)}")
# PCA投影可视化
plt.subplot(2, 2, 3)
try:
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
features_2d = pca.fit_transform(features)
plt.scatter(features_2d[labels==0, 0], features_2d[labels==0, 1], alpha=0.7, label='无孔硬币')
plt.scatter(features_2d[labels==1, 0], features_2d[labels==1, 1], alpha=0.7, label='有孔硬币')
plt.xlabel('主成分 1')
plt.ylabel('主成分 2')
plt.title('特征PCA投影')
plt.legend()
except Exception as e:
print(f"PCA投影失败: {str(e)}")
# 关键差异特征
plt.subplot(2, 2, 4)
try:
# 计算特征差异
feature_diffs = np.abs(np.mean(solid_features, axis=0) - np.mean(hole_features, axis=0))
diff_indices = np.argsort(feature_diffs)[::-1][:3]
# 绘制关键特征
for i, idx in enumerate(diff_indices):
plt.scatter([i]*len(solid_features), solid_features[:, idx], alpha=0.3, c='blue', label='无孔硬币' if i == 0 else "")
plt.scatter([i]*len(hole_features), hole_features[:, idx], alpha=0.3, c='red', label='有孔硬币' if i == 0 else "")
plt.text(i, np.max(features[:, idx]), feature_names[idx], ha='center', va='bottom')
plt.xticks(range(len(diff_indices)), [feature_names[i] for i in diff_indices])
plt.title('关键差异特征')
plt.legend()
except Exception as e:
print(f"关键特征分析失败: {str(e)}")
diff_indices = [0, 1, 2] # 默认值
plt.tight_layout()
plt.savefig('coin_sound_differences.png')
plt.show()
return diff_indices
# 4. 河流冲积分类器(保持不变)
class EnhancedSedimentClassifier:
def __init__(self, feature_ranges, weights=None, tolerance=0.1):
self.layers = feature_ranges
self.weights = weights
self.tolerance = tolerance
def _feature_match(self, sample, layer):
layer_score = 0
total_weight = 0
for i, (min_val, max_val) in enumerate(layer):
# 处理特征值缺失的情况
if i >= len(sample) or np.isnan(sample[i]):
continue
# 特征值在范围内
if min_val <= sample[i] <= max_val:
score = 1.0
# 在容忍边界内
elif (min_val - self.tolerance) <= sample[i] <= (max_val + self.tolerance):
distance = min(abs(sample[i] - min_val), abs(sample[i] - max_val))
score = 1.0 - (distance / self.tolerance)
else:
score = 0.0
# 加权得分
weight = self.weights[i] if self.weights and i < len(self.weights) else 1.0
layer_score += score * weight
total_weight += weight
return layer_score / total_weight if total_weight > 0 else 0.0
def classify(self, X):
results = []
layers = []
confidences = []
for sample in X:
best_layer = -1
best_score = 0.0
for layer_idx, layer in enumerate(self.layers):
layer_score = self._feature_match(sample, layer)
if layer_score > best_score:
best_score = layer_score
best_layer = layer_idx
if layer_score > 0.8:
break
results.append(0 if best_layer == 0 else 1)
layers.append(best_layer)
confidences.append(best_score)
return np.array(results), np.array(layers), np.array(confidences)
# 5. 主流程(修复数据标准化问题)
def analyze_coin_sounds(solid_folder, hole_folder):
# 1. 加载音频数据
sounds, labels, sample_rate = load_audio_data(solid_folder, hole_folder)
print(f"采样率: {sample_rate} Hz")
# 2. 提取特征
print("\n提取音频特征...")
features = extract_features(sounds, sample_rate)
# 3. 标准化特征
scaler = StandardScaler()
scaled_features = scaler.fit_transform(features)
# 4. 可视化差异
print("可视化特征差异...")
key_feature_indices = visualize_differences(scaled_features, labels)
print(f"识别出的关键差异特征索引: {key_feature_indices}")
# 5. 训练河流冲积分类器
print("\n训练河流冲积分类器...")
# 自动生成特征范围(基于标准化后的特征)
solid_features = scaled_features[labels == 0]
hole_features = scaled_features[labels == 1]
# 动态生成特征范围
layer_solid = []
layer_hole = []
layer_fine = []
for i in range(scaled_features.shape[1]):
# 计算特征范围
solid_min = np.min(solid_features[:, i]) if solid_features.size > 0 else -1
solid_max = np.max(solid_features[:, i]) if solid_features.size > 0 else 1
hole_min = np.min(hole_features[:, i]) if hole_features.size > 0 else -1
hole_max = np.max(hole_features[:, i]) if hole_features.size > 0 else 1
# 第一层:无孔硬币特征范围
layer_solid.append((solid_min, solid_max))
# 第二层:有孔硬币特征范围
layer_hole.append((hole_min, hole_max))
# 第三层:更宽松的范围
layer_fine.append((min(solid_min, hole_min), max(solid_max, hole_max)))
# 创建分类器
classifier = EnhancedSedimentClassifier(
feature_ranges=[layer_solid, layer_hole, layer_fine],
tolerance=0.3
)
# 进行分类
predictions, layers, confidences = classifier.classify(scaled_features)
# 6. 评估结果
accuracy = np.mean(predictions == labels)
print(f"\n分类准确率: {accuracy:.2%}")
print(f"平均置信度: {np.mean(confidences):.2f}")
# 7. 可视化分类结果
plt.figure(figsize=(14, 6))
# 置信度分布
plt.subplot(1, 2, 1)
plt.hist(confidences[labels==0], alpha=0.5, bins=20, label='无孔硬币')
plt.hist(confidences[labels==1], alpha=0.5, bins=20, label='有孔硬币')
plt.xlabel('置信度得分')
plt.ylabel('样本数量')
plt.title('分类置信度分布')
plt.legend()
# 层级分布
plt.subplot(1, 2, 2)
solid_layers = layers[labels==0]
hole_layers = layers[labels==1]
plt.hist(solid_layers, alpha=0.5, bins=range(4), align='left', rwidth=0.8, label='无孔硬币')
plt.hist(hole_layers, alpha=0.5, bins=range(4), align='left', rwidth=0.8, label='有孔硬币')
plt.xticks([0, 1, 2], ['层1(无孔)', '层2(有孔)', '层3(精细)'])
plt.xlabel('沉淀层级')
plt.ylabel('样本数量')
plt.title('样本沉淀层级分布')
plt.legend()
plt.tight_layout()
plt.savefig('coin_classification_results.png')
plt.show()
return accuracy, confidences, layers
# 6. 修复后的使用方法
if __name__ == "__main__":
# 设置您的文件夹路径
SOLID_COIN_FOLDER = "F:/zhuanli_jianbao/paper/25_11_11/11_15_15/normal_audio"
HOLE_COIN_FOLDER = "F:/zhuanli_jianbao/paper/25_11_11/11_15_15/hole_audio"
# 确保安装了所需库: pip install numpy matplotlib scipy librosa soundfile
try:
analyze_coin_sounds(SOLID_COIN_FOLDER, HOLE_COIN_FOLDER)
except Exception as e:
print(f"程序运行出错: {str(e)}")
import traceback
traceback.print_exc()
```
### 问题修复说明:
1. **采样率问题修复**:
```python
# 收集所有采样率
sample_rates = []
# ...
# 找出最常见的采样率
sample_rate = max(set(sample_rates), key=sample_rates.count)
# 重新采样所有音频到最常见的采样率
```
您的音频采样率为8000Hz,而原始代码默认44100Hz,现在会自适应采样率
2. **特征提取异常处理**:
```python
# 确保音频长度大于0
if len(sound) == 0:
features.append([0]*9)
# ...
try:
spectral_centroid = librosa.feature.spectral_centroid(y=sound, sr=sample_rate)[0, 0]