Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Руководим веб-камерой с поддержкой джойстика

Anna | 17.06.2014 | нет комментариев

Вступление

Лирика

Добродушный день. Мотивированный многочисленными постами на Прогре о самодельных роботах решил сделать и что-нибудь свое больше менее стоящее и увлекательное.

Вообще роботами я увлекаюсь давным-давно, но до типичного плана руки не доходили, в основном только игрался. Немножко подумав, придумал свой план, поискал детали, нарисовал очерки, пофантазировал на тему грядущих вероятностей робота. Детали заказал не небезызвестном сайте, и пока детали преодолевают путь из поднебесной решил реализовать один из модулей грядущего робота из того что есть под рукой. Правильней даже не реализовать сам модуль, а собрать прототип и написать софт, Дабы потом не отвлекаться на написание программы, да и тем больше пока идут все детали есть море свободного времени, а желание что-либо сделать, не дает покоя.

Под рукой у меня оказалась платка Arduino Diecimila, несколько сервоприводов, веб-камера, джойстик и ультразвуковой дальномер. Соответственно сразу появилось желание сделать «компьютерное зрение» на основе веб-камеры, с вероятностью как самостоятельной работы, так и ручного управления (джойстиком).

Что меня сподвигло написать эту статью?

Порывшись в интернете, я в основном находил каждый мусор, невнятные вопросы на форумах, отрывки из статей, немножко отдаленных от надобностей. В всеобщем и целом я не обнаружил отличной, полновесной статьи, которая бы от начала и до конца описывала создание двигающейся веб-камеры, с примерами кода, а уж тем больше совмещенные с дальномером и джойстиком.
Тогда решено было ничего огромнее не искать, так как времени на обработку статей и собирание во цельно каждой информации уходить стало огромнее, чем если делать все с нуля самому, тем больше, что множество статей теснее давным-давно устарело.

Задача чай банальная, посылать информацию с джойстика на Arduino, которая на определенный угол будет поворачивать 2 сервопривода с прикрепленной веб-камерой, и по необходимости считывать информацию с дальномера, отсылая ее в SerialPort.
Обдумав все еще раз, решил приступить к созданию данного прототипа самосильно. Поехали!

Основная часть

Сборка прототипа

Прототип был сделан в течение 5 минут. Внешний вид прототипа не волнует вообще, основная его цель — отработка программной части до приезда деталей для робота.
А сделал я его из первой попавшейся баночки из под каких-то витаминов, 2-х сервоприводов, веб-камеры, скрепки, изоленты и клеевого пистолета. Получилось следующее:

Фото

image

Сборка закончена, сервоприводы и ультразвуковой дальномер подключены к Arduino, Arduino к ПК, приступаем к программированию Arduino.

Программируем Arduino

Здесь все казалось дюже легко, так как джойстик подключается к ПК, основная обработка видео тоже будет на ПК, то Arduino займется лишь приемом и обработкой информации с ПК и управлением сервоприводами. Следственно нам нужно лишь читать Serial Port, обрабатывать каким-то образом поступающую информацию и как-то на нее реагировать.

Забегая немножко вперед сразу скажу, здесь и случилась оплошность, к которой мне пришлось возвратиться теснее позже написания программы на C#. Оплошность была вот в чем — я, наивный и полный энтузиазма, написал программку которая разбирает поступающую в Serial Port строку приблизительно дальнейшего вида «90:90» на две части, соответственно первая часть это градусы по координате X, вторая часть Y. При помощи монитора порта все было оттестировано и работало восхитительно, но когда была написана программа для управления с джойстика, при усиленной атаке порта строками с изменяющимися значениями, Arduino легко не поспевала считывать все ступенчато, следственно нередко строки превращались в «0:909», “:9090″ и тому сходственное.
Соответственно сервоприводы сходили с ума и принимали все расположения, помимо тех, что необходимы нам.

Следственно, не длинно думая, я пришел к итогу что нам необходим символ начала строки и символ конца строки. Вновь же, не длинно думая, символом начала строки был выбран 1-й символ латинского алфавита — «a», концом строки конечный — «z», а символы начала значений осей «x» и «y» соответственно. Итого входная строка принимала дальнейший вид: «ax90y90z».

Все бы отлично, если бы не дальномер. Дальномер ультразвуковой, расстояние он определяет на ура, но есть несколько нюансов. Во-первых, если угол между дальномером и стеной острее 45 градусов (плюс-минус), то звук отражается от стены по касательной, и значение, не соответствует реальности. Во-вторых достаточно огромный угол испускания сигнала, около 30 градусов(по мануалу), а замеряется расстояние до ближайшего объекта, благо что сигнал от объектов к которым датчик находится под углом, отражается в иную сторону, и мы получаем больше менее настоящее расстояние по прямой, но помехи все же бывают, и достаточно Зачастую. Следственно я дописал еще одну функцию, которая берет n замеров расстояния, складывает их и делит на кол-во, выставил n=10, так помехи стали больше сглажены и менее невидимы.

Код на Arduino был здесь же переписан и принял дальнейший вид:

Код Arduino

#include <Servo.h>
#include <String.h>
/*
Здесь реализован алгорифм приема строки
строка должна быть вида ax180y180z
Где a - символ начала строки
x - символ начала координат x
y - символ начала координат y
z - символ конца строки
*/
String str_X="";
String str_Y="";

int XY_Flag=0; // 1 = X, 2 = Y

Servo X_Servo;
Servo Y_Servo;

const int distancePin = 12;
const int distancePin2 = 11;

void setup()
{
  Serial.begin(115200);
  X_Servo.attach(7);
  Y_Servo.attach(8);
}

void loop()
{
  delay(50);
  if(Serial.available()>0) //считываем значения из порта
  {
    int inChar=Serial.read(); //считываем байт
    if(inChar == 97) { // Если это предисловие строки

      while(Serial.available()>0)
      {
        inChar=Serial.read(); //считываем байт
        if(inChar==120){ // x
          XY_Flag=1; 
          continue;
        }
        if(inChar==121){ // y
          XY_Flag=2;
          continue;
        }
        if(inChar==122){ // z (конец строки)
          XY_Flag=0;
        }

        if(XY_Flag==0)
          break; // Если конец строки, то преждевременный выход из цикла
        if(XY_Flag==1)
          str_X  =(char)inChar; //если X, то пишем в X
        if(XY_Flag==2)
          str_Y  =(char)inChar; //Если Y, то пишем в Y
      }
      if(XY_Flag==0) // Если был конец строки, то исполняем...
      {
        servo(str_X.toInt(), str_Y.toInt());     

        str_X=""; 
        str_Y=""; //очищаем переменные

       Serial.println("d"   String(trueDistance())   "z");
      }
    }
  }
}

void servo(int x, int y){ //говорим сервоприводам сколько градусов им необходимо взять :) 
  X_Servo.write(x);
  Y_Servo.write(y);
}

long trueDistance() //считываем датчик n раз и возвращаем среднее значение
{ 
  int n=10;
  long _value=0;

  for(int i =0; i<n; i  )
    _value  = distance();

  return _value/n;
}

long distance() //считываем показания ультразвукового дальномера
{
  long duration, cm;

  pinMode(distancePin, OUTPUT);
  digitalWrite(distancePin, LOW);
  delayMicroseconds(2);
  digitalWrite(distancePin, HIGH);
  delayMicroseconds(10);
  digitalWrite(distancePin, LOW);

  pinMode(distancePin, INPUT);
  duration = pulseIn(distancePin, HIGH);

  cm = microsecondsToCentimeters(duration);
 return cm;
}

long microsecondsToCentimeters(long microseconds) //переводим микросекунды в сантиметры
{
  return microseconds / 29 / 2;
}

Задача с неправильным разбором координат исчезла на вовсе, 100 из 100 испытаний пройдены удачно.

Основная руководящая программа (C#)

По началу хотел писать все на C под Qt, но в последствии все же пришлось писать на C#, ну да хорошо.

Что хотелось получить:
1. Идентификация лиц людей.
2. Наблюдение за лицом человека.
3. Ручное управление с поддержкой джойстика.
4. Определение расстояния до объекта.

Для распознавания лиц и итога изображения с веб-камеры, без каждых вопросов, была выбрана библиотека OpenCV, а правильней ее оболочка для C# — Emgu CV.

Для считывания расположения джойстика по началу применялась библиотека Microsoft.DirectX.DirectInput, которая мне чудовищно не понравилась, и я применил библиотеку SharpDX, притом достаточно удачно.

Что требовалось от программы:
1. Захватывать изображение с веб-камеры и выводить его на экран.
2. Распознавать лица на изображении, обводить их и получать координаты лица на изображении.
3. Формировать строку вида «ax90y90z» и отправлять ее в Serial Port для управления сервоприводами.
4. Считывать значения расположения джойстика.
5. Считывать показания с дальномера.

Сформулpermark! private void serialPortWrite(int X, int Y) //отсылаем ардуине координаты и читаем из порта дистанцию { try { coords = “ax” X “y” Y “z”; _serialPort.Write(coords); _distance = _serialPort.ReadLine(); if (_distance[0] == ‘d’) if (_distance[_distance.Length - 2] == ‘z’) { _distance = _distance.Remove(_distance.LastIndexOf(‘z’)).Replace(‘d’, ‘ ‘); } else _distance = “0″; else _distance = “0″; } catch { } } private void joy() //ручное управление джойстиком { joystick.Poll(); var datas = joystick.GetBufferedData(); foreach (var state in datas) { if (state.Offset.ToString() == “X”) X_joy = 180 – (state.Value / 363); else if (state.Offset.ToString() == “Y”) Y_joy = state.Value / 363; } serialPortWrite(X_joy, Y_joy); } private void Form1_Load(object sender, EventArgs e) { if (myCapture == null) { try { myCapture = new Capture(); } catch (NullReferenceException excpt) { MessageBox.Show(excpt.Message); } } if (myCapture != null) { if (captureInProgress) { Application.Idle -= GetVideo; } else { Application.Idle = GetVideo; } captureInProgress = !captureInProgress; } _serialPort.PortName = “COM3″; _serialPort.BaudRate = 115200; if (_serialPort.IsOpen) _serialPort.Close(); if (!_serialPort.IsOpen) _serialPort.Open(); directInput = new DirectInput(); joystickGuid = Guid.Empty; foreach (var deviceInstance in directInput.GetDevices(DeviceType.Gamepad, DeviceEnumerationFlags.AllDevices)) joystickGuid = deviceInstance.InstanceGuid; if (joystickGuid == Guid.Empty) foreach (var deviceInstance in directInput.GetDevices(DeviceType.Joystick, DeviceEnumerationFlags.AllDevices)) joystickGuid = deviceInstance.InstanceGuid; joystick = new Joystick(directInput, joystickGuid); joystick.Properties.BufferSize = 128; joystick.Acquire(); } private void JoyCheck_CheckedChanged(object sender, EventArgs e) { if (FaceCheck.Checked) FaceCheck.Checked = !JoyCheck.Checked; } private void FaceCheck_CheckedChanged(object sender, EventArgs e) { if (JoyCheck.Checked) JoyCheck.Checked = !FaceCheck.Checked; } private void RadarPaint() { Bitmap map = new Bitmap(pictureBox1.Size.Width, pictureBox1.Size.Height); Graphics g = Graphics.FromImage(map); var p = new Pen(System.Drawing.Color.Black, 2); System.Drawing.Point p1 = new System.Drawing.Point(); System.Drawing.Point p2 = new System.Drawing.Point(); System.Drawing.Point p3 = new System.Drawing.Point(); System.Drawing.Point p4 = new System.Drawing.Point(); p1.X = pictureBox1.Size.Width/2 ; //начало координат переводим в комфортное нам место p1.Y = pictureBox1.Size.Height; //посередине pictureBox’a внизу for (int i = 0; i < 181; i ) { serialPortWrite(i, 90); p2.X = Convert.ToInt32(Math.Ceiling(320 int.Parse(_distance) * Math.Cos(i * Math.PI / 180))); //считаем координаты точки p2.Y = Convert.ToInt32(Math.Ceiling(480 – int.Parse(_distance) * Math.Sin(i * Math.PI / 180))); if (i > 0) g.DrawLine(p, p2, p3); if (i % 18 == 0) { p4 = p2; p4.Y -= 50; g.DrawString(_distance, new Font(“Arial”, 18), new SolidBrush(System.Drawing.Color.Red), p4); } p3.X = p2.X; p3.Y = p2.Y; g.DrawLine(p, p1, p2); try { pictureBox1.Image = map; } catch (Exception e) { MessageBox.Show(e.Message); } } } private void button1_Click(object sender, EventArgs e) { if (FaceCheck.Checked || JoyCheck.Checked) { FaceCheck.Checked = false; JoyCheck.Checked = false; } Thread t = new Thread(RadarPaint); t.Start(); }

Класс DetectFace

  class DetectFace
    {
        public static void Detect(Image<Bgr, Byte> image, String faceFileName, String eyeFileName, List<Rectangle> faces)
        {
            CascadeClassifier face = new CascadeClassifier(faceFileName);
           // CascadeClassifier eye = new CascadeClassifier(eyeFileName);

            Image<Gray, Byte> gray = image.Convert<Gray, Byte>();

            gray._EqualizeHist();

            Rectangle[] facesDetected = face.DetectMultiScale(
               gray,
               1.1,
               5,
               new Size(70, 70),
               Size.Empty);
            faces.AddRange(facesDetected);
        }
    }

В выводе получаем в

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB