воскресенье, 15 октября 2017 г.

N3uralV1s10n

Камера, подключенная к LEGO Mindstorms EV3 уже давно не является чем-то необычным. Конечно, в комплекте с набором ее нет, да и стандартное ПО от LEGO лишено возможности ее использования, но с появлением "прошивок" от сторонних разработчиков, таких как ev3dev и leJOS, появилась возможность подключить практически любую современную веб-камеру с USB-интерфейсом.
В нашем сегодняшнем проекте мы будем использовать камеру в качестве элемента системы машинного зрения, запрограммировав на Python простейшую нейронную сеть для распознавания образов.


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

Для получения информации с камеры мы будем использовать легковесный (по сравнению с OpenCV) модуль PyGame. Он не установлен в ev3dev "из коробки", но его можно доустановить используя менеджер модулей pip.

Конструкция у робота незамысловатая, по сути это крепление для камеры и листа бумаги А4, но тем не менее мы традиционно выкладывает инструкцию по ее сборке в формате LEGO Digital Designer, скачать ее можно по ссылке.


Мы используем камеру Logitech C110, это простейшая веб-камера с разрешением 640x480, которая имеет поддержку со стороны Linux. В конструкции используется пара датчиков-кнопок подключенных к 1 и 4 портам - они используются для "поощрения" и "наказания" нейронной сети в процессе обучения, и означают, соответственно, "Да" и "Нет".

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

Иску́сственная нейро́нная се́ть  - математическая модель, а также её программное или аппаратное воплощение, построенная по принципу организации и функционирования биологических нейронных сетей - сетей нервных клеток живого организма. Это понятие возникло при изучении процессов, протекающих в мозге, и при попытке смоделировать эти процессы. 
Искусственный нейрон — это такая функция, которая преобразует несколько входных фактов в один выходной.

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

В силу довольно скромной производительности блока EV3 мы не будем работать с полным разрешением камеры, мы снизим его в программе до 16x16 пикселов, что вполне достаточно для решения учебной задачи.



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

Первый алгоритм заключается в следующем:
1) В памяти робота перечисляются сущности, которые он сможет отличать друг от друга. Для каждой сущности создается нейрон сети с 16x16=256 входами и 1 выходом. Веса на входах нейронов в начале одинаковы у всех входов и всех нейронов.
2) Роботу показывается сущность из числа тех, которые перечислены в его памяти, он пытается угадать что это такое. Поначалу, конечно, он в большинстве случаев ошибается. Человек нажимает кнопку "Да", если робот угадал (в этом случае мы увеличиваем веса входов соответствующего нейрона, на которых были не белые пиксели), и кнопку "Нет", если не угадал (в этом случае уменьшаем веса входов соответствующего нейрона с закрашенными пикселами).
3) Робот пытается угадать снова и пересчитывает веса на входах нейронов до тех пор, пока не научится стабильно распознавать все сущности из имеющегося у него списка.

Код первой программы на Python выглядит следующим образом::

from ev3dev.ev3 import *
import pygame
import time
import pygame.camera
from random import random
from PIL import Image, ImageDraw, ImageFont

lcd = Screen()
btn = Button()

res = 16

S1 = TouchSensor("in1")
S2 = TouchSensor("in4")

buf = [ [0] * res for i in range(res)]

class number:
    def __init__(self, n):
        self.name = n
        self.sum = 0
        self.picture = [ [0] * res for i in range(res)]
               
myNumbers = [number(1), number(2), number(3), number(4)]

def camera_update(x):
    for i in range(x):
         
        image = cam.get_image()
        
        image = pygame.transform.scale(image,(res,res))
        image2buf(image)
        for i in range(res):
            for j in range(res):
                if buf[i][j] == 0:
                    lcd.draw.rectangle((i*8+25, j*8, i*8+7+25, j*8+7),fill='white')
                else:
                    lcd.draw.rectangle((i*8+25, j*8, i*8+7+25, j*8+7),fill='black')
        lcd.update()

def write(n):
    f = ImageFont.truetype('FreeMonoBold.ttf', 175)
    lcd.draw.text((30,-15), str(n), font=f)
    lcd.update()

def image2buf(surf):
    width, height = surf.get_size() 
    for y in range(height): 
        for x in range(width): 
            red, green, blue, alpha = surf.get_at((x, y)) 
            L = 0.3 * red + 0.59 * green + 0.11 * blue
            if L > 100:
                buf[x][y] = 0
            else:
                buf[x][y] = 1
      
pygame.init()
pygame.camera.init()
cameras = pygame.camera.list_cameras()
cam = pygame.camera.Camera(cameras[0])
cam.start()

f = ImageFont.truetype('FreeMonoBold.ttf', 25)
lcd.draw.text((0,50), "N3uralV1s10n", font=f)
lcd.update()
Sound.speak("nerual vision programm 1").wait()
time.sleep(2)

lcd.clear()

str1 = "please put"
str2 = "first object"
str3 = "and press enter"
lcd.draw.text((0,30), str1, font=f)
lcd.draw.text((0,55), str2, font=f)
f = ImageFont.truetype('FreeMonoBold.ttf', 20)
lcd.draw.text((0,80), str3, font=f)
lcd.update()
Sound.speak("please put first object and press enter").wait()

while(True):
    if(btn.enter): break
lcd.clear()

while(True):
    camera_update(15)
     
    image = cam.get_image()
    
    image = pygame.transform.scale(image,(res,res))
    image2buf(image)
     
    for i in range(res):
        for j in range(res):
            if buf[i][j] == 0:
                lcd.draw.rectangle(((i+25)*8, j*8, (i+25)*8+7, j*8+7),fill='white')
            else:
                lcd.draw.rectangle(((i+25)*8, j*8, (i+25)*8+7, j*8+7),fill='black')

    lcd.update()
    
    for o in myNumbers:
        o.sum = 0
    for o in myNumbers:    
        for i in range(res):
            for j in range(res):
                o.sum += buf[i][j] * o.picture[i][j]

    max_sum = -100000

    for num in myNumbers:
        if num.sum > max_sum:
            max_sum = num.sum
            tmp_obj = num
    
    lcd.clear()
    write(tmp_obj.name)
    
    Sound.speak("It is "+str(tmp_obj.name)).wait()
    while(True):
        if(S1.value()): break
        if(S2.value()): break

    if(S1.value()): 
        Sound.speak("ok yes").wait()
        a = 1
    else: 
        Sound.speak("no no").wait()
        a = -1
        
    for i in range(res):
        for j in range(res):
            if(buf[i][j] == 1):
                tmp_obj.picture[i][j] += a
   
    Sound.speak("put a new object and press enter").wait()
    while(True):
        if(btn.enter): break
        if(btn.backspace):
           Sound.speak("Exit programm").wait() 
           exit()    
    lcd.clear()

cam.stop()   

Второй алгоритм несколько отличается:
1) Изначально память робота пуста.
2) Показываем ему объект и нажимаем кнопку "Запомни эту сущность".
3) Выбираем имя для объекта кнопками на блоке.
4) В памяти робота формируется нейрон с 16x16=256 входами, при этом веса входов, которые видят закрашенные пиксели выше, чем входов с белыми пикселами.
5) показываем роботу следующий объект, он пытается сопоставить его с теми, что уже знает.
6) если робот угадал, поощряем его, нажимая "Да" (выполнится усиление связей с пересчетом весов на входах соответствующего нейрона). Если робот не угадал уже знакомый ему объект, нажимаем "Нет" (ослабляем связи), если объект новый для робота - нажимаем "Запомни эту сущность" и переходим к п. 2

Код второй программы на Python выглядит так::

from ev3dev.ev3 import *
import pygame
import time
import pygame.camera
from random import random
from PIL import Image, ImageDraw, ImageFont

lcd = Screen()
btn = Button()

res = 16

S1 = TouchSensor("in1")
S2 = TouchSensor("in4")

buf = [ [0] * res for i in range(res)]

class number:
    def __init__(self, n):
        self.name = n
        self.sum = 0
        self.picture = [ [0] * res for i in range(res)]
               

myNumbers = []

def camera_update(x):
    for i in range(x):
         
        image = cam.get_image()
        
        image = pygame.transform.scale(image,(res,res))
        image2buf(image)
        for i in range(res):
            for j in range(res):
                if buf[i][j] == 0:
                    lcd.draw.rectangle(((i*8+25), j*8, (i*8+25)+7, j*8+7),fill='white')
                else:
                    lcd.draw.rectangle(((i*8+25), j*8, (i*8+25)+7, j*8+7),fill='black')
        lcd.update()

def write(n):
    f = ImageFont.truetype('FreeMonoBold.ttf', 175)
    lcd.draw.text((30,-15), str(n), font=f)
    lcd.update()

def image2buf(surf):
    width, height = surf.get_size() 
    for y in range(height): 
        for x in range(width): 
            red, green, blue, alpha = surf.get_at((x, y)) 
            L = 0.3 * red + 0.59 * green + 0.11 * blue
            if L > 100:
                buf[x][y] = 0
            else:
                buf[x][y] = 1
      
pygame.init()
pygame.camera.init()
cameras = pygame.camera.list_cameras()
cam = pygame.camera.Camera(cameras[0])
cam.start()

lcd.clear()

f = ImageFont.truetype('FreeMonoBold.ttf', 25)
lcd.draw.text((0,50), "N3uralV1s10n", font=f)
lcd.update()
Sound.speak("neural vision programm 2").wait()
time.sleep(2)
lcd.clear()

str1 = "please put"
str2 = "first object"
str3 = "and press enter"
f = ImageFont.truetype('FreeMonoBold.ttf', 25)
lcd.draw.text((0,30), str1, font=f)
lcd.draw.text((0,55), str2, font=f)
f = ImageFont.truetype('FreeMonoBold.ttf', 20)
lcd.draw.text((0,80), str3, font=f)
lcd.update()
Sound.speak("please put first object and press enter").wait()

while(True):
    if(btn.enter): break
lcd.clear()

while(True):
    camera_update(15)
     
    image = cam.get_image()
    
    image = pygame.transform.scale(image,(res,res))
    image2buf(image)
        
    for i in range(res):
        for j in range(res):
            if buf[i][j] == 0:
                lcd.draw.rectangle(((i+25)*8, j*8, (i+25)*8+7, j*8+7),fill='white')
            else:
                lcd.draw.rectangle(((i+25)*8, j*8, (i+25)*8+7, j*8+7),fill='black')

    lcd.update()
    
    for o in myNumbers:
        o.sum = 0
    for o in myNumbers:    
        for i in range(res):
            for j in range(res):
                o.sum += buf[i][j] * o.picture[i][j]

    max_sum = -100000
    
    for num in myNumbers:
        if num.sum > max_sum:
            max_sum = num.sum
            tmp_obj = num
    
    lcd.clear()
    
    if(len(myNumbers)!=0): 
        write(tmp_obj.name)
    
        Sound.speak("It is "+str(tmp_obj.name)).wait()
    else: Sound.speak("I do not know object").wait()
    while(True):
        if(S1.value() and len(myNumbers)!=0): break
        if(S2.value() and len(myNumbers)!=0): break
        if(btn.enter or len(myNumbers)==0): break
    a=0
    if(S1.value() and len(myNumbers)!=0): 
        Sound.speak("ok yes").wait()
        a = 1
    elif(S2.value() and len(myNumbers)!=0): 
        Sound.speak("no no").wait()
        a = -1
    else: 
        Sound.speak("new object").wait()
        time.sleep(1)
        i = 48
        while(True):
            if(btn.enter): break
            if(btn.right): i+=1
            if(btn.left): i-=1
            if(i>90): i=48
            if(i<48): i=90
            if(i>=58 and i<=64): 
                if(btn.right): i=65
                else: i=57
            lcd.clear()
            
            time.sleep(0.15)
            write(chr(i))
        myNumbers.append(number(chr(i)))
        Sound.speak("new object it is" + chr(i)).wait()
        for i in range(res):
            for j in range(res):
                myNumbers[len(myNumbers)-1].picture[i][j] = buf[i][j]
    if(a!=0):
        for i in range(res):
            for j in range(res):
                if(buf[i][j] == 1):
                    tmp_obj.picture[i][j] += a
   
    Sound.speak("put a new object and press enter").wait()
    while(True):
        if(btn.enter): break
        if(btn.backspace): 
           Sound.speak("Exit programm").wait()
           exit()
    lcd.clear()

cam.stop()   

6 комментариев:

  1. Подскажите, как загрузить библиотеки на ev3dev.

    ОтветитьУдалить
  2. Подскажите, как подключить библиотеки на ev3dev

    ОтветитьУдалить
    Ответы
    1. С помощью менеджера python-пакетов pip, например https://pythonworld.ru/osnovy/pip.html

      Удалить
  3. При попытке установить (sudo pip3 install pygame) выдает:
    Downloading/unpacking pygame
    /usr/lib/python3/dist-packages/urllib3/connection.py:228: SystemTimeWarning: System time is way off (before 2014-01-01). This will probably lead to SSL verification errors
    SystemTimeWarning
    Cannot fetch index base URL https://pypi.python.org/simple/
    Could not find any downloads that satisfy the requirement pygame
    Cleaning up...
    No distributions at all found for pygame
    Storing debug log for failure in /home/robot/.pip/pip.log

    ОтветитьУдалить
  4. в ответ на sudo pip3 install pygame получаем:

    Downloading/unpacking pygame
    /usr/lib/python3/dist-packages/urllib3/connection.py:228: SystemTimeWarning: System time is way off (before 2014-01-01). This will probably lead to SSL verification errors
    SystemTimeWarning
    Cannot fetch index base URL https://pypi.python.org/simple/
    Could not find any downloads that satisfy the requirement pygame
    Cleaning up...
    No distributions at all found for pygame
    Storing debug log for failure in /home/robot/.pip/pip.log
    как обойти?

    ОтветитьУдалить
    Ответы
    1. Без базовых знаний Linux для грамотной разработки под ev3dev не обойтись. В данном конкретном случае проблемы две - не установлено время в системе, оно отличается от времени в репозитории. Вторая загвоздка - pygame для 3-го питона лучше ставить из исходников, в отличии от 2-го. Вот вся инфа по установке http://www.pygame.org/wiki/GettingStarted#Pygame Installation

      Удалить

Самое популярное