上升沿与下降沿识别及周期信号提取详解
1.前言
在普通算法开发或深度学习中,经常需要先检测信号上升沿,然后使用两个跳变信号提取出相应的周期信号,以便进行后续处理。在线识别跳变信号任务通常相当复杂,与我们在既定规则下采集数据集时相比更为困难。在在线检测中,通常会遇到周期不确定、多样的幅值、以及干扰跳变等多种情况。如果后续算法具有足够的鲁棒性,那么第一步信号检测就显得非常重要。如果此步骤处理不当,那么后续算法的性能就会大打折扣。OK,让我们分析一下下面种常见的信号。
2. 分析周期信号
下面,以我本次项目遇到的周期信号示例。首先我们分析信号的特点:
图1. 部分原始数据
从上图1中可以看出信号大致呈现一定的周期性,但从局部放大图上看信号的变化趋势较为复杂多变。有同学肯定会想到一种简单的方法:
我不关注信号过程性,因为信号变化过程过于复杂,但周期信号发生的前后却较为干净,因此可以利用以下方法求解:
(1)若信号在
时间内保持在一个相对趋向零附近的值,且下一时刻幅值突然增大开始记录数据;
(2)信号在另一个在另一个
时间内又回到相对趋向零附近的值,停止记录,生成一组数据。
注意 :这种方法隐藏的假设条件:
(1)周期信号的间隔时间必须大于局部放大图中展示的接近零点的那段时间,否则失效;
(2)信号的滞后性不影响后续处理。因为你是利用结束的一段零点区间判定,因此无法做到动作结束,信号检测结束。
另一种常规方法:
2.1 信号变换
从学过的高数中我们依稀记得如果一个变量的表达式过于复杂,我们可以对变量做处理,只要变量的功能能完全取代旧变量,这样处理完全没问题,例如比较根号下x的大小与比较x平方是等价的。ok,让我们对信号变换,这里只对我处理的信号变换,你的要自己选择合适的变换,变换后的信号如图2所示。
图2. 预处理后的信号
从图2可以看出变换后的信号复杂性明显降低,可以简单分析一下,存在以下特点:
(1)“近似”由两个抛物线组成,只是近似!
(2)一个周期信号可以看作由两段曲线组成,每一段曲线的开头和结束均是上升沿和下降沿。
抓住这几个特点就是我们解题的方向和条件,下一步我们希望我们的方法可以不易遗漏的找到信号中所有的上升沿和下降沿,然后再根据信号的特点消掉部分跳变检测,最后得到想要的信号。
3. 基于阈值+固定窗口法的跳变检测
3.1 阈值法检测
首先,我们先识别上升沿,大家可能第一解题思路就是取阈值,当检测到信号大于阈值th1时,则认为可以记录信号了。然后再小于另一个趋向0的信号便结束。
有同学肯定会问“中间有一段趋向零的信号,遇到后岂不是停止了?”
答案是肯定的!如图3所示。下一步该怎么办呢?
图3. 阈值法周期检测
3.2 阈值+固定窗口法检测
有同学肯定又会说“我给他增加一个结束的判定条件:只有当记录的数据维度大于一定数量才结束,这不就锁死了么?”
方法总结如下:
(1)若信号在
时间内保持在一个相对趋向零附近的值,且下一时刻幅值突然增大开始记录数据;
(2)信号在另一个在另一个
时间内记录的数量大于th2,且回到相对趋向零附近的值,停止记录,生成一组数据。
OK,按照上面的意思,我们取一个固定的阈值,只有当记录的数量大于阈值th2时我们才检测是否接近零点,得到图4。可出现了两种很不情愿的情况:
(1)阈值设置的太小,有些周期较长的信号被检测错误;
(2)阈值设置的太大,有些周期较短的信号“尾巴”太长,丧失了动态检测的意义。
图4. 阈值+固定窗口法周期检测
如果你在乎尾巴参差不齐,那你可以考虑这种方法,毕竟简单粗暴。但是有一个非常大的隐患值得引起你的注意:如果检测到一个周期信号的另一个上升沿,那就会直到检测到下一个周期信号的第一个下降沿才结束,后面就会一直错位。当然你可以通过设定记录的长度加以限制,还的有初始化操作。
OK,怎么可以更灵敏的检测周期并去掉尾巴,获得信息量更足的周期信号?
4. 基于梯度+阈值的周期信号检测方法
阈值+固定窗口法还隐藏着另一个隐患:当遇到小凸起的噪音时会引发疯狂错位,如下图所示,蓝框中的噪音信号触发了开始信号,后面即使判定失败,又会继续判定一个周期信号中另一个上升沿作为起始信号(错误),往复下去!所以必须有相应的纠错机制。
图5. 噪音干扰示例
4.1跳变检测
OK,一步步来吧,步骤如下:
(1)首先把信号中的上升沿和下降沿全部检测出;
(2)若第一步成功,每检测到第二个上升沿后可以准备在检测到下降沿后准备收网。
如何检测上升沿或者下降沿呢?我估计大家肯定想到梯度(),这里展示一个示例:
def decend_cal(input,input_old):
d1 = input[0] - input_old[0]
d2 = input[1] - input_old[1]
d3 = input[2] - input_old[2]
d4 = input[3] - input_old[3]
d5 = input[4] - input_old[4]
d6 = input[5] - input_old[5]
re = [d1,d2,d3,d4,d5,d6]
return re
gradient = decend_cal(inp, inp_old)
inp_old = inp
"""利用梯度捕捉上升沿与下降沿"""
if any(num > 500 for num in gradient): # 捕捉上升沿
up_gradient_label = True
down_gradient_label = False
if any(num < -200 for num in gradient):
up_gradient_label = False
down_gradient_label = True
当一个上升沿来临时,会有一串的梯度都会大于阈值,我们只想在一个抛物线上识别一次,而不是很多次,那该怎么办呢?
用PLC领域的“互锁”思想!上面的代码也是一个简单的互锁,A发生时禁止B发生,B发生时禁止A发生。代码如下:
if (w_2_le_aver > yuzhi and up_allow==True and up_gradient_label == True): # 发现上升沿
up_allow = False
decend_start_label = True
first_10_label = True # 保存上升前10个数据
up_count += 1
if up_count %2 == 0 and up_count>1: # 统计上升沿数量
up_count = 0
second_up_label = True
if decend_start_label == True:
a_arr.append(a_le[i]) # 保存数据
if first_10_label == True:
w_2_l_aver_array.append(int(w_2))
if(len(w_2_l_aver_array)>5 and first_10_label == True):
if all(num < 100000 for num in ss):
print("上升沿捕捉失败")
"""重新开始"""
decend_start_label = False #
up_allow = True
if err_occur == False:
up_count -=1 # 误检,重新回到上一次位置
err_occur = True
a_arr.clear()
else:
err_occur = False
if (w_2_li_aver < yuzhi_decend and decend_start_label == True and down_gradient_label==True and 5<len(w_x_little_arr) < 60):
decend_start_label = False # 互锁
up_allow = True
if second_up_label == True:
second_up_label = False
a_arr.clear()
识别的上升沿和下降沿如下:
图6. 上升沿检测
图7. 下降沿检测
4.2 周期信号检测
识别多了不可怕,怕的是漏掉识别的,从图6和图7上看,没有遗漏的,就可以利用互锁识别一个周期信号了,识别效果如下:
图8. 周期检测
图8 展示了一个周期信号的示意图,比较完美检测到所有的周期信号,像图中的很色跳变指示线逻辑上比较复杂,不建议尝试展示了,用图3所示的效果即可,逻辑实现上比较简单。这种方法也会受到噪音的干扰,如图9所示,该怎么处理呢?
图9.噪音干扰
解决方法就是:利用检测到的第一个上升沿后面连取10个值,如果最大值都很小,说明就是很小的干扰源忽略不计,需要放弃本次采集的数据,并将保存的数据,标志位清零!尤其是记录的上升沿数量一定回到初始值!!!但是,还有另一个问题:如果你采集的上升沿连续都是噪音,那么一味减1处理肯定是错误的!因此还要判定噪声的位置,具体处理见上面的代码。OK,处理完这一步,就可以轻松实现了周期信号的提取了,如下图所示:
图10.最终周期信号切割示意图
有同学肯定好奇图10示例不也有连续的上升沿检测失误吗?
是的,但是不影响。后面我检测到我会以后面的为准,这就是上面提到的清零,请标志位。另外还涉及到参数初始化,这里不再讲解,比较容易实现了,整个流程就处理完了。
作者:小博达人