public void onSensorChanged(SensorEvent sensorEvent) {
if (sensorEvent.sensor == null) {
return;
}
if (sensorEvent.sensor.getType() == accelerometerSensorType) {
float accelerationZ = sensorEvent.values[2];
if (accelerationZ > 0) {
recognitionKnockRatio = 20;
recognitionUniqueRatio = 10;
smoothSectionMaxRatio = 5f;
} else {
recognitionKnockRatio = 7.5f;
recognitionUniqueRatio = 6;
smoothSectionMaxRatio = 2.5f;
}
gravityZ = alpha * gravityZ + (1 - alpha) * accelerationZ;
linearAccelerationZ = accelerationZ - gravityZ;
if (calibrateLinearAcceleration) {
calibrateLinearAccelerationIndex++;
if (calibrateLinearAccelerationIndex <= calibrateLinearAccelerationSectionNumber) {
return;
}
calibrateLinearAcceleration = false;
}
if (sensorDataShowIndex >= sensorDataShowNumber) {
sensorDataShowIndex = sensorDataShowNumber - sensorDataShowDurationNumber;
Iterator> it = linearAccelerationZShowList.listIterator(0);
for (int i = 0; i < sensorDataShowDurationNumber; i++) {
it.next();
it.remove();
}
MainActivity.UpdateSensorData(linearAccelerationZShowList);
}
linearAccelerationZShowList.add(linearAccelerationZ);
sensorDataShowIndex++;
if (!stable) {
linearAccelerationZList.add(linearAccelerationZ);
if (linearAccelerationZList.size() >= stableSectionNumber) {
stableRecognition();
linearAccelerationZList.clear();
}
return;
}
knockRecognition(linearAccelerationZ);
}
}
传感器数据回调的方法中对加速度传感器获取的数据分别进行了处理,首先,根据z轴加速度的正负,为recognitionKnockRatio,recognitionUniqueRatio,smoothSectionMaxRatio三个变量赋予不同的数值,至于为什么要进行这样处理,是因为对Android手机实际进行敲击操作发现,加速度传感器对正面敲击操作反馈敏感,对背面敲击操作反馈相对迟钝,反馈到数据层面就是,敲击正面导致的加速度传感器数据变化相比敲击背面明显很多,故而针对敲击屏幕和敲击背面要分配不同的数值,然而事实上站在手机的角度,运用现在的数据是完全无法分析敲击操作导致的加速度明显变化来源于敲击正面还是敲击背面,所以就使用z轴加速度的正负来简单判断,毕竟绝大多数情况下z轴加速度为正,那就是手机背面偏向地面,用户更可能敲击手机屏幕,而为负就是手机屏幕偏向地面,用户更可能敲击手机背面。至于导致敲击屏幕和敲击背面加速度传感器反馈敏感程度不同这种情况的原因不外乎两个,一是加速度传感器相比于背面距离屏幕更近,再者就是Android手机外壳的问题了,这一点在LG G3上尤为明显,LG G3的是有一定弧度的塑料外壳,在背面敲击引发的传感器数据变化相比于敲击屏幕要低很多,而金属外壳的三星S6,在背面敲击引发的传感器数据变化接近于敲击屏幕。事实上上述三个系数属于经验系数,并且对于不同类型手机尽量提供不同的数值,原因可参见刚才所说的LG G3和三星S6,再一次感慨Android手机的多样性,Android手机种类太多,硬件设计的不同导致在一款手机上适用的系数在另一款手机上可能完全无法适用,要是如iphone一样只有那几款机型的话无疑好处理很多。 *
* It should be apparent that in order to measure the real acceleration of
* the device, the contribution of the force of gravity must be eliminated.
* This can be achieved by applying a high-pass filter. Conversely, a
* low-pass filter can be used to isolate the force of gravity.
*
*
*
*
* public void onSensorChanged(SensorEvent event)
* {
* // alpha is calculated as t / (t + dT)
* // with t, the low-pass filter's time-constant
* // and dT, the event delivery rate
*
* final float alpha = 0.8;
*
* gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
* gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
* gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];
*
* linear_acceleration[0] = event.values[0] - gravity[0];
* linear_acceleration[1] = event.values[1] - gravity[1];
* linear_acceleration[2] = event.values[2] - gravity[2];
* }
*
通过高通滤波和低通滤波对加速度进行处理排除重力影响以获取线性加速度,但在此过程中是需要传入一定数量的数据进行校准以获取较精准的线性加速度,在这里我们设定calibrateLinearAccelerationSectionNumber作为用以校准数据的数据长度,用calibrateLinearAccelerationIndex和calibrateLinearAcceleration来控制何时校准结束。 private void stableRecognition() {
int exceptionNumber = 0;
float accelerationZValue;
float minAccelerationZValue = Integer.MAX_VALUE;
float maxAccelerationZValue = Integer.MIN_VALUE;
for (int i = stableSectionNumber - 1; i >= 0; i--) {
accelerationZValue = linearAccelerationZList.get(i);
if (Math.abs(accelerationZValue) > maxStableOffset) {
exceptionNumber++;
} else {
if (accelerationZValue > maxAccelerationZValue) {
maxAccelerationZValue = accelerationZValue;
} else {
if (accelerationZValue < minAccelerationZValue) {
minAccelerationZValue = accelerationZValue;
}
}
}
}
stable = exceptionNumber <= maxExceptionNumber;
if (stable) {
if (linearAccelerationZStableSection == 0) {
linearAccelerationZStableSection =
(maxAccelerationZValue - minAccelerationZValue) / 2;
}
if (linearAccelerationZStableSection > maxStableOffset) {
linearAccelerationZStableSection = maxStableOffset;
}
}
MainActivity.UpdateStable(stable);
LogFunction.log("stable", "" + stable);
LogFunction.log("exceptionNumber", "" + exceptionNumber);
LogFunction.log("linearAccelerationZStableSection", "" + linearAccelerationZStableSection);
}
在此次功能实现过程中,判断稳态的方式是采样50个点,然后计算每个点的绝对值,如果大于最大偏差maxStableOffset就视为异常点,异常点大于最大异常点数目maxExceptionNumber就视为非稳态,反之视为稳态。判断稳态结束后,如果处于稳态则将剔除异常点数据后的Z轴最大加速度和最小加速度之间差值的一半视为波动区间linearAccelerationZStableSection。maxStableOffset与maxExceptionNumber相同都是经验系数,是对Android手机实际提供的不同场景下的线性加速度分析得出的。现在存在一个问题,那就是如果原本状态处于稳态,然后用户突然对手机进行操作,将手机状态转变为非稳态那要如何处理,不要着急,这个问题会在敲击识别的过程中进行处理的。
private void knockRecognition(float linearAccelerationZ) {
float linearAccelerationZAbsolute = Math.abs(linearAccelerationZ);
float linearAccelerationZAbsoluteRadio =
linearAccelerationZAbsolute / linearAccelerationZStableSection;
if (linearAccelerationZAbsoluteRadio > recognitionUniqueRatio) {
uniqueLinearAccelerationZList.add(linearAccelerationZ);
currentForecastNumber = forecastNumber;
} else {
if (uniqueLinearAccelerationZList.size() > 0) {
if (currentForecastNumber > 0) {
currentForecastNumber--;
} else {
handleUniqueLinearAccelerationZ();
}
}
}
if (linearAccelerationZAbsoluteRadio < smoothSectionMaxRatio) {
float offsetWeight = 0.001f;
linearAccelerationZStableSection =
weightedMean(offsetWeight, linearAccelerationZAbsolute,
linearAccelerationZStableSection);
}
}
knockRecognition就是用来处理线性加速度进而确认是否有敲击操作的方法,首先对传入参数线性加速度进行处理,获取线性加速度绝对值,接着如果线性加速度绝对值与波动区间的比值大于recognitionUniqueRatio,那就认为手机正在受到力的作用,为确定是敲击操作还是用户其他操作,先将线性加速度加入到独特线性加速度列表中, 反之如果小于等于recognitionUniqueRatio,那就认为手机处于相对稳定状态,在此时如果此时独特线性加速度列表长度大于0,如果currentForecastNumber大于0,则currentForecastNumber减1,如果currentForecastNumber小于等于0,则开始处理独特线性加速度列表,而在处理独特线性加速度列表的过程中正式开始识别是否敲击,以及当前状态是否转变为非稳态。在进行上述操作的同时,如果线性加速度绝对值与波动区间的比值小于smoothSectionMaxRatio则用线性加速度绝对值来平滑波动区间。
在这里,大家肯定对currentForecastNumber有疑问,这个变量代表什么含义,为什么会有这个变量,原因是这样的,一次敲击可能导致两个接近但不连续的独特线性加速度。如果没有currentForecastNumber这个变量就会导致现实的一次敲击可能被识别为两次敲击操作。 private void handleUniqueLinearAccelerationZ() {
LogFunction.log("linearAccelerationZStableSection", "" + linearAccelerationZStableSection);
int recognitionKnockNumber = 1;
int uniqueLinearAccelerationZListLength = uniqueLinearAccelerationZList.size();
float accelerationZOffsetAbsolute;
float maxAccelerationZOffsetAbsolute = 0;
for (int i = 0; i < uniqueLinearAccelerationZListLength; i++) {
accelerationZOffsetAbsolute = Math.abs(uniqueLinearAccelerationZList.get(i));
if (maxAccelerationZOffsetAbsolute < accelerationZOffsetAbsolute) {
maxAccelerationZOffsetAbsolute = accelerationZOffsetAbsolute;
}
LogFunction.log("uniqueLinearAccelerationZList index" + i,
"" + uniqueLinearAccelerationZList.get(i));
}
uniqueLinearAccelerationZList.clear();
LogFunction.log("uniqueLinearAccelerationZListLength",
"" + uniqueLinearAccelerationZListLength);
if (uniqueLinearAccelerationZListLength > unstableListLength) {
stable = false;
MainActivity.UpdateStable(stable);
return;
}
LogFunction.log("maxAccelerationZOffsetAbsolute / linearAccelerationZStableSection",
"" + (maxAccelerationZOffsetAbsolute / linearAccelerationZStableSection));
if (maxAccelerationZOffsetAbsolute >
linearAccelerationZStableSection * recognitionKnockRatio) {
LogFunction.log("recognitionKnockRatio", "" + recognitionKnockRatio);
LogFunction.log("recognitionUniqueRatio", "" + recognitionUniqueRatio);
knockRecognitionSuccess(recognitionKnockNumber);
}
}
终于到了最后的handleUniqueLinearAccelerationZ方法,顾名思义,就是用来处理独特线性加速度列表的,在这个方法内,进行了敲击识别和稳态状态是否转变的判定,如果独特线性加速度列表长度超过非稳态独特线性加速度列表长度,则认为现在手机状态此刻状态转变为非稳态并结束方法,如果发现加速度偏移数据列表中最大偏移值超过波动区间一定倍数则识别为敲击。