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

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

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

Але одного разу ми поставили собі питання, чи існує можливість «удивити» Android-пристрій 3D-жестом? Для реалізації цієї ідеї гаджети вже оснащені всім необхідним, залишилося тільки виробити підхід.

Ідея полягала в тому, щоб користувач міг записати жест за допомогою гаджета, а потім, повторивши його, розблокувати екран.

Unlocker 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;
        }
    }

На виході маємо лише те, що необхідно для порівняння.

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

Порівняння даних

Переходим до найцікавішого: порівняння двох наборів даних з урахуванням масштабування, зсуву по індексах тощо. Тут нам знадобляться знання (хто б міг подумати) статистики. Для порівняння графіків ми будемо використовувати кореляційний аналіз, а саме кореляцію Пірсона.

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 (дані ідентичні).

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

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

Додаток в Google Play

Репозиторій на GitHub