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

В этой статье мы обсудим некоторые приемы и приемы разметки в OpenCV. С помощью этих методов мы создадим автоматизированный инструмент для разметки одного класса. Он также будет иметь функцию отклонения ненужных объектов. Все это использует возможности некоторых простых алгоритмов в OpenCV.

Содержание

  1. Зачем создавать пользовательский инструмент разметки?

  2. Инструмент разметки изображений с использованием OpenCV

  3. Базовый рабочий процесс разметки изображений в OpenCV

  4. Кодовое объяснение трюков с разметкой изображений в OpenCV

    1. Изображения в оттенках серого

    2. Разметка на основе цветовой сегментации

    3. Анализ цветового пространства RGB

    4. Анализ цветового пространства HSV

  5. Сохранение разметок в разных форматах

  6. Демонстрация инструмента автоматической разметки изображений

  7. Заключение

1. Зачем создавать пользовательский инструмент разметки с использованием OpenCV?

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

  1. Ограниченные бесплатные сервисы. Учитывая связанные с этим затраты, эти услуги могут быть недоступны для всех.

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

  3. Существует также проблема конфиденциальности данных.

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

  1. В отличие от ресурсоемких моделей глубокого обучения, OpenCV требует небольшой вычислительной мощности.

  2. Это значительно быстрее.

  3. Инвариантен к ряду объектов; следовательно, он хорош для изображений с большим количеством объектов.

  4. Подписка или единовременный платеж не требуются. Это абсолютно БЕСПЛАТНО!

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

2. Инструмент разметки изображений с использованием OpenCV

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

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

Рис. 1. Разметка дикой природы в инфракрасных наборах данных
Рис. 1. Разметка дикой природы в инфракрасных наборах данных
Рис. 2: Описание спелой клубники и косяка красных рыб
Рис. 2: Описание спелой клубники и косяка красных рыб
Рис. 3: Разметка монет и коробок на разных конвейерных лентах
Рис. 3: Разметка монет и коробок на разных конвейерных лентах

3. Базовый рабочий процесс разметки изображений в OpenCV

Контурный анализ в OpenCV - это мощный инструмент. Мы будем использовать его для оценки ограничивающих рамок вокруг обнаруженных контуров. Однако мы не должны использовать готовое изображение напрямую. Во-первых, он должен пройти некоторую предварительную обработку, чтобы замаскировать объекты.

Рис. 4. Основные этапы разметки с использованием OpenCV
Рис. 4. Основные этапы разметки с использованием OpenCV

OpenCV по умолчанию считывает изображение в формате BGR, тогда как операция порогового значения принимает только 1-канальное изображение. Мы можем поэкспериментировать с различными цветовыми каналами, чтобы увидеть, какой канал является наиболее контрастным.

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

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

4. Кодовое объяснение приемов разметки изображений в OpenCV

Мы начинаем с импорта необходимых библиотек, NumPy и OpenCV.

import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['image.cmap'] = 'gray'

Загружаем изображения

stags = cv2.imread('stags.jpg')
boars = cv2.imread('boar.jpg')
berries = cv2.imread('strawberries.jpg')
fishes = cv2.imread('fishes.jpg')
coins = cv2.imread('coins.png')
boxes = cv2.imread('boxes2.jpg')

Выбераем Цветовое пространство

Давайте определим служебную функцию для выбора требуемого цветового пространства. Мы добавили RGB и HSV. Отдельные каналы хранятся в словаре. Не стесняйтесь добавлять больше цветовых пространств для экспериментов.

def select_colorsp(img, colorsp='gray'):
    # Преобразование в оттенки серого.
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # Разделить BGR.
    red, green, blue = cv2.split(img)
    # Преобразовать в HSV.
    im_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    # Разделить HSV.
    hue, sat, val = cv2.split(im_hsv)
    # Записываем каналы в словаре.
    channels = {'gray':gray, 'red':red, 'green':green, 
                'blue':blue, 'hue':hue, 'sat':sat, 'val':val}
     
    return channels[colorsp]

Служебная функция для отображения изображений размером 1 × 2

Функция display() принимает два изображения и отображает их рядом друг с другом. Необязательными аргументами являются названия графиков и размер рисунка, как показано ниже.

def display(im_left, im_right, name_l='Left', name_r='Right', figsize=(10,7)):
     
    # Переключайте каналы для отображения, если RGB как matplotlib требует RGB.
    im_l_dis = im_left[...,::-1]  if len(im_left.shape) > 2 else im_left
    im_r_dis = im_right[...,::-1] if len(im_right.shape) > 2 else im_right
     
    plt.figure(figsize=figsize)
    plt.subplot(121); plt.imshow(im_l_dis);
    plt.title(name_l); plt.axis(False);
    plt.subplot(122); plt.imshow(im_r_dis);
    plt.title(name_r); plt.axis(False);

Выполните пороговое значение

Функция thresh() принимает 1-канальное изображение в оттенках серого. Пороговое значение по умолчанию равно 127. Мы выполняем обратное пороговое значение, поскольку это более удобно при выполнении контурного анализа. Он возвращает одноканальное пороговое изображение.

def threshold(img, thresh=127, mode='inverse'):
    im = img.copy()
     
    if mode == 'direct':
        thresh_mode = cv2.THRESH_BINARY
    else:
        thresh_mode = cv2.THRESH_BINARY_INV
     
    ret, thresh = cv2.threshold(im, thresh, 255, thresh_mode)
         
    return thresh

Хотите знать, как выбрать идеальное пороговое значение? Используйте это потоковое приложение openThreshold, чтобы получать обратную связь в режиме реального времени при пороговом значении изображения.

4.1 Разметка инфракрасного изображения оленей

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

  1. Выбираем цветовое пространство.

  2. Выполняем пороговое значение.

  3. Выполняем морфологические операции.

  4. Анализируем контуры для поиска ограничивающих рамок.

  5. Фильтруем ненужные контуры.

  6. Рисуем разметку.

  7. Сохраняем в требуемом формате.

Самая важная часть - визуализировать каждый шаг по мере продвижения вперед. Это поможет вам принять решение о следующем шаге.

Выполним пороговое значение для соответствующего цветового пространства

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

# Выбираем цветовое пространство.
gray_stags = select_colorsp(stags)
# Perform thresholding.
thresh_stags = threshold(gray_stags, thresh=110)
 
 
# Display.
display(stags, thresh_stags, 
        name_l='Stags original infrared', 
        name_r='Thresholded Stags',
        figsize=(20,14))

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

Выполните анализ контуров для извлечения ограничивающих рамок

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

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

def get_bboxes(img):
    contours, hierarchy = cv2.findContours(img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    # Сортировка по площади контуров в порядке убывания.
    sorted_cnt = sorted(contours, key=cv2.contourArea, reverse = True)
    # Удалите максимальную площадь, самый внешний контур.
    sorted_cnt.remove(sorted_cnt[0])
    bboxes = []
    for cnt in sorted_cnt:
        x,y,w,h = cv2.boundingRect(cnt)
        cnt_area = w * h
        bboxes.append((x, y, x+w, y+h))
    return bboxes

Служебная функция для рисования разхметки

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

def draw_annotations(img, bboxes, thickness=2, color=(0,255,0)):
    annotations = img.copy()
    for box in bboxes:
        tlc = (box[0], box[1])
        brc = (box[2], box[3])
        cv2.rectangle(annotations, tlc, brc, color, thickness, cv2.LINE_AA)
     
    return annotations

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

  • Выполнить морфологическую операцию.

  • Отфильтруйте меньшие области.

Уточнение обнаружений с помощью морфологических операций

Функция morph_op() определена для приема 1-канального изображения для выполнения морфологической операции. Выберите режим между ‘размыванием’‘расширением’‘открытием’ и ‘закрытием’. Необязательными аргументами являются ksize (размер ядра) и количество итераций.

def morph_op(img, mode='open', ksize=5, iterations=1):
    im = img.copy()
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(ksize, ksize))
     
    if mode == 'open':
        morphed = cv2.morphologyEx(im, cv2.MORPH_OPEN, kernel)
    elif mode == 'close':
        morphed = cv2.morphologyEx(im, cv2.MORPH_CLOSE, kernel)
    elif mode == 'erode':
        morphed = cv2.erode(im, kernel)
    else:
        morphed = cv2.dilate(im, kernel)
     
    return morphed

# Выполняем морфологическую операцию.
morphed_stags = morph_op(thresh_stags)
 
# Display.
display(thresh_stags, morphed_stags, 
        name_l='Thresholded Stags', 
        name_r='Morphological Operations Result',
        figsize=(20,14))

Результаты выглядят почти так же! Но был ли он успешным в удалении более мелких сгустков? Давайте нарисуем примечания к измененным оленям, чтобы выяснить это.

Нарисуйте разметку на измененных стадиях

bboxes = get_bboxes(morphed_stags)
ann_morphed_stags = draw_annotations(stags, bboxes, thickness=5, color=(0,0,255))
 
# Display.
display(ann_stags, ann_morphed_stags, 
        name_l='Annotating Thresholded Stags', 
        name_r='Annotating Morphed Stags',
        figsize=(20,14))

Мы видим, что улучшение незначительно. Исчезла только клякса на втором олене (слева).

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

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

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

Уточнение обнаружений путем фильтрации блоков меньшего размера

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

def get_filtered_bboxes(img, min_area_ratio=0.001):
    contours, hierarchy = cv2.findContours(img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    # Отсортируем контуры по площади, от большего к меньшему.
    sorted_cnt = sorted(contours, key=cv2.contourArea, reverse = True)
    # Удаляем максимальную площадь, самый внешний контур.
    sorted_cnt.remove(sorted_cnt[0])
    # Container to store filtered bboxes.
    bboxes = []
    # Область изображения.
    im_area = img.shape[0] * img.shape[1]
    for cnt in sorted_cnt:
        x,y,w,h = cv2.boundingRect(cnt)
        cnt_area = w * h
        # Удалите очень мелкие дефекты.
        if cnt_area > min_area_ratio * im_area:
            bboxes.append((x, y, x+w, y+h))
    return bboxes

Нарисуйте разметку после фильтрации небольших блоков

bboxes = get_filtered_bboxes(thresh_stags, min_area_ratio=0.001)
filtered_ann_stags = draw_annotations(stags, bboxes, thickness=5, color=(0,0,255))
 
# Display.
display(ann_stags, filtered_ann_stags, 
        name_l='Annotating Thresholded Stags', 
        name_r='Annotation After Filtering Smaller Boxes',
        figsize=(20,14))

Как вы можете видеть, мы получили желаемый результат. Теперь все, что нам нужно сделать, это сохранить координаты ограничивающей рамки в требуемом формате. Мы немного обсудим форматы разметки. Давайте посмотрим на результаты в полном видео. Обратите внимание на сумасшедший FPS!

Разметка изображения диких кабанов

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

  • Выбираем цветовое пространство.

  • Выполняем пороговое значение.

  • Выполняем морфологические операции.

  • Находим контуры и рисуем разметку.

  • Находим отфильтрованные контуры и рисуем разметку.

Выберите цветовое пространство и пороговое значение

# Выбираем цветовое пространство.
gray_boars = select_colorsp(boars, colorsp='gray')
 
# Выполняем пороговое значение.
thresh_boars = threshold(gray_boars, thresh=140)
 
# Display.
display(boars, thresh_boars, 
        name_l='Boars Original Infrared', 
        name_r='Thresholded Boars',
        figsize=(20, 14))

Выполнить морфологическую операцию

Мы можем видеть, что пороговое изображение имеет значительно большее количество белых точек. Давайте удалим их с помощью морфологических операций. Открытый режим с kernel_size= 13, похоже, работает отлично.

# Выполняем морфологическую операцию.
morph_boars = morph_op(thresh_boars, mode='open', ksize=13)
 
display(thresh_boars, morph_boars,
        name_l='Thresholded Boars', 
        name_r='Morphed Boars',
        figsize=(20, 14))

Получение отфильтрованных контуров и создание разметки

# Находим контуры.
bboxes = get_filtered_bboxes(morph_boars)
# Рисуем разметку.
ann_boars = draw_annotations(boars, bboxes, thickness=4)
 
display(boars, ann_boars,
        name_l='Original Boars Infrared', 
        name_r='Annotated Boars',
        figsize=(20, 14))

Как вы можете видеть, обнаружения являются приличными, за исключением случаев, когда объекты перекрываются. Это ограничение обнаружения контуров. Мы увидим, как это немного исправить.

4.2 Разметки на основе цветовой сегментации

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

Шаги, которые необходимо выполнить:

  1. Выбираем цвет. Здесь мы будем использовать цветовое пространство hsv.

  2. Находим цветовую маску.

  3. Выполняем пороговые и морфологические операции.

  4. Выполняем анализ контуров.

  5. Рисуем разметку.

display(berries, fishes,
       name_l='Red Strawberries',
       name_r='School of Fishes',
       figsize=(20,14))

Создание цветовой маски

Функция get_colormask() принимает изображение RGB вместе с диапазоном значений цвета HSV. Аргументы ключевого слова lower и upper представляют нижнюю и верхнюю границы HSV. Он возвращает маску, используя функцию OpenCV inRange.

def get_color_mask(img, lower=[0,0,0], upper=[0,255,255]):
    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    low = np.array(lower)
    up = np.array(upper)
    mask = cv2.inRange(img_hsv, low, up)
    inv_mask = 255 - mask
     
    return inv_mask

Пороговые значения и морфологические операции

mask_berries = get_color_mask(berries, 
                              lower=[0, 211, 111], 
                              upper=[16, 255,255])
 
# Морфологическая операция, значение по умолчанию - 'open'.
morphed_berries = morph_op(mask_berries)

Вышеуказанные границы были установлены для маскировки красного цвета клубники. Как мы можем это получить? Вы можете либо сойти с ума, пробуя 225 комбинаций C 6 , либо использовать streamlit app openSegment, который очень прост. Отрегулируйте ползунки, чтобы замаскировать нужные цвета.

Выполните анализ контуров и рисуем разметку

# Контурный анализ.
bboxes = get_filtered_bboxes(morphed_berries, 
                             min_area_ratio=0.0005)
 
# Рисуем разметку.
ann_berries = draw_annotations(berries, bboxes, 
                               thickness=2, 
                               color=(255,0,0))
 
# Display.
display(berries, ann_berries,
        name_l='Strawberries', 
        name_r='Annotated Strawberries',
        figsize=(20, 14))

Результаты выглядят многообещающими. Теперь вы можете легко размечать тысячи изображений клубники с минимальным ручным вмешательством. Как насчет создания робота для сбора клубники?

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

# Get the color mask.
mask_fishes = get_color_mask(fishes, 
                             lower=[0, 159, 100], 
                             upper=[71, 255, 255])
 
# Выполняем морфологическую операцию, значение по умолчанию равно'open'.
morphed_fishes = morph_op(mask_fishes, mode='open')
# Получите ограничивающие рамки.
bboxes = get_filtered_bboxes(morphed_fishes)
# Рисуем разметку.
ann_fishes = draw_annotations(fishes, bboxes, thickness=1)
 
# Display.
display(fishes, ann_fishes,
        name_l='Fishes', 
        name_r='Annotated Fishes',
        figsize=(20, 14))

4.3 Анализ цветового пространства RGB

Как упоминалось ранее, чем больше контраст, тем лучше пороговое значение. Следовательно, иногда лучше выполнять пороговое значение на разных каналах. Давайте возьмем пример с монетой, чтобы лучше понять его.

Выберите цветовое пространство

Мы будем использовать функцию select_colorsp() для получения отдельных цветовых каналов.

blue = select_colorsp(coins, colorsp='blue')
green = select_colorsp(coins, colorsp='green')
red = select_colorsp(coins, colorsp='red')
gray = select_colorsp(coins, colorsp='gray')
 
# Display.
plt.figure(figsize=(20,14))
plt.subplot(141); plt.imshow(blue);
plt.title('Blue'); plt.axis(False);
plt.subplot(142); plt.imshow(green);
plt.title('Green'); plt.axis(False);
plt.subplot(143); plt.imshow(red);
plt.title('Red'); plt.axis(False);
plt.subplot(144); plt.imshow(gray);
plt.title('Gray'); plt.axis(False);

Выполните пороговое значение

В данном случае мы используем пороговое значение = 74. Подобно приведенным выше примерам, он был получен с использованием openThreshold.

blue_thresh  = threshold(blue,  thresh=74)
green_thresh = threshold(green, thresh=74)
red_thresh   = threshold(red,   thresh=74)
gray_thresh  = threshold(gray,  thresh=74)
 
# Display.
plt.figure(figsize=(20,14))
plt.subplot(141); plt.imshow(blue_thresh);
plt.title('Blue Threshold'); plt.axis(False);
plt.subplot(142); plt.imshow(green_thresh);
plt.title('Green Threshold'); plt.axis(False);
plt.subplot(143); plt.imshow(red_thresh);
plt.title('Red Threshold'); plt.axis(False);
plt.subplot(144); plt.imshow(gray_thresh);
plt.title('Gray Threshold'); plt.axis(False);

Очевидно, что пороговое значение работает намного лучше на Зеленый канал. Давайте продолжим с настройками по умолчанию для порогового значения Зеленый канал.

Морфологические операции, анализ контуров и разметка для рисования

# Выполняе морфологическую операцию.
morph_coin = morph_op(green_thresh)
# Получаем ограничивающие рамки.
bboxes = get_filtered_bboxes(morph_coin)
# Рисуйте разметку.
ann_coins = draw_annotations(coins, bboxes)
 
# Display.
display(coins, ann_coins, 
        name_l='Coins Original', 
        name_r='Annotated Coins', 
        figsize=(10,6))

4.4 Анализ цветового пространства HSV

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

Давайте сначала проверим отдельные каналы RGB изображения в рамке.

Проверка каналов RGB

# RGB colorspace.
blue_boxes = select_colorsp(boxes, colorsp='blue')
green_boxes = select_colorsp(boxes, colorsp='green')
red_boxes = select_colorsp(boxes, colorsp='red')
gray_boxes = select_colorsp(boxes, colorsp='gray')
 
# Display.
plt.figure(figsize=(20,7))
plt.subplot(221); plt.imshow(blue_boxes);
plt.title('Blue'); plt.axis(False);
plt.subplot(222); plt.imshow(green_boxes);
plt.title('Green'); plt.axis(False);
plt.subplot(223); plt.imshow(red_boxes);
plt.title('Red'); plt.axis(False);
plt.subplot(224); plt.imshow(gray_boxes);
plt.title('Gray'); plt.axis(False);

Мы видим, что ни один из каналов R, G, B или Gray не является контрастным. Давайте попробуем использовать цветовое пространство HSV.

Проверка каналов HSV

# HSV colorspace.
hue_boxes = select_colorsp(boxes, colorsp='hue')
sat_boxes = select_colorsp(boxes, colorsp='sat')
val_boxes = select_colorsp(boxes, colorsp='val')
 
# Display.
plt.figure(figsize=(20,7))
plt.subplot(221); plt.imshow(hue_boxes);
plt.title('Hue'); plt.axis(False);
plt.subplot(222); plt.imshow(sat_boxes);
plt.title('Saturation'); plt.axis(False);
plt.subplot(223); plt.imshow(val_boxes);
plt.title('Lightness'); plt.axis(False);
plt.subplot(224); plt.imshow(gray_boxes);
plt.title('Gray'); plt.axis(False);

Разметка поля

Здесь это становится интересным. Слой насыщенности имеет хорошо контрастирующие поля. Все, что нам нужно сделать, это пороговое значение слоя насыщенности и выполнить анализ контуров. Опять же, мы использовали приложение streamlit openThreshold, чтобы получить пороговое значение (thresh = 70) для маскировки объектов.

boxes_thresh = threshold(sat_boxes, thresh=70)
morphed_boxes = morph_op(boxes_thresh, mode='open')
bboxes = get_filtered_bboxes(morphed_boxes)
ann_boxes = draw_annotations(boxes, bboxes, thickness=4, color=(0,0,255))
 
plt.figure(figsize=(10, 7))
plt.subplot(211); plt.imshow(boxes[...,::-1]); 
plt.title('Boxes Original'); plt.axis(False);
plt.subplot(212); plt.imshow(ann_boxes[...,::-1]); 
plt.title('Annotated Boxes'); plt.axis(False);

5. Сохранение разметку в разных форматах

Pascal VOC, YOLO и COCO - это три популярных формата разметки, используемых при обнаружении объектов. Давайте изучим их структуру.

5.1 Pascal VOC

Pascal VOC хранит разметку в формате XML и выглядит следующим образом.

<annotation>
  <folder>Folder_Name</folder>
  <filename>image.jpg</filename>
  <path>PATH_TO_THE_IMAGE</path>
  <size>
    <width>800</width>
    <height>598</height>
    <depth>3</depth>
  </size>
  <object>
    <name>cow</name>
    <bndbox>
      <xmin>40</xmin>
      <ymin>90</ymin>
      <xmax>100</xmax>
      <ymax>350</ymax>
    </bndbox>
  </object>
</annotation>

5.2 YOLO

Разметка YOLO сохраняются в текстовом файле. Для каждой ограничивающей рамки это выглядит следующим образом.

0 45 55 30 68
 
# The format is
<object-class> <x> <y> <width> <height>

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

0 0.0123 0.2345 0.123 0.754
<object-class> <x_centre_norm> <y_centre_norm> <box_width_norm> <box_height_norm>

Пусть верхние левые и нижние правые координаты ограничивающей рамки представлены как (x1, y1) и (x2, y2). Затем,

x_centre = int ((x2 - x1) / 2)
y_centre = int ((y2 - y1) / 2)
 
x_centre_norm = x_centre / image_width
y_centre_norm = y_centre / image_height
 
box_width_norm  = (x2  - x1) / image_width
box_height_norm = (x2  - x1) / image_height

5.3 MS COCO

annotation{
  "id": int,
  "image_id": int,
  "category_id": int, 
  "bbox": [x, y, width, height],
}
categories[{
  "id": int,
  "name": str, 
  "supercategory":str,
}]

5.4 Сохранение разметки

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

def save_annotations(img, bboxes):
    img_height = img.shape[0]
    img_width = img.shape[1]
    with open('image.txt', 'w') as f:
        for box in boxes:
            x1, y1 = box[0], box[1]
            x2, y2 = box[2], box[3]
             
            if x1 > x2:
                x1, x2 = x2, x1
            if y1 > y2:
                y1, y2 = y2, y1
                 
            width = x2 - x1
            height = y2 - y1
            x_centre, y_centre = int(width/2), int(height/2)
 
            norm_xc = x_centre/img_width
            norm_yc = y_centre/img_height
            norm_width = width/img_width
            norm_height = height/img_height
 
            yolo_annotations = ['0', ' ' + str(norm_xc), 
                                ' ' + str(norm_yc), 
                                ' ' + str(norm_width), 
                                ' ' + str(norm_height), '\n']
             
            f.writelines(yolo_annotations)

6. Демонстрация инструмента автоматической разметки изображений

Разметки с использованием OpenCV не идеальны. Самым большим недостатком является то, что он не может различать классы. Использование подхода, основанного на контурах, позволяет выбирать объекты независимо от класса. Более того, при наличии перекрывающихся границ два или более объектов могут быть размечены как один.

Мы разработали инструмент разметки на основе графического интерфейса pyOpenAnnotate для устранения вышеупомянутого ограничения. Он использует различные методы, которые мы обсуждали выше, для извлечения масок переднего плана.

Если выходные данные имеют недостатки, неправильную маркировку классов или нежелательную разметку, можно удалить одним щелчком мыши. Отсутствующую разметку также можно нарисовать вручную.

Заключение

Мы изучили различные методы локализации ограничивающих рамок вокруг объектов. Подводя итог, ниже приведены необходимые шаги.

  1. Выбираем подходящее цветовое пространство

  2. Выполняем пороговое значение

  3. Выполняем морфологических операций для сглаживания

  4. Выполняем анализ контуров

  5. Фильтруем нежелательные контуры

  6. Получаем ограничивающие рамки и нарисуйте контуры

  7. Сохраняем обнаружений в различных форматах

Код на GitHub

Комментарии (0)