Это 3-й день моего участия в ноябрьском испытании обновлений, узнайте подробности события:Вызов последнего обновления 2021 г.
Идентификация номеров кредитных карт с использованием OpenCV и Python
В предыдущем сообщении в блоге мы узнали, как установить двоичный файл Tesseract и использовать его для OCR. Затем мы узнали, как использовать базовые методы обработки изображений для очистки изображений, чтобы улучшить вывод Tesseract OCR.
Однако Tesseract не следует рассматривать как готовое решение общего назначения для высокоточного оптического распознавания символов. В некоторых случаях он будет работать просто отлично, в других случаях он с треском провалится. Хорошим примером такого варианта использования является распознавание кредитных карт, учитывая входное изображение, которое мы хотим:
Локализуйте четыре набора из четырех цифр относительно шестнадцати цифр на кредитной карте. Примените OCR для распознавания шестнадцати цифр на кредитной карте. Определите тип кредитной карты (например, Visa, MasterCard, American Express и т. д.).
В этих случаях библиотека Tesseract неправильно распознает числа (вероятно, потому, что Tesseract не был обучен на примере шрифта кредитной карты). Поэтому нам нужно было разработать собственное решение для кредитных карт OCR. В сегодняшней записи блога я покажу, как сопоставление шаблонов можно использовать в качестве формы OCR, чтобы помочь нам создать решение для автоматического распознавания кредитных карт и извлечения соответствующих номеров кредитных карт из изображений.
Сегодняшняя запись в блоге разделена на три части. В первой части мы обсудим шрифты OCR-A, созданные специально для поддержки алгоритмов оптического распознавания символов. Затем мы разработаем алгоритм компьютерного зрения и обработки изображений, который:
- Четыре набора из четырех цифр на локализованных кредитных картах.
- Извлеките каждую из этих четырех групп, затем разделите каждое из 16 чисел по отдельности.
- Определите каждый из 16 номеров кредитных карт, используя сопоставление с шаблоном и шрифты OCR-A.
Наконец, мы рассмотрим несколько примеров применения алгоритма OCR кредитной карты к реальным изображениям.
По OCR, соответствующему шаблону OpenCV
В этом разделе мы будем использовать Python + OpenCV для реализации нашего алгоритма сопоставления шаблонов для автоматического распознавания номеров кредитных карт.
Чтобы достичь этого, нам необходимо применить ряд операций обработки изображений, включая определение порога, вычисление представлений величины градиента, морфологические операции и извлечение контуров. Эти методы использовались в других сообщениях блога для обнаружения штрих-кодов на изображениях и определения машиночитаемых областей на изображениях паспорта.
Поскольку для обнаружения и извлечения номеров кредитных карт будет применяться множество операций обработки изображений, я добавляю много промежуточных снимков экрана, когда входное изображение проходит через наш конвейер обработки изображений.
Эти дополнительные скриншоты дадут вам более глубокое понимание того, как мы можем объединить основные методы обработки изображений для создания решений для проектов компьютерного зрения. Давайте начнем.
Откройте новый файл с именем ocr_template_match.py и приступим к работе:
# import the necessary packages
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
Чтобы установить/обновить imutils, просто используйте pip:
pip install --upgrade imutils
Примечание. Если вы используете виртуальную среду Python (как и все мои руководства по установке OpenCV), обязательно сначала получите доступ к вашей виртуальной среде с помощью команды workon, а затем установите/обновите imutils.
Теперь, когда у нас установлен и импортирован пакет, мы можем проанализировать аргументы командной строки:
# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="path to input image")
ap.add_argument("-r", "--reference", required=True,
help="path to reference OCR-A image")
args = vars(ap.parse_args())
Создает анализатор аргументов, добавляет два аргумента, затем анализирует их, сохраняя как переменную args. Два обязательных аргумента командной строки:
--image : путь к изображению для распознавания.
--reference : путь к эталонному изображению OCR-A. Изображение содержит числа 0–9 шрифтом OCR-A, что позволяет нам выполнять сопоставление с шаблоном позже в конвейере.
Далее давайте определим тип кредитной карты:
# define a dictionary that maps the first digit of a credit card
# number to the credit card type
FIRST_NUMBER = {
"3": "American Express",
"4": "Visa",
"5": "MasterCard",
"6": "Discover Card"
}
Тип кредитной карты, такой как American Express, Visa и т. д., можно определить, проверив первую цифру 16-значного номера кредитной карты. Мы определяем словарь FIRST_NUMBER, который сопоставляет первое число с соответствующим типом кредитной карты. Давайте начнем наш конвейер обработки изображений, загрузив эталонное изображение OCR-A:
# load the reference OCR-A image from disk, convert it to grayscale,
# and threshold it, such that the digits appear as *white* on a
# *black* background
# and invert it, such that the digits appear as *white* on a *black*
ref = cv2.imread(args["reference"])
ref = cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY)
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
Сначала мы загружаем эталонное изображение OCR-A, затем конвертируем его в оттенки серого и порог + инвертируем. В каждой из этих операций мы сохраняем или перезаписываем ref, наше эталонное изображение.
Теперь давайте расположим контур на изображении шрифта OCR-A:
# find contours in the OCR-A image (i.e,. the outlines of the digits)
# sort them from left to right, and initialize a dictionary to map
# digit name to the ROI
refCnts = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
refCnts = imutils.grab_contours(refCnts)
refCnts = contours.sort_contours(refCnts, method="left-to-right")[0]
digits = {}
Контуры на эталонном изображении найдены. Затем из-за того, что OpenCV версий 2.4, 3 и 4 по-разному хранит возвращенную информацию о контурах, мы проверяем версию и вносим соответствующие изменения в refCnts. Затем мы сортируем контуры слева направо и инициализируем словарь digits, который сопоставляет названия цифр с областями интереса.
На этом этапе мы должны перебрать контуры, извлечь область интереса и связать ее с соответствующим номером:
# loop over the OCR-A reference contours
for (i, c) in enumerate(refCnts):
# compute the bounding box for the digit, extract it, and resize
# it to a fixed size
(x, y, w, h) = cv2.boundingRect(c)
roi = ref[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
# update the digits dictionary, mapping the digit name to the ROI
digits[i] = roi
Пройдите по контурам эталонного изображения.
В цикле i содержит числовое имя/номер, а c содержит контур. Мы вычисляем ограничивающую рамку вокруг каждого контура c, которая хранит координаты прямоугольника (x, y) и ширину/высоту. Извлеките область интереса из ссылки (эталонное изображение), используя параметр ограничивающего прямоугольника. Этот ROI содержит числа.
Мы изменяем размер каждой ROI до фиксированного размера 57 × 88 пикселей. Нам нужно убедиться, что размер каждой цифры изменен до фиксированного размера, чтобы применить сопоставление шаблонов при распознавании цифр позже в этом руководстве.
Мы связываем каждое число 0-9 (ключ словаря) с каждым изображением области интереса (значение словаря).
На этом мы закончили извлекать цифры из эталонного изображения и связывать их с соответствующими именами цифр.
Наша следующая цель — выделить 16-значный номер кредитной карты во входных данных --image. Нам нужно найти и изолировать числа, прежде чем мы сможем начать сопоставление шаблонов для идентификации каждого числа. Эти этапы обработки изображений очень интересны и содержательны, особенно если вы никогда раньше не разрабатывали конвейер обработки изображений, обязательно обратите на них пристальное внимание.
Давайте продолжим и инициализируем несколько структурированных ядер:
# initialize a rectangular (wider than it is tall) and square
# structuring kernel
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
Вы можете думать о ядрах как о небольших матрицах, которые мы перемещаем по изображению для выполнения операций (свертки), таких как размытие, повышение резкости, обнаружение краев или другие операции обработки изображения.
Строятся два таких ядра – прямоугольник и квадрат. Мы будем использовать прямоугольник в качестве оператора формы цилиндра и квадрат в качестве оператора закрытия. Мы скоро увидим их. Теперь давайте подготовим изображение для OCR:
# load the input image, resize it, and convert it to grayscale
image = cv2.imread(args["image"])
image = imutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
Загружается изображение аргумента командной строки, содержащее фотографию кредитной карты. Затем мы изменяем его размер до width=300, сохраняя соотношение сторон, и преобразуем его в оттенки серого. Давайте посмотрим на наше входное изображение:
Далее идут наши операции по изменению размера и оттенкам серого:
Теперь, когда наши изображения окрашены в оттенки серого и имеют одинаковый размер, давайте выполним морфологические операции:
# apply a tophat (whitehat) morphological operator to find light
# regions against a dark background (i.e., the credit card numbers)
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
Используя наш rectKernel и наше изображение в градациях серого, мы выполняем морфологическую операцию цилиндра, сохраняя результат как tophat.
Действие «Цилиндр» отображает область светлого цвета на темном фоне (т. е. номер кредитной карты), как показано на следующем рисунке:
Учитывая наше изображение цилиндра, давайте вычислим градиент вдоль направления x:
# compute the Scharr gradient of the tophat image, then scale
# the rest back into the range [0, 255]
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0,
ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")
Следующим шагом в наших усилиях по выделению цифр является вычисление градиента Шарра изображения цилиндра в направлении x. Завершите вычисление, сохраните результат как gradX.
После вычисления абсолютного значения каждого элемента в массиве gradX мы предпримем несколько шагов, чтобы масштабировать значение до диапазона [0-255] (поскольку изображение в настоящее время имеет тип данных с плавающей запятой). Для этого мы вычисляем minVal и maxVal для gradX, а затем вычисляем уравнение масштабирования, показанное в строке 73 (т. е. нормализацию мин/макс). Последний шаг — преобразовать gradX в uint8 в диапазоне [0-255]. Результат показан ниже:
Продолжим совершенствовать алгоритм поиска номера кредитной карты:
# apply a closing operation using the rectangular kernel to help
# cloes gaps in between credit card number digits, then apply
# Otsu's thresholding method to binarize the image
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
thresh = cv2.threshold(gradX, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# apply a second closing operation to the binary image, again
# to help close gaps between credit card number regions
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
Чтобы закрыть разрыв, мы выполнили операцию закрытия. Обратите внимание, что мы снова используем rectKernel. Затем мы выполняем Otsu и двоичную пороговую обработку изображения gradX, после чего следует еще одна операция закрытия. Результаты этих шагов показаны ниже:
Далее давайте найдем контуры и инициализируем список сгруппированных позиций цифр.
# find contours in the thresholded image, then initialize the
# list of digit locations
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
locs = []
Мы нашли контуры и сохранили их в списке cnts. Затем мы инициализируем список для хранения позиций группы номеров.
Теперь давайте пройдемся по контурам, фильтруя на основе соотношения сторон каждого контура, что позволит нам обрезать позиции групп цифр из нерелевантных областей кредитной карты:
# loop over the contours
for (i, c) in enumerate(cnts):
# compute the bounding box of the contour, then use the
# bounding box coordinates to derive the aspect ratio
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
# since credit cards used a fixed size fonts with 4 groups
# of 4 digits, we can prune potential contours based on the
# aspect ratio
if ar > 2.5 and ar < 4.0:
# contours can further be pruned on minimum/maximum width
# and height
if (w > 40 and w < 55) and (h > 10 and h < 20):
# append the bounding box region of the digits group
# to our locations list
locs.append((x, y, w, h))
Мы прокручиваем контуры так же, как на эталонном изображении. После вычисления ограничивающего прямоугольника c для каждого контура мы вычисляем соотношение сторон ar путем деления ширины на высоту. Используя соотношение сторон, мы анализируем форму каждого контура. Если ar находится в диапазоне от 2,5 до 4,0 (ширина больше высоты), w находится в диапазоне от 40 до 55 пикселей, а h — в диапазоне от 10 до 20 пикселей, мы добавляем параметр ограничивающего прямоугольника в удобный кортеж к locs.
На изображении ниже показаны группы, которые мы нашли — в демонстрационных целях я попросил OpenCV нарисовать ограничивающую рамку вокруг каждой группы:
Далее мы отсортируем группы слева направо и инициализируем список номеров кредитных карт:
# sort the digit locations from left-to-right, then initialize the
# list of classified digits
locs = sorted(locs, key=lambda x:x[0])
output = []
Мы сортируем loc на основе значения x, поэтому они будут отсортированы слева направо. Мы инициализируем вывод списка, который будет содержать номера кредитных карт изображения. Теперь, когда мы знаем, где находится каждая группа из четырех цифр, давайте пройдемся по четырем отсортированным группам и определим числа в них.
Этот цикл достаточно длинный и разделен на три блока кода — это первый блок:
# loop over the 4 groupings of 4 digits
for (i, (gX, gY, gW, gH)) in enumerate(locs):
# initialize the list of group digits
groupOutput = []
# extract the group ROI of 4 digits from the grayscale image,
# then apply thresholding to segment the digits from the
# background of the credit card
group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
group = cv2.threshold(group, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# detect the contours of each individual digit in the group,
# then sort the digit contours from left to right
digitCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
digitCnts = imutils.grab_contours(digitCnts)
digitCnts = contours.sort_contours(digitCnts,
method="left-to-right")[0]
В первом блоке этого цикла мы извлекаем и дополняем группы по 5 пикселей с каждой стороны, применяем пороговое значение, а также находим и сортируем контуры. Подробную информацию см. в коде. Ниже показана одна группа, которая была извлечена:
Давайте продолжим цикл с вложенными циклами для сопоставления шаблонов и извлечения оценки сходства:
# loop over the digit contours
for c in digitCnts:
# compute the bounding box of the individual digit, extract
# the digit, and resize it to have the same fixed size as
# the reference OCR-A images
(x, y, w, h) = cv2.boundingRect(c)
roi = group[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
# initialize a list of template matching scores
scores = []
# loop over the reference digit name and digit ROI
for (digit, digitROI) in digits.items():
# apply correlation-based template matching, take the
# score, and update the scores list
result = cv2.matchTemplate(roi, digitROI,
cv2.TM_CCOEFF)
(_, score, _, _) = cv2.minMaxLoc(result)
scores.append(score)
# the classification for the digit ROI will be the reference
# digit name with the *largest* template matching score
groupOutput.append(str(np.argmax(scores)))
Используя cv2.boundingRect, мы получаем параметры, необходимые для извлечения области интереса, содержащей каждую цифру. Чтобы сопоставление с шаблоном работало с некоторой степенью точности, мы изменяем размер области интереса, чтобы он был такого же размера, как наше эталонное изображение цифры шрифта OCR-A (57 × 88 пикселей) в строке 144.
Мы инициализируем список оценок. Думайте об этом как о нашей оценке достоверности: чем она выше, тем больше вероятность того, что это правильный шаблон.
Теперь давайте пройдемся по каждому ссылочному номеру (третий вложенный цикл) и выполним сопоставление с шаблоном. Именно здесь выполняется тяжелая работа для этого скрипта.
В OpenCV есть удобная функция cv2.matchTemplate, где вы можете предоставить два изображения: одно — шаблон, а другое — входное изображение. Цель применения cv2.matchTemplate к этим двум изображениям — определить, насколько они похожи.
В этом случае мы предоставляем эталонное изображение digitROI и ROI кредитной карты, содержащие цифры-кандидаты. Используя эти два изображения, мы вызываем функцию сопоставления шаблонов и сохраняем результат. Затем мы извлекаем оценку из результата и добавляем ее в наш список оценок. Это завершает самый внутренний цикл.
Используя дроби (по одной на каждую цифру от 0 до 9), берем максимальную дробь — максимальной дробью должно быть число, которое мы правильно определили. Мы находим число с наивысшим баллом, получаем конкретный индекс через np.argmax. Целочисленное имя для этого индекса представляет собой наиболее вероятное число, основанное на сравнении с каждым шаблоном (помните, что индекс предварительно отсортирован от 0 до 9).
Наконец, давайте нарисуем прямоугольник вокруг каждой группы и увидим номер кредитной карты на изображении красным текстом:
# draw the digit classifications around the group
cv2.rectangle(image, (gX - 5, gY - 5),
(gX + gW + 5, gY + gH + 5), (0, 0, 255), 2)
cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
# update the output digits list
output.extend(groupOutput)
Для третьего и последнего блока этого цикла мы рисуем 5-пиксельный заполненный прямоугольник вокруг группы, а затем рисуем текст на экране.
Последним шагом является добавление чисел в выходной список. Путь Pythonic заключается в использовании функции расширения для добавления каждого элемента итерируемого объекта (в данном случае списка) в конец списка.
Чтобы увидеть выполнение скрипта, давайте выведем результаты в терминал и выведем наше изображение на экран.
# display the output credit card information to the screen
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)
Выведите тип кредитной карты в консоль, а затем номер кредитной карты в строке 173.
В последних нескольких строках мы выводим изображение на экран и ждем нажатия любой клавиши, затем выходим из скрипта в строках 174 и 175.
Найдите минутку, чтобы поздравить себя - вы сделали это. Подводя итог (в общих чертах), этот скрипт:
- Храните типы кредитных карт в словаре.
- Возьмите эталонное изображение и извлеките числа.
- Храните числовые шаблоны в словаре.
- Локализуйте четыре группы номеров кредитных карт, каждая из которых содержит четыре цифры (всего 16 цифр).
- Извлеките числа, чтобы «соответствовать».
- Выполните сопоставление шаблонов для каждой цифры, сравнивая каждую отдельную область интереса с каждым цифровым шаблоном 0-9, сохраняя при этом оценку для каждой попытки сопоставления.
- Найдите наивысший балл для каждого номера-кандидата и создайте список под названием output, содержащий номера кредитных карт.
- Выведите номер кредитной карты и тип кредитной карты на наш терминал и отобразите выходное изображение на нашем экране.
Теперь пришло время посмотреть на скрипт в действии и проверить наши результаты.
Результаты распознавания кредитной карты
Теперь, когда мы закодировали систему распознавания кредитных карт, давайте попробуем ее.
Очевидно, что в этом примере мы не можем использовать реальный номер кредитной карты, поэтому я собрал несколько примеров изображений кредитных карт с помощью Google.
Эти кредитные карты явно поддельные и предназначены только для демонстрационных целей. Тем не менее, вы можете применить ту же технику в этом сообщении в блоге, чтобы определить номера на вашей реальной кредитной карте.
Чтобы увидеть нашу систему распознавания кредитных карт в действии, откройте терминал и выполните следующую команду:
$ python ocr_template_match.py --reference ocr_a_reference.png \
--image images/credit_card_05.png
Credit Card Type: MasterCard
Credit Card #: 5476767898765432
Наше первое изображение результата, правильное на 100%:
Обратите внимание, как мы смогли правильно пометить кредитную карту как Mastercard, просто проверив первую цифру в номере кредитной карты. Давайте попробуем второе изображение, на этот раз визу:
$ python ocr_template_match.py --reference ocr_a_reference.png \
--image images/credit_card_01.png
Credit Card Type: Visa
Credit Card #: 4000123456789010
Мы снова смогли правильно распознать кредитную карту, используя сопоставление с шаблоном.
$ python ocr_template_match.py --reference ocr_a_reference.png \
--image images/credit_card_02.png
Credit Card Type: Visa
Credit Card #: 4020340002345678
Суммировать
В этом руководстве мы узнали, как выполнять оптическое распознавание символов (OCR), используя сопоставление шаблонов с OpenCV и Python.
В частности, мы применяем наш метод сопоставления шаблонов OCR для определения типа кредитной карты, а также 16-значного номера кредитной карты.
Для этого разделим конвейер обработки изображения на 4 шага:
- Четыре набора из четырех цифр на кредитных картах обнаруживаются с помощью различных методов обработки изображений, включая морфологические операции, пороговую обработку и извлечение контуров.
- Извлеките каждое отдельное число из четырех групп, в результате чего получится 16 чисел, которые необходимо отсортировать.
- Сопоставление с шаблоном применяется к каждой цифре для получения нашей классификации цифр путем сравнения каждой цифры со шрифтом OCR-A.
- Проверьте первую цифру номера кредитной карты, чтобы определить компанию-эмитента.
Оценив нашу систему распознавания кредитных карт, мы пришли к выводу, что она на 100 % точна, при условии, что компания-эмитент кредитной карты использует для номеров шрифт OCR-A.
Чтобы расширить это приложение, вы можете собрать реальные изображения кредитных карт в дикой природе и, возможно, обучить модель машинного обучения (либо с помощью стандартного извлечения признаков, либо обучения, либо сверточных нейронных сетей) для дальнейшего повышения точности этой системы.
Надеюсь, вам понравился этот пост в блоге об OCR.