☰
Audiveris GRID 步骤深度解析:从黑白图像到系统与谱表网格的完整构建流程
2026/10/3 1:59:32 网站建设 项目流程
  • 桌面应用
  • 计算机视觉
  • 图像处理
  • OCR

【免费下载链接】audiveris

Latest generation of Audiveris OMR engine

项目地址:https://gitcode.com/gh_mirrors/au/audiveris
点击查看免费下载

导读

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()方法依次执行三件事:

  1. new GridBuilder(sheet).buildInfo()—— 构建系统网格主体(L96-L97);
  2. new StaffLineCleaner(sheet).process()—— 清理谱表线(L99-L100);
  3. 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() 的主线:

  1. 构建初始 filaments:FilamentFactory.retrieveFilaments(hLag.getEntities())(L1493-L1499);
  2. 剔除弯曲的 filaments:purgeCurvedFilaments()用样条(NaturalSpline)计算中点的旋转角,若超过maxFilamentRotation(0.1 弧度)则丢弃(L1149-L1189);
  3. 计算全局斜率:retrieveGlobalSlope()取最长的前 10% filaments(topRatioForSlope = 0.1)求平均斜率,存入Skew对象(L1433-L1455、L1506-L1508);
  4. 剔除斜率偏离的 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)**查看:

模式视图
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.1filament 允许的最大中心旋转角(弯曲剔除阈值)
maxSlopeDiff弧度0.025filament 与全局斜率的允许最大差值
minRunLengthinterline0.25水平 run 被纳入考虑的最小长度
minStaffLengthinterline30谱表(簇)的最小水平长度
minSlope正切0.0002值得记录的最小绝对斜率
maxOneLineSlope正切0.001完美 sheet 上一线谱表的最大绝对斜率
minTrueLengthRatio比例0.3相对最长簇真实长度的最小占比(空洞剔除)
patternWidthinterline1.0谱表端点模式探测的宽度
patternJitterinterline0.25谱表端点模式的最大纵坐标抖动
minRadiusinterline12曲率抛光(polish)的最小可接受半径
displayRuns布尔false是否显示 runs 视图

这些常量大多在 Parameters 构造函数 中按scale.toPixels(...)换算为像素值,体现了 Audiveris 的**缩放无关(scale-independent)**设计:同一套常量在不同分辨率乐谱下都能自适应。

六、小结

GRID 步骤以三条证据链完成了乐谱网格的复原:

  1. 水平证据链:黑色像素 → 水平 run → 水平 sections(长短分流)→ filaments(去弯、去偏斜)→ 簇 → 谱表(标准/一线/tablature);
  2. 垂直证据链:黑色像素 → 垂直 run → 垂直 LAG → 谱表垂直投影峰值 → 垂直 filaments → 峰值图(对齐与连接判定);
  3. 聚合证据链:系统内列对齐 → 起始列细化 → 花括号/方括号搜索 → 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

项目地址:https://gitcode.com/gh_mirrors/au/audiveris
点击查看免费下载

相关推荐

上一篇:google-font-download高级技巧:自定义字体格式与语言子集
下一篇:PostgresApp时间序列数据处理:使用TimescaleDB扩展

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询