Oemer 乐谱照片如何变 MusicXML事件驱动生成机制完整详解【免费下载链接】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/oemerOemer 是一个端到端的光学音乐识别OMR, Optical Music Recognition系统能把手机拍摄的乐谱照片转写为可编辑、可转 MIDI 的 MusicXML 文件。本文将深入讲解 Omer 最后的临门一脚——事件驱动的 MusicXML 生成机制并拆解其中两个最巧妙的算法调性发现Key Finding与多轨节拍对齐Beat Alignment帮你看懂从一堆音符检测到一份完整乐谱文件的底层逻辑。一图看懂OMR 流水线的 8 个环节Omer 的整体入口在oemer/ete.py的extract()函数中流程非常清晰双 UNet 语义分割模型一预测五线线与所有符号模型二细分为符头、谱号、符干/休止符、升降号等oemer/inference.py去畸变纠偏Dewarping把手机拍摄产生的弯曲、倾斜校正为水平五线谱oemer/dewarp.py五线线提取得到最关键的unit_size线距它是后续一切距离计算的标尺oemer/staffline_extraction.py符头提取定位每个音符的音高位置oemer/notehead_extraction.py符头分组同一时间演奏的音符聚成和弦并根据符干方向区分声部oemer/note_group_extraction.py符号提取小节线、谱号、升降号、休止符oemer/symbol_extraction.py节奏解析圆点与符梁/符尾数量决定时值oemer/rhythm_extraction.py构建 MusicXML本文主角oemer/build_system.py左图是原始照片右图是 Omer 生成的 MusicXML 渲染结果可以直观看到转写效果事件驱动机制为什么 MusicXML 生成需要舞台调度MusicXML 本质上是一个流式 XML 文档小节的先后、属性调号/拍号必须先于音符、backup/forward的插入位置稍有偏差播放器就会错拍。如果一边扫描符号一边写 XML状态当前调号、当前谱号、每个音级的临时变化音极易混乱。Omer 的解法是经典的事件驱动Event-based架构先把识别结果编译成一串动作Action再按顺序回放生成 XML。核心就在 oemer/build_system.py 中Action基类所有事件的父类每个事件有perform()方法负责生成 XML 元素并更新共享上下文Action.ctx全局上下文贯穿整个文档的乐谱记忆包含三项状态key当前调号clefs每条轨当前的谱号sfn_stateA~G 七个音级各自的升降号状态调号 小节内临时变化音共同决定事件类型作用AddInit初始化首小节写入调号、谱号、divisionsAddMeasure开启新小节重置小节内变化音状态KeyChange调号改变时更新ctx.keyClefChange谱号改变时更新ctx.clefsAddNote写入音符同时读写sfn_state若该音级已有升降号且与新识别的一致直接沿用避免重复写入 alterAddRest写入休止符AddBackup/AddForward回退/推进拍位用于多声部切换MusicXMLBuilder.build()只负责生产事件队列to_musicxml()负责消费队列遇到AddInit/AddMeasure就把元素挂到part下其余事件挂到当前measure下最后补上 DOCTYPE 头、默认速度tempo90和 Piano 的 part-list输出可在 MuseScore 等软件中直接编辑的 MusicXML 文件。调性发现算法5 步判定这首歌是什么调识别到一堆升号/降号SFN之后如何断定它们是调号而非小节内的临时变化音Measure.get_key()oemer/build_system.py用了一套稳健的五步算法第 1 步判断当前小节是否含调号。检查第一个谱号之后、按轨数track_nums取出的若干符号是否全部是 SFNhas_key()。只要有一个不是比如是音符直接返回 C 大调Key(0)。第 2 步定义扫描范围。若小节位于行首at_beginning前track_nums个符号必须是谱号因此扫描起点跳过它们终点则取起点 每轨最多 6 个升降号 × 7 4 的容错track_nums * 7 4并取符号列表长度兜底。第 3 步统计出现次数。按轨号分别统计候选 SFN 的数量sfn_counts为判断每轨调号数量应一致做准备。第 4 步校验合法性。理想情况下所有轨的符号类型一致全升或全降。若不一致则投票表决且有一条巧妙的规则当还原号票数最高时把第一名与第二名互换——因为升/降号误识别为还原号的概率极低还原号最多说明该调本身含还原号如 bE 大调不应主导调号类型。第 5 步返回调号。数量取各轨平均count round(sum / track_nums)若是降号则取负。调号用五度圈偏移量表示G_MAJOR 1一个升、F_SHARP_MAJOR 6、F_MAJOR -1一个降……同时把参与调号的 SFN 标记is_key True避免后续把它们当临时变化音重复写入。确定调号后Action.init_sfn_state()会按五度圈顺序初始化七个音级的升降号状态——升号序F C G D A E降号序B E A D G C取前 N 个即为该调的调号这正是西方记谱的标准规则。多轨节拍对齐让两只手在同一拍上重逢钢琴谱上下两轨左右手独立识别时值误差不可避免。Omer 的核心假设是同一拍位置上的音符此前累计的时值应当相等。Measure.align_symbols()就是围绕这一假设做校正时间槽Time Slot聚类按 x 坐标扫描相邻符号水平距离小于unit_size就归入同一时间槽——它们被视为同一拍位置。构建时值矩阵track_duras[slot, track]记录每个时间槽中每条轨的最小时值同一槽内可能有多声部。寻找检查点Checkpoint当某一行的两条轨都有符号t10 且 t20时累计差值diff理论上必须为 0若不为 0说明此前某处时值识别有误需要回补。modify()校正策略只加不减保证小节总时值单调不降目标轨在该位置有符号→extend_symbol_length()直接延长该符号如八分音符变四分音符、或补上附点目标轨在该位置没符号→get_rest()按差值反查最接近的休止符时值插入一个休止符补齐节奏diff累减/累加逻辑落后轨独占的时间槽持续累积差距领先轨独占时逐段抵销抵销中途反超的位置也会打标记add_idx等待下一个检查点统一结算。下面是一个真实风格的对齐矩阵示例行号后注释为累计差值# Tracks Accum. Diff. # T1 T2 [[ 8., 24.], # 16 [ 8., 0.], # 8 [ 8., 0.], # 0 [ 0., 24.], # 24 - 此处需插入一个八分休止符平衡节奏 [ 8., 0.], # 16 [ 8., 0.], # 8 [ 4., 4.]] # 0 - 检查点两轨都有符号差值归零第 4 行右手独占了 24 个时值单位而左手空缺算法就会在该行给左手插入一个八分休止符16 单位并延长相邻音符直到检查点差值归零。这套机制保证了最终 MusicXML 中两轨严格同拍对齐播放时不会出现左右手脱节。事件回放从 Action 队列到可编辑的 MusicXML对齐完成只是编译执行阶段还有两个关键设计声部Voice切分get_voices()把音符组转成Voice对象若一组音符符干方向一上一下则拆成voice1上与voice2下两个声部。回放时同一轨道的第二声部写入前会先输出backup/forward把拍位拨回正确位置——这正是 MusicXML 多声部语法的要求。小节切分sort_symbols()按组号 groupx 坐标排序所有符号后gen_measures()以小节线barline为界切分小节连续两条小节线自动识别为终止线double barline第一小节触发at_beginning从而进入调性发现流程。总结Omer 事件驱动架构的文件地图机制所在文件事件队列、上下文、调性发现、节拍对齐oemer/build_system.py端到端流水线入口oemer/ete.py符头分组与声部划分oemer/note_group_extraction.py升降号/休止符识别oemer/symbol_extraction.py时值符梁/圆点解析oemer/rhythm_extraction.pySVM 分类器模型文件oemer/sklearn_models/想亲手跑一遍安装后一条命令即可pip install oemer oemer 你的乐谱图片路径首次运行会自动下载模型检查点GPU 环境下约 3~5 分钟输出.musicxml文件导入 MuseScore 即可编辑并导出 MIDI。理解了事件驱动、调性发现与多轨对齐这三块拼图你就掌握了 Omer 从像素到音乐的核心路径。【免费下载链接】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),仅供参考