2014级数字信号处理课程设计报
告
时间:2021.03.02 创作:欧阳数 题目:钢琴音符识别
姓名:邱晨曦 学号:2014010909008 答辩时间:2016/12/9
一. 题目要求:
(1) 播放和记录一段钢琴音乐中的音符;
(2) 记录到音符以后,找到音符所对应的现代标准钢琴的钢琴键,并分析结果。 二. 课程设计思路:
(1) 涉及到的知识点:
快速傅里叶变换、钢琴音频信号的时域和频域的特性、能熵比的概念、频率校正、频率与音符的转换关系。
(2) 方案分析: A.
预处理部分:
1.
直接用audioread函数读出来的原始数据。 优点:准确率较高;
欧阳数创编
欧阳数创编
缺点:数据量较大,采样频率为44kHz,远大于奈奎斯特采样率。
2.
以11kHz的采样率重新采样,并转换为单声道。 优点:数据量小了很多,易于处理;
缺点:牺牲了部分的准确率,但对于音符的判断影响可以忽略。
B. 端点检测算法: <1> . 双门限法:
1. 计算短时能量(高门限)和过零率(低门限);
2. 选取一个较高的门限T2,语音信号的能量包络大
部分都在此门限之上,进行一次初判,语音起止点位于该门限与短时能量包络交点所对应的时间间隔之外;
3. 根据噪声能量,确定一个较低的门限T1,并从初
判起点往左,从初判终点往右搜索,分别找到能零比曲线第一次与门限T1相交的两个点,两点之间段就是用双门限方法所判定的语音段;
4. 以短时平均过零率为准,从低门限点往左右搜
索,找到短时平均过零率低于某阈值的两点,为语音的起止点;
图1:双门限法示意图
欧阳数创编
欧阳数创编
说明:算法中的阀值是根据实验过程调节的。
该算法在实际应用的过程中发现:在语音信号频率分布较为集中的时候,端点检测出来的结果比较准确,但当语音信号频率分布比较分散的时候,很难通过控制固定的阀值来检测到每个音符; <2>. 自相关法:
由于两种信号的自相关函数存在极大的差异,可以利用这种差别来提取语音端点。根据噪声的情况,设置两个阈值T1和T2,当相关函数最大值大于T2时,便判定是语音;当相关函数最大值大于或小于T1时,则判定为语音信号的端点。
该算法同样存在当语音信号频率分布较广的时候,阀值比较难控制的问题。 <3>. 基于谱熵的端点检测:
基于谱熵语音端点检测方法是通过检测谱的平坦程度,来进行语音端点检测的,为了更好进行语音端点检测,采用语音信号的短时功率谱构造语音信息谱熵,从而对语音段和噪声段进行区分。检测思路:
1. 2. 3.
对语音信号进行分帧加窗; 计算每一帧的谱能量;
计算出每一帧中每个样本点的概率密度函数
欧阳数创编
欧阳数创编
pnYn(k)Y(l)nl0N2Yn(k); EnN2l04.
计算出每一帧的谱熵值Hnpn(l)lnpn(l)(由信息论知识知道,熵值在自变量服从均匀分布的时候,熵值达到最大值,所以噪声的熵值是比较大的,而钢琴音符的熵值是比较小的,由此区别了噪声和音符);
5. 6.
设置判决门限;
根据各帧的谱熵值进行端点检测。
在实验过程中发现:依然存在当语音信号频率分布较广时,阀值不太好控制的问题。因此对该方法进行改进,引入,能熵比的概念:
谱熵值类似于过零率,能熵比的表示为
EEFn1LEnHn。由于噪声和信号的能熵比差别很大。因此在能熵比的图像中,每一个“尖刺”就代表了一个特定频率的语言信号。
图2:能熵比图中的“尖刺”
在检测过程中,依然不能通过简单的设置阀值的办法来进行端点检测,原因是语音频率分布较广时,每个音符的能熵比变化范围差别较大,如下图所示,有的“尖刺”完全在门限之上,而有的则完全在门限之下。
图3:88阶全音的能熵比图
欧阳数创编
欧阳数创编
因此,采用检测能熵比中的“低谷点”(该点比左右两边的一定数目的点的能熵比都小)的方法。语音信号一定位于两个低谷点之间的部分,再对低谷点进行适当的左右移动作为语音信号的起止点。如下图所示:
图4:标记起止点的能熵比图 (绿色为起始点,红色为截止点)
(3)设计框架和流程: 1.
用audioread函数读入钢琴音乐,并用sound函数播放;
2. 为了方便处理,对信号以11.025kHz的频率进行重新采样,并统一转换成单声道的信号;
3. 因为语言信号可以在短时间内认为是平稳的,因此对
语音信号进行分帧的处理,设置帧长320,为了减小误差,两帧之间设置重叠部分,因此帧移取80; 4. 计算每一帧的能熵比;
5. 找到能熵比中的“低谷点”(该点比左右两边的一定数目的点的能熵比都小);
6. 如果两个低谷点之间的距离大于miniL(认为持续长度
超过一定长度的为音符,最小长度miniL可自行设置)。则低谷点右移sr (即shift right,数值可自行调节)帧作为一段信号的起始点,将低谷点左移sl(即shift left,数值可自行调节)帧作为截止点
欧阳数创编
欧阳数创编
[注:采用该方法的优点是通过调节相关参数能适应多种情况,缺点是检测环境发生较大变化时,需要重新设置参数];
7. 将找到的语音段转换成未分帧时对应坐标的语音段,并对每段做快速傅里叶变换;
8. 找到每段快速傅里叶变换中的最大值以及最大值所对
应的横坐标(fft点),将横坐标转换成相应的频率,得到的频率即为该段音符的频率;
9. 利用比值法进行频率的校正,窗函数选择矩形窗; 10. 根据检测到的频率确定音符,计算公式为:
fn12log2()49,n为第几个按键,再通过查表
440得到对应音符;
11. 分析结果。 三. 具体设计过程:
(1)部分代码(测试部分缺省):
主函数部分:
[x,fs]=audioread('钢琴音频.WAV'); format short;
wlen=320; inc=80; % 分帧的帧长和帧移 overlap=wlen-inc; % 帧之间的重叠部分 sound(x,fs); % 播放音乐
欧阳数创编
欧阳数创编
x=calsample(x,fs); % 为了方便处理,重新以11025Hz的频率采样,并转换成单声道 x=x-mean(x); % 消去直流分量 x=x/max(abs(x)); % 幅值归一化 y = Enframe(wlen,inc,x)'; % 分帧 fn = size(y,2); % 取得帧数
time = (0 : length(x)-1)/11025; % 计算时间坐标 frameTime = frame2time(fn, wlen, inc, 11025); % 计算各帧对应的时间坐标
sr=2;sl=13;miniL=33; % 配置左右移动的帧数和要求的最短帧数
[voicesegment,vos,Ef]=get_segment(y,fn,sr,sl,miniL); % 获得语音段
[real_f,ft,ax]=get_f(x,voicesegment,vos,wlen,inc); % 检测频率的结果 for i=1:length(real_f)
real_f(i)=roundn(real_f(i),-4); end
for i=1:length(real_f)
real_node(i)=get_node(real_f(i)) end
%**********************************绘图部分
欧阳数创编
欧阳数创编
******************************** subplot 211; stem(real_f);
title('频率检测结果');xlabel('音符/个');ylabel('频率/Hz'); subplot 212;
stem(real_node,'r');
title('音符检测结果');xlabel('音符/个');ylabel('对应按键'); figure(2); subplot 211; plot(Ef);
title('能熵比图及语音起止点');xlabel('帧数/个');ylabel('能熵比');
for i=1:length(voicesegment)
text(voicesegment(i).begin,Ef(voicesegment(i).begin),'o','color','g')
text(voicesegment(i).end,Ef(voicesegment(i).end),'o','color','r') end
欧阳数创编
欧阳数创编
subplot 212, plot(time,x,'k'); title('语音信号端点检测结果')
axis([0 max(time) -1 1]); ylabel('幅值');
for k=1 : vos % 标出有话段
nx1=voicesegment(k).begin; nx2=voicesegment(k).end; nxl=voicesegment(k).duration;
fprintf('%4d %4d %4d %4d\\n',k,nx1,nx2,nxl); subplot 212
line([frameTime(nx1) frameTime(nx1)],[-1 1],'color','r','linestyle','-');
line([frameTime(nx2) frameTime(nx2)],[-1 1],'color','r','linestyle','--'); end
其中的用到的子函数:
1.calsample.m (调整采样率和声道) function sample = calsample(sampledata,FS)
temp_sample = resample(sampledata,1,FS/11025); %调整采样频率
[~,n] = size(temp_sample);
欧阳数创编
欧阳数创编
if (n == 2) %转换成单声道 sample = temp_sample(:,1); else
sample = temp_sample; end end
2. Enframe.m (分帧函数)
function f=Enframe(len,inc,x) %对读入的语音进行分帧,len为帧长,
%inc为帧重叠样点数,x为输入语音数据
fh=fix(((size(x,1)-len)/inc)+1); %计算帧数 f=zeros(fh,len); %设置一个零矩阵,行为帧数,列为帧长
i=1; n=1;
while i<=fh %帧间循环 j=1;
while j<=len %帧内循环 f(i,j)=x(n); j=j+1; n=n+1;
欧阳数创编
欧阳数创编
end
n=n-len+inc; %下一帧开始位置 i=i+1; end
3. frame2time.m(坐标刻度转换) function
frameTime=frame2time(frameNum,framelen,inc,fs)
frameTime=(((1:frameNum)-1)*inc+framelen/2)/fs; % 求对应的时间坐标
4.get_segment.m(端点检测,确定音符段) function
[voicesegment,vos,Ef]=get_segment(y,fn,sr,sl,miniL) if size(y,2)~=fn, y=y'; end % 把y转换为每列数据表示一帧语音信号
wlen=size(y,1); % 取得帧长 for i=1:fn
Sp = abs(fft(y(:,i))); % FFT取幅值 Sp = Sp(1:wlen/2+1);
% 只取正频率部分
Esum(i) = sum(Sp.*Sp); % 计算能量值 prob = Sp/(sum(Sp));
% 计算概率
H(i) = -sum(prob.*log(prob+eps)); % 求谱熵值 end
欧阳数创编
欧阳数创编
hindex=find(H<0.1); H(hindex)=max(H);
Ef=sqrt(1 + abs(Esum./H)); % 计算能熵比 Ef=Ef/max(Ef); % 归一化
[x1,y1] = get_max(Ef); % 找到能熵比中的“高峰点”
[x2,y2] = get_min(Ef); % 找到能熵比中的“低估点”
voicesegment(1).begin = x1(1)-8; % 由于仅仅靠低谷点无法检测出第一个音符起始位置, 因此将高峰点的第一个值左移8帧作为第一个音符的起始点
voicesegment(1).end = x2(1)-sl; % 将第一个低谷点作为第一个音符的截止点
voicesegment(1).duration=voicesegment(1).end -voicesegment(1).begin+1; % 一个音符的持续帧数 j=1;
for k=2 : length(x2) % 将找到的低谷点作为音符的起止点
if x2(k)-x2(k-1)>=miniL % 剔除持续帧长度小于miniL的音符段
j=j+1; temp1=x2(k-1)+sr; % 将低谷点右移sr个帧,
欧阳数创编
欧阳数创编
作为一个音符的起始点
voicesegment(j).begin=temp1;
temp2=x2(k)-sl; % 将低谷点左移sl个帧,作为一个音符的截止点
voicesegment(j).end=temp2;
voicesegment(j).duration=voicesegment(j).end-voicesegment(j).begin+1; %音符持续帧数 end end
vos=length(voicesegment); %返回音符个数 end
5.get_max.m(找到高峰点) function [max_x,max]=get_max(x) l=length(x); %获得数组的长度 max=[]; max_x=[]; j=1;
for i=20:l-15 %找到“峰值点”
if (x(i)>x(i-1))&&(x(i)>x(i-2))&&(x(i)>x(i-3))&&(x(i)>x(i-4))&&(x(i)>x(i-5))...
&&(x(i)>x(i-6))&&(x(i)>x(i-欧阳数创编
欧阳数创编
7))&&(x(i)>x(i-8))&&(x(i)>x(i-9))&&(x(i)>x(i-10))... &&(x(i)>x(i-11))&&(x(i)>x(i-12))&&(x(i)>x(i-13))&&(x(i)>x(i-14))... &&(x(i)>x(i-15))&&(x(i)>x(i-16))&&(x(i)>x(i-17))...
&&(x(i)>x(i+1))&&(x(i)>x(i+2))&&(x(i)>x(i+3))&&(x(i)>x(i+4))&&(x(i)>x(i+5))...
&&(x(i)>x(i+6))&&(x(i)>x(i+7))&&(x(i)>x(i+8))&&(x(i)>x(i+9))&&(x(i)>x(i+10))...
&&(x(i)>x(i+11))&&(x(i)>x(i+11))&&(x(i)>x(i+12))&&(x(i)>x(i+13))&&(x(i)>x(i+14))...
&&(x(i)>x(i+15))&&(x(i)>x(i+16))... &&(x(i)>0.1)
max_x(j)=i; %找到后赋值给返回参数
max(j)=x(i); i=i+1; j=j+1; else
欧阳数创编
欧阳数创编
i=i+1; end end end
6.get_min.m(找到低谷点) function [min_x,min]=get_min(x) l=length(x); %获得数组的长度 min=[]; min_x=[]; j=1;
for i=100:l-10 %寻找低谷点
if (x(i) 24))... &&(x(i) 欧阳数创编 欧阳数创编 7.get_f.m(确定频率) function [real_f,ft,ax]=get_f(x,voicesegment,vos,wlen,inc) for i=1:vos % 横坐标转化,将起止点坐标的单位由帧转换成点 ax(i).begin=(voicesegment(i).begin-1)*inc+1; ax(i).end=(voicesegment(i).end-1)*inc+wlen; ax(i).duration=ax(i).end-ax(i).begin+1; temp=x(ax(i).begin:ax(i).end); %获得语音段 ft{i}=fft(temp); % 做快速傅里叶变换,并将结果保存在元胞矩阵中 ft{i}=abs(ft{i}); % 将得到的fft去模值 [fm(i),fm_x(i)]=max(ft{i}); % 找到每个语音段对应的fft中的最大值及最大值对应的横坐标 real_f1(i)=fm_x(i)*11025./ax(i).duration; % 进行频率转换,将横坐标乘以分辨率得到真实频率 real_f(i)=Specorrm(x(ax(i).begin:ax(i).end),11025,ax(i).duration,real_f1(i)-10,real_f1(i)+10); % 利用比值法进行频率修正,窗函数选择矩形窗 end 欧阳数创编 欧阳数创编 8.Specorrm.m(频率校正) function Z=Specorrm(x,fs,N,nx1,nx2) %x是被测信号, fs是采样频率 N为FFT的长度,nx1和nx2被测信号频率的区间,nx2>nx1 [nx,mx]=size(x); if mx==1, x=x';end %转换成行矩阵 [nx,mx]=size(x); M=fix(N/2)+mod(N,2); xf=fft(x); %xf=xf(1:M)*2/N; ddf=fs/N; % 频率分辨率 n1=fix(nx1/ddf); % 将频率转换成fft对应的点 n2=round(nx2/ddf); A=abs(xf); % 取fft的模值 [Amax,index]=max(A(n1:n2)); %找到fft点n1到n2之间幅值的最大值 index=index+n1-1; % 移动到n1和n2中间 %比值法 %加矩形窗 indsecL=A(index-1)>A(index+1); %满足条件则为1,不满足则为0 df=indsecL.*A(index-1)./(Amax+A(index-1))-(1-欧阳数创编 欧阳数创编 indsecL).*A(index+1)./(Amax+A(index+1)); Z=(index-1-df)*ddf; %修正后的频率 end 9.get_node.m(确定音符) function node=get_node(f) node=12*log2(f/440)+49; node=round(node); %四舍五入确定对应按键 end (2)生成的图与数据等 序号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 端点检测结果 起始帧 截止帧 40 97 112 166 181 235 250 303 318 372 387 441 456 510 525 579 594 648 663 714 729 786 801 854 869 923 938 993 1008 1051 1066 1122 1137 1176 1191 1267 1282 1337 1352 1391 1406 1475 1490 1513 1528 1588 1603 1666 1681 1730 1760 1808 1823 1873 持续帧数 58 55 55 54 55 55 55 55 55 52 58 54 55 56 44 57 40 77 56 40 70 24 61 64 50 49 51 欧阳数创编 欧阳数创编 28 29 30 31 32 33 34 35 1903 1959 2032 2105 2177 2240 2317 2380 1944 2017 2090 2136 2225 2278 2340 2429 42 59 59 32 49 39 24 50 对数据的分析: 真实按键 真实音符 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 G♯7/A♭7 G7 F♯7/G♭7 F7 E7 D♯7/E♭7 D7 C♯7/D♭7 C7 Double high C B6 A♯6/B♭6 A6 G♯6/A♭6 G6 F♯6/G♭6 F6 E6 D♯6/E♭6 D6 C♯6/D♭6 C6 Soprano C (High C) B5 A♯5/B♭5 A5 G♯5/A♭5 G5 F♯5/G♭5 F5 E5 D♯5/E♭5 D5 C♯5/D♭5 C5 Tenor C 真实频率/Hz 523.251 554.365 587.33 622.254 659.255 698.456 739.989 783.991 830.609 880 932.328 987.767 1046.5 1108.73 1174.66 1244.51 1318.51 1396.91 1479.98 1567.98 1661.22 1760 1864.66 1975.53 2093 2217.46 2349.32 2489.02 2637.02 2793.83 2959.96 3135.96 3322.44 检测频率/Hz 524.8973 555.0437 589.8768 623.1585 659.7079 699.2968 741.8503 785.9485 832.414 883.2303 933.8565 989.9964 1047.01 1112.7244 1177.1137 1248.1238 1322.609 1403.0084 1486.3162 1571.9648 1669.9962 1773.532 1881.0102 1988.0017 2112.8655 2241.0368 2377.199 2512.4893 2515.4901 2824.2907 2994.1387 3192.2427 3364.7355 对应按键 检测音符 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 79 81 82 83 84 G♯7/A♭7 G7 F♯7/G♭7 F7 E7 D♯7/E♭7 D7 C♯7/D♭7 C7 Double high C B6 A♯6/B♭6 A6 G♯6/A♭6 G6 F♯6/G♭6 F6 E6 D♯6/E♭6 D6 C♯6/D♭6 C6 Soprano C (High C) B5 A♯5/B♭5 A5 G♯5/A♭5 G5 F♯5/G♭5 F5 F5 D♯5/E♭5 D5 C♯5/D♭5 C5 Tenor C 绝对误差/Hz 1.6463 0.6787 2.5468 0.9045 0.4529 0.8408 1.8613 1.9575 1.805 3.2303 1.5285 2.2294 0.51 3.9944 2.4537 3.6138 4.099 6.0984 6.3362 3.9848 8.7762 13.532 16.3502 12.4717 19.8655 23.5768 27.879 23.4693 121.5299 30.4607 34.1787 56.2827 42.2955 相对误差 0.31% 0.12% 0.43% 0.15% 0.07% 0.12% 0.25% 0.25% 0.22% 0.37% 0.16% 0.23% 0.05% 0.36% 0.21% 0.29% 0.31% 0.44% 0.43% 0.25% 0.53% 0.77% 0.88% 0.63% 0.95% 1.06% 1.19% 0.94% 4.61% 1.09% 1.15% 1.79% 1.27% 欧阳数创编 欧阳数创编 85 86 A7 A♯7/B♭7 3520 3729.31 3575.9524 3771.6582 85 86 A7 A♯7/B♭7 55.9524 42.3482 1.59% 1.14% 红色标记出检测错误的音符。 上表为检测结果的统计及误差统计。大部分误差都小于1%,平均误差为0.82%,误差较小.音符检测发生一个错误,正确了为97.14%,很好地完成了任务。 四. 总结: (1)收获 (2)小组分工情况 小组就邱晨曦一个人,邱晨曦负责全部工作。 参考文献:谢明,丁康:离散频谱的一种新校正方法,重庆,载重庆大学学报:1995年3月 第8卷第2期 47~54 时间:2021.03.02 创作:欧阳数 欧阳数创编 因篇幅问题不能全部显示,请点此查看更多更全内容