воскресенье, 14 октября 2012 г.

Python, NXT и компьютерное зрение

OpenCV (Open Source Computer Vision Library) - библиотека алгоритмов компьютерного зрения, обработки изображений и численных алгоритмов общего назначения. Она обладает большим потенциалом, который позволяет применять ее в робототехнических нуждах, так что роботы могут, подобно человеку, взаимодействовать с предметами окружающего мира - выделять и распознавать их, реагировать соответствующим образом.
Библиотека поддерживается многими языками программирования, в том числе и Python. А это значит, что благодаря модулю nxt-python, для LEGO роботов тоже можно проектировать программы, использующие в своей основе алгоритмы компьютерного зрения.

Для примера, можно рассмотреть такую возможность, как слежение за цветными объектами - пусть тележка с LEGO-пассажиром будет стремиться всегда находиться в поле зрения web-камеры:


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

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

Первый момент:
Для уменьшения цветового шума, который обязательно выдает практически любая камера используется следующая конструкция:
#размытие исходной картинки для уменьшения цветового шума
cv.Smooth(img, buf, cv.CV_BLUR, 3);
Это приводит к примерно следующему результату:
Программу демонстрирующую работу данного кода, можно скачать вот здесь.

Второй момент:
Определение зон в изображении совпадающих с заданным цветом. Причем, следует отметить, что для поиска заданного цвета изображение переводится из цветового пространства RGB (когда каждая точка изображения кодируется тремя цветами: красным, зеленым и синим) в пространство HSV (здесь тоже три компоненты, но другие: тон, насыщенность, яркость). После чего, уже и ищутся все зоны чьи точки находятся в заданном диапазоне значений.
#распознавание цвета лучше производить в пространстве
#HSV(Hue, Saturation, Value), а не RGB, поэтому необходимо
#переконверировть изображение.
cv.CvtColor(buf, buf, cv.CV_BGR2HSV) 
 
#отфильтровать только пиксели, попадающие попадающие
#под заданный диапазон
cv.InRangeS(buf, (110, 140, 200), (120, 200, 255), 
            thresholded_img)

Синий LEGO-человечек будет отмечен в результате, как следующий набор точек.
Поэкспериментировать с фильтрацией точек определенного цвета можно с помощью вот этой программы.

А что если нужно следить не за синим, а за другим цветом? Для этого придется взять другой диапазон значений, вычислить его самостоятельно. Другой вариант воспользоваться специальным Python скриптом. Он позволяет выбрать мышкой объект, по цвету отличающийся от остальных точек в изображении, а затем с помощью клавиш, сделать более точную настройку диапазона:
  • q и w - нижняя граница диапазона для значения Hue, e и r - верхняя
  • a и s - нижняя граница диапазона для значения Saturation, d и f - верхняя
  • z и x - нижняя граница диапазона для значения Value, c и v - верхняя
Программа выбора диапазона работает, примерно, следующим образом:

Итоговый Python скрипт для слежения за синим объектом и удержанием его перед камерой, посредством управления моторами на LEGO Mindstorms тележке.
Программа основана на примере, взятом вот из этого источника.
import cv
from nxt.locator import find_one_brick
from nxt.motor import *
 
# коэффициенты ПИД регулятора
KP = 0.3
KI = 0.2
KD = 3
 
camera_window = "Track a Brick" 
 
#основной класс программы 
class BrickTracker: 
 
    def __init__(self):
        #поиск NXT блока и инициализация синхронизированный моторов
        brick = find_one_brick(debug=True)
        left_engine = Motor(brick, PORT_B)
        right_engine = Motor(brick, PORT_C)
        self.vehicle = SynchronizedMotors(left_engine, right_engine, 0)
 
        #переменные используемые во время перемещения тележки
        self.flMove = False
        self.I = 0
        self.preverr = 0
 
        #создать основное окно программы, только в случае, если
        #блок успешно проинициализировался
        cv.NamedWindow(camera_window, 1)
 
        #инициализировать web-камеру
        self.capture = cv.CaptureFromCAM(0)
 
    #движение тележки вперед или назад по ПИД регулятору.
    #Параметр "shift" указывает, как далеко тележка от желаемой позиции
    def move_vehicle(self, shift):
        err = shift
        #ПИД регулятор
        P = err * KP
        self.I = err * KI + self.I
        D = (err - self.preverr) * KD
        power = P + self.I + D
        self.preverr = err
        self.I = self.I * 0.6
 
        #ограничение скорости
        if power > 80:
            power = 80
        if power < -80:
            power = -80
 
        #позиционирование не происходит абсолютно точно на заданное
        #расположение
        # - достаточно просто оказаться вблизи (+/- 5 пикселей)
        if abs(err) > 5:
            self.flMove = True
            self.vehicle.run(power)
        else:
            if self.flMove:
                self.flMove = False
                self.stop_vehicle()
 
    #остановить моторы тележки
    def stop_vehicle(self):
        self.vehicle.idle()
 
    #основной метод класса, выполняется до тех пор, пока не нажата
    #клавиша ESC
    def run(self):
        x = 320
 
        #подготовить буфера, по которым будет вестись распознавание
        buf = cv.CreateImage(cv.GetSize(cv.QueryFrame(self.capture)), 8, 3)
        thresholded_img = cv.CreateImage(cv.GetSize(buf), 8, 1) 
 
        while True: 
            #получить один кадр из камеры
            img = cv.QueryFrame(self.capture) 
 
            #размытие исходной картинки для уменьшения цветового шума
            cv.Smooth(img, buf, cv.CV_BLUR, 3); 
 
            #распознавание цвета лучше производить в пространстве
            #HSV(Hue, Saturation, Value), а не RGB, поэтому необходимо
            #переконверировть изображение.
            cv.CvtColor(buf, buf, cv.CV_BGR2HSV) 
 
            #отфильтровать только пиксели, попадающие попадающие
            #под заданный диапазон
            cv.InRangeS(buf, (110, 140, 200), (120, 200, 255), 
                        thresholded_img)
 
            #определить, если группа отфильтрованные пикселей достаточно
            #большая, чтобы быть нужным объектом. Маленькие области
            #игнорируются
            moments = cv.Moments(cv.GetMat(thresholded_img,1), 0)
            area = cv.GetCentralMoment(moments, 0, 0) 
 
            #только если область достаточно большая
            if(area > 100000): 
                #определить центр распознанного объекта 
                x = int(cv.GetSpatialMoment(moments, 1, 0)/area)
                y = int(cv.GetSpatialMoment(moments, 0, 1)/area)
 
                #поверх картинки с камеры отрисовать маркеры:
                # круг и горизонтальную линию
                cv.Circle(img, (x, y), 2, (255, 255, 255), 10)
                cv.Line(img, (0, y), (639, y), (255, 255, 255))
 
            #отобразить получившееся изображение в окне программы  
            cv.ShowImage(camera_window, img)
 
            #в зависимости от положения распознанного участка
            #передвинуть тележку
            self.move_vehicle(x - 320)
 
            #проверить нажата ли клавиша ESC
            ch = 0xFF & cv.WaitKey(10)
            if ch == 27:
                break
 
        #закрыть окно программы
        cv.DestroyAllWindows()
        #после завершения основного цикла программы отключить подачу
        #энергии на моторы
        self.stop_vehicle()
        #завершить работу с камерой
        del(self.capture)
 
if __name__=="__main__": 
    tracker = BrickTracker() 
    tracker.run()

Скачать Python-скрипт можно здесь.

Комментариев нет:

Отправить комментарий

Related Posts Plugin for WordPress, Blogger...