Управление жестами: Часть 1 – простые движения

Молодая, но интересная концепция управления чем бы то ни было при помощи жестов, пока не сравнится по быстроте, точности и набору функций с привычными нам кнопками. Согласитесь, что для того, чтобы включить чайник, проще нажать на кнопку, чем скажем махнуть рукой или выполнить какой-то специальный жест. Однако уже сейчас есть области применения, в которых традиционные интерфейсы проигрывают.

Если вы едете в машине, то нажатие на кнопку вовремя движения (например, чтобы включить обдув лобового стекла) требует от вас оторвать глаза от дороги и найти эту кнопку… ну или нащупать по памяти. Гораздо проще произнести команду или махнуть рукой. И хотя распознавание речи тоже интересная тема, но тут я решил сконцентрироваться исключительно на устройствах распознавания жестов, с которыми мне удалось поработать – это Myo armband, Leap Motion и SparkFun RGB and Gesture Sensor.

20151215_123444 20151215_123832

Три разные “игрушки” , которые обладают разным функционалом, ценой, размерами и, видимо, областью применения.

Но прежде чем перейти к непосредственно устройствам я бы хотел остановиться на более простых вариантах распознавания движения с использованием дальномеров.

одним из типичных представителей является ультразвуковой датчик определения расстояния, например hc-sr04.

20160227_221429Использовать его просто: согласно datasheet-у подается напряжение на триггер в течении 10 микросекунд, а затем считывается сигнал с ножки echo. Конечно уже есть готовые библиотеки для работы с этим датчиком, например NewPing, правда качество измерения крайне низкое если расстояние больше 10см.

20160301_134902Соответственно можно сделать простой скетч, который бы в случае обнаружения препятствия на расстоянии меньше, скажем, 6 сантиметров срабатывал как нажатие на кнопку. Вот пример реализации с ипользованием простенького i2c дисплея для отображения информации:

#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define Trig_pin 8
#define Echo_pin 7

#define press_range 6
#define measures 20

bool pressed = false;

void setup() {
  // put your setup code here, to run once:
  pinMode(Trig_pin, OUTPUT);
  digitalWrite(Trig_pin, LOW);
  pinMode(Echo_pin, INPUT);
 
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  pressed = false;
  display.display();
}

void loop() {
  unsigned int range = Ranging();


  if (range < press_range)
    pressed = !pressed;

  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
 
  display.print("cm: ");
  display.println(range);
  if (pressed)
    display.println("pressed");
  else
    display.println("released");
 
  display.display();
  delay(300);
}

unsigned int Timing()
{
  digitalWrite(Trig_pin, HIGH);
  delayMicroseconds(10);
  digitalWrite(Trig_pin, LOW);
 
  return pulseIn(Echo_pin, HIGH);
}

unsigned int Ranging()
{
  unsigned int avgduration = 0;
  unsigned int maxdur = 0;
  unsigned int mindur = 0;
 
  for (int i = 0; i < measures; i++)
  {
    unsigned int etime = Timing();
    if (etime > maxdur)
      maxdur = etime;
    if (etime < mindur)
      mindur = etime;

    avgduration += etime;
    delay(1);
  }
 
  avgduration = (avgduration - (maxdur + mindur)) / (measures-2);
  return (avgduration /29 / 2);
}

Функция Timing() замеряет время которое потребовалось отражённому сигналу, чтобы вернуться. Функция Ranging() вызывает её 20 раз с задержкой 1 милисекунда, максимальное и минимальное значение отбрасываются, а оставшиеся усредняются. Затем просто проверяется, что если расстояние меньше заданного значения – 6 см, то произошло нажатие на кнопку. Работает это таким вот образом:

Другой пример использования датчика расстояния для распознавания движений – это взять например два таких датчика и поставить их рядом. Соответственно , когда оба они сработают, то в зависимости от того, который первый сработал определить направление. Для разнообразия решил использовать инфракрасные дальномеры.

20160302_183300У них меньше дальность обнаружения, но есть цифровой выход, который говорит – близко объект или нет. Вот мой пример реализации:

#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define din1 4
#define ain1 A0

#define din2 2
#define ain2 A1

bool t1 = false;
bool t2 = false;
short drctn = 0;

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.display();
 
  pinMode(din1,INPUT);
  pinMode(ain1,INPUT);

  pinMode(din2,INPUT);
  pinMode(ain2,INPUT);  
 
}

void loop() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);  

  unsigned int ad1=analogRead(ain1);
  unsigned int ad2=analogRead(ain2);

  int din1_val = digitalRead(din1);
  int din2_val = digitalRead(din2);

  if (t1 && !t2 && (din2_val == LOW))
    drctn = 1;
  else if (t2 && !t1 && (din1_val == LOW))
    drctn = -1;
  else if (!t1 && !t2)
    drctn = 0;
    
  if(din1_val==LOW)
  {
    display.print("1: Near | ");   
    t1 = true;
  }
  else
  {
    t1 = false;
    display.print("1: Far  | ");       
  }
 
  if(din2_val==LOW)
  {
    display.println("2: Near ");   
    t2 = true;
  }
  else
  {
    t2 = false;
    display.println("2: Far  ");       
  }
 
  display.print(ad1);
  display.print(" | ");
  display.println(ad2);

  if (drctn == -1)
    display.println("left");
  else if (drctn == 1)
    display.println("right");

 
  display.display();
  delay(100);
}

И вот как это работает:

Соответственно если добавить ещё один датчик расстояния, то можно расширить количество распознаваемых движений.

20160303_143009Например теперь можно распознать движения вверх и вниз. Вот как это работает:

Скетч отличается не сильно – идея та же самая. Если сначала сработал верхний дальномер а потом оба нижних, то движение вниз, если сначала оба нижних, а потом верхний, то движение вверх.

Ещё один момент, которй можно сюда добавить – это “нажатие” как в примере с ультразвуковым датчиком. Вот полный скетч, который включает движения влево, вправо, вверх, вниз и нажатие.

#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define din1 4
#define ain1 A0

#define din2 2
#define ain2 A1

#define din3 7
#define ain3 A2

bool t1 = false;
bool t2 = false;
bool t3 = false;
short drctn = 0;

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.display();
 
  pinMode(din1,INPUT);
  pinMode(ain1,INPUT);

  pinMode(din2,INPUT);
  pinMode(ain2,INPUT);  

  pinMode(din3,INPUT);
  pinMode(ain3,INPUT);    
 
}

void loop() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);  

  unsigned int ad1=analogRead(ain1);
  unsigned int ad2=analogRead(ain2);
  unsigned int ad3=analogRead(ain3);

  int din1_val = digitalRead(din1);
  int din2_val = digitalRead(din2);
  int din3_val = digitalRead(din3);

  if (drctn == 0)
  {
    if (t1 && !t2 && (din2_val==LOW))
      drctn = 1;
    else if (t2 && !t1 && (din1_val==LOW))
      drctn = -1;
    else if (t1 && t2 && (din3_val==LOW))
      drctn = 2;
    else if (t3 && !t2 && !t2 && (din1_val==LOW) && (din2_val==LOW))
      drctn = -2;    
    else if (!t1 && !t2 && !t3 &&(din1_val==LOW) && (din2_val==LOW)&& (din3_val==LOW))
      drctn = -3;
  }
  if(din1_val==LOW)
  {
    display.print("1 Near ");   
    t1 = true;
  }
  else
  {
    t1 = false;
    display.print("1 Far ");       
  }
 
  if(din2_val==LOW)
  {
    display.print("2 Near ");   
    t2 = true;
  }
  else
  {
    t2 = false;
    display.print("2 Far ");       
  }

  if (din3_val==LOW)
  {
    display.println("3 Near");
    t3=true;
  }
  else
  {
    t3 = false;
    display.println("3 Far");
  }
 
  display.print(ad1);
  display.print(" ");
  display.print(ad2);
  display.print(" ");
  display.println(ad3);

  if (drctn == -1)
    display.println("left");
  else if (drctn == 1)
    display.println("right");
  else if (drctn == 2)
    display.println("up");
  else if (drctn == -2)
    display.println("down");
  else if (drctn == -3)
    display.println("push");
 
  display.display();

  if (!t1 && !t2 && !t3)
    drctn = 0;  
  delay(100);
}

и  пример того, как это работает

в следующем посте уже будут готовые решения вместо кустарных. Как вам идея управления жестами в целом?

Leave a Reply