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 (дані ідентичні).
Звичайно, на цьому робота не закінчується, потрібно підібрати правильні коефіцієнти для фільтрування і порівняння, зберігати дані, підтверджувати збереження даних, але це вже зовсім інша історія...