Заключение
Результатом работы сталоустройство, способноепередавать на полетный контроллер квадрокоптера сигнал, базирующийся на информацииполученной на основе анализа положения руки в пространстве и характера жеста. Устройство является прототипом, и его реализация требует существенной доработки. По мере продвижения в разработке, на каждом шаге приобретались новые знания и каждый шаг алгоритма, с высоты приобретенного опыта, безусловно, следует оптимизировать.
В первую очередь следует добавить в программу распознавания жестов систему распознавания объекта по цвету, чтобы повысить помехоустойчивость, а также улучшить алгоритмы фильтрации и сделать управление оперативной памятью более рациональным. В перспективе следуетмодернизировать программу для распознавания новых, более сложных жестов и оптимизировать распознавание уже имеющихся.
Тем не менее, устройство имеет реальные перспективы развития и использования в практических целях.
Разработка устройства потребовала навыки в
областях радиофизики, программирования, обработки сигналов, робототехники,
электроники и аэродинамики.
Список использованных источников
1. Герасимов В.Г. Электротехника и электроника. Кн. 2. Электромагнитные устройства и электрические машины. / В.Г. Герасимов, Э.В. Кузнецов, О.В. Николаева. - М: Энергоатомиздат, 1997. - 288 с.
2. Корнилов В. А. Система управления мультикоптером / В.А. Корнилов, Д.С. Молодяков, Ю.А. Синявская. // Электронный журнал «Труды МАИ». - 2015. - № 62. - С. 1-8.
3. Ang K.H. PID control system analysis, design, and technology / K.H. Ang, G. Chong, Y. Li. // IEEE Transactions on Control Systems Technology. -2005.- № 4. - C. 559-576.
. Kalman R.E. A new approach to linear filtering and prediction problems / R.E. Kalman. // Journal of Basic Engineering. - 1960. - №82.-С. 35-45
5. ФорсайтД.А. Компьютерноезрение. Современныйподход. / Д.А. Форсайт, Ж. Понс. - М :Издательскийдом «Вильямс», 2004. - 982с.
. Шапиро Л. Компьютерное зрение. / Л. Шапиро,000 Дж. Стокман. - М : Бином. Лаборатория знаний, 2013. - 752 с.
. Желтов С.Ю. Обработка и анализ изображений в задачах машинного зрения. / С.Ю. Желтов [и др.]; ред. С.Ю. Желтов. - М : Физматкнига, 2010. - 672 с.
9. G. Bradski. LearningOpenCV. / GaryBradskiandAdrianKaehler. - M :O’ReillyMedia, Inc, 2008. - 571 c.
. OpenCVdocumentationindex [Электронныйресурс] :сайт. - URL: https://docs.opencv.org.
. E. R. Davies. Machine Vision: Theory, Algorithms, Practicalities. M: - Morgan Kaufmann, 2004. - 579 c.
12. IMU-сенсор на 10 степеней свободы [Электронныйресурс] :сайт. - URL: http://wiki.amperka.ru/продукты:troyka-imu-10-dof
Приложения
Приложение 1
Main.java
package sample;
// импортирование JavaFX-классов
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
// импортирование библиотеки OpenCV
import org.opencv.core.Core;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
// связываем с FXML-документомloader = new FXMLLoader(getClass().getResource("sample.fxml")); // load the FXML resourceroot = FXMLLoader.load(getClass().getResource("sample.fxml"));
// заголовок окна.setTitle("Gesture Detection 0.1");
// создаем окно с параметрами высоты и ширины
primaryStage.setScene(new Scene(root, 517, 441));
// запрет изменения размеров окна.setResizable(false);
// создаем сцену и выводим на экран
primaryStage.show();
}
public static void main(String[] args) {
// загружаем OpenCV.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// запускаем JavaFX-код(args);
}
}.java
package sample;
// импортирование средства для буферизации изображений
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
// импортирование JavaFX-классов
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
// импортирование библиотеки OpenCV
import org.opencv.core.Mat;
public final class Utils
{
//Конвертирует Mat-объект из OpenCV в Image из JavaFX
public static Image mat2Image(Mat frame)
{
try
{
return SwingFXUtils.toFXImage(matToBufferedImage(frame), null);
}
catch (Exception e)
{.err.println("Cannot convert the Mat obejct: " + e);
return null;
}
}
// метод для импортирования элементов в "нить" JavaFX
// взят из открытых источников
public static <T> void onFXThread(final ObjectProperty<T> property, final T value)
{.runLater(() -> {.set(value);
});
}
// буферизация изображения
private static BufferedImage matToBufferedImage(Mat original)
{image = null;
int width = original.width(), height = original.height(), channels = original.channels();
byte[] sourcePixels = new byte[width * height * channels];.get(0, 0, sourcePixels);
if (original.channels() > 1)
{= new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
}
else
{= new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
}
final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();.arraycopy(sourcePixels, 0, targetPixels, 0, sourcePixels.length);
return image;
}
}.FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane alignment="center" hgap="10" maxHeight="600.0" maxWidth="726.0" minHeight="426.0" minWidth="517.0" prefHeight="441.0" prefWidth="517.0" vgap="10" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<columnConstraints>
<ColumnConstraints />
</columnConstraints>
<rowConstraints>
<RowConstraints />
</rowConstraints>
<children>
<BorderPane maxHeight="600.0" maxWidth="700.0" minHeight="438.0" minWidth="530.0" prefHeight="438.0" prefWidth="530.0">
<bottom>
<Button fx:id="button" mnemonicParsing="false" onAction="#startCamera" text="Start Camera" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets bottom="15.0" />
</BorderPane.margin>
</Button>
</bottom>
<center>
<ImageView fx:id="currentFrame" fitHeight="325.0" fitWidth="431.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets right="-300.0" />
</BorderPane.margin></ImageView>
</center>
<top>
<Slider fx:id="slider" max="300.0" maxWidth="400.0" minWidth="100.0" prefHeight="14.0" prefWidth="100.0" value="150.0" BorderPane.alignment="TOP_RIGHT">
<BorderPane.margin>
<Insets right="5.0" top="10.0" />
</BorderPane.margin></Slider>
</top>
<left>
<Label alignment="TOP_LEFT" text="Threshold" BorderPane.alignment="TOP_LEFT">
<font>
<Font size="17.0" />
</font>
<padding>
<Insets left="30.0" top="-20.0" />
</padding>
</Label>
</left>
<right>
<Slider fx:id="sliderBlur" max="20.0" min="2.0" prefHeight="14.0" prefWidth="402.0" value="5.0" BorderPane.alignment="CENTER">
<padding>
<Insets right="5.0" top="-350.0" />
</padding>
</Slider>
</right>
</BorderPane>
<Label text="Blur">
<GridPane.margin>
<Insets left="50.0" top="-365.0" />
</GridPane.margin>
<font>
<Font size="17.0" />
</font>
</Label>
</children>
</GridPane>.java
package sample;
// импортирование JavaFX-классов
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
// импортирование библиотеки OpenCV
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.VideoCapture;
// импортирование утилит Java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Controller {
@FXML
private Button button; // кнопка
@FXML
private Slider slider; // слайдер выбора порога
@FXML
private Slider sliderBlur; //слайдер размытия
@FXML
private ImageView currentFrame; //объект просмотра изображения
private ScheduledExecutorService timer; // таймер для контроля видеострима
private VideoCapture capture = new VideoCapture(); // объект OpenCV, захватывающий видео
private boolean cameraActive = false; // флаг, меняющий поведение кнопки
private static int cameraId = 0; // id используемой камеры
Serial serial = new Serial(); // объект для работы с серийным портом
// событие нажатия кнопки
@FXML
protected void startCamera(ActionEvent event)
{
if (!this.cameraActive) //если камера в данный момент выключена
{
// начинаем захват видео
this.capture.open(cameraId);
// видео доступно?
if (this.capture.isOpened())
{
this.cameraActive = true; //флажок поднимается
// захватываем 30 кадров в секунду (33 мс)
Runnable frameGrabber = new Runnable()
{
@Override
public void run()
{
// захватываем отдельный кадрframe = grabFrame();
// теперь в изображение егоimageToShow = Utils.mat2Image(frame);(currentFrame, imageToShow);
}
};
// настройка таймера
this.timer = Executors.newSingleThreadScheduledExecutor();
this.timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);
// надпись на кнопке меняется
this.button.setText("Stop Camera");.initialize();
}
else
{
// ошибка.err.println("Impossible to open the camera connection...");
}
}
else
{
// выключаем камеру
this.cameraActive = false;
// меняем надпись на кнопке
this.button.setText("Start Camera");
// останавливаем таймер
this.stopAcquisition();
}
}
//функция, захватывающая кадр из видео, возвращает переменную frame
private Mat grabFrame()
{
// создаем объект фреймаframe = new Mat();
// объявляем переменную, которая будет отправляться по серийному порту
Byte sendByte = 0;
// если захват работает
if (this.capture.isOpened())
{
// прочитать текущий кадр
this.capture.read(frame);
// если кадр не пустой, обрабатываем его
if (!frame.empty())
{
// переводим в оттенки серого
Imgproc.cvtColor(frame, frame, Imgproc.COLOR_BGR2GRAY);
// переводим в черное и белое.threshold(frame, frame, slider.getValue(), 5000, 1);
// сглаживаем (избавляемся от шумов).medianBlur(frame, frame, ((int) sliderBlur.getValue() * 2) - 1);
// объявляем массив контуров и очищаем<MatOfPoint> contours = new LinkedList<>();.clear();
// вспомогательный массивhierarchy = new Mat();
// ищем контуры.findContours(frame, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_TC89_KCOS);
// Выделяем самый большой контур<MatOfPoint> finalContours = new LinkedList<>();.addFirst(contours.get(0));
for (int j = 0; j < contours.size(); j++)
{
if (Imgproc.contourArea(contours.get(j)) > Imgproc.contourArea(finalContours.getFirst()))
{.removeFirst();.addFirst(contours.get(j));
}
}
// Находим обводящие контуры<MatOfInt> convexHull = new LinkedList<>();.add(new MatOfInt());.convexHull(finalContours.get(0), convexHull.get(0));
// конвертируем MatOfInt в Point для рисования обводок
// цикл по всем контурам<Point[]> hullpoints = new ArrayList<>();
for (int j = 0; j < convexHull.size(); j++)
{[] points = new Point[convexHull.get(j).rows()];
// цикл по всем выпуклостям
for (int k = 0; k < convexHull.get(j).rows(); k++)
{
int index2 = (int) convexHull.get(j).get(k, 0)[0];[k] = new Point(finalContours.get(j).get(index2, 0)[0], finalContours.get(j).get(index2, 0)[1]);
}.add(points);
}
// конвертируем массив Point в MatOfPoint<MatOfPoint> hullmop = new ArrayList<>();
for (int j = 0; j < hullpoints.size(); j++)
{m = new MatOfPoint();.fromArray(hullpoints.get(j));
hullmop.add(m);
}
// ищем дефекты контуров
List<Integer> cdList;<MatOfInt4> convDef = new LinkedList<>();.add(new MatOfInt4());
// функция ищет дефекты.convexityDefects(finalContours.get(0), convexHull.get(0),.get(0));
// оставляем только первый= convDef.get(0).toList();data[] = finalContours.get(0).toArray();
// разбираем дефекты на состанвые точкиdefect[] = new Point[20];
Point start[] = new Point[20];end [] = new Point [20];
int defectCounter = 0;
int defectFilteredCounter = 0;
// переносим в массив точек
for (int j = 0; j < cdList.size(); j = j + 4)
{[defectCounter] = data[cdList.get(j + 2)];[defectCounter] = data[cdList.get(j + 1)];[defectCounter] = data[cdList.get(j)];++;
}
// массивы для отфильтрованных точекdefectFiltered[] = new Point[15];
Point startFiltered[] = new Point[15];
// переменная для угла и ее счетчик
double angle;
double angleCount = 0;
// фильтрация
for (int i = 0; i < defectCounter; i++)
{
// координаты, по которым считается угол
double dx1 = Math.abs (start[i].x - defect[i].x);
double dy1 = Math.abs (start[i].y - defect[i].y);
double dx2 = Math.abs (defect[i].x - end[i].x);
double dy2 = Math.abs (defect[i].y - end[i].y);
// вычисление угла= (dx1*dx2 + dy1*dy2)/Math.sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2));
// вычисление расстояния между соответствующими точками end и start
double lenght = Math.sqrt (Math.pow((start[i].x - end[i].x), 2) + Math.pow((start[i].y - end[i].y), 2);
// условие с подобранными значениями
if ((start[i].y < 450 || start[i].y > 50) && angle > 4.6 && lenght < 5.1)
{
// записываем точки в массив[defectFilteredCounter] = defect [i];[defectFilteredCounter] = start [i];