https://mp.weixin.qq.com/s/LQCfzSrnXU69MTVlTmXwEg
正文部分
1
波峰波谷用處
對於信號波峰波谷識別在嵌入式領域應該是非常廣泛的,因為大部分的信號都處於一種時變的狀態,信號在時域上處於一種類似於正弦波的波動狀態。比如計步軟件就是通過IMU模塊所采集的變化的波形狀態來識別波峰波谷,最終估算你所走過步數;

圖片來源網絡侵刪上圖顯示了一個典型的x-, y-和z-測量模式,對應於一個跑步者的垂直,向前和側面加速度。無論如何佩戴計步器,至少有一個軸會有相對較大的周期性加速度變化,因此通過檢測其波峰波谷等算法即可對於檢測步行或跑步的單位周期至關重要。


還有在電力系統中的交流電壓電流,我們需要通過檢測波峰波谷來確定電壓電流在交流周期中的最大最小值,從而動態調節系統參數來達到自適應的目的,所以波峰波谷的檢測是非常有用的。
2
比較法識別

常規的設計辦法為比較法 : 其中x表示當前采樣點波峰:f(x) > f(x−1) 且 f(x) > f(x+1)波谷:f(x) < f(x−1) 且 f(x) < f(x+1)
然而這樣識別對於沒有什么噪聲,且每個采樣點為不同的信號來說還是合適的,但在嚴苛的環境中還需要構造更多的判斷條件來進行一些錯誤判斷的規避,終究還是麻煩了一些,並且容易遺漏。
3
差分識別
在學生階段我們就學習了導數的概念,如果一個函數一階導數左右異號,那分別就是波峰或者波谷。而對於數字信號的處理通過采樣都會變成離散信號,信號對時間的微分在離散域內即為差分。在進行波形識別之前數據采集是必不可少的,其中最重要的是采樣速率和精度,以便從采樣信號中不失真的恢復原連續信號。(香農采樣)采樣的過程中由於電子器件的雜訊等,數據難免會引入噪聲,為了簡化識別算法一般都會進行濾波處理,比如一些平滑處理等,然后才開始波峰波谷識別。
A
識別算法過程
1、獲得采樣點序列

2、進行差分處理

3、由於不在乎具體的差分幅值,把所有數據歸一到-1,0,1
4、差分值為0的點即為相同點,如果使用比較法則峰值檢測可能失效,便需要更多的條件,而這里我們直接把相同點0置為前一個非0即可規避該問題。

5、最終Diff再次進行差分,-2/+2即為波峰/波谷。

#include <stdio.h>
#include <stdlib.h>
#define SAMPLE_MAX 20
#define PV_MAX 10
int Sample[SAMPLE_MAX] = {1, 2, 3, 4, 4, 4, 5, 2, 1, 0, 0, 5, 1, 0, 0, 1, 2, 3, 4, 0};
int SampleDiff[SAMPLE_MAX] = {0};
typedef struct _tag_FindPV
{
int Pos_Peak[PV_MAX]; //波峰位置存儲
int Pos_Valley[PV_MAX]; //波谷位置存儲
int Pcnt; //所識別的波峰計數
int Vcnt; //所識別的波谷計數
} SFindPV;
SFindPV stFindPV;
/********************************************
* Fuction : initialFindPV
* Note : 初始化相關數據
*******************************************/
void initialFindPV(void)
{
int Index = 0;
for (Index = 0; Index < SAMPLE_MAX; Index ++)
{
SampleDiff[Index] = 0;
}
for (Index = 0; Index < PV_MAX; Index ++)
{
stFindPV.Pos_Peak[Index] = -1;
stFindPV.Pos_Valley[Index] = -1;
}
stFindPV.Pcnt = 0;
stFindPV.Vcnt = 0;
}
/********************************************
* Fuction : FindPV
* Note : 找波峰波谷
*******************************************/
void FindPV(SFindPV *pFindPV, float *Sample)
{
int i = 0;
//step 1 :首先進行前向差分,並歸一化
for (i = 0; i < SAMPLE_MAX - 1; i++)
{
if (Sample[i + 1] - Sample[i] > 0)
{
SampleDiff[i] = 1;
}
else if (Sample[i + 1] - Sample[i] < 0)
{
SampleDiff[i] = -1;
}
else
{
SampleDiff[i] = 0;
}
}
for (int i = 0; i < SAMPLE_MAX; i++)
{
printf("diff[%d] = %d \t", i, SampleDiff[i]);
if ( ((i + 1) & 0x03) == 0)
{
printf("\n");
}
}
//step 2 :對相鄰相等的點進行領邊坡度處理
for (i = 0; i < SAMPLE_MAX - 1; i++)
{
if (SampleDiff[i] == 0)
{
if (i == (SAMPLE_MAX - 2))
{
if (SampleDiff[i - 1] >= 0)
{
SampleDiff[i] = 1;
}
else
{
SampleDiff[i] = -1;
}
}
else
{
if (SampleDiff[i + 1] >= 0)
{
SampleDiff[i] = 1;
}
else
{
SampleDiff[i] = -1;
}
}
}
}
printf("\n");
for (int i = 0; i < SAMPLE_MAX; i++)
{
printf("diff2[%d] = %d \t", i, SampleDiff[i]);
if ( ((i + 1) & 0x03) == 0)
{
printf("\n");
}
}
//step 3 :對相鄰相等的點進行領邊坡度處理
for (i = 0; i < SAMPLE_MAX - 1; i++)
{
if (SampleDiff[i + 1] - SampleDiff[i] == -2) //波峰識別
{
pFindPV->Pos_Peak[pFindPV->Pcnt] = i + 1;
pFindPV->Pcnt++;
}
else if (SampleDiff[i + 1] - SampleDiff[i] == 2) //波谷識別
{
pFindPV->Pos_Valley[pFindPV->Vcnt] = i + 1;
pFindPV->Vcnt++;
}
}
for (int i = 0; i < SAMPLE_MAX; i++)
{
printf("pFindPV->Pos_Valley[%d] = %d \t", i, pFindPV->Pos_Valley[i]);
printf("pFindPV->Pos_Peak[%d] = %d \t", i, pFindPV->Pos_Peak[i]);
if ( ((i + 1) & 0x01) == 0)
{
printf("\n");
}
}
}
/********************************************
* Fuction : main
* Note : 模擬查找波峰波谷
*******************************************/
int main(int argc, char *argv[])
{
int i = 0;
initialFindPV();
FindPV(&stFindPV, Sample);
printf("Peak\n");
for (i = 0 ; i < stFindPV.Pcnt; i++)
{
printf("-%d", stFindPV.Pos_Peak[i] + 1); //加1是為了與上圖橫坐標一致
}
printf("\nValley\n");
for (i = 0 ; i < stFindPV.Vcnt; i++)
{
printf("-%d", stFindPV.Pos_Valley[i] + 1);
}
printf("\n\n");
printf("歡迎關注:最后一個bug\n");
return 0;
}

