Oemer的SVM符号分类器详解:谱号、升降号与休止符识别的原理
【免费下载链接】oemerEnd-to-end Optical Music Recognition (OMR) system. Transcribe phone-taken music sheet image into MusicXML, which can be edited and converted to MIDI.项目地址: https://gitcode.com/gh_mirrors/oe/oemer
Oemer 是一个端到端的光学音乐识别(OMR, Optical Music Recognition)系统,能把手机拍摄的五线谱照片转成可编辑的 MusicXML 文件,并进一步导出 MIDI。而在整条识别流水线的末端,真正给符号"定性"的,是几组轻量级 SVM 符号分类器——它们负责区分谱号、升降号(sharp/flat/natural)与各种休止符。这篇文章带你从零看懂这套分类器的数据准备、训练与调用原理。
🧭 SVM 符号分类器在整个 OMR 流程中的位置
Oemer 的识别流程大致分为四步:
- 去歪斜(dewarping):把拍摄弯曲、倾斜的乐谱照片拉直;
- 双 UNet 语义分割:第一个网络区分五线谱线与其他符号;第二个网络把符号细分成音符头、谱号/升降号、符干/休止符等通道;
- 几何提取:从分割结果中找出连通的符号区域,算出每个符号的边界框;
- SVM 符号分类器定性:把每个符号的小图块丢给轻量级分类器,判断它到底是高音谱号还是低音谱号、是升号还是还原号、是八分休止还是十六分休止。
也就是说,深度学习网络只负责"把符号圈出来",最终的符号类型判定交给 SVM 完成。下面是第二个分割网络的预测结果,其中粉色通道就是接下来要交给分类器的谱号与升降号:
📦 四个开箱即用的符号分类模型
训练好的分类器以 pickle 文件形式存放在 oemer/sklearn_models/ 目录下,共 4 个,分工非常清晰:
| 模型文件 | 识别对象 | 类别 |
|---|---|---|
clef.model | 谱号 | 高音谱号 (gclef)、低音谱号 (fclef) |
sfn.model | 升/降/还原号 | sharp、flat、natural |
rests.model | 休止符(粗分) | 全休止、四分休止、八分休止 |
rests_above8.model | 休止符(细分) | 八分、十六分、三十二分、六十四分休止 |
🏷️ 训练数据:从彩色标签切出符号切片
分类器的训练数据来自 DeepScores-extended 数据集的彩色标注图——每个符号类别用一种固定颜色标记。oemer/classifier.py 中的collect_data()定义了完整的类别表,例如颜色 74 代表升号、10 代表高音谱号、97 代表全休止符等。
采集流程可以概括为:
- 找区域:用
get_bbox()在彩色通道里找出所有连通区域(每个区域就是一个符号); - 切切片:沿边界框外扩 10 像素,把符号图块切出来;
- 数据增强:随机缩放 0.6~1.3 倍 + 随机透视变换(模拟手机拍摄时的畸变),再二值化为纯黑白;
- 数量控制:每个类别各采集 400 张训练样本 + 400 张测试样本。
训练时,每张切片被统一缩放到40×70 像素并展平成一个 2800 维的向量——这就是 SVM 看到的"样本"。
🧠 SVM 是如何工作的?三步看懂
- 展平:40×70 的黑白符号图被拉直成长度为 2800 的一维特征向量;
- 核技巧:SVM 使用 RBF(径向基)核函数,把低维特征映射到高维空间,在那里找到一个"最大间隔"的分离超平面,把不同符号分开;
- 多分类:三分类、四分类等场景下,SVM 通过 one-vs-one(ovo)或 one-vs-rest(ovr)策略组合多个两两分类器得到最终结果。
代码中的SVM_PARAM_GRID给出了典型超参数搜索空间:C(正则化强度)、gamma(核宽)、degree与kernel(rbf/poly)。值得注意的是,oemer/classifier.py 同时提供了一个轻量级 CNN(三层小卷积 + 两层全连接)作为备选方案——两者的输入输出接口完全一致,你可以按需替换。
⚡ 一行推理:统一的 predict() 接口
无论训练时用的是哪种模型,推理入口都是 oemer/inference.py 中的predict(region, model_name):
- 加载
sklearn_models/{model_name}.model,里面打包了模型本体、目标尺寸w/h和类别映射表class_map; - 把符号图块 resize 到训练时的统一尺寸;
- 展平后调用
model.predict(),再用class_map把预测编号翻译回符号名(如"sharp")。
整个过程不需要 GPU,单符号推理在毫秒级完成——这正是选择轻量级分类器的价值所在。
🎼 实战一:区分谱号与升降号(parse_clefs_keys)
第二个 UNet 故意把谱号和升降号"混"在同一个预测通道里(见上图粉色区域)。算法用两条几何规则先做粗分(oemer/symbol_extraction.py 中的parse_clefs_keys()):
- 面积比 > 3.5×unit_size²且填充率 < 45%→ 又大又"空",是谱号;
- 尺寸较小 → 是升降号。
其中unit_size是五线谱线间距,整个系统所有尺寸判断的"尺子"。粗分之后,真正的定性交给分类器:谱号候选送clef二分类,升降号候选送sfn三分类。识别结果如下图所示,红色框标注谱号(G_CLEF/F_CLEF),紫色标注升降还原号(SHARP/FLAT/NATURAL):
🎵 实战二:休止符的两段式识别(parse_rests)
休止符的识别更巧妙,采用了两段式(层级)分类:
- 从"符干/休止符"通道中减去音符组掩膜和谱线区域,剩下的连通块就是休止符候选;
- 用
unit_size做尺寸过滤(高度不超过 3.5 倍线距、宽度不小于半倍线距); - 第一段:用
rests.model在"全休止 / 四分休止 / 八分休止"三个类别中做粗分; - 第二段:如果粗分结果是"八分",再交给
rests_above8.model细分到八分 / 十六分 / 三十二分 / 六十四分。
为什么分两段?因为 16 分、32 分、64 分休止符长得几乎一样(只是"尾巴"多几道),单独一组 6 分类容易混淆;先粗分再细分,把决策拆成两个更简单的分类问题,准确率更高。识别结果如下图所示,绿色框标注了 EIGHTH(八分休止)等类型:
🤔 为什么不直接全用深度学习?
- 活已经干完了:语义分割网络承担了大量工作,剩下的只是 40×70 的小图块、最多 6 个类别,SVM 级别的模型绰绰有余;
- 轻快:模型文件只有 KB~MB 级,CPU 上毫秒级推理,不需要 GPU;
- 易扩展:想加新符号?在
classifier.py的color_map里加一行、建个新文件夹重新训练即可; - 可替换:SVM 参数网格和
train_tf()备选 CNN 都写在同一份代码里,换模型零成本。
📚 总结
Oemer 的 SVM 符号分类器体现了"大模型做分割、小模型做定性"的实用主义思路:
- 训练侧:oemer/classifier.py —— 数据切片、增强、SVM/CNN 训练与打包;
- 推理侧:oemer/inference.py —— 统一的
predict()接口; - 调用侧:oemer/symbol_extraction.py —— 谱号/升降号粗分 + 休止符两段式识别;
- 模型文件:oemer/sklearn_models/ ——
clef.model、sfn.model、rests.model、rests_above8.model; - 整体流程入口:oemer/ete.py。
理解这套分类器后,你也就看懂了 OMR 系统"从像素到符号"的最后一块拼图。🎶
【免费下载链接】oemerEnd-to-end Optical Music Recognition (OMR) system. Transcribe phone-taken music sheet image into MusicXML, which can be edited and converted to MIDI.项目地址: https://gitcode.com/gh_mirrors/oe/oemer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考