Unlocker 3D. Особливості розробки

Жести — невід'ємна частина нашого життя. Навіть при роботі з нашими мобільними пристроями ми постійно ними користуємося: торкаємося екрана, викликаємо ті чи інші функції, перегортаємо сторінки. Що й казати, це одна з причин, чому ми в захваті від сенсорного функціоналу. Все це сміливо можна назвати 2D-жестами.
Але одного разу ми поставили собі питання, чи існує можливість «удивити» Android-пристрій 3D-жестом? Для реалізації цієї ідеї гаджети вже оснащені всім необхідним, залишилося тільки виробити підхід.
Ідея полягала в тому, щоб користувач міг записати жест за допомогою гаджета, а потім, повторивши його, розблокувати екран.

Роботу з сенсорами можна розбити на такі завдання:
- Отримання даних сенсорів.
- Фільтрування даних.
- Підготовка даних.
- Порівняння даних.
Отримання даних сенсорів
Для початку необхідно розібратися, які сенсори нам потрібно використовувати. Датчики ОС Android діляться на три категорії: руху, положення та навколишнього середовища. Датчики можуть бути найрізноманітнішими:
- акселерометр;
- гіроскоп;
- датчик освітлення;
- датчик магнітних полів;
- барометр;
- датчик піднесення телефону до голови;
- датчик температури пристрою;
- датчик температури навколишнього середовища;
- вимірювач відносної вологості тощо.
Для наших цілей нам знадобляться гіроскоп і акселерометр.
Першим ділом активність повинна реалізувати інтерфейс SensorEventListener:
public class MainActivity extends Activity implements SensorEventListener
Тепер потрібен доступний для реалізації метод onSensorChanged.
Отримуємо доступ до менеджера сенсорів:
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Реєструємо слухачів:
sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_GAME);
Тепер можемо отримувати дані:
Фільтрування даних
Якщо виводити лінійний графік після отримання даних, відразу видно, що не все так гладко: всі датчики дуже сильно «шумлять». Щоб виправити ситуацію, пропускаємо дані через фільтр нижніх частот (low-pass filter):
public static double[] lowFilter(double x, double y, double z) {
double[] acceleration = new double[3];
acceleration[0] = x * kFilteringFactor + acceleration[0] * (1.0 - kFilteringFactor);
x = x - acceleration[0];
acceleration[0] = x;
acceleration[1] = y * kFilteringFactor + acceleration[1] * (1.0 - kFilteringFactor);
y = y - acceleration[1];
acceleration[1] = y;
acceleration[2] = z * kFilteringFactor + acceleration[2] * (1.0 - kFilteringFactor);
z = z - acceleration[2];
acceleration[2] = z;
return acceleration;
}
Тепер графіки стали більш плавними, але працювати з ними все ще не вдасться. Нам знадобиться фільтр поскладніше. Після недовгих пошуків виявилося, що нам підійде або фільтр Калмана, або альфа-бета фільтр (Complementary filter). Для наших цілей більше підходить другий варіант. Його реалізація досить проста:
На виході отримуємо pitch-roll (нахил-крен). Ну от, наші графіки досить красиві та гладкі :).
Підготовка даних
Далі нам потрібно підготувати дані для їх подальшого порівняння. Наприклад, у більшості випадків перед початком і в кінці жесту отримуються майже нульові графіки — це бездіяльність користувача, тому ці дані нам варто опустити. Застосовуємо такий метод:
public static List<double[]> prepareArrays(double[] arrayX, double[] arrayY) {
try {
double offset = 0.00009;
int len;
if (arrayX.length > arrayY.length) {
len = arrayY.length;
} else {
len = arrayX.length;
}
int i;
for (i = 0; i < len; i++) {
if ((arrayX[i] < offset && arrayX[i] > (-offset)) && (arrayY[i] < offset && arrayY[i] > (-offset))) {
} else {
break;
}
}
double[] pArrayX = Arrays.copyOfRange(arrayX, i, arrayX.length);
double[] pArrayY = Arrays.copyOfRange(arrayY, i, arrayY.length);
for (i = len - 1; i >= 0; i--) {
if ((arrayX[i] < offset && arrayX[i] > (-offset)) && (arrayY[i] < offset && arrayY[i] > (-offset))) {
} else {
break;
}
}
pArrayX = Arrays.copyOfRange(pArrayX, 0, i);
pArrayY = Arrays.copyOfRange(pArrayY, 0, i);
ArrayList<double[]> doubles = new ArrayList<double[]>();
doubles.add(pArrayX);
doubles.add(pArrayY);
return doubles;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}На виході маємо лише те, що необхідно для порівняння.

Порівняння даних
Переходим до найцікавішого: порівняння двох наборів даних з урахуванням масштабування, зсуву по індексах тощо. Тут нам знадобляться знання (хто б міг подумати) статистики. Для порівняння графіків ми будемо використовувати кореляційний аналіз, а саме кореляцію Пірсона.
public static double pirsonCompare(double[] x, double[] y) {
int len;
if (x.length > y.length) {
len = y.length;
} else {
if (((y.length * 0.6) > x.length)) {
return -1;
}
len = x.length;
}
double xs = 0;
for (int i = 0; i < len; i++) {
xs += x[i];
}
xs = xs / x.length;
double ys = 0;
for (int i = 0; i < len; i++) {
ys += y[i];
}
ys = ys / y.length;
double dxy = 0;
for (int i = 0; i < len; i++) {
dxy += (x[i] - xs) * (y[i] - ys);
}
double mqx = 0;
for (int i = 0; i < len; i++) {
mqx += Math.pow(x[i] - xs, 2);
}
mqx = Math.sqrt(mqx);
double mqy = 0;
for (int i = 0; i < len; i++) {
mqy += Math.pow(y[i] - ys, 2);
}
mqy = Math.sqrt(mqy);
double rxy = dxy / (mqx * mqy);
return rxy;
}Результат - коефіцієнт кореляції Пірсона, який може приймати значення від -1 (дані протилежні) до 1 (дані ідентичні).

Звичайно, на цьому робота не закінчується, потрібно підібрати правильні коефіцієнти для фільтрування і порівняння, зберігати дані, підтверджувати збереження даних, але це вже зовсім інша історія...


