音频进阶学习十九——逆系统(简单进行回声消除)

news/2025/3/11 6:47:56/

文章目录

  • 前言
  • 一、可逆系统
    • 1.定义
    • 2.解卷积
    • 3.逆系统恢复原始信号过程
    • 4.逆系统与原系统的零极点关系
  • 二、使用逆系统去除回声
    • 获取原信号的频谱
    • 原系统和逆系统幅频响应和相频响应
    • 使用逆系统恢复原始信号
    • 整体代码如下
  • 总结


前言

在上一篇音频进阶学习十八——幅频响应相同系统、全通系统、最小相位系统文章中,我们在最小相位系统中提到了逆系统:一个稳定因果的LTI系统,同时会有一个稳定因果的逆系统。

本文中将会介绍逆系统的含义,通过对于零极点的分析,来对比逆系统和原系统的关联。最后,将会使用逆系统来对于给定的近端信号和参考信号来进行回声消除。

|版本声明:山河君,未经博主允许,禁止转载


一、可逆系统

1.定义

如果一个系统的输入是 x [ n ] x[n] x[n],经过系统 H ( z ) H(z) H(z)后系统的输出是 y [ n ] y[n] y[n],那么将 y [ n ] y[n] y[n]通过系统 G ( z ) G(z) G(z)后输出的是 x [ n ] x[n] x[n],此时系统 G ( z ) G(z) G(z)是系统 H ( z ) H(z) H(z)的逆系统。

G ( z ) G(z) G(z) H ( z ) H(z) H(z)的关系:

  • 频域关系
    G ( z ) H ( z ) = 1 = > g [ n ] = h [ n ] G(z)H(z)=1=>g[n]=h[n] G(z)H(z)=1=>g[n]=h[n]
  • 时域关系
    对于系统 H H H的脉冲响应有: y [ n ] = x [ n ] ∗ h [ n ] y[n]=x[n]*h[n] y[n]=x[n]h[n]
    对于系统 G G G的脉冲响应有: x ′ [ n ] = y [ n ] ∗ g [ n ] = ( x [ n ] ∗ h [ n ] ) ∗ g [ n ] = x [ n ] ∗ ( h [ n ] ∗ g [ n ] ) x'[n]=y[n]*g[n]=(x[n]*h[n])*g[n]=x[n] *(h[n]*g[n]) x[n]=y[n]g[n]=(x[n]h[n])g[n]=x[n](h[n]g[n])
    如果想要 x ′ [ n ] = x [ n ] x'[n]=x[n] x[n]=x[n],那么 h [ n ] ∗ g [ n ] = δ n h[n]*g[n]=\delta n h[n]g[n]=δn

2.解卷积

对于给定的输入信号 x [ n ] x[n] x[n]和输出信号 y [ n ] y[n] y[n],我们知道通过系统有:
X ( f ) = F { x [ n ] } , Y ( f ) = F { x [ n ] } , H ( f ) = X ( f ) Y ( f ) X(f)=F\{x[n]\},Y(f)=F\{x[n]\},H(f)=\frac{X(f)}{Y(f)} X(f)=F{x[n]},Y(f)=F{x[n]},H(f)=Y(f)X(f)
通过逆变换, h [ n ] = F − 1 { H ( f ) } h[n]=F^{-1}\{H(f)\} h[n]=F1{H(f)}得到冲激响应的这一过程,叫做解卷积

3.逆系统恢复原始信号过程

首先结合具体场景,假设发射了一个信号 x [ n ] x[n] x[n],通过多条途径例如墙体、窗户的反射,导致接收的信号是 y [ n ] y[n] y[n],我们想通过 y [ n ] y[n] y[n]恢复为原始的 x [ n ] x[n] x[n],如下图:
在这里插入图片描述

  • 发射约定好的信号 T [ n ] T[n] T[n]
  • 接收 T ′ [ n ] = T [ n ] ∗ h [ n ] T'[n]=T[n]*h[n] T[n]=T[n]h[n]
  • 通过解卷积得到 h [ n ] h[n] h[n]
  • 构建逆系统 g [ n ] g[n] g[n]
  • 通过逆系统获得原始信号 x [ n ] = y [ n ] ∗ G [ n ] x[n]=y[n]*G[n] x[n]=y[n]G[n]

值得注意的是,在实际场景中,过程会比较复杂,例如不存在发射约定好的信号,或者场景变换导致冲激响应的变化等等,这就需要一个自适应滤波器不断的估计回声信号,当然这是以后的内容。

4.逆系统与原系统的零极点关系

对于Z变换的频率响应,它的逆系统
H ( z ) = ∏ m = 1 M ( 1 − c m z − 1 ) ∏ n = 1 N ( 1 − d n z − 1 ) , G ( z ) = ∏ n = 1 N ( 1 − d n z − 1 ) ∏ m = 1 M ( 1 − c m z − 1 ) H(z)=\frac{\prod_{m=1}^M(1-c_mz^{-1})}{\prod_{n=1}^N(1-d_nz^{-1})},G(z)=\frac{\prod_{n=1}^N(1-d_nz^{-1})}{\prod_{m=1}^M(1-c_mz^{-1})} H(z)=n=1N(1dnz1)m=1M(1cmz1),G(z)=m=1M(1cmz1)n=1N(1dnz1)
也就是原系统 H ( z ) H(z) H(z)和逆系统 G ( z ) G(z) G(z)的零极点是互换的,那么此时如果逆系统 G ( z ) G(z) G(z)是因果稳定的,那么逆系统 G ( z ) G(z) G(z)的零点和极点都在单位圆内,而此时原系统的 H ( z ) H(z) H(z)零点和极点同样都在单位圆内,此时都为最小相位系统。

这也是上一篇文章中最小相位系统提到的特性。

二、使用逆系统去除回声

现在使用matlab来设计一个逆系统,对于已知的输入信号和输出信号进行转换。

获取原信号的频谱

需要注意的是,这里的输入信号和输出信号是笔者自己通过pcm叠加转换的,所以频域上可能看不出特别大的区别,但是实际可以明显听到回声。

核心代码

%输入信号和输出信号频域图
H_in=fft(input_signal);
H_out=fft(output_signal);
R_in = abs(H_in);
R_out = abs(H_out);
freq=linspace(0,fs/2, floor(min_len/2));figure;
subplot(2,1,1);
plot(freq, R_in(1:floor(min_len/2)));
title("输入信号频域图");xlabel("频率H(z)");ylabel("幅度");grid on;
subplot(2,1,2);
plot(freq, R_out(1:floor(min_len/2)));
title("输出信号频域图");xlabel("频率H(z)");ylabel("幅度");grid on;

得到结果:
在这里插入图片描述

原系统和逆系统幅频响应和相频响应

通过图像幅频响应图像和相频响应的图像,可以很清楚的看到原系统和逆系统的幅频响应和相频响应是倒过来的。

同时值得注意的是:

  • 某些频率上幅值接近零(即原系统在这些频率上有很强的衰减),那么其倒数 H i n v H_{inv} Hinv可能会变得非常大,导致放大效应。
  • 在计算 H i n v H_{inv} Hinv浮点数精度问题可能会导致不稳定。
  • 幅度部分可能被正常恢复,但相位的误差会导致合成信号在时域上发生相干叠加,造成某些时刻的峰值更大,最终影响整体信号能量。

而这些问题需要结合窗口平滑来进行解决,这里是通过增加了一些阈值来减少极端值的出现。

代码如下:

H_c = H_in ./ (H_out + eps);
%H_inv= 1 ./ (H_c + eps);
%H_inv(abs(H_c) < 1e-3) = 0;
H_inv = 1 ./ (H_c + 0.01);
H_inv(abs(H_c) < 1e-2) = 0;
R_c=abs(H_c);
R_inv=abs(H_inv);figure;
subplot(2,2,1);
plot(freq, 20*log10(R_c(1:floor(min_len/2))));
title('原系统幅频响应');xlabel('频率 (Hz)'); ylabel('幅度 (dB)'); grid on;
subplot(2,2,2);
plot(freq, rad2deg(angle(H_c(1:floor(min_len/2)))));
title('原系统相频响应');xlabel('频率 (Hz)'); ylabel('相位(度)'); grid on;
subplot(2,2,3);
plot(freq, 20*log10(R_inv(1:floor(min_len/2))));
title('逆系统幅频响应');xlabel('频率 (Hz)'); ylabel('幅度 (dB)'); grid on;
subplot(2,2,4);
plot(freq, rad2deg(angle(H_inv(1:floor(min_len/2)))));
title('逆系统相频响应');xlabel('频率 (Hz)'); ylabel('相位(度)'); grid on;

得到
在这里插入图片描述

使用逆系统恢复原始信号

最后恢复的信号还是变大了,但是此时已经听不见回声了,这里在恢复时已经使用整体能量归一化来尽量减少能量放大的影响。

代码如下:

%还原后的信号频谱
restored_signal = real(ifft(fft(output_signal) .* H_inv));
restored_signal = restored_signal / norm(restored_signal) * norm(input_signal);restored_signal = int16(restored_signal);
fwrite(turn_file, restored_signal, 'int16');
fclose(turn_file);
H_restored = fft(restored_signal);
R_res = abs(H_restored);
figure;
subplot(1,1,1);
plot(freq, R_res(1:floor(min_len/2)));
title('还原信号的频域图');
xlabel('频率 (Hz)'); ylabel('幅度'); grid on;

得到
在这里插入图片描述

整体代码如下

fs=16000;
input_file=fopen("input.pcm",'rb');
output_file=fopen("output.pcm",'rb');
turn_file=fopen("turn.pcm",'wb+');input_signal=fread(input_file, inf, 'int16');
output_signal=fread(output_file, inf, 'int16');
fclose(input_file);
fclose(output_file);min_len=min(length(input_signal), length(output_signal));
input_signal=input_signal(1:min_len);
output_signal=output_signal(1:min_len);%输入信号和输出信号频域图
H_in=fft(input_signal);
H_out=fft(output_signal);
R_in = abs(H_in);
R_out = abs(H_out);
freq=linspace(0,fs/2, floor(min_len/2));figure;
subplot(2,1,1);
plot(freq, R_in(1:floor(min_len/2)));
title("输入信号频域图");xlabel("频率H(z)");ylabel("幅度");grid on;
subplot(2,1,2);
plot(freq, R_out(1:floor(min_len/2)));
title("输入信号相频响应");xlabel("频率H(z)");ylabel("幅度");grid on;%原系统和逆系统幅频响应和相频响应
H_c = H_in ./ (H_out + eps);
%H_inv= 1 ./ (H_c + eps);
%H_inv(abs(H_c) < 1e-3) = 0;
H_inv = 1 ./ (H_c + 0.01);
H_inv(abs(H_c) < 1e-2) = 0;
R_c=abs(H_c);
R_inv=abs(H_inv);figure;
subplot(2,2,1);
plot(freq, 20*log10(R_c(1:floor(min_len/2))));
title('原系统幅频响应');xlabel('频率 (Hz)'); ylabel('幅度 (dB)'); grid on;
subplot(2,2,2);
plot(freq, rad2deg(angle(H_c(1:floor(min_len/2)))));
title('原系统相频响应');xlabel('频率 (Hz)'); ylabel('相位(度)'); grid on;
subplot(2,2,3);
plot(freq, 20*log10(R_inv(1:floor(min_len/2))));
title('逆系统幅频响应');xlabel('频率 (Hz)'); ylabel('幅度 (dB)'); grid on;
subplot(2,2,4);
plot(freq, rad2deg(angle(H_inv(1:floor(min_len/2)))));
title('逆系统相频响应');xlabel('频率 (Hz)'); ylabel('相位(度)'); grid on;%还原后的信号频谱
restored_signal = real(ifft(fft(output_signal) .* H_inv));
restored_signal = restored_signal / norm(restored_signal) * norm(input_signal);restored_signal = int16(restored_signal);
fwrite(turn_file, restored_signal, 'int16');
fclose(turn_file);
H_restored = fft(restored_signal);
R_res = abs(H_restored);
figure;
subplot(1,1,1);
plot(freq, R_res(1:floor(min_len/2)));
title('还原信号的频域图');
xlabel('频率 (Hz)'); ylabel('幅度'); grid on;

总结

本文中所使用的逆系统是基于原系统是一个最小相位系统来构建的。那如果原系统不是最小相位系统是存在一个不会发散的逆系统呢?其实答案已经在上一篇文章中给出了,那就是使用全通分解,去除零极点在单位圆外的影响。只使用全通分解中对于最小相位系统的部分。

其次,值得注意的是,本文中的例子是给定了参考信号来进行回声消除,但在实际环境中,对于参考信号如何辨别是否是回声,这需要使用自适应滤波器来进行回声建模。

反正收藏也不会看,不如点个赞吧!


http://www.ppmy.cn/news/1578276.html

相关文章

docker无法pull镜像问题解决for win10

docker无法pull镜像问题解决for win10 问题原因分析解决方法 问题 在win10系统上安装好doker-desktop后ping registry-1.docker.io不同&#xff0c;并且也无法登陆hub.docker.com, 使用docker pull xx也无法正常下载 原因分析 hub.docker.com在2024年5月之后&#xff0c;国内…

【含文档+PPT+源码】基于微信小程序的乡村振兴民宿管理系统

项目介绍 本课程演示的是一款基于微信小程序的乡村振兴民宿管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3.该…

009---基于Verilog HDL的单比特信号边沿检测

文章目录 摘要一、边沿检测二、时序逻辑实现2.1 rtl2.2 tb 三、组合逻辑实现3.1 rtl3.2 tb 摘要 文章为学习记录。采用时序逻辑和组合逻辑实现边沿检测的核心逻辑。组合逻辑实现的上升沿和下降沿的脉冲比时序逻辑实现的上升沿和下降沿的脉冲提前一拍。 一、边沿检测 边沿检测…

ThreadLocal源码剖析

文章目录 四种引用的概念Entry的类定义弱引用和内存泄漏如果key使用强引用如果key使用弱引用为什么使用弱引用 hash冲突的解决ThreadLocalMap中的set方法演示垃圾回收get触发GCset触发GC ThreadLocal-内存清理探测式清理&#xff08;ExpungeStaleEntry&#xff09;启发式清理&a…

图形编辑器基于Paper.js教程24:图像转gcode的重构,元素翻转,旋转

前段时间在雕刻图片时&#xff0c;旋转图片&#xff0c;翻转图片后&#xff0c;发现生成准确的gcode&#xff0c;虽然尺寸对&#xff0c;但是都是以没有旋转&#xff0c;没有翻转的图片进行生成的。后来思考了一下&#xff0c;发现这真是一个大bug&#xff0c;无论图片如何选择…

FreeRTOS第17篇:FreeRTOS链表实现细节05_MiniListItem_t:FreeRTOS内存优化

文/指尖动听知识库-星愿 文章为付费内容,商业行为,禁止私自转载及抄袭,违者必究!!! 文章专栏:深入FreeRTOS内核:从原理到实战的嵌入式开发指南 1 为什么需要迷你列表项? 在嵌入式系统中,内存资源极其宝贵。FreeRTOS为满足不同场景需求,设计了标准列表项(ListItem_…

C++后端服务器开发技术栈有哪些?有哪些资源或开源库拿来用?

一、 C后台服务器开发是一个涉及多方面技术选择的复杂领域&#xff0c;特别是在高性能、高并发的场景下。以下是C后台服务器开发的一种常见技术路线&#xff0c;涵盖了从基础到高级的技术栈。 1. 基础技术栈 C标准库 C11/C14/C17/C20&#xff1a;使用现代C特性&#xff0c;如…

Linux系统编程--线程同步

目录 一、前言 二、线程饥饿 三、线程同步 四、条件变量 1、cond 2、条件变量的使用 五、条件变量与互斥锁 一、前言 上篇文章我们讲解了线程互斥的概念&#xff0c;为了防止多个线程同时访问一份临界资源而出问题&#xff0c;我们引入了线程互斥&#xff0c;线程互斥其实…