Привет, Хабр! В этой статье я хотел бы рассказать вам, как распознавать объекты, используя только OpenCV, на примере игральных карт:
Введение
Допустим, у нас есть следующее изображение с карточками:У нас также есть эталонные изображения каждой карты:
И теперь, чтобы обнаружить каждую карту, нам нужно написать три ключевые функции, которые:
- находит контуры всех карт;
- находит координаты каждой отдельной карты;
- распознает карту по ключевым точкам.
Поиск контуров карты
В качестве первого аргумента этой функции мы передаем изображение в оттенках серого, к которому применяем размытие по Гауссу, чтобы упростить поиск контуров карт. После этого мы используем функцию порога() для преобразования нашего серого изображения в двоичное.def find_contours_of_cards(image): blurred = cv2.GaussianBlur(image, (3, 3), 0) T, thresh_img = cv2.threshold(blurred, 215, 255, cv2.THRESH_BINARY) (_, cnts, _) = cv2.findContours(thresh_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) return cnts
Эта функция принимает изображение в качестве первого параметра, пороговое значение — в качестве второго, а максимальное значение, которое присваивается значениям пикселей, превышающих порог, — в качестве третьего.
В нашем примере любое значение пикселя больше 215 устанавливается равным 255, а любое значение меньше 215 устанавливается равным нулю.
И в качестве последнего параметра мы передаем метод порогового значения.
Мы используем THRESH_BINARY(), который указывает, что значениям пикселей, превышающим 215, присваивается максимальное значение, которое мы передали в качестве третьего параметра.
Эта функция возвращает два значения, первое — это значение, которое мы передали этой функции в качестве второго аргумента, а второе — черно-белое изображение, которое выглядит следующим образом:
Теперь мы можем найти контуры наших карт, где контур — это кривая, соединяющая все непрерывные точки одного цвета.
Поиск контуров осуществляется с помощью метода findContours(), где эта функция принимает изображение в качестве первого аргумента, а второй — тип контуров, которые мы хотим извлечь.
Я использую cv2.RETR_EXTERNAL для извлечения только внешних контуров.
Например, чтобы извлечь все контуры, используется cv2.RETR_LIST, а последним параметром мы указываем метод аппроксимации контура.
Мы используем cv2.CHAIN_APPROX_SIMPLE, указывая, что все ненужные точки будут удалены, тем самым экономя память.
Например, если вы нашли контур прямой линии, действительно ли вам нужны все точки этой линии, чтобы представлять эту линию? Нет, нам нужны только две конечные точки этой линии.
Это именно то, что делает cv2.CHAIN_APPROX_SIMPLE.
Находим координаты карты
def find_coordinates_of_cards(cnts, image):
cards_coordinates = {}
for i in range(0, len(cnts)):
x, y, w, h = cv2.boundingRect(cnts[i])
if w > 20 and h > 30:
img_crop = image[y - 15:y + h + 15,
x - 15:x + w + 15]
cards_name = find_features(img_crop)
cards_coordinates[cards_name] = (x - 15,
y - 15, x + w + 15, y + h + 15)
return cards_coordinates
Эта функция принимает контуры, которые мы нашли в предыдущей функции, а также основное изображение в оттенках серого.
Прежде всего, мы создаем словарь, где имя карты будет выступать в качестве ключа, а координаты каждой карты — в качестве значения.
Затем мы проходим по нашим путям, используя функциюboundingRect(), чтобы найти ограничивающие рамки каждого пути: начальные координаты x и y, за которыми следуют ширина и высота рамки.
Так получилось, что функция, искавшая контуры, нашла целых 31 контур, хотя карт всего 4. Это могут быть незначительные контуры, которые мы далее сортируем по условию по размеру контура.
Теперь, зная координаты контура, мы можем вырезать каждую карту по этим координатам, что мы собственно и делаем в следующей строке кода.
Далее мы передаем вырезанное изображение в функцию find_features() (о ней поговорим ниже), которая на основе ключевых точек возвращает нам имя вырезанной карты.
После этого добавляем все в словарь.
Распознавание карт
Как уже говорилось выше, распознавание карт будет основываться на ключевых моментах.Ключевые точки — это области интереса на изображении.
Области интересов – это области, которые неоднородны.
Например, это могут быть углы, поскольку происходит резкое изменение интенсивности в двух разных направлениях.
Если мы посмотрим на изображение ниже, а затем закроем глаза и попытаемся визуализировать этот образ, то вряд ли мы сможем увидеть в этом изображении что-то конкретное и особенное.
Причина этого в том, что изображение не содержит никакой интересной информации:
Теперь закройте глаза и попытайтесь представить себе этот образ:
И вы увидите, что помните многие детали этого образа.
Причина этого в том, что на изображении много интересных областей.
Теперь перейдем к практике: def find_features(img1):
correct_matches_dct = {}
directory = 'images/cards/sample/'
for image in os.listdir(directory):
img2 = cv2.imread(directory+image, 0)
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
correct_matches = []
for m, n in matches:
if m.distance < 0.75*n.distance:
correct_matches.append([m])
correct_matches_dct[image.split('.
')[0]]
= len(correct_matches)
correct_matches_dct =
dict(sorted(correct_matches_dct.items(),
key=lambda item: item[1], reverse=True))
return list(correct_matches_dct.keys())[0]
Для поиска ключевых точек я использую ORB, который инициализируем вызовом функции ORB_create(), после чего находим ключевые точки и дескрипторы (кодирующие интересную информацию в ряд чисел) для эталонной карты и для вырезаемой карты.
из основного изображения.
Вот, например, так выглядят ключевые моменты для короля:
Затем нам нужно сравнить (вычислить расстояние) дескрипторы первого изображения с дескрипторами второго и взять наиболее близкий.
Для этого мы создаем объект BFMatcher, вызывая метод BFMatcher().
Теперь мы можем использовать функцию knnMatch(), чтобы найти k лучших совпадений для каждого дескриптора, где k в нашем случае равно 2. Далее нам нужно выбрать только хорошие совпадения на основе расстояния.
Поэтому мы просматриваем наши совпадения и, если они удовлетворяют условию m.distance < 0.75*n.distance, then we count this match as good and add it to the list. Then we count the number of good matches (the more, the better) and based on this we conclude what kind of card it is. Here are the matches found for each king card:
И после этого нарисуйте прямоугольник вокруг карты с помощью функции draw_rectangle_aroud_cards(): def draw_rectangle_aroud_cards(cards_coordinates, image):
for key, value in cards_coordinates.items():
rec = cv2.rectangle(image, (value[0], value[1]),
(value[2], value[3]),
(255, 255, 0), 2)
cv2.putText(rec, key, (value[0], value[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.5, (36, 255, 12), 1)
cv2.imshow('Image', image)
cv2.waitKey(0)
Вот и все.
Надеюсь было информативно) Код и картинки можно найти по адресу github .
Еще увидимся :) Теги: #python #учебник #Обработка изображений #OpenCV
-
Химия И Методы Переработки Нефти
19 Oct, 24 -
Три Тюльпана
19 Oct, 24 -
Как Я Писал Систему Визуализации Для Стенда
19 Oct, 24 -
Можно Ли Запрограммировать Случайность?
19 Oct, 24