根据三轴加速度计算赛艇划桨数的算法

 

一. 简介

        现代体育竞技早已不止是单纯的体力比拼,科技能力也成为决定运动员竞技水平和比赛成绩的重要因素。对于赛艇运动来说,能够测量运动员划桨的桨频以及划桨次数是最基础的数据,本文旨在阐述一种利用三轴加速计来测量桨频的算法。三轴加速计存在于桨频表或手机内,桨频表或手机则固定在赛艇上。运动员划桨时使得赛艇的速度发生变化,本文介绍的算法利用三轴加速计测量这种速度上的变化,并据此来计算桨频和划桨次数。

(说明:桨频Stroke Rate 指的是每分钟划多少桨。一般训练中,大概每分钟划20下,在比赛中划桨率可达每分钟40下。)

二. 赛艇加速度分析

        首先,赛艇是一项周期运动。每一个划桨周期都包括拉桨和回桨的两个阶段。拉桨阶段(The Drive Phase)是指桨叶在水中用力驱动船前进的过程。回桨阶段(The Recovery Phase)是指桨叶出水后,桨手从拉桨用力状态恢复到下一个抓水动作的过程。其中抓水(Catch)是指桨叶入水的过程,是回桨和拉桨的衔接。

        在拉桨阶段赛艇会获得一个比较大的向前的加速度,赛艇速度会明显提高。在回桨阶段,由于失去了推力,船速会在平稳滑行一段时间后逐步降低。此时的加速度时降低到0后转为负,再次拉桨时加速度转正,赛艇继续加速向前。如此循环往复。

        由上图可以看出在每一个划桨周期中,拉桨阶段向前的加速度有一个骤增的过程。若是能够通过算法捕获到这个现象,就可以成功识别每一次划桨的动作,从而实现对桨频和划桨次数的计算。

 

三. 算法

3.1 算法思路

        配置一个50ms定时器,每50ms读取一下当下的原始加数据(acc_new_sample),并将上一个50ms的原始数据保存(acc_old_sample)。同时实时更新出现过的加速度原始数据最大值(acc_max)、最小值(acc_min)和二者平均值(acc_mid)。一旦出现下情况:

        acc_old_sample < acc_mid < acc_new_sample (加速度骤增现象)

        就记为一次有效的划桨。如下图所示。

 

3.2  均值滤波采样、可信赖变化量、记录MAX和MIN

        单次采样数据必然不可信,所以要多次采样取平均值。取完平均值后的数据依然不一定是可信的数据,如果本次采样的数据和上一个50ms采样的数据差值过小或者过大,应当不予采纳,需要设置可信赖变化量的下限和上限,上限和下限的值需要根据实际情况进行设置,这里的值仅作参考。全局变量定义如下:

#define ABS(a) (0 - (a)) > 0 ? (-(a)) : (a)   //取a的绝对值
#define SAMPLE_NUM                10          //采样10次取平均值
#define MIN_RELIABLE_VARIATION    500         //最小可信赖变化量
#define MAX_RELIABLE_VARIATION    5000        //最大可信赖变化量
 
//三轴数据
typedef struct
{
	int16_t X;
	int16_t Y;
	int16_t Z;
}axis_value_t;              
axis_value_t old_ave_AccValue, ave_AccValue;
//极值数据
typedef struct
{
	axis_value_t max;
	axis_value_t min;
}peak_value_t;
peak_value_t peak_value;

        每一次采样的过程是:保存上一次采样数据 → 均值采样本次数据 → 计算本次和上次的差值 → 检验差值大小(若超最大或最小可信变化量则将本次数据回退到上一次的大小) → 保存最大值和最小值 。这个函数50ms会被调用一次。参考代码如下:

void acc_sample_update(void)
{
	axis_value_t accValue;
	axis_value_t change;
	int sum[3] = {0};
	uint8_t success_num = 0;
 
	//保存上一次测量的原始数据
	old_ave_accValue.X = ave_accValue.X;
	old_ave_accValue.Y = ave_accValue.Y;
	old_ave_accValue.Z = ave_accValue.Z;
 
	//多次测量取平均值
	for(uint8_t i = 0; i < SAMPLE_NUM; i++)
	{
		if(ReadAcc(&accValue.X , &accValue.Y , &accValue.Z ) == true)
		{
			sum[0] += accValue.X;
			sum[1] += accValue.Y;
			sum[2] += accValue.Z;
			success_num ++;
		}
	}
	ave_accValue.X = sum[0] / success_num;
	ave_accValue.Y = sum[1] / success_num;
	ave_accValue.Z = sum[2] / success_num;
 
	//原始数据变化量
	change.X = ABS(ave_accValue.X - old_ave_accValue.X);
	change.Y = ABS(ave_accValue.Y - old_ave_accValue.Y);
	change.Z = ABS(ave_accValue.Z - old_ave_accValue.Z);
	
	//如果变化量超出可接受的变化值,则将原始数据退回到上一次的大小
	if(change.X < MIN_RELIABLE_VARIATION || change.X > MAX_RELIABLE_VARIATION)
	{
		ave_accValue.X = old_ave_accValue.X;
	}
	if(change.Y < MIN_RELIABLE_VARIATION || change.Y > MAX_RELIABLE_VARIATION)
	{
		ave_accValue.Y = old_ave_accValue.Y;
	}	
	if(change.Z < MIN_RELIABLE_VARIATION || change.Z > MAX_RELIABLE_VARIATION)
	{
		ave_accValue.Z = old_ave_accValue.Z;
	}
 
	//分别保存三轴角速度原始数据的最大值和最小值
	peak_value.max.X = MAX(peak_value.max.X , ave_accValue.X);
	peak_value.min.X = MIN(peak_value.min.X , ave_accValue.X);
	peak_value.max.Y = MAX(peak_value.max.Y , ave_accValue.Y);
	peak_value.min.Y = MIN(peak_value.min.Y , ave_accValue.Y);
	peak_value.max.Z = MAX(peak_value.max.Z , ave_accValue.Z);
	peak_value.min.Z = MIN(peak_value.min.Z , ave_accValue.Z);
}

3.3 判断最活跃轴

        桨频表和手机固定在床上的角度未知,且有可能发生变化。只有知道哪个轴是更接近于水平面船前进的方向的,才能确定究竟要使用哪个轴的原始数据进行计算。正常情况下,赛艇始终朝前方行进,加速度变化最大的轴可以作为活跃轴。

#define ACTIVE_NUM          30            //最活跃轴更新周期
#define ACTIVE_NULL         0             //最活跃轴未知
#define ACTIVE_X            1             //最活跃轴是X
#define ACTIVE_Y            2             //最活跃轴是Y
#define ACTIVE_Z            3             //最活跃轴是Z
uint8_t most_active_axis = ACTIVE_NULL;   //记录最活跃轴

        最活跃轴的判断函数也是50ms被调用一次。每一次被调用时,都会计算一次change值,也就是上一次和这一次原始数据的差值,然后比较这三个差值的大小,增加最大差值轴的活跃度权重。这里是每1.5秒(由ACTIVE_NUM决定)比较一次权重值,权重最大的轴就是最活跃轴,然后把权重都清零,下一个1.5秒再重新判断一次。

void which_is_active(void)
{
	axis_value_t change;
	static axis_value_t active;        //三个轴的活跃度权重
	static uint8_t active_sample_num; 
 
	acc_sample_update();
	active_sample_num ++;
 
	//每隔一段时间,比较一次权重大小,判断最活跃轴
	if(active_sample_num >= ACTIVE_NUM)
	{
		if(active.X > active.Y && active.X > active.Z)
		{
			most_active_axis = ACTIVE_X;
		}
		else if(active.Y > active.X && active.Y > active.Z)
		{
			most_active_axis = ACTIVE_Y;
		}
		else if(active.Z > active.X && active.Z > active.Y)
		{
			most_active_axis = ACTIVE_Z;
		}
		else
		{
			most_active_axis = ACTIVE_NULL;
		}
		active_sample_num = 0;
		active.X = 0;
		active.Y = 0;
		active.Z = 0;
	}
 
	//原始数据变化量
	change.X = ABS(ave_accValue.X - old_ave_accValue.X);
	change.Y = ABS(ave_accValue.Y - old_ave_accValue.Y);
	change.Z = ABS(ave_accValue.Z - old_ave_accValue.Z);
 
	//增加三轴活跃度权重
	if(change.X > change.Y && change.X > change.Z)
	{
		active.X ++;
	}
	else if(change.Y > change.X && change.Y > change.Z)
	{
		active.Y ++;
	}
	else if(change.Z > change.X && change.Z > change.Y)
	{
		active.Z ++;
	}
}

3.4 初步判定划桨

        一切准备就绪,下一部要捕获原始数据骤增和骤减现象了。

volatile uint16_t stroke_count;

        取最大值和最小值的均值mid,每当出现算法思路中的描述的情况,则算作一次有效划桨。

void detect_stroke(void)
{
	int16_t mid;
	which_is_active();
	switch(most_active_axis)
	{
		case ACTIVE_NULL:
			break;
		//捕捉原始数据骤增和骤减现象
		case ACTIVE_X:
			mid = (peak_value.max.X + peak_value.min.X) / 2;
			if(old_ave_accValue.X < mid && ave_accValue.X > mid)
			{
				stroke_count ++;
			}
			break;
		case ACTIVE_Y:
			mid = (peak_value.max.Y + peak_value.min.Y) / 2;
			if(old_ave_accValue.Y < mid && ave_accValue.Y > mid)
			{
				stroke_count ++;
			}
			break;	
		case ACTIVE_Z:
			mid = (peak_value.max.Z + peak_value.min.Z) / 2;
			if(old_ave_accValue.Z < mid && ave_accValue.Z > mid)
			{
				stroke_count ++;
			}
			break;
		default:
			break;
	}
}

按照以上的算法计算出的stroke_count作为最终结果还是不够严谨,依然有可能产生扰动,导致stroke_count被多计算的情况,需要根据人类实际的划桨的速度,再对划桨数进行一次调整。赛艇比赛的桨频从30—40桨/分不等。以男子八人赛 艇的桨频为最高,尤以起航时的桨频更为突出,甚至高达48 桨/分。但不会超过60桨/分。也就是说,正常人划桨1秒内不会超过1次。因此我们还需要有一个50ms定时器回调函数,每1000ms查看一次stroke_count 是不是为0 ,只要不是0 ,无论是多大都只算作1桨,stroke就是最终的步数。

uint16_t stroke;
 
void timer_handler()  //invoked per 50ms
{
	static uint8_t stroke_time_count = 0;

	detect_stroke();
	stroke_time_count ++;
	if(step_time_count == 20)   //1000ms
	{
		stroke_time_count = 0;
		if(stroke_count != 0)
		{
			stroke_count = 0;
			stroke ++;
		}
	}

}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值