音乐符号识别技术详解
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
。通过合理组合这些方法和工具,可以实现高效、准确的音乐符号识别。
希望以上介绍能帮助你更好地理解音乐符号识别技术,并在实际应用中发挥作用。
超级会员免费看
26万+

被折叠的 条评论
为什么被折叠?



