Настройка motion на две web камеры. Настраиваем видеонаблюдение на базе Motion. Что мы имеем на выходе

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

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

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

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

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

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

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

Фото

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

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

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

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

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

Код на Arduino был тут же переписан и принял следующий вид:

Код Arduino

#include #include /* Тут реализован алгоритм приема строки строка должна быть вида 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

Проблема с неправильным разбором координат исчезла на совсем, 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. Считывать показания с дальномера.

Сформулировав задачи, приступаем к программированию.

Библиотечка SharpDX позволяет нам находить подключенный джойстик и получать с него значения осей (от 0 до 65535), нажатие и отпускание клавиш джойстика. Сервоприводы могут поворачиваться от 0 до 180 градусов, соответственно нужно преобразовывать значения осей джойстика от 0 до 180. Я просто поделил возвращаемое значение на 363, и получил на выходе значения от 0 до 180. Далее написал функцию которая формирует строку положения сервоприводов и отправляет ее в порт.

Вывод изображения и распознавание лиц написаны с использованием OpenCV и ничего сложного не представляют (для нас).

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

Код (на всякий случай с подробными комментариями, по возможности):

Класс формы

Capture myCapture; private bool captureInProgress = false; string _distance = "0"; string coords; int X_joy = 90; int Y_joy = 90; SerialPort _serialPort = new SerialPort(); Image image; DirectInput directInput; Guid joystickGuid; Joystick joystick; Thread th; private int GRAD_TURN_X = 2; private int GRAD_TURN_Y = 2; private void GetVideo(object sender, EventArgs e) { myCapture.FlipHorizontal = true; image = myCapture.QueryFrame(); try { // Image gray = image.Convert().Canny(100, 60); // CamImageBoxGray.Image = gray; } catch { } /*детектор лиц */ if (FaceCheck.Checked) { List faces = new List(); DetectFace.Detect(image, "haarcascade_frontalface_default.xml", "haarcascade_eye.xml", faces); foreach (System.Drawing.Rectangle face in faces) { image.Draw(face, new Bgr(System.Drawing.Color.Red), 2); int faceX = face.X + face.Width / 2; int faceY = face.Y + face.Height / 2; if ((faceX - 320 > 120) || (faceX - 320 < -120)) //Чем дальше от центра изображения лицо, тем быстрее двигаем камеру GRAD_TURN_X = 4; else if ((faceX - 320 > 80) || (faceX - 320 < -80)) GRAD_TURN_X = 3; else GRAD_TURN_X = 2; if ((faceY - 240 > 120) || (faceY - 240 < -120)) GRAD_TURN_Y = 4; else if ((faceY - 240 > 80) || (faceY - 240 < -80)) GRAD_TURN_Y = 3; else GRAD_TURN_Y = 2; label7.Text = faceX.ToString(); label8.Text = faceY.ToString(); if (!JoyCheck.Checked) { if (faceX > 370) X_joy += GRAD_TURN_X; else if (faceX < 290) X_joy -= GRAD_TURN_X; if (faceY > 270) Y_joy -= GRAD_TURN_Y; else if (faceY < 210) Y_joy += GRAD_TURN_Y; serialPortWrite(X_joy, Y_joy); } } } /*=============*/ System.Drawing.Rectangle rect1 = new System.Drawing.Rectangle(305, 240, 30, 1); System.Drawing.Rectangle rect2 = new System.Drawing.Rectangle(320, 225, 1, 30); System.Drawing.Rectangle rect3 = new System.Drawing.Rectangle(0, 0, 640, 22); image.Draw(rect1, new Bgr(System.Drawing.Color.Yellow), 1); image.Draw(rect2, new Bgr(System.Drawing.Color.Yellow), 1); image.Draw(rect3, new Bgr(System.Drawing.Color.Black), 22); MCvFont f = new MCvFont(FONT.CV_FONT_HERSHEY_TRIPLEX, 0.9, 0.9); image.Draw("Distance: " + _distance + " cm", ref f, new System.Drawing.Point(0, 30), new Bgr(0, 255, 255)); CamImageBox.Image = image; if (JoyCheck.Checked) { th = new Thread(joy); // ручное управление, запускаем в потоке th.Start(); } label1.Text = X_joy.ToString(); label2.Text = Y_joy.ToString(); label3.Text = coords; } private void ReleaseData() { if (myCapture != null) myCapture.Dispose(); } public Form1() { InitializeComponent(); } private void serialPortWrite(int X, int Y) //отсылаем ардуине координаты и читаем из порта дистанцию { try { coords = "ax" + X + "y" + Y + "z"; _serialPort.Write(coords); _distance = _serialPort.ReadLine(); if (_distance == "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 image, String faceFileName, String eyeFileName, List faces) { CascadeClassifier face = new CascadeClassifier(faceFileName); // CascadeClassifier eye = new CascadeClassifier(eyeFileName); Image gray = image.Convert(); gray._EqualizeHist(); Rectangle facesDetected = face.DetectMultiScale(gray, 1.1, 5, new Size(70, 70), Size.Empty); faces.AddRange(facesDetected); } }

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

Видео
Вот, что получилось по завершении.



Заключение

Итоги
Оказалось все довольно просто. Цель достигнута, прототип готов. Есть над чем работать и заняться в свободное время, ожидая посылку с компонентами для робота.
Планы на будущее
Следующим шагом будет построение колесной платформы для робота, настройка удаленного управления (WiFi, 3G)., навешивание датчиков (температура, давление и прочее), синтез речи. В хотелках так же имеются планы по поводу механической руки.

Думаю, если будет интерес к данной статье и ее продолжению, то оно обязательно последует! Исправления и критика приветствуются!

Спасибо за внимание!

Теги:

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

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

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

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

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

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

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

Фото

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

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

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

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

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

Код на Arduino был тут же переписан и принял следующий вид:

Код Arduino

#include #include /* Тут реализован алгоритм приема строки строка должна быть вида 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

Проблема с неправильным разбором координат исчезла на совсем, 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. Считывать показания с дальномера.

Сформулировав задачи, приступаем к программированию.

Библиотечка SharpDX позволяет нам находить подключенный джойстик и получать с него значения осей (от 0 до 65535), нажатие и отпускание клавиш джойстика. Сервоприводы могут поворачиваться от 0 до 180 градусов, соответственно нужно преобразовывать значения осей джойстика от 0 до 180. Я просто поделил возвращаемое значение на 363, и получил на выходе значения от 0 до 180. Далее написал функцию которая формирует строку положения сервоприводов и отправляет ее в порт.

Вывод изображения и распознавание лиц написаны с использованием OpenCV и ничего сложного не представляют (для нас).

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

Код (на всякий случай с подробными комментариями, по возможности):

Класс формы

Capture myCapture; private bool captureInProgress = false; string _distance = "0"; string coords; int X_joy = 90; int Y_joy = 90; SerialPort _serialPort = new SerialPort(); Image image; DirectInput directInput; Guid joystickGuid; Joystick joystick; Thread th; private int GRAD_TURN_X = 2; private int GRAD_TURN_Y = 2; private void GetVideo(object sender, EventArgs e) { myCapture.FlipHorizontal = true; image = myCapture.QueryFrame(); try { // Image gray = image.Convert().Canny(100, 60); // CamImageBoxGray.Image = gray; } catch { } /*детектор лиц */ if (FaceCheck.Checked) { List faces = new List(); DetectFace.Detect(image, "haarcascade_frontalface_default.xml", "haarcascade_eye.xml", faces); foreach (System.Drawing.Rectangle face in faces) { image.Draw(face, new Bgr(System.Drawing.Color.Red), 2); int faceX = face.X + face.Width / 2; int faceY = face.Y + face.Height / 2; if ((faceX - 320 > 120) || (faceX - 320 < -120)) //Чем дальше от центра изображения лицо, тем быстрее двигаем камеру GRAD_TURN_X = 4; else if ((faceX - 320 > 80) || (faceX - 320 < -80)) GRAD_TURN_X = 3; else GRAD_TURN_X = 2; if ((faceY - 240 > 120) || (faceY - 240 < -120)) GRAD_TURN_Y = 4; else if ((faceY - 240 > 80) || (faceY - 240 < -80)) GRAD_TURN_Y = 3; else GRAD_TURN_Y = 2; label7.Text = faceX.ToString(); label8.Text = faceY.ToString(); if (!JoyCheck.Checked) { if (faceX > 370) X_joy += GRAD_TURN_X; else if (faceX < 290) X_joy -= GRAD_TURN_X; if (faceY > 270) Y_joy -= GRAD_TURN_Y; else if (faceY < 210) Y_joy += GRAD_TURN_Y; serialPortWrite(X_joy, Y_joy); } } } /*=============*/ System.Drawing.Rectangle rect1 = new System.Drawing.Rectangle(305, 240, 30, 1); System.Drawing.Rectangle rect2 = new System.Drawing.Rectangle(320, 225, 1, 30); System.Drawing.Rectangle rect3 = new System.Drawing.Rectangle(0, 0, 640, 22); image.Draw(rect1, new Bgr(System.Drawing.Color.Yellow), 1); image.Draw(rect2, new Bgr(System.Drawing.Color.Yellow), 1); image.Draw(rect3, new Bgr(System.Drawing.Color.Black), 22); MCvFont f = new MCvFont(FONT.CV_FONT_HERSHEY_TRIPLEX, 0.9, 0.9); image.Draw("Distance: " + _distance + " cm", ref f, new System.Drawing.Point(0, 30), new Bgr(0, 255, 255)); CamImageBox.Image = image; if (JoyCheck.Checked) { th = new Thread(joy); // ручное управление, запускаем в потоке th.Start(); } label1.Text = X_joy.ToString(); label2.Text = Y_joy.ToString(); label3.Text = coords; } private void ReleaseData() { if (myCapture != null) myCapture.Dispose(); } public Form1() { InitializeComponent(); } private void serialPortWrite(int X, int Y) //отсылаем ардуине координаты и читаем из порта дистанцию { try { coords = "ax" + X + "y" + Y + "z"; _serialPort.Write(coords); _distance = _serialPort.ReadLine(); if (_distance == "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 image, String faceFileName, String eyeFileName, List faces) { CascadeClassifier face = new CascadeClassifier(faceFileName); // CascadeClassifier eye = new CascadeClassifier(eyeFileName); Image gray = image.Convert(); gray._EqualizeHist(); Rectangle facesDetected = face.DetectMultiScale(gray, 1.1, 5, new Size(70, 70), Size.Empty); faces.AddRange(facesDetected); } }

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

Видео
Вот, что получилось по завершении.



Заключение

Итоги
Оказалось все довольно просто. Цель достигнута, прототип готов. Есть над чем работать и заняться в свободное время, ожидая посылку с компонентами для робота.
Планы на будущее
Следующим шагом будет построение колесной платформы для робота, настройка удаленного управления (WiFi, 3G)., навешивание датчиков (температура, давление и прочее), синтез речи. В хотелках так же имеются планы по поводу механической руки.

Думаю, если будет интерес к данной статье и ее продолжению, то оно обязательно последует! Исправления и критика приветствуются!

Спасибо за внимание!

13 ноября 2010 в 03:39

Если нет денег на AXIS, или управляемая вебкамера своими, в меру кривыми, руками

  • DIY или Сделай сам

Когда я впервые увидел трансляцию видео с управляемой вебкамеры на 13-м этаже МГУ, меня это очень впечатлило. Настолько, что захотелось устроить нечто аналогичное. На камеры типа AXIS 214PTZ лишних денег у меня нет, так что пришлось на некоторое время эту идею оставить. До тех пор, пока я не узнал про Arduino и про то, что к ней можно приделать «сетевую карту» и моторчики. Необходимые комплектующие быстренько были приобретены и у меня в наличии оказалось всё для сооружения полноценной вебкамеры, управляемой по сети.

Изучение интернетов показало что идея приделать камеру к сервам приходит первой ко всем счастливым обладателей ардуины и сервомашинок. Однако, ни одна из уже реализованных схем управления (кнопками, мышкой PS/2, с управляемого роутера, с компа, с вебстранички на встроенном в ардуину сервере) меня не впечатлило, ибо неудобно, имхо. Хотелось чего-то универсального, чтобы можно было выложить веб-интерфейс на сайт и с него управлять.

В итоге появилась вот такая штукенция:


Обычная вебкамера Logitech Pro 9000 (выигранная давным-давно в «Компьютерре») прикреплена к качалке сервы №1 (ось Y). Серва №1, в свою очередь, прикреплена к качалке сервы №2 (ось X). А серва №2 довольно крепко прикручена к основанию - тяжёлому деревянному бруску. Сервы - самые простенькие и дешёвые SRM-102 (примерно 400 руб. за штуку).

Вот и вся конструкция, всё элементарно. Для соединений использованы планки от детского конструктора (помните, такие старые советские металлические конструкторы? Они ещё есть в продаже).

Контролер всего этого безобразия состоит из связки Arduino (точнее, Freeduino 2009) и Ethernet Shield v2. Тоже ничего сложного.

Придумалась примерная схема управления. Скрипт в веб-интерфейсе получает команды (нажатия кнопок, движения мыши и т.д.) и в определённом формате пишет их в базу. Ардуина циклически делает GET-запрос скрипту на сервере и скрипт выдаёт все команды из БД, после чего база очищается. Может, можно и покрасивее сделать, но меня в таком виде пока всё устраивает.

На серваке у меня MySQL крутится, база HEAP (чтоб пошустрее было). Структура вот такая:

CREATE TABLE `servo` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`cam_id` tinyint(4) NOT NULL,
`action` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cam_id` (`cam_id`)
) ENGINE=MEMORY DEFAULT CHARSET=cp1251 AUTO_INCREMENT=1 ;

Далее идут скрипты. Первый, самый главный, скетч для собссно Ардуины. Скетч не особо вылизан, так что его стопудово можно улучшить. Но, так как он стабильно работает, меня пока устраивает. Как заливать скетч не объясняю, ибо если вы знакомы с Ардуино, вы это уже знаете. Я использую IDE Arduino 18, так что и скетч под неё. В новой версии IDE скорее всего не заведётся!

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

Index.php - собссно, страница с веб-интерфейсом управления камерой.
set.php - асинхронно вызываеся из index.php и пишет в БД нужные команды на перемещение камеры
get.php - скрипт вызывается удалённо Ардуиной, читает команды из БД и выдаёт их plain text"ом

В принципе, всё. Надеюсь, теперь все те, кто называл меня мегамозгом расслабятся и скажут «да это каждый дурак может»:-)

Конечно, тут есть над чем работать. Необходима какая-нибудь авторизация, дабы камеру не шмонали кто попало. Я намеренно выкладываю как есть для демонстрации базовых возможностей, элементарные детали управления.

Логика управления прекрасно программируется на Javascript в основном файле index.php Так как за размещение команд отвечает функция MakeAction, с ней можно творить что угодно. Например, на вебморде есть ссылки типа «Фиксированные точки - Балкон». Это обычный вызов этой самой MakeAction(11, координата_x, координата_y), т.е. куда камеру надо направить. И таким образом можно программировать любые перемещения. Но надо не забывать о том что таким образом можно засрать очередь команд по самое нехочу. Например, у меня была функция Javascript «влево_вправо». Которая в цикле изменяла координату X, в итоге камера циклично гоняла туда-сюда. И если у кого-то в браузере была включена эта функция, никак нельзя было её остановить, пока её не выключал тот кто её включил. Временное решение было ставить таймер на подгрузку через jQuery ещё одного файлика, который принудительно убивал тот таймер. Это некрасиво, но работало.

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

Теперь немного о процессе тестирования, ибо о нём я просто должен рассказать.

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

Я всё пытался разместить описание камеры здесь на Хабре, но из-за маленькой, но очень положительной кармы никак не мог это сделать. Ну ни как, хоть ты тресни. Отчаявшись, я разместил вопрос в Q&A . В результате мне карму залили по-полной, намного больше чем требовалось для размещения топика. За что, в очередной раз, спасибо ребятам. Народу с Хабра очень понравилась (судя по комментам) моя реализация управления, так что камера на данный момент, почти непрерывно крутится уже девять часов (взято из статистики трансляции на смотри.ком). Сервы холодные, не греются нифига. Ардуина слегка тёпленькая. За всё это время был только один глюк - ардуина сетку потеряла. Как оказалось, «у меня был обрыв» (с)Антон Уральский

Были ещё глюки, связанные с включенным очень долгим воспроизведением обученных перемещений (кто-то врубил обучение, долго водил мышой а потом нажал Play), так что этот режим я ограничил сотней команд. Само собой, это ограничение легко обходится:-)

Завтра у меня рабочий день, на ночь я камеру отключу, но утром включу снова, на работе. Там ещё веселее будет.

UPD> Первая конструкция (на деревянном бруске) оказалось не очень устойчивой - удалось несколько раз её опрокинуть, резко мотнув камеру из стороны в сторону. Так что появилась новая модификация - серва №2 прикручена к маленькому деревянному брусочку. В брусочке просверлена дырочка, как раз под диаметр винта на стандартном крепеже от штатива. Ну и брусочек приклеплён к штативу от обычного фотоаппарата. Конструкция получилась меганадёжная - народ упорно вот уже 7 часов пытается её опрокинуть, а не получается.

Вдобавок, рядом с основной камерой была поставлена вторая (точно такая же Logitech, но стационарная), через которую можно было наблюдать, как колбасится первая. Очень увлекательное зрелище, если честно.

На удивление быстрой получилась реакция камеры - задержка по отношению к управлению в веб-морде составляет (у меня, во всяком случае) доли секунды. Хотя я дома сижу, а сервер на работе и между нами обычный корбино/билайновский интернет. Притормаживает видеотрансляция на smotri.com, но это не всегда.

Несколько демонстрационных видеороликов (видео что-то не вставляется, так что просто ссылки даю).

166

Всем доброго времени суток.

Как всегда начнем с предыстории. Необходимо было реализовать видеонаблюдение за помещением, естественно с нулевым бюджетом, а как иначе. В наличие имелся сервер с Linux на борту и домашняя usb web камера. За основу был взят программный пакет Motion. В принципе ничего сложного в настройке нет, правим пару строк в конфиге и вуаля. Писать было ни о чем.

На днях обновил Ubuntu до версии 16.04.1 LTS и заодно, решено было обновить часть пакетов, в том числе Motion, и переделать систему видеонаблюдения. От usb камеры давно было решено отказаться, и ей на смену пришла дешевая IP камера (поддерживающая MJPEG поток) купленная на распродаже.


Отразим все на "бумаге"

Имеется :

  • Сервер Linux Ubuntu 16.04.1 LTS
  • Пакет Motion 3.2.12
  • IP камера с поддержкой MJPEG

Требуется :

  • Писать только видео (никаких фото и таймлапсов)
  • Писать только по движению
  • Оповещать об обнаружении движения
  • Транслировать поток в реальном времени с сервера

В Ubuntu установка пакетов проста до безобразия

apt - get update && apt - get install motion

По завершении установки переходим в каталог с файлом конфигурации

cd / etc / motion nano motion . conf

Сразу разберемся с порядком действий т.к понимая, что сейчас будем делать, мы сможем настроить эту программу вне зависимости от её версии и версии операционной системы на которой она установлена.

Сам Motion может работать одновременно с несколькими потоками (камерами для обывателя), неважно будь то IP камеры, USB камеры или иные устройства захвата видео. Да хоть все вперемешку. Самое главное, что если мы используем IP камеры, то они должны поддерживать MJPEG поток. Никакого RTSP (Real Time Streaming Protocol) Motion не поддерживает.

Файл конфигурации motion.conf представляет из себя набор основных настроек программы и настройки для первого потока. Все последующие потоки можно конфигурировать в файлах thread1.conf, thread2.conf ... thread9.conf (по умолчанию они отсутствуют) и подключать в конце файла motion.conf

Лично я предпочитаю основные настройки и настройки по умолчанию хранить в основном файле конфигурации, а настройки самих камер вынести во внешние файлы thread, по одному файлу на каждую камеру. Таким образом, не будет никакой путаницы если например камера №2 выйдет из строя и её понадобится заменить на не типичную камеру.

Основные настройки представлены в двух секциях

############################################################ # Daemon ############################################################ # Запуск в фоновом режиме (default: off) daemon on # Файл для хранения идентификатора процесса (default: not defined) process_id_file /var/run/motion/motion.pid ############################################################ # Basic Setup Mode ############################################################ # Запускать в Setup-Mode, отключает режим демона. (default: off) setup_mode off # Путь до файла с логами. (default: not defined) logfile /var/log/motion/motion.log # Уровель лог сообщений (EMR, ALR, CRT, ERR, WRN, NTC, INF, DBG, ALL). (default: 6 / NTC) log_level 6 # Фильтр лог сообщений по типу (COR, STR, ENC, NET, DBL, EVT, TRK, VID, ALL). (default: ALL) log_type all

Тут нас интересует только запуск программы в фоновом режиме, остальные настройки можно оставить без изменения.

########################################################### # Capture device options ############################################################ # Videodevice to be used for capturing (default /dev/video0) # for FreeBSD default is /dev/bktr0 ;videodevice /dev/video0 ...

Закомментируем все настройки в этой секции, в этом файле они нам не нужны.

Отключаем создание снимков при обнаружении движения. Можно указать один из режимов (first, best, center) и тогда мы будем получать по одному снимку на одно видео. Это может пригодиться в том случае, если вы собираетесь организовывать WEB доступ к архиву видеозаписей.

############################################################ # Image File Output ############################################################ # Output "normal" pictures when motion is detected (default: on) # Valid values: on, off, first, best, center # When set to "first", only the first picture of an event is saved. # Picture with most motion of an event is saved when set to "best". # Picture with motion nearest center of picture is saved when set to "center". # Can be used as preview shot for the corresponding movie. output_pictures off

Отключаем стриминг по умолчанию

############################################################ # Live Stream Server ############################################################ # The mini-http server listens to this port for requests (default: 0 = disabled) stream_port 0

Отключаем встроенный web сервер

############################################################ # HTTP Based Control ############################################################ # TCP/IP port for the http server to listen on (default: 0 = disabled) webcontrol_port 0

В самом конце основного файла конфигурации motion.conf имеется секция для подключения дополнительных потоков. По умолчанию файл motion.conf соответствовал файлу thread0.conf, но мы это доблестно исправили выше. Теперь нам необходимо включить в файл конфигурации файлы с настройками для работы с нашими камерами. Мы рассмотрим на примере конфигурации одной камеры, раскомментируем thread1.conf

############################################################## # Thread config files - One for each camera. # Except if only one camera - You only need this config file. # If you have more than one camera you MUST define one thread # config file for each camera in addition to this config file. ############################################################## # Remember: If you have more than one camera you must have one # thread file for each camera. E.g. 2 cameras requires 3 files: # This motion.conf file AND thread1.conf and thread2.conf. # Only put the options that are unique to each camera in the # thread config files. thread /etc/motion/thread1.conf ; thread /etc/motion/thread2.conf ; thread /etc/motion/thread3.conf ; thread /etc/motion/thread4.conf

Создадим файл thread1.conf

Cd /etc/motion && touch thread1.conf && nano thread1.conf

Первым делом вам необходимо узнать:

  • адрес MJPEG потока вашей камеры
  • разрешение камеры (обычно для дешевых камер это 640х480)
  • частоту кадров (у дешевых камер обычно не более 20)
# Разрешение картинки width 640 height 480 # Частота кадров framerate 20

Укажем настройки для доступа к видео потоку. В моем случае камеры находятся в отдельной физической сети, и доступ к потоку по логину и паролю не требуется (только для доступа к настройкам камер).

# Сетевой адрес для захвата изображения с камеры (mjpeg поток) netcam_url http://10.10.1.101/mjpeg.cgi # Логин и пароль к видео потоку (only if required). Default: not defined ; netcam_userpass admin:password

Укажем подпись для камеры, это поможет быстро идентификации её местоположение (можно использовать символ перевода каретки)

# Подпись к камере (название камеры) text_left CAMERA 1\nSH-21 STORAGE

Укажем путь до места хранения видео фрагментов (лучше создать каталог заранее)

# Путь до места сохранения видео\фото target_dir /media/share/motion/cam1

Включаем детектор движения

# Количество изменившихся пикселей для триггера обнаружения движения threshold 2000

У меня камера стоит под потолком, рядом со стеной с большим количеством окон. Ну и естественно окна смотрят на солнечную сторону, а это значит, что открывается прекрасный вид на быстро плывущие облака отбрасывающие тень на здание, а во время летней грозы помещение может освещаться завораживающими вспышка молний. И это все можно будет посмотреть на видео, если не добавить следующие параметры. Можно поэкспериментировать и добиться приемлемого результата для вас

# Не реагировать на резкое изменение яркости (0 - отключено, 0-100% от общего числа пикселей) lightswitch 60 # Минимальное число кадров, в которых фиксируется движение для взведения триггера тревоги minimum_motion_frames 5

Добавляем мертвую зону. Она позволит не создавать кучу мусора в папке с видео

# Количество секунд (мертвая зона) после окончания движения, в течении которых отключен детектор event_gap 10

Для наглядности можно добавить рамку вокруг движущегося объекта. Также рекомендую включить эти параметры на время, пока проходит настройка и обкатка системы

# Рисуем рамку вокруг движущегося объекта locate_motion_mode on # Стиль рамки вокруг движущегося объекта locate_motion_style redbox

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

# Показывать количество изменившихся пикселей (используем для настройки threshold) text_changes on

Включаем возможность стримить с сервера все происходящее на данной камере

# Настройки потока вещания (порт\качество\частота кадров\доступ только с 172.0.0.1\ограничение потока) stream_port 8081 stream_quality 70 stream_maxrate 20 stream_localhost off stream_limit 0

Можно (и нужно, если установлен stream_localhost off) требовать авторизацию при доступе к потоку с этой камеры из вне

В принципе этого уже достаточно для организации работы системы и на этом этапе можно сохранить файл. Но в моем случае требуется организовать внешнюю систему оповещения. Благо Motion умеет вызывать внешние программы и передавать им различные параметры.

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

При вызове внешнего скрипта ему можно передать следующие параметры

Скрытый текст

%Y = year %m = month %d = date %H = hour %M = minute %S = second, %v = event %q = frame number %t = thread (camera) number %D = changed pixels %N = noise level %i and %J = width and height of motion area %K and %L = X and Y coordinates of motion center %C = value defined by text_event %f = filename with full path %n = number indicating filetype # Both %f and %n are only defined for on_picture_save, # on_movie_start and on_movie_end # Quotation marks round string are allowed.

Добавим строку вызова внешнего исполняемого файла с передачей ему параметрами - номер камеры и координаты центра начала движения

# При обнаружении движения запускать внешний скрипт on_event_start /etc/motion/zone_alarm.sh %t %K %L

Содержимое zone_alarm.sh

#!/bin/bash CAM = "$1" X = "$2" Y = "$3" if (("$CAM" == "1" )) then if (("$X" < "320" && "$Y" < "240" )) then beep - f 4000 - l 200 - n - f 4000 - l 200 - n - f 4000 - l 200 - n - f 4000 - l 200 - n - f 4000 - l 200 fi fi

В данном случае происходит звуковое оповещение на самом сервере с помощью утилиты beep, по умолчанию её может и не быть

Apt-get install beep

Также можно сигнализировать об обнаружении движения на удаленную станцию или на мобильное устройство. Очень актуально если камера наблюдает за помещением, в котором не бывает и не должно бывать людей. Например, склад.

Необходимо разрешить Motion работать в режиме демона, для этого правим файл /etc/default/motion

# set to "yes" to enable the motion daemon start_motion_daemon = yes

Проверяем работу

/ etc / init . d / motion start

Что мы имеем на выходе


Все прекрасно работает, но остается вопрос переполнения жесткого диска. Чтобы этот счастливый момент никогда не настал, стоит автоматизировать удаление устаревших видеозаписей. В этом нам поможет планировщик CRON.

Crontab -e

И добавим запись, которая будет выполняться ежедневно в 23:30 и искать файлы формата.avi в указанном нами каталоге и если найденный файл существует более 14 суток, то он удаляется. Естественно если камера стоит в проходном месте, то стоит уменьшить время жизни видео.

30 23 * * * find /media/share/motion/ -name "*.avi" -mtime +14 -exec rm {} \;

На этом настройка завершена.

PS : в файле motion.conf можно найти еще очень много интересных параметров!

166

Для того, чтобы motion стартовал автоматически при запуске системы, попробуйте добавить разрешение на запуск юнита в systemd

systemctl enable motion / lib / systemd / systemd - sysv - install enable motion

перезапускаем систему и смотрим вывод

service motion status

Скрытый текст

Service motion status ● motion . service - LSB : Start Motion detection Loaded : loaded (/ etc / init . d / motion ; bad ; vendor preset : enabled ) Active : active (running ) since Пн 2016 - 10 - 17 15 : 26 : 47 MSK ; 2min 24s ago Docs : man : systemd - sysv - generator (8 ) Process : 568 ExecStart =/ etc / init . d / motion start (code = exited , status = 0 / SUCCESS ) CGroup : / system . slice / motion . service └─ 1324 / usr / bin / motion окт 17 15 : 26 : 46 hostname systemd [ 1 ]: Starting LSB : Start Motion detection ... окт 17 15 : 26 : 47 hostname motion [ 568 ]: * Starting motion detection daemon motion окт 17 15 : 26 : 47 hostname motion [ 568 ]: ... done . окт 17 15 : 26 : 47 hostname systemd [ 1 ]: Started LSB : Start Motion detection . окт 17 15 : 26 : 53 hostname motion [ 796 ]: [ 25567616 ] [ NTC ] [ ALL ] conf_load : Processing thread 0 - config file / etc / motion / motion . conf окт 17 15 : 26 : 53 hostname motion [ 796 ]: [ 25567616 ] [ NTC ] [ ALL ] config_thread : Processing config file / etc / motion / thread1 . conf окт 17 15 : 26 : 53 hostname motion [ 796 ]: [ 25567616 ] [ NTC ] [ ALL ] motion_startup : Motion 3.2 . 12 + git20140228 Started with SDL support окт 17 15 : 26 : 53 hostname motion [ 796 ]: [ 25567616 ] [ NTC ] [ ALL ] motion_startup : Logging to file (/ var / log / motion / motion . log )

После обновления видеосервера под debian, было принято решение переделать систему видеонаблюдения.


Motion было решено оставить, но возникла необходимость в более человеческом интерфейсе для просмотра архивных записей. Очередные поиски в Интернет так и не дали никаких приемлемых результатов, в результате чего было принято решение о создании собственного продукта. После небольших колебаний, выбор пал на Rails. Никакой религии, просто захотелось получше изучить этот фреймворк и замечательный язык программирования. В качестве СУБД используется PostgreSQL. Результат работы под капотом...

Для начала чуть-чуть поподробнее про настройку motion.


В связи с тем, что хотелось, по-возможности, обойтись чистым HTML5, пришлось переустановить motion вручную, включив ему поддержку записи файлов в ogg. Благо авторы данной программы реализовали ее, за что им большое спасибо. Процесс сборки и установки описан хорошо на странице проекта, поэтому не буду его здесь расписывать, тем более, что под разные дистрибутивы он будет отличаться. Ссылка на домашнюю страницу www.lavrsen.dk/foswiki/bin/view/Motion/MotionGuideInstallation.

Заострю внимание только на настройке продукта.

Так как изначально motion был установлен из пакетов, то после переустановки не стал переносить папку с конфигурацией из /etc в /usr/local/etc . И еще один пункт, сам motion запускается при помощи runit, поэтому в конфиге он отключен режим демона. Расстановка сил следующая:

  1. Конфиги motion лежат в /etc/motion.
  2. Видео пишется на отдельный жесткий диск, смонтированный в директорию /video, в папки с именами камер.
  3. В базу откладываются записи, в которых хранится информация о времени события, полному пути к файлу события, типом файла (в моем случае видео).

Структура таблицы

CREATE TABLE records
id serial NOT NULL,
thread integer,
filename character varying(255),
frame integer,
file_type integer,
event_timestamp timestamp without time zone,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL,
CONSTRAINT records_pkey PRIMARY KEY (id)
WITH (
OIDS=FALSE
);
ALTER TABLE records
OWNER TO motion;

Index: thread

DROP INDEX thread;

CREATE INDEX thread
ON records
USING btree
(thread);

4. Для живого просмотра (в режиме реального времени) с камер использутеся интерфейс motion.

Основные изменения в конфиге следующие:

/etc/motion.conf
ffmpeg_video_codec ogg


webcontrol_port 8080
webcontrol_localhost off (в том случае, если web-interface будет запускаться на другом сервере)
webcontrol_html_output on
webcontrol_authentication login:pass


sql_query insert into records(thread, filename, frame, file_type, event_timestamp, created_at, updated_at) values("%t", "%f", "%q", "%n", "%Y-%m-%d %T", NOW(), NOW())

И соответственно настройки для подключения к БД.

thread /etc/motion/thread1.conf

thread /etc/motion/thread2.conf

thread /etc/motion/threadN.conf,

где N зависит от количества наших камер.

Основные пункты в threadX.conf, где X - любое число

stream_port PortNumber - данный порт надо будет писать в поле «Порт потокового вещания» при настройке камер в web-interface. Это основные изменения при настройке motion. Как настроить сам motion в данной статье расписывать не буду. По настройке Rails хорошо расписано в статье habrahabr.ru/post/140910 . Также может возникнуть необходимость в установке NodeJS - github.com/joyent/node/wiki/Installing-Node.js-via-package-manager

Теперь перейдем непосредственно к настройке интерфейса:


1. Клонировать при помощи git сайт.

Bitbucket:

git clone [email protected]/webdev4u/motion_web.git

Github:

git clone github.com/webdev4u/motion_web.git

2. Переименовать config/settings.local.yml в config/settings.yml и вписать туда адрес сервера, на котором запущен motion.

3. Переименовать config/database.yml.example в config/database.yml и вписать туда настройки для вашей базы.

4. Измените данные в db/seeds.rb для пользователя admin .

5. rake db:migrate

6. rake db:seed

7. Для проверки можно будет запустить rails s . Сервер будет слушать на 3000 порту. Если все нормально, можно работать.

8. И напоследок, настроить задание для крона по чистке базы. По умолчанию хранятся записи за 21 день, но можно изменить этот параметр в файле app/models/record.rb 12 строка, но лучше в lib/tasks/crontask.rake строку

Record.clean_old_records

заменить на

Record.clean_old_records Нужное_количество_дней.

После чего прогнать команду

whenever --update-crontab

из под пользователя, от имени которого будет работать сайт.

Скриншоты


Скриншот главной страницы:

Страница входа:

Живой просмотр:

Список камер:


Просмотр архива:


Добавление пользователя:


Добавление камеры:



Рекомендуем почитать

Наверх