34、音乐符号识别技术详解

音乐符号识别技术详解

1. 图像分割

图像分割是识别音乐符号的重要步骤,其目的是确定图像中包含音乐、文本和艺术作品的区域,将包含音乐的区域作为处理对象,其余部分视为“噪声”。具体操作从去除五线谱线的图像开始,识别其中的连通组件。连通组件是指一组像素,在这些像素中,任意一个像素都可以仅通过黑色像素到达其他像素。

1.1 线邻接图(LAG)的使用

可以使用递归跟踪策略来查找连通组件,但一种高效的方法是使用线邻接图(LAG)。扫描图像的行或列时,每一段连续的黑色像素成为一个节点,节点的坐标是该段像素的中心像素坐标。如果与节点关联的像素段重叠,并且这些像素段位于相邻的行(列),则在两个节点之间放置一条边。

1.2 LAG的压缩

为了减少LAG占用的空间,可以对其进行压缩。具体方法是合并满足以下条件的节点A和B(假设为水平像素段长度):
1. A在B的上方(位于前一行)。
2. A的度为(A, 1),B的度为(1, B)。
3. A和B的像素段长度几乎相同。
4. 合并后的节点可以近似用一条直线表示。

节点的度是指其上下两侧的边的数量。例如,一个节点如果有两条边从上方(前一行)连接到它,有三条边从下方(后一行)连接到它,则该节点的度为(2, 3)。

1.3 文本的去除

完成整体连通组件分析后,乐谱中的每个组件都有一个LAG。由于LAG是在去除五线谱线后得到的,因此每个LAG很可能代表一个符号或一组相关符号。与五线谱相交的符号被认为是音乐符号,但乐谱上还有许多其他符号,尤其是文本(如歌词),这些符号会显著干扰识别。因此,需要尽可能去除文本。

可以使用一种算法来区分文档页面上的文本和图形区域。该算法的基本思想是,文本由沿直线排列的连通组件组成。一组具有规则间距的共线对象形成一个单词,由于文本对光学音乐识别(OMR)系统没有意义,因此可以将其去除。具体操作是将每个连通区域的质心输入到霍夫变换中,以确定共线性,然后根据间距标记为单词的LAG集,以便后续删除。

1.4 分割结果示例

分割过程的结果可以通过一个简单乐谱页面的图像来示例。原始乐谱图像经过分割后,可以准确识别出文本区域和非文本(可能是音乐)区域。虽然可能仍有少量文本与音乐区域在一起,但后续的匹配过程可以将其丢弃。

下面是分割过程的mermaid流程图:

graph TD;
    A[去除五线谱线的图像] --> B[识别连通组件];
    B --> C[构建LAG];
    C --> D[压缩LAG];
    D --> E[分析LAG确定音乐和文本区域];
    E --> F[去除文本区域];
    F --> G[得到非文本(音乐)区域];

2. 音乐符号识别

音乐符号的识别利用了乐谱中存在大量水平和垂直线段的特点。例如,小节由垂直线分隔,大多数音符由垂直的符干和椭圆形的符头组成。这种特点使得LAG表示法非常适合用于识别线条,因为压缩后的LAG中的每个节点都代表一个线段,如果相邻线段的斜率几乎相同,则可以将它们合并成一个更长的线段。

2.1 符号识别的分类

符号识别分为三个部分:水平线、垂直线和其他部分,其他部分可以进一步分为符头和符号。使用LAG来定位水平和垂直线可以得到较为准确的结果,这种方法为识别其他特征提供了基础,特别是符头通常位于垂直符干的末端。

2.2 孤立符号的识别

对于通过去除五线谱线而孤立出来的符号(如升号、降号和休止符),使用字符轮廓方法进行识别取得了很大成功。该方法通过测量一组已知符号来找到用于识别的特征,并使用这些特征的数据库来识别未知符号。

2.3 符头的定位

符头几乎总是与符干相连,因此使用模板匹配方案来定位符头。为了加快匹配过程,使用了分层匹配方案,因为整个OMR系统的大部分处理时间都用于模板匹配。由于去除五线谱线并不完美,经常会在符头与五线谱线相交的地方留下小符干,而且对于全音符和二分音符(有空心部分),五线谱线可能会穿过符头中心,因此需要使用多种符头变体作为模板。

2.4 符号的合成

识别出单个符号后,可以利用上下文将它们合成为更复杂的形式。结构以图语法的形式表示,例如,在合适的情况下,一个升号、两个符头、两个符干和一个连音线可以组合成一个高级符号。从乐谱中的低级符号创建图,并使用图解析器进行高级匹配。

下面是音乐符号识别过程的表格总结:
| 识别部分 | 识别方法 |
| ---- | ---- |
| 水平线和垂直线 | 使用LAG定位 |
| 孤立符号 | 字符轮廓方法 |
| 符头 | 模板匹配方案 |
| 复杂符号 | 图语法合成 |

3. 神经网络识别系统的源代码

以下是一个用于神经网络识别系统的源代码,该系统结合了反向传播网络和字符识别功能,测试数据是二进制到十进制的转换。

/*Backpropagation network + character recognition */
/* TRIAL I: Test data is a binary to decimal conversion */
#include <stdio.h>
#include <math.h>
#include <malloc.h>
int NO_OF_INPUTS = 0;
int NO_OF_HIDDEN = 0;
int NO_OF_OUTPUTS = 0;
float
*inputs;/* Input values in the first layer
*/
float
**hweights;/* Weights for hidden layer. HWEIGHTS [i] [j]
*/
/* is the weight for hidden node i from
*/
/* input node j.
*/
float
*hidden;/* Outputs from the hidden layer
*/
float
*vhidden;
float
*err_hidden; /* Errors in hidden nodes
*/
float
**oweights;/* Weights for the output layer, as before
*/
float
*outputs;/*Final outputs */
float
*voutputs;
float
*err_out;/* Errors in output nodes
*/
float
*should;/* Correct output vector for training datum
*/
float learning_rate = 0.3;
FILE
*training_data=0;/* File with training data
*/
FILE
*test_data=0; /* File with unknown data
*/
FILE
*infile;
/* One of the two files above
*/
int actual;
/* Actual digits - tells us the output
*/
void compute_hidden (int node);
void compute_output (int node);
float output_function (float x);
void compute_all_hidden ();
void compute_all_outputs ();
float weight_init (void);
void initialize_all_weights ();
float compute_output_error ();
float compute_hidden_node_error (int node);
void compute_hidden_error ();
void update_output ();
void update_hidden (void);
float
* fvector (int n);
float
**fmatrix (int n, int m);

void setup (void);
void get_params (int
*ni, int
*nh, int
*no);
int get_inputs (float
*x);
int printf ();
int scanf
();
/*
Compute the output from hidden node NODE*/
void compute_hidden (int node)
{
int i = 0;
float x = 0;
for (i=0; i<NO_OF_INPUTS; i++)
x += inputs[i]*hweights [node] [i];
hidden [node] = output_function (x);
vhidden [node] = x;
}
/*
Compute the output from output node NODE*/
void compute_output (int node)
{
int i = 0;
float x = 0;
for (i=0; i<NO_OF_HIDDEN; i++)
x += hidden [i]*oweights[node] [i];
outputs [node] = output_function (x);
voutputs [node] = x;
}
/*
Output function for hidden node — linear or sigmoid*/
float output_function (float x)
{
return 1.0/(1.0 + exp ((double) (-x)));
return x; /* Linear
*/
}
/*
Derivative of the output function*/
float of_derivatives (float x)
{
float a = 0.0;
a = output_function (x);
return 1.0; /* Linear
*/
return a* (1.0 - a);
}
/*
Compute all hidden nodes*/
void compute_all_hidden ()
{
int i = 0;
for (i = 0; i<NO_OF_HIDDEN; i ++)
compute_hidden (i);
}
/*
Compute all hidden nodes*/
void compute_all_outputs ()
{
int i = 0;
for (i=0; i<NO_OF_OUTPUTS; i++)
compute_output (i);
}
/*
Initialize a weight*/
float weight_init (void)
{
double drand48();
return (float) (drand48 () - 0.5);
}
/*
Initialize all weights*/
void initialize_all_weights ()
{
int i = 0, j = 0;
for (i=0; i<NO_OF_INPUTS; i++);
for (j=0; j<NO_OF_HIDDEN; j++)
hweights [j] [i] = weight_init ();
for (i = 0; i<NO_OF_HIDDEN; i++)
for (j=0; j<NO_OF_OUTPUTS; j++)
oweights [j] [i] = weight_init ();
}
/*
Calculate the error in the output nodes*/
float compute_output_error ()
{
int i = 0;
int x = 0;
for (i=0; i<NO_OF_OUTPUTS; i++)
{
err_out [i] = (should [i]-outputs [i])
*
of_derivative (voutputs [i]);
x + = err_out [i];
}
return x;
}
/*
What SHOULD the output vector be?*/
compute_training_outputs()
{
int i;
printf ("Output SHOULD be:\n");
printf ("0 1 2 3 4 5 6 7 8 9\n");
for (i = 0; i<NO_OF_OUTPUTS; i++)
{
if (i==actual) should[i] = 1.0;
else should[i] = 0.0;
printf ("%5.1f", should [i]);
}
printf ("\n");
}
/*
Compute the error term for the given hidden node*/
float compute_hidden_node_error (int node)
{
int i = 0;
float x = 0.0;
for (i=0; i<NO_OF_OUTPUTS; i++)
x += err_out[i]*oweights[i][node];
return of_derivative (vhidden [node]) * x;
}
/*
Compute all hidden node error terms*/
void compute_hidden_error ()
{
int i = 0;
for (i=0; i<NO_OF_HIDDEN; i++)
err_hidden[i] = compute_hidden_node_error(i);
}
/*
Update the output layer weights*/
void update_output ()
{
int i=0, j=0;
for (i=0; i<NO_OF_OUTPUTS; i++)
for (j=0; j<NO_OF_HIDDEN; j++)
oweights [i] [j] +=
learning_rate*err_out [i]*hidden [j];
}
/*
Update the hidden layer weights*/
void update_hidden (void)
{
int i=0, j=0;
for (i=0; i<NO_OF_HIDDEN; i++)
for (j=0; j<NO_OF_INPUTS; j++)
hweights[i][j] +=
learning_rate*err_hidden[i]*inputs[j];
}
float compute_error_term ()
{
int i = 0;
float x = 0.0;
for (i=0; i<NO_OF_OUTPUTS; i++)
x += (err_out [i]
*err_out [i]);
return x/2.0;
}
float
* fvector (int n)
{
return (float
*) malloc (sizeof (float)
*n);
}
float
**fmatrix (int n, int m)
{
int i = 0;
float
*x,
**y;
/*Allocate rows
*/
y = (float
**)malloc (sizeof (float)
*n);
/* Allocate NXM array of floats
*/
x = (float
*)malloc (sizeof (float)
*n*m);
/*Set pointers in y to each row in x
*/
for (i=0; i<n; i++)
y[i] = & (x[i*m]);
return y;
}
/*
Allocate all arrays and matrices*/
void setup (void)
{
inputs
= fvector (NO_OF_INPUTS);
hweights
= fmatrix (NO_OF_HIDDEN, NO_OF_INPUTS);
hidden
= fvector (NO_OF_HIDDEN);
vhidden
= fvector (NO_OF_HIDDEN);
err_hidden
= fvector (NO_OF_HIDDEN);
oweights
= fmatrix (NO_OF_OUTPUTS, NO_OF_HIDDEN);
outputs
= fvector (NO_OF_OUTPUTS);
voutputs
= fvector (NO_OF_OUTPUTS);
err_out
= fvector (NO_OF_OUTPUTS);
should
= fvector (NO_OF_OUTPUTS);
}
void get_params (int
*ni, int
*nh, int
*no)
{
printf ("How many input nodes:");
scanf ("%d", ni);
printf ("How many hidden nodes:");
scanf ("%d", nh);
printf ("How many output nodes:");
scanf ("%d", no);
}
int get_inputs (float
*x)
{
int i, k;
for (i = 0; i<NO_OF_INPUTS; i++)
{
k = fscanf (infile, "%f", &(x[i]));
if (k<1) return 0;
}
if (infile == training_data)
fscanf (infile, "%d", &actual);
return 1;
}
void print_outputs ()
{
int i, j;
j = 0;
for (i=0; i<NO_OF_OUTPUTS; i++)
{
printf ("%f", outputs[i]);
if (outputs[i] > outputs[j]) j = i;
}
printf ("Actual %d NN classified as %d\n", actual, j);
}
int main (int argc, char
*argv[])
{
int k = 0;
float x = 0.0;
int dset = 1;
/* Look for data files
*/
if (argc < 3)
{
printf ("bpn <training set> <data set>\n");
exit(1);
}
training_data = fopen (argv[1], "r");
if (training_data == NULL)
{
printf ("Can't open training data '%s'\n",
argv[1]);
exit (2);
}
infile = training_data;
/* Get the size of the net
*/
get_params (&NO_OF_INPUTS, &NO_OF_HIDDEN, &NO_OF_OUTPUTS);
/* Initialize */
setup ();
initialize_all_weights ();
/* Train
*/
k = get_inputs (inputs);
while (k)
{
printf ("Training on set %d\n", dset);
compute_all_hidden ();
compute_all_outputs ();
/* Weight errors propagate backwards */
compute_training_outputs ();
compute_output_error ();
compute_hidden_error ();
update_output ();
update_hidden ();
x = compute_error_term();
printf ("Set %d error term is %f\n", dset, x);
k = get_inputs (inputs);
dset++;
}
fclose (training_data);
training_data = NULL; infile = NULL;
test_data = fopen (argv[2], "r");
if (test_data == NULL)
{
printf ("Can't open data '%s'\n", argv[2]);
exit (3);
}
infile = test_data;
/* Now apply the NN to the remaining inputs
*/
k = get_inputs (inputs);
while (k)
{
compute_all_hidden ();
compute_all_outputs ();
print_outputs ();
k = get_inputs (inputs);
}
fclose (test_data);
}

以上代码实现了一个简单的神经网络识别系统,包括网络的初始化、训练和测试过程。通过输入训练数据和测试数据,可以对系统进行训练和测试,以识别字符或进行其他分类任务。

4. 相关网站文件介绍

有许多相关的网站文件可用于不同的任务,以下是这些文件及其功能的详细列表:

文件名称 功能描述
baird.c 用于倾斜检测的Baird算法,为第二部分创建点图像
hskew.c Baird算法的第二部分,使用霍夫变换查找倾斜角度
kfill.c 用于平滑和降噪的kFill算法
learn.c 从样本图像中学习完美模板,为模板识别( ocr.c )创建数据文件
learn2.c 从高质量扫描图像中学习模板的第二个版本
slhist.c 从给定图像文件计算斜率直方图
ocr1.c 字符的模板识别
ocr2.c 从扫描模板中识别字符
ocr3.c 使用统计识别方法识别符号
nnlearn.c 基本神经网络的学习(训练),输入一个数据集(如 datapc1 datapc2 ),它将“学习”数字等
nnclass.c 使用学习到的权重,该神经网络将尝试对对象进行分类,使用预处理的数据集,但可以更改
nncvt.c 将图像(如字形)转换为神经网络可以使用的数据,即从任意大小的像素数据创建48个输入数据点
recc.c 使用凸缺陷的数字分类器,输入是数字图像,它会告诉你是什么数字,包含4 - 和8 - 邻域计数、面积和周长、面向对象的边界框、基本几何等大量有趣/有用的代码
recp.c 使用对象轮廓的数字分类器,输入是数字图像,它会告诉你是什么数字
recv.c 使用向量模板算法的数字分类器,输入是数字图像
sig.c 计算二值图像的角度 - 距离签名
lib.h 所需的头文件
vect.c 字体轮廓矢量化
datapc1 神经网络输入数据,6x8像素的图像,像素值为0 - 1之间的浮点数
datapc2 神经网络输入数据,6x8像素的图像,像素值为0 - 1之间的浮点数
helv.db 用于识别Helvetica字体字符的模板
prof.db 用于字符识别的模板
tr.db ocr3.c 使用的模板
B.pbm 字母B的字形
C.pbm 字母C的字形
D.pbm D2.pbm D3.pbm 字母D的字形
digi.pbm (i = 0, 1, 2, … 9) 手写数字字形,例如 dig0.pbm 是数字0的字形
paged.pbm 示例段落的Time - Roman文本
sample14.pbm 打印文本的示例段落
sample.pbm Helvetica文本的示例段落
pagec.pbm Time - Roman的训练文本
sk10.pbm 倾斜10度的文本
sk15.pbm 倾斜15度的文本
text14.pbm 段落的灰度图像
testpage14.pbm 训练模板的灰度图像
testpage.pbm 训练模板
testtext.pbm 两个段落的二值图像
weights.dat 存储的神经网络权重

下面是这些文件在整个音乐符号识别流程中的mermaid流程图:

graph LR;
    A[图像输入] --> B[baird.c/hskew.c 倾斜检测]
    B --> C[kfill.c 平滑降噪]
    C --> D[learn.c/learn2.c 学习模板]
    D --> E[ocr1.c/ocr2.c/ocr3.c 字符识别]
    E --> F[nnlearn.c 神经网络训练]
    F --> G[nnclass.c 神经网络分类]
    H[图像数据] --> I[nncvt.c 数据转换]
    I --> G
    J[数字图像] --> K[recc.c/recp.c/recv.c 数字分类]
    L[二值图像] --> M[sig.c 角度 - 距离签名计算]
    N[字体文件] --> O[vect.c 字体轮廓矢量化]

5. 总结

音乐符号识别是一个复杂的过程,涉及图像分割、符号识别、神经网络等多个方面。通过使用线邻接图(LAG)进行图像分割,可以有效地分离音乐和文本区域。对于音乐符号的识别,利用乐谱中大量的水平和垂直线段特点,结合字符轮廓方法、模板匹配方案和图语法合成等技术,可以准确地识别单个符号并将其合成为复杂形式。

神经网络识别系统为字符识别和分类提供了强大的工具,通过反向传播算法进行训练,可以不断优化权重以提高识别准确率。同时,众多相关的网站文件为不同的任务提供了丰富的功能,从倾斜检测到模板学习,再到各种识别和分类方法,形成了一个完整的音乐符号识别生态系统。

在实际应用中,可以根据具体需求选择合适的方法和工具,例如对于倾斜的乐谱图像,先使用 baird.c hskew.c 进行倾斜检测和校正;对于字符识别,可以根据图像质量选择 ocr1.c ocr2.c ocr3.c 。通过合理组合这些方法和工具,可以实现高效、准确的音乐符号识别。

希望以上介绍能帮助你更好地理解音乐符号识别技术,并在实际应用中发挥作用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值