Урок 13

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

Цвет — это человеческое восприятие светового потока, попадающего в глаз. Говоря простым языком, цвет — это ощущение, которое получает человек при попадании ему в глаз световых лучей. Поток света с одним и тем же спектральным составом вызовет похожие ощущения у разных людей в силу того, что у них похожие физиологические характеристики глаз, и для каждого из них цвет будет слегка разным, но в целом похожим. А что такое "желтый" цвет? Существует популярное представление света как смеси трех основных цветов Красного, Синего и Зеленого. Т.е. любой видимый нами цвет можно представить как совокупность этих трех цветов в различной интенсивности.

К примеру, вот такой вот желтый цвет, можно представить в виде композиции Красного с интенсивностью 247, Зеленого с интенсивностью 255, и Синего нулевой интенсивности. Обычно интенсивность цвета измеряется в градации от 0 до 255. Где 0 это минимальная интенсивность, а 255 максимальная. Данную цветовую модель называют “Модель RGB”, по названиям базовых цветов Red-Green-Blue, Красный-Зеленый-Синий.

Однако люди воспринимают цвет в несколько другой интерпретации, мы не всегда понимаем, какие именно цвета и в каких пропорциях надо смешать, чтобы получить тот или иной цвет, который мы видим. И таким образом нам тяжело сказать, как нам надо изменить соотношение базовых цветов, чтобы получить цвет, который мы хотим. К примеру, какое сочетание цветов RGB будет для Фиолетового? Наверное, там будет много Синего и Красного, а вот сколько там должно быть Зеленого? Не понятно… Или в случае с нашим шариком, допустим мы подберем такое сочетание цветов, которое будет означать желтый цвет именно этого шарика, а если изменится освещение (а оно обязательно изменится), то в какую сторону нам изменять значения цветов, чтобы получить новый, более темный или более светлый желтый?

Именно поэтому для облегчения ориентации в цветовом пространстве часто применяют другую цветовую модель - HSV. Где первое число H отвечает чисто за цвет в спектре радуги, второе Saturation за насыщенность цвета, а третье Value за яркость.

В такой цветовой модели гораздо проще ориентироваться. Например, всем цветам, которые люди могут охарактеризовать как «желтый» соответствует диапазон H от 30 до 70, а яркость и насыщенность у них может быть любой – все равно это будут желтые цвета, просто от темно-желтого - почти черного, до светло-желтого - почти белого.

Таким образом, если в световой модели HSV отфильтровать все цвета, значения H которых лежат вне диапазона от 30 до 70, то оставшиеся пиксели на изображении будут соответствовать только объектам желтого цвета. Т.е. нам надо перевести изображение, которое у нас первоначально было в RGB-модели в HSV-модель и применить фильтр к получившемуся массиву данных.

Подробнее про цветовые модели вы можете почитать здесь). В этой же статье есть формулы, которые надо применить к значениям каждого пикселя изображения в модели RGB, чтобы перевести ее в HSV. Вот они:

Все что нам надо сделать, для нашего RGB массива из прошлого занятия, это пробежать в цикле по всем пикселям, взять значения R, G и B для каждого пикселя, применить формулы для перевода в HSV и получить новый массив уже в новой цветовой модели.

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

  1. Возьмем программу прошлого урока как заготовку
  2. Возьмем массив сконвертированный в RGB JPG
  3. В цикле применим к каждому пикселю формулу перевода из одной цветовой модели в другую
  4. Сохраним новый полученный массив в файл.

Решение: Возьмем решение из прошлого урока как заготовку

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)

rospy.Subscriber("/front_camera/image_raw/compressed", CompressedImage, cb_video_capture)
rospy.sleep(1)

cv2.imwrite("/home/pi/ball_JPG.jpg", image_from_ros_camera)

Теперь опишем функцию перевода цветовой модели из RGB в HSV, назовем ее RGB2HSV

def RGB2HSV(im):

Переменная im, которая является аргументом нашей функции, содержит тот самый по-пиксельный массив данных нашего изображения, который мы и будем обрабатывать. Это класс многомерного массива библиотеки numpy именно его мы получили применив функции np.frombuffer и cv2.imdecode к данным переданным в функцию обратного вызова подписчика на данные с камеры. Этот класс имеет несколько свойств, и в частности у него есть такое свойство как shape, это кортеж из 3-х элементов в котором указана ширина и высота изображения в пикселях и глубина цвета. В нашем случае это 480 на 640 с глубиной 3. То, что размеры массива нам известны дает нам возможность легко организовать перебор изображения по каждому пикселю каждой строки. Сделаем два цикла и вложим один в другой. В первом будем пробегать по строкам, а во втором по элементам этих строк - пикселям

Если посмотреть на формулу перевода цветовой модели, то видно, что она требует, чтобы значения R,G,B,S,V лежали в пределах от 0 до 1. Давайте вытащим у каждого пикселя значения R,G и B, они находятся в 3-м измерении массива im, и так как максимум эти значений 255 поделим их значения на 255, чтобы частное от деления уложилось в интервал от 0 до 1.

    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)

Дальше к каждому из значений применим формулу перевода цветовой модели и получим уже значения H,S и V

            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 новые значения. Ведь в массиве не написано, что это именно RBG. А так как размерности совпадают, мы можем также попиксельно перезаписать новые значения.

            im[j][i][0] = int(H/2)
            im[j][i][1] = int(S*255)
            im[j][i][2] = int(V*255)

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

            print(j)
        return im

Функция перевода цветовой модели - готова, давайте передадим в нее данные с камеры и сохраним результат в файл .jpg

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)

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

В целом вся программа выглядит так:

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)

rospy.Subscriber("/front_camera/image_raw/compressed", CompressedImage, cb_video_capture)
rospy.sleep(1)

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)

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

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

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

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

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

results matching ""

    No results matching ""