简介:这份PDF是2023年全国大学生电子设计竞赛高职高专组K题「辨音识键奏乐系统」的完整赛题文档,面向备赛的专科与本科参赛队及指导教师,帮助读者快速掌握题目任务、技术指标与评分构成。文档完整给出基本要求与发挥部分:以5个水杯为琴键、敲击棒演奏、识别控制器完成拾音与音高识别,涵盖空杯与注水杯的敲击学习、杯号显示、乐曲演奏及8杯乱序识别等任务,并附说明规则与120分评分标准,涉及敲击声特征分析、水量对声音影响、传感器与信号处理电路设计等内容。资源包仅含1个PDF文件,大小约233KB,轻量便于打印与随时查阅。已有813人学习下载,适合用于赛题研读、方案论证与设计报告撰写参考。
1. 辨音识键:为什么敲杯子这件事,难在“听出是哪个杯子”
敲一下玻璃杯,听个响,然后让机器说出这是几号杯——听起来像个玩具。但 2023 年全国大学生电子设计竞赛高职高专组 K 题把它做成了 120 分的硬仗,原因只有一个:人耳觉得“一听就知道”的事,机器拿到的只是一段几百毫秒、混着敲击瞬态和环境噪声的非平稳信号。更麻烦的是,题目要求杯子外观完全相同、不能有任何标记和附加物,位置随机摆放,水量由队员现场注入且容量差可以小到 20ml。这意味着系统不能靠视觉、不能靠位置先验、不能靠贴标签,只能从敲击声本身反推出“这是哪个物理对象”。
这就是辨音识键奏乐系统的核心矛盾:你要识别的不是“声音类别”,而是“物体身份”。同一个杯子,敲重一点轻一点、敲上沿偏左偏右,频谱都会漂;不同杯子之间,水量差 20ml 带来的共振峰偏移可能只有几十赫兹。系统必须在 3 秒内、最多敲 3 次的约束下完成一次可信判决,还要在 90 到 120 秒内完成学习训练加逐个识别。搞懂这套机制,不只是为了这道题——它几乎是所有“用振动信号做物体辨识”场景的缩小版:从敲击检测到特征提取,从模板学习到在线判决,每一层都有明确的工程取舍。下面按“信号怎么来 → 特征怎么选 → 判决怎么做 → 现场怎么不翻车”的顺序拆开讲。
2. 敲击声的物理链路:从杯子振动到可判决的特征向量
2.1 玻璃杯被敲响时,到底在振什么
玻璃杯加水后,是一个典型的“薄壁圆柱壳 + 液体附加质量”耦合振动系统。空杯被敲击时,主要激发出杯壁的低阶弯曲模态,频率通常在几百赫兹到两千赫兹之间;加水之后,液体参与振动,等效质量增加、等效刚度变化,共振频率整体下移,而且不同水量下移量不同。题目里 50、100、150ml 的梯度,以及发挥部分“各杯水量差不少于 20ml”,本质上就是在利用这个频率下移量做区分。
但这里有个容易被忽略的点:敲击声不是单一频率的正弦,而是一组模态的叠加,外加一个很宽的瞬态冲击成分。敲击棒接触杯沿的几毫秒内,激励频谱近似平坦,之后杯壁按自己的模态振型自由衰减。所以拾音器拿到的波形大致是“短促冲击 + 若干指数衰减正弦”的混合。识别控制器要做的第一件事,不是直接做 FFT,而是把这段“冲击 + 衰减”从背景里切出来,否则环境噪声和上一次敲击的余音会污染特征。
常见的实现思路是:拾音器(驻极体麦克风或压电片)→ 前置放大与偏置 → 带通滤波(比如 200Hz~5kHz,覆盖杯壁主模态又压掉低频空调噪声和高频电路噪声)→ MCU 的 ADC 连续采样。判决“有没有敲击”用短时能量加过零率双门限:能量突增超过自适应阈值、且过零率落在冲击信号的合理区间,才认为是一次有效敲击。这个门限不能写死,因为不同杯子、不同水量、不同敲击力度下能量差异很大,通常用前若干帧的噪声底做滑动估计,阈值取噪声底的若干倍。
2.2 为什么不能只取一个共振峰
一个直觉方案是:对每段敲击做 FFT,找最大峰对应的频率,拿这个频率当“指纹”。这个方案在实验室里能跑通,但现场很容易翻车。原因有三:第一,敲击位置在上沿附近,杯壁的模态振型在圆周方向有节线,敲在节线附近时某个模态激励很弱,最大峰可能跳到另一个模态上;第二,水量变化会让多个模态同时移动,单峰跟踪容易跟丢;第三,玻璃杯个体差异(壁厚、椭圆度)本身就会让同水量下的峰位有几十赫兹的散布。
所以更稳的做法是取一组特征而不是一个数。典型的特征向量包括:前若干个谱峰的中心频率、各峰的相对幅度比、谱质心、谱带宽、衰减时间常数。衰减时间常数尤其有用——水量多的杯子,液体耗散增加,自由衰减更快,这个信息在频域里体现为峰更“胖”,在时域里体现为包络下降更陡。把频域峰位和时域衰减组合起来,不同水量杯子之间的可分性会明显好于单看峰位。
下面是一段特征提取的示意代码,重点看它怎么把一次敲击切成“有效段”再算特征:
import numpy as np def extract_features(frame, fs): # frame: 一次敲击触发后采集的整段样本,长度约 200~400ms # 1) 用短时能量找冲击起点,去掉触发前的静默段 win = int(0.005 * fs) # 5ms 短窗 energy = np.array([np.sum(frame[i:i+win]**2) for i in range(0, len(frame)-win, win)]) thresh = 0.2 * energy.max() # 相对阈值,抗力度差异 onset = np.argmax(energy > thresh) * win seg = frame[onset:onset + int(0.15 * fs)] # 取冲击后 150ms # 2) 加汉宁窗后做 FFT,压频谱泄漏 spec = np.abs(np.fft.rfft(seg * np.hanning(len(seg)))) freqs = np.fft.rfftfreq(len(seg), 1/fs) # 3) 在 300~2500Hz 内找前 4 个局部峰 band = (freqs > 300) & (freqs < 2500) idx = np.where(band)[0] peaks = [i for i in idx[1:-1] if spec[i] > spec[i-1] and spec[i] > spec[i+1]] peaks = sorted(peaks, key=lambda i: spec[i], reverse=True)[:4] peak_freqs = sorted(freqs[peaks]) # 4) 谱质心与谱带宽,反映整体“亮度” p = spec[idx] / (spec[idx].sum() + 1e-12) centroid = np.sum(freqs[idx] * p) bandwidth = np.sqrt(np.sum(((freqs[idx] - centroid)**2) * p)) # 5) 包络衰减时间:对 seg 做希尔伯特变换取包络,拟合指数 env = np.abs(__import__('scipy.signal', fromlist=['hilbert']).hilbert(seg)) env = env[:int(0.1 * fs)] t = np.arange(len(env)) / fs decay = -np.polyfit(t, np.log(env + 1e-9), 1)[0] # 衰减率 return np.array(peak_freqs + [centroid, bandwidth, decay])这段代码里几个参数值得说清楚。0.005 * fs的短窗是为了让能量曲线能分辨出毫秒级的冲击起点,窗太长会把起点抹平;0.2 * energy.max()用相对阈值而不是绝对阈值,是为了让轻敲和重敲都能切到同一位置;取冲击后 150ms 是因为杯壁主模态的自由衰减基本在这个时间内完成,再往后信噪比下降、环境反射占主导;峰值搜索限制在 300~2500Hz,是避开低频工频干扰和高频电路噪声的经验区间。衰减率用对数包络线性拟合,斜率越大说明衰减越快,对应水量越多。这些参数不是拍脑袋定的,而是由“杯子振动的物理时间尺度”和“ADC 采样率”共同决定的,换一套拾音器和杯子,区间要重新标定。
2.3 学习训练阶段到底在学什么
题目反复强调“敲击识别学习训练”,这个环节不是走形式。系统在训练阶段要建立的是每个杯号对应的特征模板,而模板的质量直接决定后面判决的上限。常见的实现思路是:对同一个杯子在 3 秒内敲不超过 3 次,每次提取一个特征向量,然后做两件事——求均值作为模板中心,求各维方差作为容差尺度。后面在线识别时,用马氏距离或归一化欧氏距离衡量“当前特征离哪个模板最近”。
这里有个设计取舍:模板是“每杯一个中心”,还是“每杯多个样本做最近邻”?前者存储小、判决快,但对敲击位置变化不鲁棒;后者把 3 次敲击都存下来,在线时取最小距离,鲁棒性好一些,代价是存储和计算略增。在 MCU 上,5 到 8 个杯子、每杯 3 个样本、每样本 7 维特征,存储量完全可接受,所以更稳的选择是保留多样本。训练阶段还要顺便估计类间间隔:如果两个杯子的模板中心距离太近,说明当前水量梯度不足以区分,系统应该在显示上给出提示或者要求重新注水,而不是硬判。这个“训练时自检可分性”的机制,是很多队伍忽略但现场极其救命的一步。
3. 判决机制:最近模板为什么不够,以及怎么补
3.1 单次判决的置信度从哪来
最近模板判决的输出是“离谁最近”,但它不告诉你“有多确定”。现场最怕的情况是:两个杯子水量接近,特征向量落在两个模板中间,系统每次敲击都给出不同答案,队员在 90 秒限时里反复敲、反复错,最后超时。解决办法是给每次判决算一个置信度,比如用“最近距离与次近距离之比”:比值明显小于 1 说明判决可信,接近 1 说明模糊。模糊时系统不应该立刻显示结果,而是利用题目允许的“3 秒内不超过 3 次敲击”做多敲融合——把同一杯子连续几次敲击的特征取平均再判,或者对几次判决做投票。
这个机制的设计动机很直接:单次敲击的随机性(敲击位置、力度、接触时间)是主要误差源,而多次敲击的误差近似独立,平均之后方差下降。题目给的“3 次”约束不是限制,反而是提示——它明示了出题人预期你用多次观测换可靠性。实现上要注意,融合的前提是这几次敲击确实打在同一个杯子上,所以系统需要在两次敲击之间做“同一目标”的粗判:如果连续两次特征距离过大,说明队员可能换杯子了,应该清空缓存重新开始,而不是把不同杯子的特征混在一起平均。
3.2 8 杯场景下的开集识别问题
发挥部分第(2)项是整道题最刁钻的地方:在 5 个已学习的杯子之外,再增加 3 个不同水量的杯子,打乱 8 个杯子排序,要求敲到原 1~5 号时显示对应杯号,敲到新增杯子时显示 0。这在模式识别里叫开集识别——测试时出现的类别,训练时没见过。最近模板判决在这里会必然失败:新增杯子的特征总会离某个已知模板最近,系统会自信地给出一个错误杯号。
正确的机制是引入拒识门限。训练阶段除了记录各模板中心,还要记录“类内距离分布”的上界,比如每类样本到自身模板中心距离的均值加若干倍标准差。在线判决时,如果当前特征到最近模板的距离超过这个上界,就判为“未知类”,显示 0。这个门限的设定是整道题最需要血泪经验的地方:设太松,新增杯子被误判成已知杯号;设太紧,已知杯子因为敲击位置变化被误拒。工程上通常取类内距离上界的 1.5 到 2 倍作为初始值,再用现场敲击做几次校准。
还有一个容易被忽略的细节:新增的 3 个杯子水量是 30、100、150ml,而原 5 个杯子的水量是队员自定的、彼此差不少于 20ml。这意味着新增杯子的特征很可能落在原 5 个杯子特征的“空隙”里,也可能落在某个原杯子附近。所以拒识不能只看绝对距离,还要看相对间隔:如果最近距离和次近距离差距很小,即使绝对距离没超门限,也应该倾向拒识。把绝对门限和相对间隔两个条件做与运算,误判率会明显下降。
3.3 显示与发声的时序耦合
题目要求“敲击的同时显示杯号并发出对应音高”,这句话在实现上有个时序陷阱。拾音、特征提取、判决需要时间,如果等判决完再发声,声音会明显滞后于敲击,演奏体验很差;但如果一检测到敲击就发声,又可能在判决出来之前发错音。常见的折中是把“发声”和“显示”解耦:检测到有效敲击后,立即用上一次同位置判决的缓存音高发声(或者用当前特征的快速粗判结果发声),等完整判决出来后再修正显示。对于演奏乐曲的场景,人耳对几十毫秒的延迟不敏感,但对错音敏感,所以粗判的准确率要足够高——这又回到特征设计和训练质量上。
另一个细节是音高映射。题目说“5 个琴键与不同音高间的对应关系由选手自定”,这给了自由度,但也意味着系统内部要维护一张“杯号 → 频率”的表。发声可以用 PWM 加滤波做方波/正弦,也可以用专用音频芯片。无论哪种,频率精度要够,否则演奏出来走调。常见的做法是用定时器输出 PWM,周期由目标频率反算,占空比 50% 得到基频最强的方波,再经 RC 低通取基波。这个链路的参数(定时器时钟、分频、RC 截止频率)要在设计报告里算清楚,也是评分标准里“理论分析与计算”的落点。
4. 避坑与排查:现场最容易翻车的几件事
4.1 现象:训练时识别全对,正式测试第一个杯子就错
原因通常是训练和测试的敲击条件不一致。训练时队员可能敲得比较正、比较重,正式测试时紧张,敲得偏、敲得轻,特征向量整体偏移。解决方法是训练阶段就刻意做扰动增强:同一个杯子在 3 次敲击里,有意改变敲击位置和力度,让模板的方差估计覆盖真实变化范围;同时在判决时用归一化距离而不是绝对距离,降低整体偏移的影响。
4.2 现象:加了水之后,相邻水量杯子频繁互判
原因是水量差接近 20ml 下限时,共振峰偏移量可能小于特征提取的频率分辨率。FFT 的频率分辨率由分析窗长决定,150ms 窗在 8kHz 采样下分辨率约 6.7Hz,而 20ml 水量差带来的峰移可能只有十几赫兹,加上噪声就分不开。解决办法有两个方向:一是加长分析窗(但会引入更多反射和噪声),二是换更高分辨率的频率估计方法,比如对主峰做抛物线插值或相位差分法,把峰位估计精度提到亚 bin 级别。更根本的办法是把衰减率特征用足——水量差小的时候,衰减率的区分度往往比峰位更好。
4.3 现象:连续敲击时,第二次敲击的特征被第一次的余音污染
原因是杯子自由衰减虽然快,但环境反射和控制器外壳共振会拖尾,如果触发门限设得太低,第二次敲击的采样窗里混进了上一次的残余。解决办法是设置最小敲击间隔(比如 150ms 内不重复触发),并在每次触发后做一段“静默等待”,等能量回落到噪声底附近再开放下一次检测。这个间隔要和题目“3 秒内不超过 3 次”的约束对齐,既不能太短导致混叠,也不能太长导致 3 秒内敲不完 3 次。
4.4 现象:发挥部分打乱 8 个杯子后,新增杯子有时显示 0、有时显示错误杯号
这是拒识门限没调好的典型表现。排查顺序是:先看新增杯子的特征到最近已知模板的距离分布,再看已知杯子类内距离分布,如果两者有重叠,说明门限无论如何设都会有错。这时候要回到特征层,增加对水量敏感的特征维度(比如低频段某个模态的峰位),把两类分布拉开,而不是在判决层硬调门限。另一个常见原因是训练样本太少,类内方差估计偏小,导致门限过紧,已知杯子被误拒——增加训练敲击次数或者用更稳健的方差估计(如中位数绝对偏差)可以缓解。
4.5 现象:自动演奏环节超时或节奏不对
基本要求第(1)项要求一键启动自动演奏不超过 15 秒。常见问题是发声和延时用阻塞式实现,一首曲子播完才发现超时,或者节拍靠软件延时不准。解决办法是把曲谱存成“频率 + 时值”的数组,用定时器中断驱动发声状态机,主循环只负责推进节拍,这样时值精度由定时器保证,也方便在演奏前做总时长校验。这个模块虽然简单,但它是基本要求里第一个被测试的项,翻车了很影响后面节奏。
5. 设计取舍:为什么这道题考的不是算法,而是“约束下的工程闭环”
回头看整道题,真正难的地方不在某一个算法有多先进,而在于所有约束是互相咬合的:杯子不能标记,所以只能靠声学特征;水量差可以小到 20ml,所以特征分辨率要够;3 秒 3 次的限制,逼你做多观测融合;90 到 120 秒的总时限,逼你把训练和判决都做得足够快;发挥部分的开集场景,逼你引入拒识而不是一味追求分类准确率。任何单点优化如果破坏了闭环里的另一环,整体就会崩。
一个具体的取舍例子是特征维度。维度越高,理论上可分性越好,但训练样本只有 3 次敲击,高维空间里协方差估计会严重失真,马氏距离反而不可靠。所以更稳的选择是控制在 6 到 8 维,并且优先选物理意义明确、对水量单调敏感的特征(峰位、衰减率),而不是盲目堆谱矩。另一个取舍是判决策略:是用一个复杂的分类器,还是用“模板 + 拒识 + 多敲投票”这套简单机制?在样本极少、现场条件多变的场景下,简单机制的参数少、可解释、可现场标定,往往比复杂模型更抗造。这也是我一般会这么理解这类题的原因——它考的是你能不能把物理、信号、判决、时序串成一个能在 90 秒内稳定跑完的闭环,而不是某个模型的指标。
验证这套闭环是否真的成立,最有效的方法不是只看最终识别率,而是分层记录中间量:每次敲击的触发时刻、特征向量、到各模板的距离、置信度、最终判决。把这些打成日志,现场出错时能立刻定位是触发层漏检、特征层漂移还是判决层门限问题。我在类似项目里吃过亏,一开始只看最终对错,结果一个偶发错误查了半天,后来强制每次敲击都落一行结构化日志,排查时间从小时级降到分钟级。从那以后我每次做这种“传感器 + 判决”的系统,都强制走一遍“训练自检可分性、在线记录置信度、出错可回放”的流程,哪怕题目没要求。希望帮到你。
本文还有配套的精品资源,点击获取