Это первый день моего участия в ноябрьском испытании обновлений, подробности о мероприятии:Вызов последнего обновления 2021 г.
Распознавайте числа с помощью OpenCV и Python
В этой статье показано, как распознавать числа на изображении с помощью OpenCV и Python.
В первой части этого руководства мы обсудим, что такое семисегментный дисплей и как мы можем применять компьютерное зрение и операции обработки изображений для распознавания этих типов цифр (машинное обучение не требуется!)
Семисегментный дисплей
Вы, вероятно, уже знакомы с семисегментными дисплеями, даже если не знаете конкретной терминологии. Хорошим примером такого дисплея является ваш классический цифровой будильник:
Каждый номер сигнала тревоги представлен семисегментным компонентом следующим образом:
Семисегментный дисплей может отображать в общей сложности 128 возможных состояний:
Нас интересуют только 10 из них — числа от 0 до 9:
Наша цель — написать код OpenCV и Python для распознавания каждого из этих десяти цифровых состояний в изображении.
Разработка распознавателя цифр OpenCV
Мы будем использовать изображение термостата в качестве входных данных:
Шаги для идентификации:
Шаг 1: Найдите ЖК-дисплей на термостате. Это можно сделать с помощью обнаружения краев, поскольку между пластиковым корпусом и ЖК-дисплеем имеется достаточный контраст.
Шаг 2: Извлеките ЖК-дисплей. Имея карту входных краев, я могу найти контуры и найти контуры прямоугольников — наибольшая прямоугольная область должна соответствовать ЖК-дисплею. Преобразование перспективы даст мне хорошее извлечение ЖК-дисплея.
Шаг 3: Извлеките цифровую область. Когда у меня будет сам ЖК-дисплей, я смогу сосредоточиться на извлечении чисел. Поскольку кажется, что между цифровой областью и фоном ЖК-дисплея существует контраст, я считаю, что этого можно добиться с помощью пороговой обработки и морфологических манипуляций.
Шаг 4: Определите числа. Распознавание фактических цифр с использованием OpenCV будет включать в себя разделение цифр ROI на семь частей. Оттуда я могу применить количество пикселей к пороговому изображению, чтобы определить, является ли данный сегмент «включенным» или «выключенным».
Итак, чтобы увидеть, как мы выполнили этот четырехэтапный процесс распознавания цифр с использованием OpenCV и Python, продолжайте читать.
Распознавание цифр с помощью компьютерного зрения и OpenCV
Давайте продолжим и начнем пример. Создайте новый файл, назовите его identity_digits.py и вставьте следующий код:
# import the necessary packages
from imutils.perspective import four_point_transform
from imutils import contours
import imutils
import cv2
# define the dictionary of digit segments so we can identify
# each digit on the thermostat
DIGITS_LOOKUP = {
(1, 1, 1, 0, 1, 1, 1): 0,
(0, 0, 1, 0, 0, 1, 0): 1,
(1, 0, 1, 1, 1, 1, 0): 2,
(1, 0, 1, 1, 0, 1, 1): 3,
(0, 1, 1, 1, 0, 1, 0): 4,
(1, 1, 0, 1, 0, 1, 1): 5,
(1, 1, 0, 1, 1, 1, 1): 6,
(1, 0, 1, 0, 0, 1, 0): 7,
(1, 1, 1, 1, 1, 1, 1): 8,
(1, 1, 1, 1, 0, 1, 1): 9
}
Импортируйте наши необходимые пакеты Python. Представляем mutils, мою коллекцию удобных функций, облегчающих работу с OpenCV + Python. Если у вас не установлены imutils, теперь нужно уделить время установке пакета в вашей системе с помощью pip: Распознавание цифр с помощью OpenCV и Python
pip install imutils
Определите словарь Python с именем DIGITS_LOOKUP. Их ключ к таблице представляет собой массив из семи сегментов. 1 в массиве означает, что данный сегмент открыт, ноль означает, что сегмент закрыт. Значением является само фактическое число: 0-9.
Как только мы определили сегменты на дисплее термостата, мы можем передать массив в нашу таблицу DIGITS_LOOKUP и получить числовые значения. Для справки: в словаре используется тот же порядок сегментов, что и на рис. 2 выше. Продолжим наш пример:
# load the example image
image = cv2.imread("example.jpg")
# pre-process the image by resizing it, converting it to
# graycale, blurring it, and computing an edge map
image = imutils.resize(image, height=500)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 50, 200, 255)
Загрузите наше изображение.
Затем мы предварительно обрабатываем изображение следующим образом:
- настроить размер.
- Преобразуйте изображение в оттенки серого.
- Размытие по Гауссу применяется для уменьшения высокочастотного шума с использованием ядра 5×5.
- Карта краев вычисляется детектором краев Канни.
После применения этих шагов предварительной обработки наша карта ребер выглядит так:
Обратите внимание, как четко видны контуры ЖК-дисплея — это завершает шаг №1. Теперь мы можем перейти к шагу 2 и извлечь сам ЖК-дисплей:
# find contours in the edge map, then sort them by their
# size in descending order
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
displayCnt = None
# loop over the contours
for c in cnts:
# approximate the contour
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
# if the contour has four vertices, then we have found
# the thermostat display
if len(approx) == 4:
displayCnt = approx
break
Для того, чтобы найти область ЖК-дисплея, нам нужно извлечь контур (т.е. контур) области в карте краев.
Затем мы сортируем контуры по площади, следя за тем, чтобы контуры с большей площадью ставились первыми в списке.
Учитывая наш отсортированный список контуров, перебираем их один за другим и применяем аппроксимацию контура.
Если наш приблизительный контур имеет четыре вершины, то мы предполагаем, что нашли дисплей термостата. Это разумное предположение, поскольку самой большой прямоугольной областью на входном изображении должен быть ЖК-дисплей.
После получения четырех вершин мы можем извлечь LCD с помощью четырехточечного преобразования перспективы:
# extract the thermostat display, apply a perspective transform
# to it
warped = four_point_transform(gray, displayCnt.reshape(4, 2))
output = four_point_transform(image, displayCnt.reshape(4, 2))
Применение этого преобразования перспективы дает нам вид на ЖК-дисплей с высоты птичьего полета:
Получение этого вида ЖК-дисплея удовлетворяет шагу 2 — теперь мы готовы извлечь числа из ЖК-дисплея:
# threshold the warped image, then apply a series of morphological
# operations to cleanup the thresholded image
thresh = cv2.threshold(warped, 0, 255,
cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1, 5))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
Чтобы получить сами числа, нам нужно пороговое значение искаженного изображения, чтобы показать темные области (т. е. числа) на более ярком фоне (т. е. фоне ЖК-дисплея):
Затем мы применяем серию морфологических операций для очистки порогового изображения:
Теперь, когда у нас есть хорошо сегментированное изображение, нам снова нужно применить контурную фильтрацию, только на этот раз мы ищем фактические числа:
# find contours in the thresholded image, then initialize the
# digit contours lists
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
digitCnts = []
# loop over the digit area candidates
for c in cnts:
# compute the bounding box of the contour
(x, y, w, h) = cv2.boundingRect(c)
# if the contour is sufficiently large, it must be a digit
if w >= 15 and (h >= 30 and h <= 40):
digitCnts.append(c)
Для этого на пороговом изображении находим контуры. Инициализируйте список digitsCnts — этот список будет хранить контуры самих цифр.
Проведите по каждому контуру.
Для каждого контура мы вычисляем ограничивающую рамку, убеждаемся, что ширина и высота приемлемы, и если да, то обновляем список digitsCnts.
Если мы пройдемся по контурам внутри digitsCnts и нарисуем ограничивающую рамку на изображении, результат будет выглядеть так:
Конечно же, мы нашли цифры на ЖК-дисплее! Последним шагом является фактическая идентификация каждой цифры:
# sort the contours from left-to-right, then initialize the
# actual digits themselves
digitCnts = contours.sort_contours(digitCnts,
method="left-to-right")[0]
digits = []
Здесь мы просто сортируем контуры цифр слева направо по координатам (x, y).
Этот шаг сортировки необходим, потому что нет гарантии, что контуры уже отсортированы слева направо (в том же направлении, в котором мы читаем числа).
Далее идет собственно процесс распознавания цифр:
# loop over each of the digits
for c in digitCnts:
# extract the digit ROI
(x, y, w, h) = cv2.boundingRect(c)
roi = thresh[y:y + h, x:x + w]
# compute the width and height of each of the 7 segments
# we are going to examine
(roiH, roiW) = roi.shape
(dW, dH) = (int(roiW * 0.25), int(roiH * 0.15))
dHC = int(roiH * 0.05)
# define the set of 7 segments
segments = [
((0, 0), (w, dH)), # top
((0, 0), (dW, h // 2)), # top-left
((w - dW, 0), (w, h // 2)), # top-right
((0, (h // 2) - dHC) , (w, (h // 2) + dHC)), # center
((0, h // 2), (dW, h)), # bottom-left
((w - dW, h // 2), (w, h)), # bottom-right
((0, h - dH), (w, h)) # bottom
]
on = [0] * len(segments)
Итерация по каждому цифровому контуру. Для каждой из этих областей мы вычисляем ограничивающие рамки и извлекаем цифровые области интереса.
Я включил анимированный GIF каждой цифровой области интереса ниже:
Учитывая цифровой ROI, теперь нам нужно найти и извлечь семь частей цифрового дисплея.
Рассчитайте приблизительную ширину и высоту каждого сегмента на основе размеров ROI. Затем мы определяем список координат (x, y), которые соответствуют семи сегментам линии. Этот список соответствует тому же порядку сегментов, что и на рисунке 2 выше. Вот пример анимированного GIF, который рисует зеленую рамку над текущим исследуемым фрагментом:
Наконец, инициализируем наш список on. Значение 1 в этом списке означает, что данный сегмент «включен», а значение 0 означает, что сегмент «закрыт». Учитывая координаты (x, y) семи сегментов дисплея, довольно легко определить, является ли сегмент открытым или закрытым: Наконец, инициализируем наш список on. Значение 1 в этом списке означает, что данный сегмент «включен», а значение 0 означает, что сегмент «закрыт». Учитывая координаты (x, y) семи сегментов дисплея, довольно легко определить, является ли сегмент открытым или закрытым:
# loop over the segments
for (i, ((xA, yA), (xB, yB))) in enumerate(segments):
# extract the segment ROI, count the total number of
# thresholded pixels in the segment, and then compute
# the area of the segment
segROI = roi[yA:yB, xA:xB]
total = cv2.countNonZero(segROI)
area = (xB - xA) * (yB - yA)
# if the total number of non-zero pixels is greater than
# 50% of the area, mark the segment as "on"
if total / float(area) > 0.5:
on[i]= 1
# lookup the digit and draw it on the image
digit = DIGITS_LOOKUP[tuple(on)]
digits.append(digit)
cv2.rectangle(output, (x, y), (x + w, y + h), (0, 255, 0), 1)
cv2.putText(output, str(digit), (x - 10, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 255, 0), 2)
Мы начинаем перебирать координаты (x, y) каждого сегмента линии.
Мы извлекаем ROI фрагмента, а затем подсчитываем количество ненулевых пикселей (то есть количество пикселей, которые «включены» во фрагменте).
Если отношение ненулевых пикселей к общей площади сегмента превышает 50 %, мы можем предположить, что сегмент включен, и соответствующим образом обновить наш список включения. После прохождения семи сегментов мы можем передать список в DIGITS_LOOKUP, чтобы получить сами числа.
Затем мы рисуем ограничивающую рамку вокруг чисел и отображаем числа на выходном изображении. Наконец, наш последний блок кода выводит число на экран и отображает выходное изображение:
# display the digits
print(u"{}{}.{} \u00b0C".format(*digits))
cv2.imshow("Input", image)
cv2.imshow("Output", output)
cv2.waitKey(0)
Обратите внимание, как мы правильно идентифицируем числа на ЖК-экране, используя Python и OpenCV:
Суммировать
В сегодняшней статье я показываю, как использовать OpenCV и Python для идентификации чисел в изображениях.
Этот метод специально предназначен для семисегментных дисплеев (то есть цифровых дисплеев, которые вы обычно видите на цифровых будильниках).
Извлекая каждый из семи сегментов и применяя базовые пороговые и морфологические операции, мы можем определить, какие сегменты «включены», а какие «выключены».
Оттуда мы можем найти сегмент включения/выключения в структуре данных словаря Python, чтобы быстро определить фактическое число — машинное обучение не требуется!
Как я упоминал в начале этого сообщения в блоге, применение компьютерного зрения для определения чисел на изображениях термостатов, как правило, чрезмерно усложняет саму проблему — использование термометров с регистрацией данных более надежно и требует гораздо меньше работы.
Надеюсь, вам понравился сегодняшний пост в блоге!