В этой статье мы рассмотрим достаточно интересный проект виртуальной реальности на основе платы Arduino Nano и бесплатной программной среды Processing (программа и для Arduino, и для Processing приведены в конце статьи). Наверняка многие из вас находятся под большим впечатлением от фильма "Железный человек", в котором главную роль сыграл Роберт Дауни-младший. В этом фильме главный герой в виртуальной реальности конструировал удивительные вещи, мы же в этой статье постараемся реализовать хотя бы частичку того, что было показано в этом фильме.
В этом проекте мы будем двигать рукой перед экраном компьютера, при этом на экране компьютера будет двигаться указатель и выполняться некоторые задачи. В самом простейшем варианте мы с помощью движений руки сможем рисовать на экране компьютера. Также мы рассмотрим как включать/выключать светодиод с помощью виртуальных движений вашей руки, а также делать клики с помощью ваших пальцев в воздухе. Этот проект может послужить основой для реализации многих других интересных проектов в мире виртуальной реальности.
Концепция проекта
Для реализации этого проекта мы должны объединить мощь таких технологий как Arduino и Processing. Многие из вас наверняка знакомы Arduino, а вот Processing может быть для вас в диковинку. Processing – это приложение, подобное Arduino, также с открытым исходным кодом и бесплатное для скачивания. Используя Processing вы можете написать простые программы для персонального компьютера, создать Android приложение и многое другое. Также в нем есть возможности для обработки изображений и распознавания голоса. Также, как и Arduino, его достаточно просто изучить, но если вы ничего не слышали об этом приложении и не горите желанием его изучать, то не беспокойтесь – материал этой статьи изложен таким понятным языком, что каждый сможет самостоятельно повторить то, что описано в данной статье.
В этом проекте мы будем использовать Processing для создания простого системного приложения, которое обеспечивает пользовательский интерфейс и отслеживает позицию нашей руки с помощью функций обработки изображений.
Мы будем имитировать клики левой и правой кнопки мыши с помощью наших пальцев. Для этого мы использовали два датчика Холла – один на указательном пальце и один на среднем пальце. Показания с этих датчиков будут считываться платой Arduino Nano. Затем плата Arduino будет передавать информацию об этих кликах на компьютер с помощью технологии Bluetooth.
Необходимые компоненты
- Плата Arduino Nano (купить на AliExpress).
- Датчик Холла A3144 (2 шт.) (купить на AliExpress).
- Небольшой кусок магнита.
- Bluetooth модуль (HC-05/HC-06) (купить на AliExpress).
- Батарея на 9 В.
- Соединительные провода.
- Точечная (перфорированная) плата.
- Пара перчаток.
- Arduino IDE (Software).
- Processing IDE(Software).
- Компьютер (ноутбук) с веб-камерой и Bluetooth (можно использовать внешний модуль Bluetooth и внешнюю веб-камеру).
Работа схемы
Схема устройства представлена на следующем рисунке.
Плата Arduino, резисторы и соединительная колодка припаиваются на точечную плату как показано на следующем рисунке.
Датчик Холла и Bluetooth модуль припаиваются к соединительному проводу как показано на следующем рисунке.
Как только описанные шаги будут выполнены можно приступать к креплению компонентов на перчатки. Мы использовали синтетические перчатки, которые можно купить в любой аптеке. Магнит необходимо закрепить на большом пальце, а датчики Холла – на среднем и указательном пальцах. Мы использовали липкую ленту чтобы закрепить компоненты на необходимых местах. После закрепления всех необходимых компонентов вы должны получить примерно следующую картину:
Теперь откроем Arduino IDE и начнем программирование.
Программа для Arduino
Функции программы для платы Arduino весьма просты – она должна считывать состояние датчиков Холла и передавать их в эфир с помощью Bluetooth модуля. Также она должна принимать данные по Bluetooth и включать/выключать светодиоды расположенные на плате в зависимости от принятых данных. Полный текст программы приведен в конце статьи, здесь же только объяснены наиболее важные ее фрагменты.
if (Phs1!=HallState_1 || Phs2!=HallState_2) //проверяем нажаты ли клавиши
{
if (HallState_1==LOW && HallState_2==LOW)
Aisha.write(1);
if (HallState_1==HIGH && HallState_2==LOW)
Aisha.write(2);
if (HallState_1==LOW && HallState_2==HIGH)
Aisha.write(3);
if (HallState_1==HIGH && HallState_2==HIGH)
Aisha.write(4);
}
В приведенных строках кода мы проверяем состояние датчиков Холла и в зависимости от результатов проверки передаем по Bluetooth соответствующие значения. К примеру, если с выхода датчика Холла № 1 поступает сигнал высокого уровня, а с выхода датчика Холла № 2 сигнал низкого уровня, тогда мы с помощью Bluetooth модуля будем передавать в эфир значение “2”. Удостоверьтесь в том, что вы записываете значение в Bluetooth модуль, но не печатаете его. Потому что в этом случае (если они просто записаны) их будет легко прочитать в Processing. Также значение будет передаваться по Bluetooth только если оно не то же самое как предыдущее значение.
if (BluetoothData=='y')
digitalWrite(ledpin,HIGH);
if (BluetoothData=='n')
digitalWrite(ledpin,LOW);
Эти строки используются для включения/выключения светодиода, расположенного на плате Arduino и подключенного к контакту 13, в зависимости от значения, принятого Bluetooth модулем. К примеру, если Bluetooth модуль принимает ‘y’, то светодиод включается, а если модуль принимает ‘n’ – то светодиод выключается.
Программа для Processing
Processing представляет собой достаточно мощную бесплатную среду программирования. Ее официальный сайт - https://www.processing.org/. На нашем сайте мы уже неоднократно использовали ее для создания различных проектов.
В этом проекте программа для Processing должна формировать пользовательский интерфейс, а также производить обработку изображений чтобы отслеживать определенны объект. Программа имеет 4 экрана:
- Экран калибровки.
- Главный экран.
- Экран рисования.
- Экран включения/выключения светодиода.
Мы можем производить навигацию между этими экранами простыми движениями наших рук, как бы перетаскивая экраны по воздуху. Также мы можем делать клики в необходимых местах чтобы включать/выключать светодиод или что-нибудь нарисовать на экране.
Вы можете просто скопировать и вставить текст программы на Processing (приведенный в конце статьи), а также изменить его в соответствии со своими пожеланиями, или просто скачать EXE файл (исполнимый файл) по этой ссылке с облака mail.ru. Для запуска программы необходимо выполнить следующие шаги:
- Установить JAVA на ваш компьютер (если он у вас не установлен).
- Установить веб-камеру на свой компьютер.
- Подать питание на плату Arduino и подключить ваш Bluetooth модуль по технологии Bluetooth к вашему компьютеру.
- Запустить программу.
Если все прошло нормально, то светодиод на вашем Bluetooth модуле будет гореть непрерывно, также будет работать ваша веб-камера. Посмотрите видео в конце статьи чтобы узнать как правильно калибровать и использовать эту программу.
Если вы хотите модифицировать эту программу под свои потребности, то вам будет необходимо сделать следующие вещи. Скачать Processing IDE по следующей ссылке. Обучающих материалов по Processing в сети достаточно много (в том числе и видеокурсов на youtube) – правда большинство из них на английском языке, воспользуйтесь поиском.
Processing имеет способность считывать последовательные данные (Serial data), в нашем случае эти данные поступают от Bluetooth COM порта. Вы должны выбрать к какому COM порту будет подключен ваш Bluetooth с помощью следующей команды:
port = new Serial(this,Serial.list()[1],9600);
В нашем случае мы выбрали 1-й COM порт (поскольку нумерация массивов начинается с 0, то на самом деле это 2-й COM порт из имеющихся на нашем компьютере) который будет иметь обозначение COM5 как показано на следующем рисунке. Наш Bluetooth модуль будет работать на скорости 9600 бод/с.
Также, как было указано, Processing имеет способность обрабатывать изображения, в нашем проекте изображения вставляются внутрь скетча с использованием веб-камеры. В каждом изображении мы отслеживаем конкретный объект. Больше подробностей об этом процессе вы можете узнать в следующем руководстве.
В исходном коде программы по возможности объяснены ее ключевые моменты с использованием комментариев. Вы можете скачать эти файлы по следующей ссылке.
Работа проекта
Когда вся аппаратная часть проекта будет готова вы наконец то можете одеть перчатки с закрепленным на них оборудованием. Подайте питание на плату Arduino и запустите программу Processing. Светодиод на Bluetooth модуле должен начать гореть непрерывно – это будет означать что программа установила связь по Bluetooth с вашей платой Arduino.
Вы увидите экран где вы должны будете выбрать объект, который необходимо отслеживать. Это может быть выполнено при помощи клика на объекте. В нашем случае, как видно на рисунке, мы выбрали диск синего цвета. Теперь вы можете двигать ваш объект, при этом вы заметите что указатель следует за вашим объектом. Для получения наилучших результатов используйте хорошо освещенную комнату и объект с уникальным цветом.
Теперь коснитесь указательным пальцем своего большого пальца, при этом вы увидите сообщение - “Key 1 Pressed” (клавиша 1 нажата). Затем коснитесь средним пальцем своего большого пальца, при этом вы увидите сообщение - “Key 2 Pressed” (клавиша 2 нажата). Если все работает именно так, это значит что система работает правильно и если вы увидели эти 2 сообщения это свидетельствует о том, что калибровка закончена. Теперь нажмите кнопку "Done ".
Когда кнопка Done будет нажата, вы будете перенаправлены на главный экран программы, где вы сможете рисовать на экране компьютера движениями своих рук в воздухе, а также включать/выключать встроенный в плату Arduino светодиод. Более подробно все эти процессы вы можете посмотреть в видео в конце статьи.
Исходный код программы
Код программы для Arduino
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include <SoftwareSerial.h> // подключение библиотеки последовательной связи SoftwareSerial Aisha(11, 12); // TX, RX int ledpin=13; // led on D13 will show blink on / off int hall_1=9; int hall_2=10; int BluetoothData; // данные получаемые от компьютера int HallState_1,HallState_2; int change; int Phs1,Phs2; void setup() { Aisha.begin(9600); //Bluetooth модуль работает на скорости 9600 бод/с pinMode(ledpin,OUTPUT); // контакт со светодиодом на вывод данных pinMode(hall_1,INPUT); //hall sensor 1 на ввод данных pinMode(hall_2,INPUT); //hall sensor 2 на ввод данных } void loop() { if (Aisha.available()) //если с компьютера получены данные BluetoothData=Aisha.read(); //считываем и сохраняем их в переменной BluetoothData Phs1=HallState_1; Phs2=HallState_2; HallState_1 = digitalRead(hall_1); HallState_2 = digitalRead(hall_2); if (Phs1!=HallState_1 || Phs2!=HallState_2) // проверяем нажаты ли клавиши { if (HallState_1==LOW && HallState_2==LOW) Aisha.write(1); if (HallState_1==HIGH && HallState_2==LOW) Aisha.write(2); if (HallState_1==LOW && HallState_2==HIGH) Aisha.write(3); if (HallState_1==HIGH && HallState_2==HIGH) Aisha.write(4); } if (BluetoothData=='y') digitalWrite(ledpin,HIGH); if (BluetoothData=='n') digitalWrite(ledpin,LOW); } |
Программа для Processing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
import processing.video.*; // подключаем библиотеку для работы с видео import processing.serial.*; // подключаем библиотеку для работы с последовательным портом (Bluetooth) //**Global Variable Declarations**// Serial port; //port – это переменная, которая будет использоваться для последовательной связи int data; boolean calibration= false; int mirror =0; int mirrorn =-1; PImage Done,Aisha,Paint,LED_Toggle,LED_on,LED_off; boolean key1,key2,key3,movePaint,PaintScreen,PaintScreenClear,moveLED,LEDscreen; float Paintx,Painty,avgX,avgY,LEDx,LEDy; int count; PImage img = createImage(380, 290, RGB); int Px,Py; Capture video; //создаем объект с именем video color trackColor; // переменная для хранения цвета (объекта), который мы будем отслеживать float threshold = 50 ; // можно изменить по вашему усмотрению //_____End of variable declaration______// //*функция чтобы считать все изображения из каталога данных скетча*// void loadImages() { Done = loadImage("Done.png"); Aisha = loadImage ("Aisha.png"); Paint = loadImage("Paint.png"); LED_Toggle = loadImage("LED_Toggle.png"); LED_on = loadImage("LED_on.png"); LED_off = loadImage ("LED_off.png"); } //_____End of variable declaration______// //**Executes only ones**// void setup() { size(800, 600); loadImages(); String[] cameras = Capture.list(); printArray(cameras); video = new Capture(this, cameras[34]); video.start(); key1=key2=key3=false; Paintx=width/10; Painty=height/8.5; LEDx=width/1.1; LEDy=height/8.5; movePaint=PaintScreen=PaintScreenClear=moveLED=LEDscreen=false; port = new Serial(this,Serial.list()[1],9600); println(Serial.list()); } //**End of Setup**// //**Triggered to update each frame of the video**// void captureEvent(Capture video) // когда поступает новое изображение { video.read(); } //reas it as a video //*функция чтобы указать какой цвет отслеживать*// void Calibrate() { image(video,0,0); imageMode(CORNERS); image(Done,width/1.2,height/1.1,width,height); // позиция кнопки Done if (mouseX>width/1.2 && mouseY>height/1.1) // если указатель мыши внутри кнопки Done { calibration=true; cursor(HAND); mirrorn=1; mirror=width; } fill(#1B96E0); textSize(20); if (key1==true) // если датчик Холла 1 активен в Arduino text("Key-1 Pressed",width/12,height/1.05); // текст и его позиция if (key2==true) // если датчик Холла 2 активен в Arduino text("Key-2 Pressed",width/12,height/1.05); // текст и его позиция } //_____конец калибровки______// //* функция для представления главного экрана программы*// void UI() { imageMode(CORNERS); image(Aisha,0,0,width,height); imageMode(CENTER); if ((avgX<(width/10+((width/4)/2)) && avgY<(height/8.5+((height/4)/2)) && key1==true) || (movePaint==true&&key1==true)) // если произошел клик внутри изображения { movePaint=true; image (Paint, avgX,avgY,width/4, height/4); // перетаскиваем изображение } else if (movePaint==false) image (Paint, Paintx,Painty,width/4, height/4); // помещаем изображение в угол else PaintScreen=true; if ((avgX>(width/1.1-((width/4)/2)) && avgY<(height/8.5+((height/4)/2)) && key1==true) || (moveLED==true&&key1==true)) // если произошел клик внутри изображения { moveLED=true; image (LED_Toggle, avgX,avgY,width/4, height/4); // перетаскиваем изображение } else if (moveLED==false) image (LED_Toggle, LEDx,LEDy,width/4, height/4); // помещаем изображение в угол else LEDscreen=true; } //_____End of main screen function______// //* функция для представления экрана рисования*// void Paintfun() { imageMode(CENTER); background(#0B196A); image (Paint, width/2,height/2,width/1.5, height); img.loadPixels(); for (int IX = 210, Px=0; IX<=590; IX++, Px++) { for (int IY = 85, Py=0; IY<=375; IY++, Py++) { if ((dist(avgX,avgY,IX,IY)<4) && key1==true) img.pixels[(Px+(Py*img.width))] = color(255); //color of the paint background updated if (key2==true) PaintScreen = false; } } img.updatePixels(); image(img, width/2, height/2.6); } //_____End of main Paintscreen function______// //* функция для отображения экрана включения/выключения светодиода*// void LEDfun() { imageMode(CENTER); background(255); image(LED_on,(width/2 - width/4), height/3,width/4, height/5); image(LED_off,(width/2 + width/4), height/3,width/4, height/5); textSize(50); textAlign(CENTER); if (key1==true && avgX<300 && avgY>150 && avgX>95 && avgY<260) { fill(#751EE8); text("LED turned on",width/2,height/1.5); port.write(121); } if (key1==true && avgX<700 && avgY>150 && avgX>500 && avgY<260) { fill(#FC0808); text("LED turned off",width/2,height/1.5); port.write(110); } } //_____End of main LEDscreen function_____// //* функция чтобы узнать какая клавиша нажата*// void key_select() { switch(data){ case 1: key1=true; key2=true; break; case 2: key1=false; key2=true; break; case 3: key1=true; key2=false; break; case 4: key1=false; key2=false; break; } } //_____End of function______// void draw() { if (port.available()>0) // если поступает значение по Bluetooth { data=port.read(); //считываем это значение и сохраняем в переменной println(key1,key2,data); //print for debugging (печатаем для отладки программы) key_select(); // переключаем переменные key 1 и key2 } video.loadPixels(); if (calibration==false) // если калибровка не сделана Calibrate(); //экран калибровки if (calibration==true && (PaintScreen==false || LEDscreen==false) ) UI(); //главный экран if (PaintScreen==true && calibration ==true) Paintfun(); //экран рисования if (LEDscreen==true && calibration ==true) LEDfun(); // экран включения/выключения светодиода if (key2==true) movePaint=PaintScreen=PaintScreenClear=moveLED=LEDscreen=false; //идем обратно на главный экран avgX = avgY = count = 0; // начинаем цикл чтобы исследовать каждый пиксель (точку) на экране for (int x = 0; x < video.width; x++ ) { for (int y = 0; y < video.height; y++ ) { int loc = x + y * video.width; // какой текущий цвет color currentColor = video.pixels[loc]; float r1 = red(currentColor); float g1 = green(currentColor); float b1 = blue(currentColor); float r2 = red(trackColor); float g2 = green(trackColor); float b2 = blue(trackColor); float d = distSq(r1, g1, b1, r2, g2, b2); if (d < threshold*threshold) { stroke(255); strokeWeight(1); // point((mirror-x)*mirrorn, y); avgX += x; avgY += y; count++; } } } if (count > 0) { avgX = avgX / count; avgY = avgY / count; // рисуем круг на отслеживаемом пикселе fill(#21FADB); avgX = (mirror-avgX)*mirrorn; ellipse(avgX, avgY, 15, 15); } } float distSq(float x1, float y1, float z1, float x2, float y2, float z2) { float d = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) +(z2-z1)*(z2-z1); return d; } void mousePressed() { if(calibration==false) { int loc = mouseX + mouseY*video.width; trackColor = video.pixels[loc]; // считываем цвет который нужно отслеживать } } |