Обработка изображений: улучшение алгоритма для детектора логотипа FedEx в реальном времени

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

original frame

Fedex logo

Некоторые ограничения на проект:

  • Использует сырой OpenCV (без глубокого обучения, искусственного интеллекта или обученных нейронных сетей)
  • Фон изображения может быть шумным
  • Яркость изображения может сильно различаться (утром, днем, ночью)
  • Грузовик/логотип FedEx может иметь любой масштаб, поворот или ориентацию, так как он может быть припаркован в любом месте на тротуаре
  • Логотип может быть размытым или размытым с разными оттенками в зависимости от времени суток.
  • Там может быть много других транспортных средств с аналогичными размерами или цветами в той же раме
  • Обнаружение в реальном времени (~ 25 кадров в секунду с IP-камеры)
  • IP-камера находится в фиксированном положении, и грузовик FedEx всегда будет в одной и той же ориентации (никогда не назад или вверх ногами)
  • Грузовик FedEx всегда будет "красным" вариантом вместо "зеленого" варианта

Текущая реализация/алгоритм

У меня есть две темы:

  • Поток № 1 - захватывает кадры с IP-камеры с помощью cv2.VideoCapture() и изменяет размеры кадра для дальнейшей обработки. Решили обработать захват кадров в отдельном потоке, чтобы улучшить FPS за счет уменьшения задержки ввода/вывода, поскольку cv2.VideoCapture() блокирует. Благодаря выделению независимого потока только для захвата кадров, это позволит основному потоку обработки всегда иметь доступный кадр для обнаружения.
  • Поток № 2 - основной поток обработки/обнаружения для обнаружения логотипа FedEx с использованием цветового порога и определения контура.

Общий псевдо-алгоритм

For each frame:
    Find bounding box for purple color of logo
    Find bounding box for red/orange color of logo
    If both bounding boxes are valid/adjacent and contours pass checks:
        Combine bounding boxes
        Draw combined bounding boxes on original frame
        Play sound notification for detected logo

Цветовой порог для определения логотипа

Для определения порога цвета я определил пороговые значения HSV (низкий, высокий) для фиолетового и красного для определения логотипа.

colors = {
    'purple': ([120,45,45], [150,255,255]),
    'red': ([0,130,0], [15,255,255]) 
}

Чтобы найти координаты ограничительной рамки для каждого цвета, я следую этому алгоритму:

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

После выполнения маски я получаю эти изолированные фиолетовые (слева) и красные (справа) разделы логотипа.

LrNUJ.jpgOUzoD.jpg

Ложные положительные проверки

Теперь, когда у меня есть две маски, я выполняю проверки, чтобы убедиться, что найденные ограничивающие рамки действительно образуют логотип. Для этого я использую cv2.matchShapes() который сравнивает два контура и возвращает показатель, показывающий сходство. Чем ниже результат, тем выше совпадение. Кроме того, я использую cv2.pointPolygonTest() который находит кратчайшее расстояние между точкой на изображении и контуром для дополнительной проверки. Мой ложный положительный процесс включает в себя:

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

Если ограничивающие блоки проходят тест на метрику смежности и сходства, ограничивающие блоки объединяются и запускается уведомление FedEx.

Результаты

enter image description here enter image description here

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

enter image description here enter image description here

Хотя этот подход к определению цветовых порогов и контуров работал в основных случаях, когда логотип был четким, в некоторых областях его не хватало:

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

Кто-нибудь сможет помочь мне улучшить мой алгоритм или предложить альтернативные стратегии обнаружения? Есть ли другой способ выполнить это обнаружение, так как пороговое значение цвета сильно зависит от точной калибровки? Если возможно, я бы хотел отойти от порогового цвета и нескольких слоев фильтров, так как он не очень надежен. Любое понимание или совет с благодарностью!

Ответы

Ответ 1

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

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

Example of feature detection

Я сгенерировал вышеупомянутое изображение, используя код, модифицированный из документов OpenCV при обнаружении функции SIFT:

import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('main.jpg',0)  # target Image

# Create the sift object
sift = cv2.xfeatures2d.SIFT_create(700)

# Find keypoints and descriptors directly
kp, des = sift.detectAndCompute(img, None)

# Add the keypoints to the final image
img2 = cv2.drawKeypoints(img, kp, None, (255, 0, 0), 4)

# Show the image
plt.imshow(img2)
plt.show()

При этом вы заметите, что большое количество функций попадает на логотип FedEx (выше).

Следующее, что я сделал, попытался сопоставить функции из видеопотока с функциями в логотипе FedEx. Я сделал это с помощью функции соответствия FLANN. Вы могли бы использовать много подходов (включая грубую силу), но поскольку вы работаете над видео-фидом, это, вероятно, ваш лучший вариант. Приведенный ниже код основан на документации OpenCV по сопоставлению функций:

import numpy as np
import cv2
from matplotlib import pyplot as plt

logo = cv2.imread('logo.jpg', 0) # query Image
img = cv2.imread('main2.jpg',0)  # target Image


# Create the sift object
sift = cv2.xfeatures2d.SIFT_create(700)

# Find keypoints and descriptors directly
kp1, des1 = sift.detectAndCompute(img, None)
kp2, des2 = sift.detectAndCompute(logo,None)

# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)   # or pass empty dictionary
flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)

# Need to draw only good matches, so create a mask
matchesMask = [[0,0] for i in range(len(matches))]

# ratio test as per Lowe paper
for i,(m,n) in enumerate(matches):
    if m.distance < 0.7*n.distance:
        matchesMask[i]=[1,0]

# Draw lines
draw_params = dict(matchColor = (0,255,0),
                   singlePointColor = (255,0,0),
                   matchesMask = matchesMask,
                   flags = 0)


# Display the matches
img3 = cv2.drawMatchesKnn(img,kp1,logo,kp2,matches,None,**draw_params)
plt.imshow(img3, )
plt.show()

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

Logo Matching

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

Надеюсь, это поможет!

Ответ 2

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

enter image description here

Сначала мы уменьшаем искажение ствола.

import cv2
img = cv2.imread('fedex.jpg')
margin = 150
# add border as the undistorted image is going to be larger
img = cv2.copyMakeBorder(
                 img, 
                 margin, 
                 margin, 
                 margin, 
                 margin, 
                 cv2.BORDER_CONSTANT, 
                 0)
import numpy as np

width  = img.shape[1]
height = img.shape[0]
distCoeff = np.zeros((4,1), np.float64)

k1 = -4.5e-5;
k2 = 0.0;
p1 = 0.0;
p2 = 0.0;

distCoeff[0,0] = k1;
distCoeff[1,0] = k2;
distCoeff[2,0] = p1;
distCoeff[3,0] = p2;

cam = np.eye(3, dtype=np.float32)

cam[0,2] = width/2.0  # define center x
cam[1,2] = height/2.0 # define center y
cam[0,0] = 12.        # define focal length x
cam[1,1] = 12.        # define focal length y

dst = cv2.undistort(img, cam, distCoeff)

enter image description here

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

# use four points for homography estimation, coordinated taken from undistorted image
# 1. top-left corner of F
# 2. bottom-left corner of F
# 3. top-right of E
# 4. bottom-right of E
pts_src = np.array([[1083, 235], [1069, 343], [1238, 301],[1201, 454]])
pts_dst = np.array([[1069, 235],[1069, 320],[1201, 235],[1201, 320]])
h, status = cv2.findHomography(pts_src, pts_dst)
im_out = cv2.warpPerspective(dst, h, (dst.shape[1], dst.shape[0]))