2025-04-22 02:29:37 +00:00
|
|
|
|
|
|
|
|
|
#include "find_peaks.h"
|
2025-05-09 08:25:05 +00:00
|
|
|
|
#include "MyTool.h"
|
2025-04-22 02:29:37 +00:00
|
|
|
|
|
|
|
|
|
void compute_dynamic_threshold_optimized(const float *data, float *thresholds)
|
|
|
|
|
{
|
|
|
|
|
float buffer[WINDOW_SIZE]; // 循环缓冲区
|
|
|
|
|
int buf_idx = 0; // 缓冲区当前写入位置
|
|
|
|
|
float window_sum = 0.0f;
|
|
|
|
|
|
|
|
|
|
// 第一阶段:初始化缓冲区
|
|
|
|
|
for (int i = 0; i < WINDOW_SIZE; i++)
|
|
|
|
|
{
|
|
|
|
|
float energy = data[i] * data[i];
|
|
|
|
|
buffer[buf_idx] = energy;
|
|
|
|
|
window_sum += energy;
|
|
|
|
|
buf_idx = (buf_idx + 1) % WINDOW_SIZE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第二阶段:计算首个有效阈值
|
|
|
|
|
float first_threshold = DYNAMIC_THRESH_RATIO * sqrtf(window_sum / WINDOW_SIZE);
|
|
|
|
|
|
|
|
|
|
// 填充前WINDOW_SIZE个阈值(与原算法行为一致)
|
|
|
|
|
for (int i = 0; i < WINDOW_SIZE; i++)
|
|
|
|
|
{
|
|
|
|
|
thresholds[i] = first_threshold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第三阶段:滑动窗口处理后续数据
|
|
|
|
|
for (int i = WINDOW_SIZE; i < DATA_LENGTH; i++)
|
|
|
|
|
{
|
|
|
|
|
float new_energy = data[i] * data[i];
|
|
|
|
|
float old_energy = buffer[buf_idx]; // 获取最早的能量值
|
|
|
|
|
|
|
|
|
|
window_sum += new_energy - old_energy; // 更新滑动总和
|
|
|
|
|
buffer[buf_idx] = new_energy; // 更新缓冲区
|
|
|
|
|
buf_idx = (buf_idx + 1) % WINDOW_SIZE; // 移动循环指针
|
|
|
|
|
|
|
|
|
|
// 计算动态阈值
|
|
|
|
|
thresholds[i] = DYNAMIC_THRESH_RATIO * sqrtf(window_sum / WINDOW_SIZE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 滑动平均预处理
|
|
|
|
|
void moving_average(float *data)
|
|
|
|
|
{
|
|
|
|
|
float buffer[MOVING_AVG_WINDOW] = {0.0f};
|
|
|
|
|
float sum = 0.0f;
|
|
|
|
|
|
|
|
|
|
// 初始化窗口
|
|
|
|
|
for (int i = 0; i < MOVING_AVG_WINDOW; i++)
|
|
|
|
|
{
|
|
|
|
|
buffer[i] = data[i];
|
|
|
|
|
sum += data[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 滑动处理
|
|
|
|
|
for (int i = MOVING_AVG_WINDOW; i < DATA_LENGTH; i++)
|
|
|
|
|
{
|
|
|
|
|
sum = sum - buffer[i % MOVING_AVG_WINDOW] + data[i];
|
|
|
|
|
buffer[i % MOVING_AVG_WINDOW] = data[i];
|
|
|
|
|
data[i - MOVING_AVG_WINDOW / 2] = sum / MOVING_AVG_WINDOW;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PeakResult find_peaks(const float *input)
|
|
|
|
|
{
|
|
|
|
|
PeakResult result_struct = {
|
|
|
|
|
.peaks = {0}, // 初始化peaks数组全为0
|
|
|
|
|
.thresholds = {0.0f}, // 初始化thresholds数组全为0.0f
|
2025-05-09 08:25:05 +00:00
|
|
|
|
.count = 0}; // 初始化count为0
|
2025-04-22 02:29:37 +00:00
|
|
|
|
|
|
|
|
|
// memset(&result_struct,0,sizeof(result_struct));
|
|
|
|
|
float data[DATA_LENGTH] = {0.0f};
|
|
|
|
|
int i = 0, j = 0;
|
|
|
|
|
|
|
|
|
|
// 拷贝并预处理数据
|
|
|
|
|
memcpy(data, input, sizeof(float) * DATA_LENGTH);
|
|
|
|
|
moving_average(data);
|
|
|
|
|
|
|
|
|
|
// 第一阶段:候选波峰检测
|
|
|
|
|
int candidates[MAX_CANDIDATES] = {0};
|
|
|
|
|
int candidate_count = 0;
|
|
|
|
|
float prev_diff = 0.0f;
|
|
|
|
|
|
|
|
|
|
for (i = 1; i < DATA_LENGTH; i++)
|
|
|
|
|
{
|
|
|
|
|
float diff = data[i] - data[i - 1];
|
|
|
|
|
if (prev_diff > 0 && diff < 0)
|
|
|
|
|
{ // 斜率由正转负
|
|
|
|
|
candidates[candidate_count++] = i - 1;
|
|
|
|
|
}
|
|
|
|
|
prev_diff = diff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第二阶段:动态阈值计算
|
|
|
|
|
compute_dynamic_threshold_optimized(data, result_struct.thresholds); // 动态阈值
|
|
|
|
|
|
|
|
|
|
// 第三阶段:峰值筛选
|
|
|
|
|
int last_peak = -MIN_PEAK_DISTANCE;
|
|
|
|
|
for (j = 0; j < candidate_count; j++)
|
|
|
|
|
{
|
|
|
|
|
const int idx = candidates[j];
|
|
|
|
|
if (data[idx] > result_struct.thresholds[idx] &&
|
|
|
|
|
(idx - last_peak) >= MIN_PEAK_DISTANCE)
|
|
|
|
|
{
|
|
|
|
|
result_struct.peaks[result_struct.count++] = idx;
|
|
|
|
|
last_peak = idx;
|
|
|
|
|
}
|
|
|
|
|
if (result_struct.count >= sizeof(result_struct.peaks) / sizeof(int))
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result_struct;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int calculate_heart_rate(const PeakResult *result)
|
|
|
|
|
{
|
|
|
|
|
const int sample_rate = SAMPLE_RATE; // 采样率50Hz
|
|
|
|
|
float avg_interval = 0.0f;
|
|
|
|
|
|
|
|
|
|
if (result->count < 2)
|
|
|
|
|
{
|
|
|
|
|
return -1; // 波峰不足无法计算心率
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算相邻波峰的平均间隔(网页1和网页7的结合)
|
|
|
|
|
int total_intervals = 0;
|
|
|
|
|
for (int i = 1; i < result->count; i++)
|
|
|
|
|
{
|
|
|
|
|
int interval = result->peaks[i] - result->peaks[i - 1]; // 间隔的点数
|
|
|
|
|
|
|
|
|
|
if (interval >= 30 && interval <= 116)
|
|
|
|
|
{
|
|
|
|
|
total_intervals += interval;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算平均间隔时间(秒)
|
|
|
|
|
avg_interval = total_intervals / (float)(result->count - 1) / sample_rate;
|
|
|
|
|
int hp_output = 0;
|
|
|
|
|
hp_output = (int)round(60.0f / avg_interval); // 60秒 / 平均间隔秒数
|
|
|
|
|
return hp_output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int time_domain_heart_rate(float *hp_data)
|
|
|
|
|
{
|
|
|
|
|
PeakResult time_result = find_peaks(hp_data);
|
|
|
|
|
|
|
|
|
|
int hr = calculate_heart_rate(&time_result);
|
|
|
|
|
if (hr < 30 || hr > 150)
|
|
|
|
|
{ // 生理范围验证
|
|
|
|
|
return -2; // 异常心率代码
|
|
|
|
|
}
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
2025-05-09 08:25:05 +00:00
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
float compute_target_acf(const float *data, int lag, float mean, float total_energy)
|
|
|
|
|
{
|
|
|
|
|
const int N = DATA_LENGTH - lag;
|
|
|
|
|
if (N <= 0 || total_energy < 1e-6f)
|
|
|
|
|
return 0.0f;
|
|
|
|
|
|
|
|
|
|
float sum_product = 0.0f;
|
|
|
|
|
for (int i = 0; i < N; i++)
|
|
|
|
|
{
|
|
|
|
|
float x = data[i] - mean;
|
|
|
|
|
float y = data[i + lag] - mean;
|
|
|
|
|
sum_product += x * y;
|
|
|
|
|
}
|
|
|
|
|
return sum_product / total_energy;
|
|
|
|
|
}
|
|
|
|
|
// 快速选择心率
|
|
|
|
|
int fast_select_hr(const float candidates[4], const float *data, float *fft_energy_ratios)
|
|
|
|
|
{
|
|
|
|
|
float32_t std = 0.0f;
|
|
|
|
|
float32_t avg = 0.0f;
|
|
|
|
|
avg = MyToolAvgValue((float32_t *)data, DATA_LENGTH);
|
|
|
|
|
std = MyToolStdValue((float32_t *)data, DATA_LENGTH);
|
|
|
|
|
// 预处理计算均值和总能量
|
|
|
|
|
float parsed_data[DATA_LENGTH];
|
|
|
|
|
// memset(parsed_data, data, sizeof(float) * DATA_LENGTH);
|
|
|
|
|
float mean = 0.0f, total_energy = 0.0f;
|
|
|
|
|
for (int i = 0; i < DATA_LENGTH; i++)
|
|
|
|
|
{
|
|
|
|
|
parsed_data[i] = (data[i] - avg) / std;
|
|
|
|
|
mean += parsed_data[i];
|
|
|
|
|
total_energy += parsed_data[i] * parsed_data[i];
|
|
|
|
|
}
|
|
|
|
|
mean /= DATA_LENGTH;
|
|
|
|
|
total_energy -= DATA_LENGTH * mean * mean; // 修正总能量
|
|
|
|
|
// 遍历候选心率
|
|
|
|
|
float max_score = -1.0f;
|
|
|
|
|
int selected_idx = -1;
|
|
|
|
|
float temp = 0.0f;
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
{
|
|
|
|
|
const float hr = (i < 2) ? candidates[i] / 2 : candidates[i];
|
|
|
|
|
// 转换候选心率为lag值
|
|
|
|
|
int target_lag = (int)(50 * 60.0f / hr + 0.5f);
|
|
|
|
|
if (target_lag <= 0 || target_lag >= DATA_LENGTH / 2)
|
|
|
|
|
continue;
|
|
|
|
|
// 局部窗口ACF峰值检测
|
|
|
|
|
float acf_peak = -1.0f;
|
|
|
|
|
int search_radius = 1;
|
|
|
|
|
for (int offset = 0; offset <= search_radius; offset++)
|
|
|
|
|
{
|
|
|
|
|
int current_lag = target_lag + offset;
|
|
|
|
|
if (current_lag < 1 || current_lag >= DATA_LENGTH - 1)
|
|
|
|
|
continue;
|
|
|
|
|
float acf_val = compute_target_acf(parsed_data, current_lag, mean, total_energy);
|
|
|
|
|
acf_peak = fmax(acf_peak, acf_val);
|
|
|
|
|
}
|
|
|
|
|
// 综合评分(网页6的加权策略)
|
|
|
|
|
float score = 0.7f * acf_peak + 0.3f * fft_energy_ratios[i];
|
|
|
|
|
if (score > max_score)
|
|
|
|
|
{
|
|
|
|
|
max_score = score;
|
|
|
|
|
selected_idx = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return selected_idx;
|
|
|
|
|
}
|