- 桌面应用
- 计算机视觉
- 图像处理
- OCR
【免费下载链接】audiveris
Latest generation of Audiveris OMR engine
导读
GRID是 Audiveris OMR 引擎流水线中的关键步骤,其任务是从二值化图像中恢复整张乐谱的系统(systems)与谱表(staves)网格——这是后续所有符号识别、节拍还原工作的空间骨架。本文以官方文档《GRID step》为主体,结合仓库中LinesRetriever、BarsRetriever、GridBuilder等核心源码,系统讲解该步骤的输入输出、Run/Section/LAG 组织、水平长线状物(filament)聚类、谱表垂直投影、峰值图(peak graph)与系统聚合的完整流程,并给出每个环节在图形界面中的可视化验证方法。读完本文,你将理解 Audiveris 是如何从"一堆黑色像素"逐步推断出谱表线、小节线、花括号、方括号以及最终系统网格的。
一、GRID 步骤在流水线中的位置与职责
Audiveris 的 OMR 处理链由一系列步骤组成,GRID紧跟在负责图像缩放的SCALE步骤之后。其唯一目标如官方文档所述:检索出当前 sheet 的所有系统与谱表("The purpose of the GRID step is to retrieve the systems and staves of the sheet")。
从源码结构看,这一步的落地实现集中在org.audiveris.omr.sheet.grid包中(grid 包目录),核心编排类是 GridStep.java,其doit()方法依次执行三件事:
new GridBuilder(sheet).buildInfo()—— 构建系统网格主体(L96-L97);new StaffLineCleaner(sheet).process()—— 清理谱表线(L99-L100);sheet.getStub().getBook().updateScores(...)—— 更新评分(L102-L103)。
而 GridBuilder.java 的类注释明确写道:系统网格的计算"基于水平谱表线与垂直小节线的检索",实际处理委托给两个伴侣类(companion):
- LinesRetriever—— 负责检索水平谱表线(
createBothLags→retrieveLines→addShortSections→completeLines,见 GridBuilder.buildInfo); - BarsRetriever—— 负责检索垂直小节线(
barsRetriever.process(),L153)。
也就是说,文档中描述的整个 GRID 处理,本质上是"水平线"与"垂直线"两路信号的双向协作:水平线定谱表框架,垂直线定小节与系统边界。
二、输入与输出
输入
GRID 步骤的输入来自前序步骤,共三项:
- 黑白(二值)图像:由
BINARY步骤产生,此后图像只包含黑色(前景)像素与白色背景; - 最大谱表线厚度:由
SCALE步骤给出(scale.getMaxFore()),详见 SCALE 步骤输出; - 典型 interline 值(谱表线间距),以及可选的小 interline 值(当图像中存在两套不同尺寸的谱表时)。
输出
GRID 步骤结束后,产出以下结构化结果:
- 水平 sections 的 LAG与垂直 sections 的 LAG;
- 按谱表组织的谱表线;
- 整张图像的全局倾斜角(skew);
- 对每个谱表:小节线(若有)、上下谱表间的垂直连接符(若有)、起始花括号(brace)(若有)、起始方括号(bracket)(若有);
- 谱表聚合成的系统(system);
- no-staff 图像(已擦除谱表线的二值图)。
其中 "Section" 与 "LAG"(LinearAdjacencyGraph,线性邻接图)是 Audiveris 的底层像素组装概念,可参考 Pixels assemblies 概念文档:
- Run:同一方向(水平或垂直)上颜色相同的连续像素序列,见 Run 与 RunTable;
- Section:按兼容规则(如 run 长度差上限、run 长度比上限、端部偏移上限等)将同方向相邻黑色 run 传递性连接而成的组装体;
- LAG:同方向 sections 的集合,水平与垂直各一个。
从源码看,这两个 LAG 的建立由 LinesRetriever.createBothLags() 完成:它从二值图像的垂直 RunTable 出发,将长垂直 run 分派到垂直 LAG,其余像素进入水平 RunTable,再进一步按最小 run 长度拆分为"长水平 run"与"短水平 run"两类表。
三、处理流程详解
3.1 过滤 Run 与 Section(建立两个 LAG)
处理的第一步,是将二值图像的所有前景(黑色)像素组织为垂直 run。
随后,引擎基于SCALE步骤提供的最大谱表线厚度,略微放大得出一个判定阈值——放大的目的是容纳可能更厚的加线(ledger lines,即超出五线谱上下的临时短线)。源码中该阈值通过scale.getMaxFore()与stickerThickness常量(比值 1.0)相乘得到,见 LinesRetriever.Parameters 构造函数。
判定逻辑非常直接:
- 任何长于最大谱表线厚度的垂直 run,不可能是谱表线的纯净片段,而必然是某种"交叉对象"(如小节线、符干)的一部分,因此被放入垂直 LAG(
longVertTable→buildVerticalLag); - 其余所有像素组织为水平 run,归入水平 LAG(
buildHorizontalLag)。
进入 LAG 后,相邻 run 按兼容规则聚合成 sections。水平 sections 还会进一步分为短 sections与长 sections,因为长 sections 将主导后续谱表线检测。源码中createBothLags()先只把"长水平 run"填入水平 LAG,短水平 run 暂存于shortHoriTable,待谱表线初步成形后再由addShortSections()补入(L582-L598、addShortSections)。
可视化验证:打开Tools → Constants下拉菜单,勾选布尔常量displayRuns(位于LinesRetriever类中,对应源码 Constants.displayRuns)。即可依次查看以下视图(示例素材为仓库data/examples中的chula.png):
| 内容 | 视图 |
|---|---|
| 二值图像 | |
| 长垂直 sections | |
| 短水平 sections | |
| 长水平 sections |
3.2 长水平线状物(filaments)与全局倾斜
从长水平 sections 集合出发,引擎逐步构建又长又细的 filaments——这些大概率对应谱表线的长片段。源码中对应 retrieveLines() 的主线:
- 构建初始 filaments:
FilamentFactory.retrieveFilaments(hLag.getEntities())(L1493-L1499); - 剔除弯曲的 filaments:
purgeCurvedFilaments()用样条(NaturalSpline)计算中点的旋转角,若超过maxFilamentRotation(0.1 弧度)则丢弃(L1149-L1189); - 计算全局斜率:
retrieveGlobalSlope()取最长的前 10% filaments(topRatioForSlope = 0.1)求平均斜率,存入Skew对象(L1433-L1455、L1506-L1508); - 剔除斜率偏离的 filaments:
purgeSlopedFilaments()比较每个 filament 斜率与全局斜率的差值,超过maxSlopeDiff(0.025 弧度)即丢弃——但对基于单个 section 的短 filament 放宽了阈值,因为其天然接近水平(L1205-L1248)。
接下来,水平 filaments 按垂直方向聚合成簇(cluster),每个簇大概率代表一个谱表。簇内需满足两个约束:
- filaments 间距应符合已知的谱表 interline 值;
- filament 数量必须匹配用户声明的谱表规格:1 线、4 线、5 线或 6 线,由用户在Book 参数下拉菜单中设定。见 Book 参数之 Staves 声明:1 线打击乐谱表、4 线低音 tablature、5 线标准谱表、5 线无音高打击乐谱表、6 线吉他 tablature。
最终,剩余的簇按线数转化为标准谱表(Staff)、一线谱表(OneLineStaff)或tablature。源码 buildStaves() 中可见清晰的类型分派(L266-L270):5 线 →Staff,1 线 →OneLineStaff,其他(如 4 线、6 线)→Tablature。在转化为谱表之前,purgeClusters()会依据以下规则剔除伪簇(L1032-L1077):
- 过短:宽度小于
minStaffLength(30 个 interline); - 过于倾斜:对近乎水平的乐谱,一线簇斜率超过
maxOneLineSlope(0.001)即剔除; - 近乎空洞:真实长度低于最长簇真实长度的
minTrueLengthRatio(0.3)倍。
此时每个谱表的左右横坐标界限仅由检测到的线决定,尚不精确,后续会通过垂直投影细化。
3.3 谱表垂直投影(staff vertical projection)
既然引擎已知道每个谱表的顶线与底线,就可以把这两条线之间的所有黑色像素投影到 x 轴上,得到一维投影曲线。对 1 线谱表的特殊情况,官方文档的脚注说明:引擎会改用"以 interline 数为单位的目标小节线高度"来做投影,该高度可通过Book 参数菜单调整(对应 Book 参数中的 Barline height 规格:four、twoThenFour、two、oneThenTwo四种取值)。
投影的可视化前提:先在Tools → Advanced下拉菜单中激活PLOTS主题,然后通过Sheet → Staves plots菜单,或从谱表右键的popup Staff上下文菜单查看:
在该投影曲线上,引擎检测峰值(peaks),它们可能代表:
- 小节线(barlines);
- 半花括号(half braces);
- 半方括号(half brackets);
- 长符干(long stems)等。
峰值内的细长垂直 sections 被用来构建垂直 filaments——注意这些 filaments 可能向上/向下超出谱表的顶/底线。源码中这一系列工作由 StaffProjector.java 与 BarsRetriever.java 协同完成,BarsRetriever类注释明确其职责是"检索垂直小节线、方括号与花括号"(L119-L120)。
可视化验证:在Tools → Constants中勾选displayFilaments布尔常量(定义于 GridBuilder.Constants),界面会显示一个专门的Filaments标签页,同时展示水平与垂直 filaments 的初始素材:
- 水平 filaments 为淡红色;
- 垂直 filaments 为淡蓝色。
| 内容 | 视图 |
|---|---|
| 初始 Filaments |
3.4 峰值图(peak graph)与系统聚合
所有垂直投影中的峰值都被视为sheet 级峰值图(peak graph)的顶点。若某谱表的峰值与下一谱表的峰值在垂直方向上对齐,就在全局峰值图中记录一条边(edge)。随后,每条对齐边都会被检验:两个谱表峰值之间是否存在足够的前景像素,以确认两者之间存在真实连接(例如连接上下谱表的连接符、贯穿的小节线)。
同时,任何超出谱表顶/底边界、又不是连接符的 filament,都不可能成为小节线,因此被丢弃。
基于检测到的连接,引擎现在能够把谱表聚合成系统(system)。这条"峰值→对齐→连接判定"的逻辑在 PeakGraph.java、BarAlignment.java 与 BarConnection.java 中实现;BarsRetriever中定义的StaffPeak.Attribute(THICK、THIN、BRACE_TOP/MIDDLE/BOTTOM、BRACKET_MIDDLE等,见 BarsRetriever.java)则用于刻画每个峰值所代表的符号类型。
3.5 系统内部对齐(system internal alignments)
在每个系统内部,谱表峰值被组织为基于系统的列(system-based columns),处理规则如下:
- 起始列(starting column):若存在,用于细化系统与谱表的左横坐标;
- 起始列右侧的列:必须覆盖整个系统高度,否则被丢弃;
- 超出系统垂直界限的峰值也被丢弃;
- 花括号部分与方括号部分:在起始列的左侧搜索;左边缘是否存在花括号,决定了谱表是否被聚合成parts(声部组)。
最终,在每个系统的 SIG(符号交互图)中创建以下 Inters(符号实例):
- 小节线(barlines);
- 连接符(connectors);
- 花括号(braces);
- 方括号(brackets)。
源码中BarsRetriever大量引用BarlineInter、BraceInter、BracketInter、BarConnectorInter等 Inter 类型(见 BarsRetriever.java),并在 SIG 中建立BarConnectionRelation、BarGroupRelation等关系边——这正是"Inters 被创建"的实现落点。
四、GRID 步骤的结果
GRID 步骤结束时,Data标签页已填充系统与谱表的网格,可以分别用**物理模式(Physical)与逻辑模式(Logical)**查看:
| 模式 | 视图 |
|---|---|
一个值得注意的工程取舍:GRID 步骤结束时可能残留少量误检的小节线(false barlines)。它们通常会在后续的 REDUCTION 步骤 中被清除——这是 Audiveris 各步骤"渐进式精化"设计哲学的典型体现:宁可先保留候选,由后续步骤结合更多上下文裁决。
no-staff 图像
由于引擎已确切知道哪些水平 sections 组成了谱表线,它可以逻辑上"擦除"这些 sections,从二值图像中生成所谓的no-staff 图像。该图像将供流水线后续多个步骤使用(例如符号识别时避免谱表线干扰)。
可视化方式:通过Sheet → No staff下拉菜单查看,对应源码 GridStep.displayUI() 中的NO_STAFF_TAB标签页(由常量displayNoStaff控制)。
| 内容 | 视图 |
|---|---|
| No staff(已擦除谱表线) |
五、关键常量速查(源码级)
为便于调优与深入阅读,以下列出LinesRetriever中与 GRID 水平线检测直接相关的核心常量(定义于 LinesRetriever.Constants,单位与默认值均为源码原值):
| 常量 | 单位 | 默认值 | 作用 |
|---|---|---|---|
topRatioForSlope | 比例 | 0.1 | 用于计算全局斜率的最长 filaments 占比 |
maxFilamentRotation | 弧度 | 0.1 | filament 允许的最大中心旋转角(弯曲剔除阈值) |
maxSlopeDiff | 弧度 | 0.025 | filament 与全局斜率的允许最大差值 |
minRunLength | interline | 0.25 | 水平 run 被纳入考虑的最小长度 |
minStaffLength | interline | 30 | 谱表(簇)的最小水平长度 |
minSlope | 正切 | 0.0002 | 值得记录的最小绝对斜率 |
maxOneLineSlope | 正切 | 0.001 | 完美 sheet 上一线谱表的最大绝对斜率 |
minTrueLengthRatio | 比例 | 0.3 | 相对最长簇真实长度的最小占比(空洞剔除) |
patternWidth | interline | 1.0 | 谱表端点模式探测的宽度 |
patternJitter | interline | 0.25 | 谱表端点模式的最大纵坐标抖动 |
minRadius | interline | 12 | 曲率抛光(polish)的最小可接受半径 |
displayRuns | 布尔 | false | 是否显示 runs 视图 |
这些常量大多在 Parameters 构造函数 中按scale.toPixels(...)换算为像素值,体现了 Audiveris 的**缩放无关(scale-independent)**设计:同一套常量在不同分辨率乐谱下都能自适应。
六、小结
GRID 步骤以三条证据链完成了乐谱网格的复原:
- 水平证据链:黑色像素 → 水平 run → 水平 sections(长短分流)→ filaments(去弯、去偏斜)→ 簇 → 谱表(标准/一线/tablature);
- 垂直证据链:黑色像素 → 垂直 run → 垂直 LAG → 谱表垂直投影峰值 → 垂直 filaments → 峰值图(对齐与连接判定);
- 聚合证据链:系统内列对齐 → 起始列细化 → 花括号/方括号搜索 → parts 聚合 → SIG 中创建小节线/连接符/花括号/方括号 Inters。
每一步都配有图形界面中的可视化开关(displayRuns、displayFilaments、PLOTS 主题等)与对应源码实现,便于开发者逐层调试与验证。理解了 GRID,就理解了 Audiveris 如何在像素与音乐语义之间搭起第一座桥梁——后续的符号识别、节拍还原都将在这张网格上展开。
延伸阅读
- SCALE 步骤:interline 与谱表线厚度的测量
- Run、Section、LAG 与 Filament 概念详解
- SIG(符号交互图)概念
- Book 参数:谱表规格与缩放参数设置
- 核心实现:LinesRetriever.java
- 核心实现:GridBuilder.java
- 核心实现:BarsRetriever.java
- 核心实现:GridStep.java
- 桌面应用
- 计算机视觉
- 图像处理
- OCR
【免费下载链接】audiveris
Latest generation of Audiveris OMR engine
相关推荐
Table Transformer实战指南:3步实现文档表格智能提取的革命性方案
Table Transformer实战指南:3步实现文档表格智能提取的革命性方案 在数字化办公和文档智能处理的浪潮中,PDF文档和图像中的表格数据提取一直是技术
人工智能计算机视觉深度学习OCR3步完成Telegraf容器化:从零到生产级监控采集实战
3步完成Telegraf容器化:从零到生产级监控采集实战 还在为服务器监控配置繁琐而头疼吗?面对分布式系统的指标采集,你是否希望找到一种更高效、更灵活的部署方案
可观测性指标监控运维faster-whisper 批处理实战:把长音频转写压到分钟级
faster whisper 批处理实战:把长音频转写压到分钟级 长音频转写拖慢服务响应。faster whisper 的批处理接口 BatchedInfere
人工智能语音音频本地部署
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考