PESQ语音质量客观评价:原理、Matlab实现与避坑指南
2026/9/2 1:59:22 网站建设 项目流程

简介:语音质量客观评价(PESQ)的MATLAB实现资源包,面向语音处理、编解码算法研究人员与通信工程学习者,解决语音传输或处理后的质量量化评估问题。包内共9个文件、约1.73MB,涵盖MATLAB脚本、PESQ可执行程序、原始与处理后语音样本、txt使用说明及docx/xls结果对比文档,形成从调用到结果输出的完整闭环。已有2050人学习下载,适合希望通过实际案例快速上手PESQ的读者。借助自带脚本与二进制工具,可自行替换语音样本完成不同场景失真对比;结合说明文档还能理解帧对齐、频谱失真计算等核心原理。PESQ基于心理声学模型给出-0.5至4.5的感知分数,资源包提供了全套运行环境,可直接用于语音增强、编码器评测等实验,为算法调优或项目报告提供可复现的评估支撑。 做语音处理的人,基本都绕不开PESQ这个名字。无论你是在做语音编码、VoIP通话优化,还是给降噪算法做效果评估,最终都要回答一个问题:处理完的这段语音,到底变好了还是变差了?主观试听当然可以,但人耳主观评价费时费力,而且受状态影响大,这时候就需要一套客观评价算法来兜底,PESQ(Perceptual Evaluation of Speech Quality)就是应用最广的那个标准答案。

我这次整理的是名为“语音质量客观评价(PESQ)matlab.rar”的完整Matlab代码包。很多人下载过类似压缩包,但真正能在自己的项目里跑通PESQ并正确解读结果的人并不多——不是代码有问题,而是你不知道它内部在算什么、哪些参数会影响得分、为什么报错。这篇文章就专门拆开PESQ:从算法原理到Matlab实现,从编译避坑到实际调用,把我在项目中踩过的坑一次说清楚。适合刚接触语音质量评价的研究生、做音频算法的开发工程师,以及所有需要在Matlab里快速拿到MOS分的同行参考。

1. 语音质量评价为什么需要PESQ,它到底在算什么

1.1 主观评价的难点与客观评价的诞生

语音质量评价最早的方法是主观MOS(Mean Opinion Score),找一群人听语音,然后打分,从1分(极差)到5分(极好)。这种方法的缺点是显而易见的:需要大量听音员、环境要标准化、结果受个体差异影响大、而且没法嵌入到自动化测试链路里。音频编解码算法在迭代阶段天天改参数,不可能每改一次就拉一波人来听。这就催生了一类数学模型——用算法模拟人耳的听觉过程,输入原始语音和退化语音,输出一个可重复的质量分数,这就是客观语音质量评价。

PESQ是其中最为经典的一个,由ITU-T在2001年发布为P.862标准,专门面向窄带电话语音(300Hz到3400Hz)。它把“参考信号”和“退化信号”同时送入一个仿生模型,内部按人耳的听觉机制处理,最后输出一个预测的MOS分数。PESQ的好处在于它和主观MOS的相关性很高,尤其在语音编码、网络传输、回声消除等场景下,实验结果和真实听感高度一致。后来改进的宽带版本P.862.2(WB-PESQ)扩展到了50Hz至7000Hz,再后来出现了性能更强的POLQA(P.863),但目前工业界和学术界用PESQ做基线对比的仍然最多。

1.2 PESQ算法的核心流程拆解

PESQ并不是一个简单的“误差计算器”。如果你只用一句“算一下原始信号和处理信号之间的差异”来理解它,那后面读代码时会无从下手。它内部是一条完整的感知处理流水线,我按执行顺序整理成了如下几步:

第一步,电平归一化。参考信号和退化信号先各自做增益调整,确保两者在响度上处于同一水平。这一步很关键,因为很多语音算法会在处理过程中改变整体响度,如果不归一化,最终得分会异常偏低。PESQ内部计算了一个“补偿增益”,用一段包含语音活动的区域做估计。

第二步,带通滤波。PESQ里内置了一个模拟电话听筒的滤波器,用于把信号限制在电话频段范围。即便你输入的语音是16kHz采样率,这个滤波器也会把8000Hz以上成分基本滤掉,目的是让模型专注在人耳对电话语音的敏感频率区域。

第三步,时间对齐。退化信号相对参考信号通常存在延迟(比如编码算法引入的算法延迟,或网络传输造成的时延),PESQ会把两个信号切成分块,用包络互相关的方法估计出局部延迟,再对退化信号做对齐补偿。这一步直接影响最终分数,如果时间对齐失败,PESQ会误解“时间偏移”为“严重失真”,分数会异常低。

第四步,感知模型计算。对齐后的两个信号分别做短时傅里叶变换,然后在Bark域(巴克尺度,一种模拟人耳频率分辨率特性的非线性频率尺度)上将频谱映射为响度密度,两路信号在该域相减得到扰动密度。PESQ把扰动分成两类:加性扰动(additive disturbance,类似噪声叠加)和乘性扰动(multiplicative disturbance,类似频谱包络变化),再引入非对称处理来惩罚“听起来难受的那些失真”,最后在时间与频率两个维度上做聚合,得到最终的PESQ得分。

整个模型本质上是在回答一个问题:“这段退化语音和人耳记忆中对应的参考语音之间的感知差异有多大”。PESQ输出的分值范围是-0.5到4.5,应用中通常映射到0到5的MOS-LQO区间,数值越大表示质量越好。4.5以上几乎是无损,3.5到4.5是较好质量,低于3.0往往意味着能明显感知到的染色或失真。

1.3 PESQ在算法评估中的定位和边界

我用PESQ评估过编码器、降噪算法、回声消除模块,也帮别人排查过“为什么PESQ分数和听感不符”的问题。这里必须先说清楚它的边界:PESQ对线性或近似线性的系统(如语音编码、电平调整)表现很好,但对强非线性处理(如丢包补偿后的波形合成、某些大强度噪声抑制)预测能力会下降。它专注的是“语音质量”,不是“可懂度”。一段处理后的语音即使每个字都听不清,只要听感和参考接近,PESQ分数也可能不低。做评估时最好同时跑PESQ、STOI(短时客观可懂度)和MOS试听,三者配合来看,比单看任何一个分数都可靠得多。

2. Matlab跑PESQ的方案选型:三条路怎么选

2.1 方案一:ITU-T官方参考代码编译MEX

PESQ的标准参考实现是C语言代码包(P.862),可以从ITU-T官网获取。这个包里包含完整的算法实现,但要在Matlab中使用需要自己编写MEX包装。

将C代码编译成MEX的大致步骤是:

# 1. 将官方源文件解压到本地目录,目录里包含pesqmain.c、pesq.h等源文件 # 2. 在Matlab中执行: mex -setup # 选择已安装的C编译器 mex pesqmain.c pesqmod.c dsp.c -output pesq_mex

编译完成后,在Matlab中以pesq_mex(ref_wav, deg_wav, samplerate)方式调用。因为C代码直接实现P.862标准,结果完全对齐标准算法,没有任何“黑盒”问题。缺点是编译有门槛,在Windows上需要提前装好MinGW-w64或Visual Studio的C编译器,而且官方代码风格比较“上世纪”,变量名难懂,想改内部结构并不容易。

2.2 方案二:Matlab Audio Toolbox自带pesq函数

从R2021a开始,Matlab的Audio Toolbox里直接内置了pesq函数(之前某些版本只有第三方实现)。用法极其简单:

score = pesq(ref, deg, fs);

其中refdeg是列向量形式的语音信号,fs是采样率(支持8000或16000)。函数会返回两个值:pesq_mos(窄带MOS-LQO分)和mos_lqo(映射后的MOS-LQO分)——实际返回值版本有差异,建议在命令行里先输出查看。

Audio Toolbox的好处是省去了编译环节,输入输出都做了标准接口封装,代码量最少,适合快速验证和批量测试。缺点是对Toolbox版本有依赖,旧版Matlab没有这个函数;而且内部实现可能和最新的P.863等标准有差异,如果做算法研究,一般建议以基准数据为准,两个方案互相印证。

2.3 方案三:第三方Python/MATLAB重实现

GitHub上还有不少开源的Python重实现(如pesq库)以及一些第三方Matlab工具包。这套路子的优点在于代码阅读体验好,注释清晰,方便学习算法细节;缺点也是明显的——重实现的质量参差不齐,有的在边界条件下(比如极端短语音、静音开头)计算结果和标准不一致。我的建议是:做算法预研时可以借助它们理解原理,但正式实验报告和论文里的数据,尽量用官方实现或Matlab内置版本出。

2.4 三个方案的对比与选型建议

方案优点缺点适用场景
官方C代码+MEX完全对齐标准、可定制内部模块编译复杂、代码可读性差学术引用、需要修改算法内部结构
Audio Toolbox内置pesq零编译、调用方便、接口干净依赖版本、内部黑盒日常评估、批量处理、快速验证
第三方重实现可读性好、学习友好标准一致性风险、边界情况不可控算法学习、原型验证

如果让我选,个人建议是:电脑上装了较新的Matlab(R2021a以后)就用内置pesq,完全够用;如果要做严谨的算法对比或论文实验,再额外编译一份官方代码互相验证。两边的差异如果很小(0.1分以内),说明结果可信;如果差异很大,优先查输入信号的预处理是否一致。

3. 实操演示:在Matlab里完整跑通一个PESQ评估

3.1 准备测试语音

要从“用PESQ评价语音质量”这步开始,先得有一对语音文件:一个参考文件(原始干净语音),一个退化文件(经过编解码、降噪或网络传输后的语音)。我习惯用NTT的ITU-T测试语音集,或者自己录几段长句子,保证内容一致但包含不同说话人。

文件准备好后在Matlab中读取:

[ref, fs1] = audioread('reference.wav'); [deg, fs2] = audioread('degraded.wav');

这里有一个经常被忽略的细节:audioread读出的数据是归一化到[-1, 1]的浮点数,PESQ内部会做电平调整,所以不需要手动归一化。但采样率必须明确,如果两个文件的采样率不一致,要先用resample统一。

% 统一采样率到16kHz(如果原始是8kHz建议重采样到16kHz) if fs1 > fs2 ref = resample(ref, fs1, fs2); else deg = resample(deg, fs2, fs1); end fs = max(fs1, fs2);

注意resample的用法第一个参数是目标频率、第二个是原频率,别写反了。

3.2 调用PESQ并获取分数

如果用内置函数,核心调用就一行:

% 方式1:Audio Toolbox内置pesq score = pesq(ref, deg, fs);

输出结果类似:

pesq_mos = 3.4210 mos_lqo = 3.4210

但如果用的是官方C编译的MEX版,调用方式则可能是:

% 方式2:官方代码包编译后的调用 score = pesq_mex('reference.wav', 'degraded.wav', 16000);

官方MEX直接传文件路径,内部自己读文件,采样率参数单独传入。这种方式省去了在Matlab里读取音频的步骤,但要求文件路径没有中文,且必须和调用时指定的采样率一致。

比较关键的一点:两个信号的长度不必完全一致,PESQ内部会做时间对齐,但如果长度差异过大(比如参考3秒,退化只有1秒),时间对齐会失效,最终得分基本没有参考价值。我一般会保证退化信号长度不小于参考信号的50%,并尽量让语言内容完整覆盖。

3.3 批量处理多条语音,形成评测报告

单挑一条语音跑分意义有限,实际项目中总是批量处理几十条语音,然后求平均。这里分享一个我自己用的批量脚本骨架:

% 批量计算PESQ分数 refDir = './data/ref/'; degDir = './data/deg/'; fileList = dir(fullfile(refDir, '*.wav')); scores = zeros(length(fileList), 1); for i = 1:length(fileList) fileName = fileList(i).name; [ref, fs1] = audioread(fullfile(refDir, fileName)); [deg, fs2] = audioread(fullfile(degDir, fileName)); if fs1 ~= fs2 warning('采样率不一致: %s', fileName); end fs = min(fs1, fs2); ref = resample(ref, fs, fs1); deg = resample(deg, fs, fs2); scores(i) = pesq(ref, deg, fs); end avgScore = mean(scores); disp(['平均PESQ: ', num2str(avgScore)]);

跑出来的分数可以按不同条件分组做统计,比如不同码率、不同丢包率,画成折线图就能直观看出算法在哪些区间退化严重。这里我特别提醒:批量处理时先确认所有参考和退化文件一一对应,不然中间混了对文件,平均值会被严重拉偏。

4. 实战中踩过的坑:PESQ使用避坑指南

4.1 采样率、声道与长度对齐问题

PESQ最常遇到的问题首先就是采样率不匹配。P.862标准只支持8kHz和16kHz两种采样率输入。有的项目录音是48kHz或44.1kHz,直接送进PESQ基本是灾难性的结果。正确做法是先转成16kHz——虽然标准电话是8kHz,但16kHz能保留更多语音细节,和实际VoIP场景更贴近。另外,PESQ只处理单声道,如果输入是双声道立体声,需要先取单声道,否则会出现“相位抵消”式的错误,分数完全失真。

长度对齐问题前面提过,再补充一个细节:如果参考语音开头有明显静音,PESQ在时间对齐时会把静音段优先对齐,导致后面的语音内容整体错位。解决办法是在预处理阶段就做语音活动检测(VAD),把首尾静音裁掉再送进PESQ。

4.2 “分数和听感不符”的原因排查

遇到PESQ分数和主观试听差异大的情况,我通常按这个顺序排查:

  1. 两路信号是否经过了同一种滤波器处理。比如参考信号是原始录音,退化信号经过了某种高保真渲染,但PESQ内部默认的“电话听筒”滤波器会把高频部分直接滤掉,这种情况下PESQ会认为退化信号“和参考没有太大差别”。这其实是所有标准客观评价方法的固有问题——它们的训练数据集决定了它们对某些失真不敏感。

  2. 退化信号是否带有添加的噪声。PESQ的加性扰动模型对平稳背景噪声很敏感,但对“音乐噪声”这类结构化噪声惩罚不足。如果你在做的算法是深度语音增强,输出的音乐噪声可能听感很刺耳,但PESQ分数却不一定低。这时候一定要配STOI或主观试听一起分析。

  3. 时间对齐是否失效。典型的症状是:两路信号明明听起来内容相同,只是开头差了0.5秒,PESQ却给出2分以下。这种时候检查一下退化信号相对参考是否存在明显时延,有的话先手动裁齐。

4.3 MEX编译和跨平台问题

我最初在Windows上编译官方C代码时也折腾过一阵。官方代码包里有几个.c文件直接相互引用,编译时要一起编译进去,漏一个就报链接错误。另外,官方代码里用了mallocfree,在MEX环境下需要记得在mexFunction里正确管理内存。

更隐蔽的一个坑是:某些官方版本代码使用了C89风格,而新版Visual Studio默认按C11编译,某些语法会报warning甚至error。我当时的处理方法是把pesqmain.c用文本编辑器打开,把所有void函数声明改成带显式参数的形式,再重新编译。这种事没有标准答案,就是“遇到错就搜、搜到就改”的体力活。

如果不想碰编译,另一个稳妥路线是直接用Matlab的Audio Toolbox内置版本,但要注意版本差异——R2021a之前的版本没有pesq函数,需要自己找第三方实现。我手头到现在还留着R2020b时代用第三方函数库跑出来的结果,和后来用内置函数跑的结果对比,平均有0.1分左右的偏差。写论文时如果前后用不同版本跑了数据,实验记录一定要写清楚。

4.4 解读分数时最容易犯的错误

很多刚用PESQ的同学会陷入“精确数字”的陷阱:算法A跑出来3.42,算法B跑出来3.47,就断言B比A好。但PESQ分数本身有统计波动,直接用平均值比较并不稳妥。正规做法是准备多个语料(至少二三十条),分别计算每个算法的PESQ分数分布,再做配对t检验或Wilcoxon符号秩检验,确定差异是不是统计显著的。

另外,PESQ的绝对数值在不同数据集上不可直接比较。同样的编解码器,在英文语音集上跑出3.8分,在中文语音集上可能只有3.5分——这不是算法变差了,而是语音内容、说话人、录制环境都不一致。比较算法优劣时,要保证同一批语料、同一处理流程、同一PESQ实现版本,这样分数才有可比性。这个问题在语音增强领域论文里特别常见,审稿人一看你用不同的测试集直接横向比较分数,基本会抓住不放。

5. 个人实操中的几点体会

PESQ用久了你会发现,它更像一把“固定刻度的尺子”,而不是“能感知音质的耳朵”。它能帮你从几十个候选参数中筛出哪几个方向值得继续做,但永远不要只靠它做最终决策。我个人的习惯是:每次评审算法效果时,先跑PESQ做粗筛,跑STOI看可懂度,最后抽几条典型样本自己听一遍,三者都过了才会把改动合入主分支。

最后分享一个小技巧:在Matlab里处理PESQ时,把参考信号和退化信号画在一张图上看波形对齐情况,比直接看分数更有用。具体做法是用plot把两个波形上下错开画出,如果波形形状大体一致但明显错位,说明时间对齐有问题;如果波形形状差异大但PESQ分数还不错,说明失真主要影响的是感知上不敏感的部分。这一步花不了几秒钟,但能帮你省掉大量排错时间。PESQ作为一个已经被广泛验证的客观指标,结合合理的实验设计和个人听感判断,仍然是我在语音算法评估中最常用的第一道关卡。

本文还有配套的精品资源,点击获取

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

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

立即咨询