Стабильное распознавание значков с помощью opencv python

У меня следующая проблема: мне нужно надежно обнаруживать значки на изображении. Изображение также содержит текст, а значки бывают разных размеров.

В настоящее время для этой задачи я использую Python с библиотекой cv2. Однако, к сожалению, текущий алгоритм обнаружения контуров с использованием cv2.findContours не очень надежен. Вот как я сейчас это делаю:

gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
binary = cv2.adaptiveThreshold(self.gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 17, 1)
contours, _ = cv2.findContours(self.binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

Затем следует контурная фильтрация, объединение отфильтрованных контуров и повторная фильтрация.

Однако этот метод оказался ненадежным.

Я также пробовал использовать getStructuringElement, но для значков на белом фоне это дает ненадежные результаты.

Я не могу раскрыть реальные входные данные, но я использовал логотип Amazon и создал пример, демонстрирующий проблему.

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

Мои вопросы: Что ты посоветуешь? Мои идеи:

  1. Обучите каскад Хаара. Сильно ли это поможет?
  2. Точная настройка параметров одного из текущих методов.
  3. Любые другие методы/библиотеки.

Я открыт для любых предложений или дайте мне знать, если у кого-нибудь есть опыт решения такой проблемы.

Изображение: https://i.sstatic.net/bZYrnCTU.jpg

ПС: Для других людей, которых, возможно, заинтересует более или менее та же задача.

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

Например (после тщательной обработки)

Что вы можете сделать, так это просто нарисовать контуры большей толщины (например, 2), а затем заново создать контуры на новом изображении.

contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(edges, contours, -1, (255, 255, 255), 2)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

Кажется более-менее супер-простым и очень быстрым алгоритмом слияния контуров)

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
234
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Для повышения достоверности используйте данные контура для проверки площади контура. Значки кажутся больше по площади, чем текст, поэтому вы можете установить пороговое значение области, чтобы отфильтровать их, используя minAreaRectangle = cv2.minAreaRect(contour):

import cv2
import numpy as np
srcImg = cv2.imread("iconDetection.jpg")

grayImg = cv2.cvtColor(srcImg, cv2.COLOR_BGR2GRAY)
ret, bwImg  = cv2.threshold( grayImg, 200, 255, cv2.THRESH_BINARY_INV)
cv2.imshow('Binary', bwImg) ; cv2.waitKey(0); cv2.destroyAllWindows() 

Contours , _    = cv2.findContours(bwImg.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

BBoxes=[] 
for contour in Contours:
    minAreaRectangle = cv2.minAreaRect(contour)
    bBox = cv2.boxPoints(minAreaRectangle)
    if cv2.contourArea(bBox)  > 60 : # exclude small objects  
        BBoxes += [ np.intp(bBox) ] # include only large areas 

for bBox in BBoxes: 
    tgtImg = cv2.drawContours(srcImg ,[bBox],0,(0,255,0),3)

cv2.imshow('Bounding Rectangles', tgtImg) ; cv2.waitKey(0); cv2.destroyAllWindows() 

дает:

Зачем вам нужно получать область minAreaRect, если вы можете просто фильтровать область контура с помощью cv2.contourArea()

fmw42 02.05.2024 21:15

@ fmw42: Я предполагаю, что minAreaRect охватывает больше случаев различных значков, где форма контура найденного значка по какой-то причине неправильная (например, это только крест). Почему вы считаете, что площади контура достаточно?

oOosys 03.05.2024 02:03

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

fmw42 03.05.2024 07:04

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

Vasiliy Platon 03.05.2024 10:11

@VasiliyPlaton: см. мой другой ответ о другом подходе, не имеющем этого недостатка, по крайней мере, в случае предоставленного тестового изображения. Он менее тяжел в вычислениях и улавливает значки, внешний контур которых почти белый.

oOosys 03.05.2024 14:27
Ответ принят как подходящий

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

im = cv2.imread("logos.jpg")
imGray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(imGray,5, 20)

Это дало следующий результат:

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

contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
sortedContours = sorted(contours, key = cv2.contourArea, reverse = True)
for c in sortedContours:
    print(cv2.contourArea(c))

Мы видим, что действительно три самых больших по площади контура имеют размер около 10500 пикселей:

10787.0
10715.0
10128.0
7391.5
4555.5
3539.0
3420.0
.
.
.

Заполните эти первые три контура:

im1 = cv2.drawContours(im.copy(), sortedContours, 2, (255,0,0), -1)
im2 = cv2.drawContours(im.copy(), sortedContours, 1, (0,255,0), -1)
im3 = cv2.drawContours(im.copy(), sortedContours, 0, (0,0,255), -1)

И вот что вы получите:

Я предполагаю, что вам нужна логическая маска, позволяющая получить эти пиксели. Итак, что-то вроде

mask = np.zeros_like(imGray)
mask = cv2.drawContours(mask, sortedContours, 2, 1, -1)
firstLogo = cv2.bitwise_and(im, im, mask = mask)

Может выполнить работу. Вы можете довольно легко автоматизировать это, отфильтровав контуры, я просто подталкиваю вас к POC.

E: простите за странные цвета, забыл преобразовать в RGB перед показом с помощью Matplotlib...

Протестировано Canny, WOW, работает с любыми данными как шарм. Не знаю, как вас отблагодарить, большое спасибо».

Vasiliy Platon 03.05.2024 10:07

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

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

как выглядит результат определения контура:

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

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

и код для получения этого результата:

import cv2
import numpy as np
srcImg = cv2.imread("iconDetection.jpg")

grayImg = cv2.cvtColor(srcImg, cv2.COLOR_BGR2GRAY)
ret, bwImg  = cv2.threshold( grayImg, 250, 255, cv2.THRESH_BINARY_INV)
cv2.imshow('Binary', bwImg) ; cv2.waitKey(0); cv2.destroyAllWindows() 

Contours , _    = cv2.findContours(bwImg.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

BBoxes=[] # Bounding Boxes of found reaction fields on photographed strips 
for contour in Contours:
    x, y, w, h = cv2.boundingRect(contour)
    #print(f"{x=}{y=}{w=}{h=}")
    if w in range(105, 120) and h in range(105,120):
        BBoxes += [ (x, y, w, h) ] # include only large areas 
        cv2.rectangle(srcImg, (x, y), (x + w, y + h), (0, 255, 0), 3)
cv2.imshow('Bounding Rectangles', srcImg) ; cv2.waitKey(0); cv2.destroyAllWindows() 

Обратите внимание: если вы измените подход к использованию обнаружения краев Canny, это увеличит вычислительную нагрузку и потребует изменения диапазона высоты/ширины на ( 100 , 120 ), иначе логотип Amazon будет отсутствовать в найденных ограничивающих рамках. .

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

ret, bwImg  = cv2.threshold( grayImg, 250, 255, cv2.THRESH_BINARY_INV)

с

bwImg = cv2.Canny(grayImg, 5, 20)

без изменения диапазона размеров от (105, 120) до (100, 120):

Уже пользуюсь фильтрацией по размеру и соотношению сторон, спасибо!

Vasiliy Platon 03.05.2024 21:47

Протестирован подход cv2.threshold. Вроде работает хорошо, поэтому держу это в уме и тестирую на большом объеме данных, напишу здесь, какой алгоритм работает наиболее надежно

Vasiliy Platon 03.05.2024 22:02

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

oOosys 04.05.2024 01:09

Другие вопросы по теме