Урок 14

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

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

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

Подключите rviz к вашему роботу, выведете изображение камеры в rviz и направьте робота при помощи веб-интерфейса так, чтобы камера смотрела строго на шарик. После чего выведете значение пикселя по координатам центра изображения (320х240) это и будет значение желтого цвета от которого мы будем отталкиваться. Мы получим какое-то значение, к примеру, 18, 220, 255 (для желтого цвета). Собственно в этих цветовых координатах мы и будем искать наш шарик. Введем нижнюю и верхнюю границу цвета. Скажем от 14 до 25 - это будет цвет, от 180 до 220 - насыщенность и от 80 до 255 освещенность. Кстати, именно для того, чтобы так легко оперировать цветами мы и переводили изображение в HSV. Согласитесь, так гораздо проще, чем думать, как именно изменить все три компоненты базовых цветов, чтобы получить к примеру чуть другой оттенок желтого. А сейчас мы просто чуть чуть меняем первую компоненту и получаем желтый с зеленым оттенком (салатовый) или желтый с красным (оранжевый).

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

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

Задача: Наложить цветовую маску на полученное нами изображение с камеры робота.

Общий алгоритм решения:

  1. Получить изображение с камеры робота
  2. Преобразовать его в HSV (взять алгоритм из предыдущего урока)
  3. Определить цветовые границы
  4. Наложить цветовую маску - желтого цвета
  5. Записать получившееся черно-белое изображение в jpg файл

Решение: Возьмем весь код программы прошлого урока, реализующий перевод цветовой модели и сохраним его в новом файле.

import rospy
import cv2
import numpy as np
from sensor_msgs.msg import CompressedImage

rospy.init_node("photographer")

def cb_video_capture(image_msg):
        global image_from_ros_camera
        np_arr = np.frombuffer(image_msg.data, np.uint8)
        image_from_ros_camera = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)

def RGB2HSV(im):
    for j in range(im.shape[0]):
        for i in range(im.shape[1]):
            R = float(im[j][i][0]/255)
            G = float(im[j][i][1]/255)
            B = float(im[j][i][2]/255)
            V = max(R,G,B)
            if V == 0:
                S = 0
            else:
                S = ((V - min(R,G,B)) / V)
            if R == B == G:
                H = 0
            elif V == R:
                H = (60*(G - B))/(V - min(R,G,B))
            elif V == G:
                H = (120 + 60*(B - R))/(V - min(R,G,B))
            elif V == B:
                H = (240 + 60*(R - G))/(V - min(R,G,B))
            if H < 0:
                H += 360
            im[j][i][0] = int(H/2)
            im[j][i][1] = int(S*255)
            im[j][i][2] = int(V*255)
        print(j)
    return im

rospy.sleep(1)
cv2.imwrite("/home/pi/ball_JPG.jpg", image_from_ros_camera)
HSV = RGB2HSV(image_from_ros_camera)
cv2.imwrite("/home/pi/ball_HSV.jpg", HSV)

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

Просто выведем значения массива изображения с координатами (320х240) при помощи функции print()

rospy.sleep(1)
HSV = RGB2HSV(image_from_ros_camera)
cv2.imwrite("/home/pi/ball_JPG.jpg", image_from_ros_camera)
cv2.imwrite("/home/pi/ball_HSV.jpg", HSV)
print(HSV[320][240]

Попросите учащихся сохранить программу на роботе, запустить и посмотреть в консоли какое значение HSV у пикселя с координатами 320 на 240. Пусть каждый из учащихся напишет свое получившееся значение в чат.

Обсудите с учащимися почему значения у всех немного разнятся.

Все дело в освещении и калибровке камеры

У нас с нашим освещением и нашим шариком получилось значение [18, 220, 255]. У вас может получится другое значение. Теперь создадим две переменные с цветовыми координатами границ, в соответствии с рассуждениями, которые мы сделали в началe.

import rospy
import cv2
import numpy as np
from sensor_msgs.msg import CompressedImage

rospy.init_node("photographer")

yellowLower = (100, 180, 80) # dark
yellowUpper = (120, 255, 255) # light

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

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

def inRange(im):
    for j in range(im.shape[0]):
        for i in range(im.shape[1]):

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

def inRange(im):
    for j in range(im.shape[0]):
        for i in range(im.shape[1]):
            if (im[j][i][0] < yellowLower[0] or 
                   im[j][i][0] > yellowUpper[0] or
                   im[j][i][1] < yellowLower[1] or
                   im[j][i][1] > yellowUpper[1] or
                   im[j][i][2] < yellowLower[2] or
                   im[j][i][2] > yellowUpper[2]):
                    im[j][i][0] = 0
                    im[j][i][1] = 0
                    im[j][i][2] = 0
                else:
                    im[j][i][0] = 255
                    im[j][i][1] = 255
                    im[j][i][2] = 255
        return im

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

rospy.sleep(1)
HSV = RGB2HSV(image_from_ros_camera)
mask = inRange(HSV)
cv2.imwrite("/home/pi/ball_JPG.jpg", image_from_ros_camera)
cv2.imwrite("/home/pi/ball_HSV.jpg", HSV)
cv2.imwrite("/home/pi/ball_MASK.jpg", mask)

Проверка решения:

Попросите учащихся сохранить программу на роботе и запустить ее.

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

Он должен выглядеть примерно так:

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

results matching ""

    No results matching ""