注意发病检测

Posted

技术标签:

【中文标题】注意发病检测【英文标题】:Note onset detection 【发布时间】:2010-09-22 14:36:35 【问题描述】:

我正在开发一个系统来帮助音乐家进行转录。目的是在单个乐器单声道录音上执行自动音乐转录(它不必完美,因为用户稍后会纠正故障/错误)。这里有人有自动音乐转录的经验吗?还是一般的数字信号处理?无论您的背景如何,我们都非常感谢任何人的帮助。

到目前为止,我已经研究了快速傅立叶变换在音高检测中的使用,在 MATLAB 和我自己的 Java 测试程序中进行的大量测试表明它足够快速和准确,可以满足我的需求。需要解决的任务的另一个要素是以乐谱形式显示生成的 MIDI 数据,但我现在不关心这个。

简而言之,我正在寻找一种很好的音符开始检测方法,即新音符开始的信号位置。由于缓慢的开始很难正确检测到,我最初将使用带有钢琴录音的系统。这也部分是由于我会弹钢琴,并且应该处于更好的位置以获得合适的录音进行测试。如上所述,该系统的早期版本将用于简单的单声道录音,之后可能会根据未来几周的进展情况发展为更复杂的输入。

【问题讨论】:

【参考方案1】:

通过使用平均能量测量,可以在时域中轻松检测到硬开始。

从 0 到 N 求和 (X^2)

对整个信号块执行此操作。您应该会在发作时看到峰值(窗口大小取决于您,我的建议是 50 毫秒或更多)。

关于发病检测的大量论文:

对于核心工程师:

http://www.nyu.edu/classes/bello/MIR_files/2005_BelloEtAl_IEEE_TSALP.pdf

普通人更容易理解:

http://bingweb.binghamton.edu/~ahess2/Onset_Detection_Nov302011.pdf

【讨论】:

【参考方案2】:

下图说明了音符起始检测的阈值方法:

此图像显示了一个典型的 WAV 文件,其中连续播放了三个离散的音符。红线表示选定的信号阈值,蓝线表示通过简单算法返回的音符开始位置,该算法在信号电平超过阈值时标记开始。

如图所示,选择合适的绝对阈值很困难。在这种情况下,第一个音符拾得很好,第二个音符完全错过,第三个音符(勉强)开始很晚。一般来说,低阈值会使您拾取幻影音符,而提高阈值会使您错过音符。该问题的一种解决方案是使用相对阈值,如果信号在一定时间内增加一定百分比,则触发开始,但这有其自身的问题。

一个更简单的解决方案是首先在您的波形文件上使用有点违反直觉的压缩(不是 MP3 压缩 - 这完全是另一回事)。压缩本质上是使音频数据中的尖峰变平,然后放大所有内容,使更多的音频接近最大值。上面示例的效果如下所示(这说明了为什么名称“压缩”似乎没有意义——在音频设备上它通常被标记为“响度”):

压缩后,绝对阈值方法会工作得更好(尽管它很容易过度压缩并开始拾取虚构的音符开始,与降低阈值的效果相同)。有很多波形编辑器在压缩方面做得很好,最好让他们处理这个任务——你可能需要做大量的工作来“清理”你的波形文件,然后才能检测到音符无论如何。

在编码方面,加载到内存中的 WAV 文件本质上只是一个两字节整数数组,其中 0 表示无信号,32,767 和 -32,768 表示峰值。在其最简单的形式中,阈值检测算法将从第一个样本开始并读取数组,直到找到大于阈值的值。

short threshold = 10000;
for (int i = 0; i < samples.Length; i++)

    if ((short)Math.Abs(samples[i]) > threshold) 
    
        // here is one note onset point
    

在实践中,这非常有效,因为正常音频在给定阈值之上会出现各种瞬态尖峰。一种解决方案是使用运行平均信号强度(即在最后 n 个样本的平均值高于阈值之前不要标记开始)。

short threshold = 10000;
int window_length = 100;
int running_total = 0;
// tally up the first window_length samples
for (int i = 0; i < window_length; i++)

    running_total += samples[i];

// calculate moving average
for (int i = window_length; i < samples.Length; i++)

    // remove oldest sample and add current
    running_total -= samples[i - window_length];
    running_total += samples[i];
    short moving_average = running_total / window_length;
    if (moving_average > threshold)
    
        // here is one note onset point 
        int onset_point = i - (window_length / 2);
    

所有这些都需要大量调整和调整设置以使其准确地找到 WAV 文件的起始位置,通常适用于一个文件的方法在另一个文件上效果不佳。这是您选择的一个非常困难且未完美解决的问题领域,但我认为您正在解决它很酷。

更新:此图显示了我遗漏的音符检测细节,即检测音符何时结束:

黄线表示超出阈值。一旦算法检测到音符开始,它就会假定音符会继续,直到运行平均信号强度低于该值(此处显示为紫色线)。当然,这是另一个困难的来源,例如两个或多个音符重叠(复音)的情况。

检测到每个音符的起点和终点后,您现在可以分析每个 WAV 文件数据片段以确定音高。

更新 2:我刚刚阅读了您更新的问题。如果您从头开始编写自己的 FFT,则通过自相关进行音高检测比 FFT 更容易实现,但如果您已经检查并使用了预构建的 FFT 库,则最好肯定使用它.一旦您确定了每个音符的开始和停止位置(并在开始和结束处为错过的启动和释放部分添加了一些填充),您现在可以提取每个音频数据片段并将其传递给 FFT 函数以确定音高。

这里重要的一点是不要使用压缩音频数据的切片,而是使用原始的、未修改的数据切片。压缩过程会使音频失真,并可能产生不准确的音高读数。

关于音符起音时间的最后一点是,它可能没有您想象的那么严重。通常在音乐中,起音慢的乐器(如软合成器)会比起音尖锐的乐器(如钢琴)更早开始一个音符,并且两个音符听起来好像它们同时开始。如果您以这种方式演奏乐器,那么算法会为两种乐器拾取相同的开始时间,从 WAV 到 MIDI 的角度来看,这是很好的。

最后一次更新(我希望):忘记我所说的在每个音符的早期攻击部分包含一些填充样本 - 我忘记了这对于音高检测实际上是一个坏主意。许多乐器(尤其是钢琴和其他打击乐器)的起音部分包含不是基本音高倍数的瞬变,并且往往会破坏音高检测。出于这个原因,您实际上希望在攻击后一点点开始每个切片。

哦,还有一点很重要:这里的“压缩”一词并不是指 MP3 风格的压缩

再次更新:这是一个进行非动态压缩的简单函数:

public void StaticCompress(short[] samples, float param)

    for (int i = 0; i < samples.Length; i++)
    
        int sign = (samples[i] < 0) ? -1 : 1;
        float norm = ABS(samples[i] / 32768); // NOT short.MaxValue
        norm = 1.0 - POW(1.0 - norm, param);
        samples[i] = 32768 * norm * sign;
    

当 param = 1.0 时,此函数对音频没有影响。较大的参数值(2.0 很好,它将每个样本与最大峰值之间的归一化差异平方)将产生更多的压缩和更响亮的整体(但蹩脚)声音。低于 1.0 的值会产生膨胀效果。

另外一点可能很明显:您应该在一个没有回声的小房间里录制音乐,因为回声经常被该算法拾取为幻音。

更新:这里是一个静态压缩版本,它将在 C# 中编译并显式转换所有内容。这将返回预期的结果:

public void StaticCompress(short[] samples, double param)

    for (int i = 0; i < samples.Length; i++)
    
        Compress(ref samples[i], param);
    


public void Compress(ref short orig, double param)

    double sign = 1;
    if (orig < 0)
    
        sign = -1;
    
    // 32768 is max abs value of a short. best practice is to pre-
    // normalize data or use peak value in place of 32768
    double norm = Math.Abs((double)orig / 32768.0);
    norm = 1.0 - Math.Pow(1.0 - norm, param);
    orig = (short)(32768.0 * norm * sign); // should round before cast,
        // but won't affect note onset detection

抱歉,我在 Matlab 上的知识分数是 0。如果您发布另一个问题,说明为什么您的 Matlab 函数不能按预期工作,它会得到回答(只是我没有回答)。

【讨论】:

这是一个非常详细的回复,谢谢 :) 我将不得不再次检查以确保我没有遗漏任何内容,如有任何问题,请回复您。 你说的压缩是动态范围压缩吗? @Alan:基本上是的,尽管您也可以进行非动态范围压缩。大多数 WAV 编辑器将此效果标记为“动态压缩”,可能是为了避免与文件大小压缩混淆。 谢谢。您能否向我指出一种实现动态或非动态范围压缩的算法?到目前为止,我能找到的只是许多放大器中响度特性的电路图。 我会在一秒钟内发布我自己的一个(它产生的效果听起来很可怕,但它可能适用于此目的)。我也从未找到动态范围压缩器的代码。我认为 99% 的这种类型的 DSP 工作是实时的(与全缓冲处理相反)。【参考方案3】:

您应该查看MIRToolbox - 它是为 Matlab 编写的,并且内置了一个起始检测器 - 它工作得很好。源代码是 GPL 的,所以你可以用任何适合你的语言来实现算法。您的生产代码将使用什么语言?

【讨论】:

感谢 Jason 的链接,我现在就去看看。我只是使用 MATLAB 对整个系统的不同元素的各种方法进行一些快速测试/调查。生产系统可能会用 Java 编写,利用 javax.sound.*【参考方案4】:

您可以尝试将 wav 信号转换为幅度随时间变化的图表。那么确定一致起始点的一种方法是计算信号上升沿拐点处的切线与x轴的交点。

【讨论】:

【参考方案5】:

这个库以音频标签为中心:

aubio

aubio 是一个音频标签库。它的功能包括在每次攻击之前对声音文件进行分段、执行音高检测、敲击节拍以及从现场音频中生成 MIDI 流。名称 aubio 来自 'audio' 有一个错字:在结果中也可能发现几个转录错误。

我在起始检测和音高检测方面运气不错。它在 c 中,但有 swig/python 包装器。

此外,图书馆的作者在页面上有他论文的 pdf,其中包含有关标签的大量信息和背景。

【讨论】:

【参考方案6】:

您想要做的通常称为 WAV-to-MIDI(谷歌“wav-to-midi”)。在这个过程中进行了许多尝试,结果各不相同(音符开始是困难之一;复调更难处理)。我建议从彻底搜索现成的解决方案开始,并且只有在没有任何可以接受的情况下才开始自己工作。

您需要处理的另一部分是将 MIDI 输出渲染为传统乐谱,但有无数产品可以做到这一点。

另一个答案是:是的,我已经做了很多数字信号处理(请参阅我网站上的软件 - 这是一个用 VB 和 C 编写的无限语音软件合成器),我有兴趣帮助您这个问题。 WAV-to-MIDI 部分在概念上并没有那么难,只是让它在实践中可靠地工作是很难的。 Note onset 只是设置一个阈值 - 可以轻松地及时向前或向后调整错误以补偿音符攻击差异。在录音中进行音高检测比实时检测要容易得多,并且只涉及实现一个自相关例程。

【讨论】:

感谢您的回复。我发现的大多数现成解决方案都不是很好,即使是简单的录音,准确度也常常低于 60%。此外,这是针对我的本科论文的,因此不能简单地采用现成的解决方案。我现在会用更多信息更新我的问题。

以上是关于注意发病检测的主要内容,如果未能解决你的问题,请参考以下文章

基于RealSense的坐姿检测技术

脉搏波检测数据的采集

高精度肺结节自动检测方案 | 算法解析

基于PaddleSeg实现眼底血管分割——助力医疗人员更高效检测视网膜疾病

建议2:注意Javascript数据类型的特殊性---正确检测数据类型

检测磁盘坏道并忽略方式挂载注意的细节