КОНСТРУИРОВАНИЕ И ПРОГРАММИРОВАНИЕ «РОБОТА СКАНЕРА» - Студенческий научный форум

IX Международная студенческая научная конференция Студенческий научный форум - 2017

КОНСТРУИРОВАНИЕ И ПРОГРАММИРОВАНИЕ «РОБОТА СКАНЕРА»

Заплатин А.В. 1, Кононский П.А. 1, Гребнева Д.М. 1
1Российский государственный профессионально-педагогический университет (филиал) в г. Нижнем Тагиле
 Комментарии
Текст работы размещён без изображений и формул.
Полная версия работы доступна во вкладке "Файлы работы" в формате PDF

КОНСТРУИРОВАНИЕ И ПРОГРАММИРОВАНИЕ «РОБОТА‑СКАНЕРА»

Заплатин А. В, Кононский П. А., Гребнева Д. М.

Российский государственный профессионально-педагогический университет (филиал)

Нижний Тагил, Россия

 

THE DESIGN AND PROGRAMMING OF THE SCANNING ROBOT

Zaplatin A. V., Kononskiy P. A., Grebneva D. M.

Russian State Vocational Pedagogical University (branch)

Nizhniy Tagil, Russia

 

Задача: требуется собрать конструкцию робота, который сможет считывать и распознавать цифры с помощью датчика освещенности.

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

Информационные ресурсы для создания конструкции робота: в качестве основы для создания «робота-сканера» мы взяли проект Ханса Андерссона «sudokysolver» («робот-судоку») и адаптировали его для решения поставленной задачи [4].

Необходимое оборудование: робототехнический набор Lego Mindstorm NXT 8547 версия 2.0.

Описание конструкции: для разработки робота использовались наборы LegoMindstormsNXT, включающие в себя стандартные детали набора Lego (колеса, балки, крепления, шестерни) и электронные блоки, такие как микроконтроллер, сервомотор и цветовой датчик.

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

1. Нижняя часть. База робота (рис. 1) состоит из микроконтроллера, входящего в блок NXT Brick и сервомотора, который управляет движением задних колес.


Рис. 1. Нижняя часть

NXTBrick – интеллектуальный модуль, способный принимать входные данные от четырех датчиков и контролировать до трех двигателей (в нашем роботе использован один датчик цвета и три сервомотора).

 

Рис. 2. Блок NXTBrick

Сервомотор (рис. 2) позволяет роботу двигаться и совершать какие-либо манипуляции, поворачиваясь на определенный угол. В сервомоторе имеется встроенный датчик вращения, что дает возможность роботу legomindstorm двигаться точно в заданном направлении. Сервомотор программируется в исходя из требований. Настройка происходит в градусах. Один оборот соответствует 360 градусов.

 

Рис. 3. Сервомотор

2. Верхняя часть. Верхняя часть (рис. 4) состоит из двух сервомоторов и цветового датчика.


Рис. 4. Верхняя часть

 

Рис. 5. Вращательный механизм

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

 

Рис. 6. Датчик цвета

Таким образом, готовый робот имел конструкцию, которую можно видеть на рис. 7.

 

Рис. 7. Робот-сканер

Реализация алгоритма обнаружения и идентификации объекта роботом

Для реализации алгоритма был выбран язык программирования NXC. Кратко рассмотрим необходимые функции, которые потребуются для реализации алгоритма.

Функции работы со временем.

Wait. Функция Wait(время) служит для создания задержек на определенное время, указанное в миллисекундах.

Функции управления двигателем.

Двигатели подключаются к портам A, B, C. Для указания какого-либо порта используются константы:

-     OUT_A – порт А

-     OUT_B – порт B

-     OUT_C – порт C

RotateMotor.  С помощью функции RotateMotor (порты, мощность, градус) можно вращать двигатель на определенный градус.

Пример использования:

-              RotateMotor (OUT_BC, 20, 73); // вращение вперед

-              RotateMotor (OUT_BC, -20, 73); // вращение назад

RotateMotorEx. Функция RotateMotorEx (порты, мощность, угол, распределение мощности по двигателям, синхронизация, остановка) имеет больше возможностей, чем функция RotateMotor, с ее помощью можно

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

остановить двигатели в конце вращения на определенный угол.

Функции инициализации датчиков.

Датчики подключаются к портам, обозначенными цифрами от 1 до 4. Для указания какого-либо порта используют константы:

-     S1 – первый порт

-     S2 – второй порт

-     S3 – третий порт

-     S4 – четвертый порт

В наборе имеется датчик цвета, его можно использовать как датчик освещенности, но также с помощью него можно определять цвета.

Для использования этого датчика в качестве датчика освещенности, инициализировать его необходимо следующим образом:

SetSensorColorRed(порт); //будет использоваться красная подсветка

SetSensorColorBlue(порт); //будет использоваться синяя подсветка

SetSensorColorGreen(порт); //будет использоваться зеленая подсветка

Функции для работы с дисплеем.

NumOut. Функция NumOut(координата X,координата Y, переменная) выводит на дисплей число в указанной координате дисплея.

TextOut. Функция TextOut(координата X,координата Y, "Выводимый текст") выводит на дисплей текст в указанной координате дисплея.

RectOut. Функция RectOut(координата X,координата Y, длина, высота) позволяет отобразить на дисплее прямоугольник с началом в координате X, Y с заданной длиной и высотой.

ClearScreen. Функция ClearScreen() позволяет очистить экран.

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

1. Ввод (восприятие информации). За считывание данных изображения отвечает функция ScanCell (сканирование клетки), которая, в свою очередь вызывает функции Cell, где происходит запись значения отраженного света в массив scanbuffer.

Листинг 1. Функция сканирования ячейки ScanCell

void ScanCell(int r, int c)

{

  for(int i=0;i<SIZE_X*SIZE_Y;i++)

    bmp[i]=0;                            //все значения массива bmp приравниваем к 0 (очищаем массив)

  TextOut(25,LCD_LINE1,"Scanning",true);

  for(int y=0;y<SIZE_Y;y++)          // повторит движение вперед на 20 градусов и функцию Cell() SIZE_Y - раз

  {

    RotateMotor(OUT_A, -1 * SPEED_Y,STEP_Y);

    Cell(y); //вызов функции Cell для записи данных в массив

  }

}

Листинг 2. Функция Cell

void Cell(int r)

{

  int bmpOffset=r*SIZE_X;

    for(int x=0;x<20;x+=1)

      RotateMotorEx(OUT_B,SPEED_X,-1 * 1,0,false,false);

    for(int x=SIZE_X*3-1;x>=0;x--)

    {

      byte light;

      RotateMotorEx(OUT_B,SPEED_X,-1 * 1,0,false,false);

      light=Sensor(IN_2);

      scanbuffer[x]=light;      //запись отраженного света в массив scanbuffer

    }                           //для более высокой точности для каждого градуса записывается 3 значения

                                //для каждой дуги всего получится 192 значения света

  int maxLight=0;

  int minLight=255;

  for(int x=0;x<SIZE_X;x++)

  {

    int light1=0;

    for(int i=0;i<3;i++)

      light1+=scanbuffer[x*3+i];

    light1/=3;                     //считаем и записываем среднее арифметическое трех рядом стоящих значений

    bmp[bmpOffset+x]=light1;       //в массив bmp      (значения света в дуге начинается от 0 и заканчивается 63 местом, то есть новая дуга начнется с 64 - это важно в последующей части кода)

    if(light1>maxLight)

      maxLight=light1;     //ищем максимальный отраженный свет

    if(light1<minLight)

      minLight=light1;     //ищем минимальный отраженный свет

  }

  int threshold=(maxLight+minLight)/2;  // и вычисляем порог дуги

  for(int x=0;x<SIZE_X;x++)         // вывод на экран черных пикселей

    if(bmp[bmpOffset+x]<threshold)  // если его значение меньше порога

      RectOut((9+x),r,1,1);

  RotateMotorPID(OUT_B,SPEED_X,SIZE_X*3+20,50,40,90);

}

2. Предварительная обработка изображения.  Для бинаризации изображения используем метод пороговый Оцу. После определения порога бинаризации каждый пиксель со значением ниже порогового значения устанавливается в черный. А остальные пиксели – к белым.

Листинг 3. Функция Thresholding (бинаризация изображения)

 

void  Thresholding()

void  Thresholding()

{

TextOut(10,LCD_LINE1,"Thresholding",true);

Wait(2000);

byte threshold;

lon gmaxValue=0;

int whiteCount=0;

int blackCount=0;

byte min=bmp[0];

for(int i=1;i<SIZE_X*SIZE_Y;i++)

if(bmp[i] <min)

min = bmp[i];

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

for(byte t=min+2;whiteCount >= blackCount ;t++)  

//Поиск общего порога.

//С t сравниваются значения в массиве bmp.

{

Long whiteSum=0;

Long blackSum=0;

whiteCount=0;

blackCount=0;

for(int i=0;i<SIZE_X*SIZE_Y;i++)

{

Byte value=bmp[i];

if(value> t)                   

      {

//Если значение массива больше t, то это значение на рисунке будет считаться белым пикселем.

 

whiteCount++;                  

 

//Тогда увеличивается количество белых пикселей.

 

whiteSum+=value;       

       

//Считается сумма значений света.

      }

else

      {

blackCount++;        

         

//Иначе это будет черный пиксель.

blackSum+=value;               

//Считаем количество и сумму значений света черных пикселей.

 

Pixel(i);                      

//Изображаем пиксели на экране.

if(blackCount>SIZE_X*SIZE_Y/2)

break;

      }

    }

if(blackCount>30 &&whiteCount>blackCount) 

//Теперь если количество черных пикселей больше 30 и белых больше черных то:

{

Long blackMean=blackSum*10/blackCount;

Long whiteMean=whiteSum*10/whiteCount;

long diff=whiteMean-blackMean;    

            

//Рассчитываем дисперсию.

whiteCount/=10;

blackCount/=10;

long value = diff*diff*whiteCount*blackCount;

if(value >maxValue)

      {

maxValue=value;

threshold=t;   

//Общий порог становиться равен значению t.

//Потом все действия цикла, описанные выше, повторяются, но уже с увеличенным значением t.

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

      }

    }

  }

Таким образом, мы преобразовали изображение в бинарное (рис. 8).

 

 

а) Необработанное изображение в оттенках серого цета

б) Обработанное изображение после бинаризации

Рис. 8. Бинаризация изображения

3-4. Выделение локальных особенностей на изображении и сегментация. Теперь необходимо определить центральный объект, полученный на изображении и по возможности удалить лишние элементы, возникшие из-за шумов изображения. Для этого создадим функцию Segmentation (сегментация)

Листинг 4. Функция Segmentation (выделение главного объекта)

void Segmentation()            

{

  TextOut(0,LCD_LINE1,"  Segmentation  ");

  int pos;

  int middle=SIZE_X*SIZE_Y/2;

  int stack[SIZE_X*SIZE_Y/2];

  int stackSize=0;

  for(int i=0;i<SIZE_X/2;i++)

  {

    pos=middle+i;                  //начиная от центра

    if(image[pos]==1)           //влево и вправо будет искаться

      break;                       //черный пиксель

    pos=middle-i;              //после того как найдется первый

    if(image[pos]==1)              //цикл остановится

      break;                       //и переменная pos приобретет

  }                                //определенное значение

  stack[0]=pos;

  while(stackSize>=0)

  {

    pos=stack[stackSize--]; //тут все пиксели имеющие значение 1

    for(int i=-1;i<2;i++)                //переписываются в 2

      for(int j=-1;j<2;j++)              //черным пикселям, которые имеют в соседях черные пиксели, присваивается значение «2»

      {

        int p=pos+i*SIZE_X+j;

        if(image[p]==1)

        {

          stack[++stackSize]=p;

          image[p]=2;

        }

      }  }

  for(int i=0;i<SIZE_X*SIZE_Y;i++) //тут и происходит перезапись

    if(image[i]==2)

      image[i]=1;

    else

      image[i]=0;

  DrawImage();          //изображение перерисовывают так, что

}                       //лишние объекты закрашиваются черным

После выделения центрального объекта на изображении лишние точки и линии, полученные из-за шумов должны исчезнуть (рис. 9).

 

 

а) Бинарное изображение

б) Обработанное изображение после сегментации

Рис. 9. Сегментация изображения

После сегментации еще раз обработаем полученное изображение для получения границы изображения толщиной в один пиксель. Для этого используется функция Thinning (сужение). Результат работы данной функции представлен на рис. 10.

 

 

а) Обработанное изображение после сегментации

б) Изображение в выделенной границе шириной 1 пиксель

Рис. 10. Выделение границы изображения

5. Распознавание или идентификация объекта. Метод распознания цифры: мы находим крайние пиксели (черный пиксель, который имеет только черный соседний пиксель, рис. 11). С помощью крайних пикселей мы определяем положение соседних пикселей, и присваиваем им значения, определяющие их положения относительного этих крайних пикселей.  Каждое число имеет индивидуальные характеристики, записанное в «словаре», благодаря им программа распознает данное число.

 

Рис. 11. Схема «крайние» пиксели и определение их соседей

Листинг 5. Функция Recognization (распознавание цифры)

void Recognization(int r, int c)

{ int topPixel;

  int botPixel=0;

  int leftPixel=SIZE_X;

  int rightPixel=0;

  for(int i=0;i<SIZE_X*SIZE_Y;i++)

    if(image[i])

    {     if(!botPixel)           //тут ищут значения самого нижнего

botPixel=i;         //левого правого и верхнего пикселя                //(на рисунке)

      topPixel=i;

      int col=i%SIZE_X;

      if(col<leftPixel)

        leftPixel=col;

      if(col>rightPixel)                              rightPixel=col;

    }

  int topRow=topPixel/SIZE_X;               

 

//это побочные переменные введены, чтобы облегчить написание кода

  int botRow=botPixel/SIZE_X;               

  int width=rightPixel-leftPixel+1;          //находят ширину

  int longTipUpper=0;         //5 переменных необходимых для

                             //распознавания

  int longTipLower=0;

  int leftTips=0;

  int topTip=0;

  int botTip=0;

  for(int i=0;i<SIZE_X*SIZE_Y;i++)

    if(image[i])

    {

      bool longTip=false;

      int tip=Tip(i,longTip);  //tip после выполнения всех функция приобретет одно из значений (именованных сторонами света или станет 0)

      if(tip)

      {

        if(longTip)

        {

          if(!longTipLower)

            longTipLower=tip;  //эти две переменный не смогут поменять свое значение после того, как его приобретут

          else if(!longTipUpper) //они обозначают нахождение первых крайних точек

            longTipUpper=tip;

        }

        if(tip==WEST || tip==NORTHWEST || tip==SOUTHWEST)

          leftTips++;          //количество всех крайних точек слева

        if(i/SIZE_X==topRow)   //i должен быть равен самому верхнему или самому нижнему пикселю (соответственно вверх - top низ - bot)

          topTip=tip;          //чтобы эти две переменные приобрели значение

        if(i/SIZE_X==botRow)

          botTip=tip;

      }

    }

  int digit;

  if(width<6)

    digit=1;

  else if(leftTips==3 ||((longTipUpper == WEST||longTipUpper==NORTHWEST) && (longTipLower==WEST ||longTipLower==NORTHWEST) && botTip==0))

    digit=3;

  else if((longTipUpper==WEST||longTipUpper==NORTHWEST||longTipUpper==SOUTHWEST) && (longTipLower==WEST||longTipLower==SOUTHWEST||longTipLower==SOUTH))

    digit=7;

  else if(leftTips && (longTipLower==EAST||longTipLower==SOUTHEAST||longTipLower==NORTHEAST) && topTip!=NORTHEAST && topTip!=EAST)

    digit=2;

  else if((longTipUpper==EAST||longTipUpper==NORTHEAST||longTipUpper==SOUTHEAST) && (longTipLower==WEST||longTipLower==SOUTHWEST||longTipLower==NORTHWEST))

    digit=5;

  else if(topTip==NORTH||topTip==NORTHEAST||topTip==EAST)

    digit=6;

  else if(botTip==SOUTH||botTip==SOUTHWEST||botTip==WEST || longTipLower==WEST||longTipLower==SOUTHWEST||longTipLower==SOUTH)

    digit=9;

  else if((topTip==NORTH||topTip==NORTHEAST||topTip==NORTHWEST) &&(botTip==SOUTH||botTip==SOUTHWEST||botTip==SOUTHEAST))

    digit=4;

  else

    digit=8;

//После того, как программа нашла крайний пиксели, она начинает распознавать число

// Для распознавания чисел каждому числу присвоена определенная характеристика

//То есть число идентифицируется по крайним пикселям

  ClearScreen();                 //чистим экран

  NumOut(30,25,digit,true);      //выводим распознанное число и фразу FINAL

  int z =0;

  while (z == 0)

  TextOut(10,10,"FINAL");

}

ВЫВОДЫ

1.  На основе проекта «робот-судоку» нами был собран «робот-сканер», способный сканировать и распознавать цифры, изображенные в клетке 2X2.

2.  Алгоритм обнаружения и идентификации цифр роботом реализуется посредством языка программирования NXC. Реализация алгоритма требует тщательной калибровки робота.

Список литературы

1.  Конструирование и программирование робота, решающего судоку [Электронный ресурс] URL: http://tiltedtwister.com/sudokusolver.html (дата обращения 20.01.2017)

2.  Обнаружение объектов методом Оцу [Электронный ресурс] URL: https://habrahabr.ru/post/112079/ (дата обращения 20.01.2017)

 

Просмотров работы: 317