时间采样

举报
tsinghuazhuoqing 发表于 2021/12/27 00:07:24 2021/12/27
【摘要】 近期在信号与系统课程讲完了“信号的采样与恢复”的内容。通常情况下对于信号的采样都是沿着时间轴对信号的幅值进行采样,获得信号的离散时间点上的数据。 如果将信号 ...

近期在信号与系统课程讲完了“信号的采样与恢复”的内容。通常情况下对于信号的采样都是沿着时间轴对信号的幅值进行采样,获得信号的离散时间点上的数据。

如果将信号 s ( t ) s\left( t \right) s(t)的波形绘制在直接坐标系中,那么该曲线就是分布在二维空间上的曲线。曲线上的点可以沿着时间 t t t轴进行排列,当然也可以按照幅值 s ( t ) s\left( t \right) s(t)的大小进行排列。也就是按照取值间隔,将信号通过该间隔的时间进行保存,这就是对信号的时间采样。
▲ 声音的时间波形

▲ 声音的时间波形

沿着时间轴对信号的幅值进行采样,Nyquist-Shannon定理告诉我们如何进行采样和如何进行信号恢复。但是,如何对信号时间进行采样,如何恢复是一个经典的未解决问题。

Logan定理[Logan,Jr.,1977]对一种特殊信号给出了采样与恢复的描述:如果一个信号的频谱具有倍频的性质,即信号频谱分布在一个频率范围内,最高频率是最低频率的两倍。那么这个信号可以通过它的过零点的时间值进行恢复。恢复的信号与原始信号仅仅相差一个比例因子。

信号采样与恢复

1. 幅度采样

下面是一段普通语音信号的复制采样波形。通过简单的DA转换和低通滤波便可以恢复出原始的声音波形。
▲ 幅度采集声音波形

▲ 幅度采集声音波形

2. 时间采样

如果仅仅保留该信号的过零时间点的信息,它的幅值全部去掉,所形成的波形大体上如下图所示。这很像将原来的语音信号通过一个过零点比较器,输出的信号反映了信号的极性。
声音信号过零点采样

▲ 声音信号过零点采样

当然,这个信号中包含有原来信号的部分信息。但它并不是原来语音信号的完美的回复。

尽管如此,播放这个信号,还是可以听到原来语音的信息。虽然有很大的失真和噪声。这说明原来的信号信息还是部分保留在这些过零点中。

如果语音信号满足Logan定理的要求,那么理论上是可以恢复出原来的信号的。但如何来恢复?

如果信号本身是一个周期信号,也就是信号的频谱是离散的频谱。相对回复信号的算法比较简单。Sam Roweis等人在论文“Signal Reconstruction from Zero-Crossings"中给出了通过求解数据矩阵零空间向量的方法,来通过信号的过零时间点来重构信号的方法。

 

重建算法

1. 基本原理

已知到信号 s ( t ) s\left( t \right) s(t)具有倍频窄带频谱,它的频率范围分布在 [ f min ⁡ , 2 f min ⁡ ] \left[ {f_{\min } ,2f_{\min } } \right] [fmin,2fmin]范围内。
▲ 倍频信号频谱示意图

▲ 倍频信号频谱示意图

已知信号的周期 T T T f min ⁡ f_{\min } fmin,以及 N N N个信号过零点: Z t = { t 1 , . . . , t j , . . . , t N } Z_t = \left\{ {t_1 ,...,t_j ,...,t_N } \right\} Zt={t1,...,tj,...,tN}

重构信号的计算步骤如下:
(1)计算相关参数: f 0 = 1 / T f_0 = 1/T f0=1/T以及 f k , k = 1... M f_k ,k = 1...M fk,k=1...M
(2)构造矩阵: X j k = 2 π t j f k X_{jk} = 2\pi t_j f_k Xjk=2πtjfk
(3)寻找数据矩阵零空间向量:
构造数据矩阵 [ cos ⁡ ( X ) , sin ⁡ ( X ) ] \left[ {\cos \left( X \right),\sin \left( X \right)} \right] [cos(X),sin(X)],矩阵大小是 N × 2 M N \times 2M N×2M。对该矩阵进行奇异值分解,得到 U , V , σ U,V,\sigma U,V,σ。其中 U U U R N R^N RN空间上的正交矩阵, V V V R 2 M R^{2M} R2M空间上的正交矩阵。 σ \sigma σ是奇异值向量,长度为 min ⁡ ( N , 2 M ) \min \left( {N,2M} \right) min(N,2M)

零空间向量是奇异值向量 σ \sigma σ中最小(理论上应该为0,但由于计算误差的存在,它可能是一个很小的数)对应的 V V V中的向量,由于SVD算法往往把结果 σ \sigma σ按照绝对值从大到小排列,所以 V V V的最后一个向量就对应着数据矩阵的零空间向量。

(4)构造信号函数:
将数据空间矩阵中零空间向量前 M M M个数值当做 a ^ k \hat a_k a^k,后 M M M个数值当做 b ^ k \hat b_k b^k,重建信号公式为: s ^ ( t ) = ∑ k = 1 M [ a ^ k cos ⁡ ( 2 π f k t ) + b ^ k sin ⁡ ( 2 π f k t ) ] \hat s\left( t \right) = \sum\limits_{k = 1}^M {\left[ {\hat a_k \cos \left( {2\pi f_k t} \right) + \hat b_k \sin \left( {2\pi f_k t} \right)} \right]} s^(t)=k=1M[a^kcos(2πfkt)+b^ksin(2πfkt)]

这种回复信号的过程,实际上就是根据信号的过零点来求解上面的函数中的参数。具体的理论分析在这里就不再展开了。

2. 测试函数

(1)实验信号的数学表达式:

选择一个频率分布在10Hz到20Hz之间的一个信号进行实验,随机指定对应的cos,sin信号的系数,如下:
▲ 信号的数学表达式

▲ 信号的数学表达式

这是一个周期为1的倍频信号。

(2)信号的产生Python程序:

使用下面python程序,可以产生该信号的数据。也可以通过该函数完搜索信号的过零点。

def sfunc1(x):
    pi2 = 2 * pi
    retdata  = cos(pi2*11*x) + sin(pi2*11*x)/2 + \
             cos(pi2*12*x)/33 + sin(pi2*12*x)/4 + \
             cos(pi2*13*x) + sin(pi2*13*x)/8 + \
             cos(pi2*14*x) + sin(pi2*14*x)/7 + \
             cos(pi2*15*x)/22 + sin(pi2*15*x)/3 +\
             cos(pi2*16*x) + sin(pi2*16*x)/12 +\
             cos(pi2*17*x) + sin(pi2*17*x)/40 +\
             cos(pi2*18*x) + sin(pi2*18*x)/2 +\
             cos(pi2*19*x)/3 + sin(pi2*19*x)/2

    return retdata

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

(3)实验信号的波形:

下面绘制出0~2秒两个周期内的波形。
▲ 测试函数sfunc1信号波形

▲ 测试函数sfunc1信号波形

绘制该信号的过零点饱和信号,它仅仅保留了该信号的过零点的时间和相位信息。计算公式为: s 1 ( t ) = s g n [ s ( t ) ] × 2 − 1 s_1 \left( t \right) = {\mathop{\rm sgn}} \left[ {s\left( t \right)} \right] \times 2 - 1 s1(t)=sgn[s(t)]×21

▲ 该信号的幅值饱和信号

▲ 该信号的幅值饱和信号

(4)信号的过零点:

通过数值计算,来获得信号的过零点。下面重新绘制出信号一个周期内的波形,没有添加任何噪声。

▲ 一个周期(0~1)之间的信号波形

▲ 一个周期(0~1)之间的信号波形

通过对区间(0,1)采集106个数值,然后通过寻找过零点,获得二十八个信号的过零点的值:

[0.020366020366020365, 0.05264605264605264, 0.08225308225308224, 0.10514510514510514, 0.12902412902412902, 0.15825015825015823, 0.18724018724018723, 0.2127262127262127, 0.23897923897923895, 0.2701412701412701, 0.3053293053293053, 0.34866534866534865, 0.3915033915033915, 0.4291844291844291, 0.46993546993546986, 0.5351945351945352, 0.5752205752205751, 0.6136616136616136, 0.6501956501956502, 0.6852306852306852, 0.717056717056717, 0.7439707439707439, 0.7804877804877804, 0.8177278177278177, 0.8532438532438532, 0.9194179194179193, 0.9536469536469535, 0.9872359872359872]

搜寻函数值过零点的python程序如下crosszero(t,val)。其中 t t t是函数的自变量, v a l val val是函数值的采样。函数返回是对应函数过零点时的 t t t的数值。

def crosszero(t, val):
    valsign = sign(val)
    valsignchange = [int(x!=y) for x,y in zip(valsign[0:-1],valsign[1:])]
    tvalue = [(x,y) for x,y in zip(t[0:-1], valsignchange)]
    zerot = filter(lambda t: t[1]!= 0, tvalue)

    return [zt[0] for zt in zerot]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

通过scipy.optimize.root来寻找信号的根,用于确定信号的过零点。利用上面搜索的结果作为初始值。

sol = scipy.optimize.root(tssub.sfunc1, czt, method='lm')

  
 
  • 1
  • tssub.sfunc1:定义的信号函数;
  • czt:是前面通过数值过零点搜索获得的28个根的数值;

如下是sol[‘x’]中的数值,包含了最终优化后的数值,对比前面通过搜索获得数值,可以看到基本上在10-6的内存在一定的误差。

[0.02036688 0.05264705 0.082254 0.10514582 0.12902459 0.15825048
0.18724039 0.21272638 0.2389802 0.27014134 0.30532965 0.34866578
0.39150363 0.42918453 0.46993606 0.5351952 0.57522075 0.61366181
0.65019586 0.68523072 0.71705679 0.74397169 0.78048871 0.81772859
0.85324476 0.91941802 0.95364771 0.98723646]

将通过数值计算所得到的28个函数的根绘制在信号波形上,看到他们的分布。
▲ 寻找到的信号过零点

▲ 寻找到的信号过零点

3. 重建结果

根据前面所叙述的方法,使用28个过零点信息重构出的信号波形如下图所示。重构的信号与原始的信号之间波形基本一致,只是相差了一个比例因子。
▲ 重建的波形结果

▲ 重建的波形结果

重构的Python程序如下所示:

#!/usr/local/bin/python
# -*- coding: gbk -*-
#============================================================
# TSTEST2.PY                   -- by Dr. ZhuoQing 2020-04-20
#
# Note:
#============================================================

from headm import *

sol = tspload('root1000000', 'sol')[newaxis]

printf(sol)

#------------------------------------------------------------
T = 1                               # Funcntion period
f0 = 10
f1 = 20

fk = linspace(f0, f1, int((f1 - f0) / T), endpoint=False)[newaxis]
printf(fk)

#printf(type(fk))

Xjk = 2 * pi * sol.T * fk
printf(shape(Xjk))

CosX = cos(Xjk)
SinX = sin(Xjk)

CSinX = concatenate((CosX, SinX), axis=1)



#------------------------------------------------------------
u,s,v = linalg.svd(CSinX)
zs = v[-1]

#------------------------------------------------------------
t = linspace(0, 1, 10000)
a = zs[0:int(len(zs)/2)]
b = zs[int(len(zs)/2):]

#------------------------------------------------------------

fr = zeros(size(t))
k = list(range(size(fk)))

for id,ak,bk in zip(k,a,b):
    printff(id,ak,bk)
    fr = fr + ak*cos(2*pi*fk[0][id]*t) + bk*sin(2*pi*fk[0][id]*t)

plt.plot(t, fr)
plt.xlabel('Time(s)')
plt.ylabel('Amplitude')
plt.grid(True)

plt.show()
#------------------------------------------------------------
#        END OF FILE : TSTEST2.PY
#============================================================

  
 
  • 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
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

 

辅助程序


1. 将WAV文件转换成ClipVoice文件。

#!/usr/local/bin/python
# -*- coding: gbk -*-
#============================================================
# CLIPVOICE.PY                     -- by Dr. ZhuoQing 2020-04-19
#
# Note:
#============================================================
from headm import *
import wave
#============================================================
voicefile = r'd:\temp\voice2.wav'
voicedata = wave.open(voicefile, 'rb')
frames = voicedata.getnframes()
params = voicedata.getparams()
printff(frames, params)
voiceframe= voicedata.readframes(frames)

#------------------------------------------------------------
def voice2lr(data):
    dataint = [(lambda x:-(x&0x8000)+(x&0x7fff))(x+y*256) for x,y in zip(data[0::2],data[1::2])]
    return dataint[0::2], dataint[1::2]
datal, datar = voice2lr(voiceframe)
#------------------------------------------------------------
clipvalue = 8000

def wave2clip(data):
    clip = []
    for id, d in enumerate(data):
        if d >= 0:
            num = clipvalue
        else: num = 0x10000 - clipvalue
        clip.append(num)

    return clip

dataunsigned = wave2clip(datal)
wavea = [(int(d/0x100), int(d%0x100)) for d in dataunsigned]
wavebytes = bytes([y for x in wavea for y in x])
#------------------------------------------------------------
wavefile = wave.open(r'd:\temp\clipvoice2.wav', 'wb')
wavefile.setparams((1, 2, params[2], frames, 'NONE', 'TsingHua'))
wavefile.writeframes(wavebytes)
wavefile.close()
#------------------------------------------------------------
#        END OF FILE : CLIPVOICE.PY
#============================================================

  
 
  • 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
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

文章来源: zhuoqing.blog.csdn.net,作者:卓晴,版权归原作者所有,如需转载,请联系作者。

原文链接:zhuoqing.blog.csdn.net/article/details/105572008

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。