#python #opencv #ocr
#python #opencv #ocr
Вопрос:
У меня есть изображения, которые выглядят следующим образом:
Я хочу найти ограничивающие рамки для 8 цифр. Моей первой попыткой было использовать cv2 со следующим кодом:
import cv2
import matplotlib.pyplot as plt
import cvlib as cv
from cvlib.object_detection import draw_bbox
im = cv2.imread('31197402.png')
bbox, label, conf = cv.detect_common_objects(im)
output_image = draw_bbox(im, bbox, label, conf)
plt.imshow(output_image)
plt.show()
К сожалению, это не работает. У кого-нибудь есть идея?
Ответ №1:
Проблема в вашем решении, скорее всего, заключается во входном изображении, которое очень низкого качества. Почти нет никакого контраста между персонажами и фоном. Алгоритм обнаружения больших двоичных cvlib
объектов, вероятно, не в состоянии различить большие двоичные объекты символов и фон, создавая бесполезную двоичную маску. Давайте попробуем решить эту проблему чисто с помощью OpenCV
.
Я предлагаю следующие шаги:
- Примените адаптивный порог, чтобы получить достаточно хорошую двоичную маску.
- Очистите двоичную маску от шума больших двоичных объектов с помощью фильтра области.
- Улучшите качество двоичного изображения с помощью морфологии.
- Получите внешние контуры каждого символа и установите ограничивающий прямоугольник для каждого символа blob.
- Обрезайте каждый символ, используя ранее рассчитанный ограничивающий прямоугольник.
Давайте посмотрим код:
# importing cv2 amp; numpy:
import numpy as np
import cv2
# Set image path
path = "C:/opencvImages/"
fileName = "mrrm9.png"
# Read input image:
inputImage = cv2.imread(path fileName)
inputCopy = inputImage.copy()
# Convert BGR to grayscale:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
С этого момента обсуждать особо нечего, достаточно просто прочитать BGR
изображение и преобразовать его в grayscale
. Теперь давайте adaptive threshold
применим этот gaussian
метод. Это сложная часть, так как параметры настраиваются вручную в зависимости от качества ввода. Принцип работы метода заключается в разделении изображения на сетку ячеек windowSize
, затем применяется локальный порог, чтобы найти оптимальное разделение между передним планом и фоном. Дополнительная константа, обозначенная windowConstant
символом, может быть добавлена к порогу для точной настройки выходного сигнала:
# Set the adaptive thresholding (gasussian) parameters:
windowSize = 31
windowConstant = -1
# Apply the threshold:
binaryImage = cv2.adaptiveThreshold(grayscaleImage, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, windowSize, windowConstant)
Вы получаете это красивое двоичное изображение:
Теперь, как вы можете видеть, на изображении присутствует некоторый шум больших двоичных объектов. Давайте применим area filter
, чтобы избавиться от шума. Шум меньше, чем целевые объекты, представляющие интерес, поэтому мы можем легко фильтровать их по площади, например:
# Perform an area filter on the binary blobs:
componentsNumber, labeledImage, componentStats, componentCentroids =
cv2.connectedComponentsWithStats(binaryImage, connectivity=4)
# Set the minimum pixels for the area filter:
minArea = 20
# Get the indices/labels of the remaining components based on the area stat
# (skip the background component at index 0)
remainingComponentLabels = [i for i in range(1, componentsNumber) if componentStats[i][4] >= minArea]
# Filter the labeled pixels based on the remaining labels,
# assign pixel intensity to 255 (uint8) for the remaining pixels
filteredImage = np.where(np.isin(labeledImage, remainingComponentLabels) == True, 255, 0).astype('uint8')
Это отфильтрованное изображение:
Мы можем улучшить качество этого изображения с помощью некоторой морфологии. Некоторые символы, похоже, сломаны (проверьте первый 3
— он разбит на два разделенных больших двоичных объекта). Мы можем объединить их, применив операцию закрытия:
# Set kernel (structuring element) size:
kernelSize = 3
# Set operation iterations:
opIterations = 1
# Get the structuring element:
maxKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
# Perform closing:
closingImage = cv2.morphologyEx(filteredImage, cv2.MORPH_CLOSE, maxKernel, None, None, opIterations, cv2.BORDER_REFLECT101)
Это «закрытое» изображение:
Теперь вы хотите получить bounding boxes
для каждого символа. Давайте определим внешний контур каждого большого двоичного объекта и обведем его красивым прямоугольником:
# Get each bounding box
# Find the big contours/blobs on the filtered image:
contours, hierarchy = cv2.findContours(closingImage, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
contours_poly = [None] * len(contours)
# The Bounding Rectangles will be stored here:
boundRect = []
# Alright, just look for the outer bounding boxes:
for i, c in enumerate(contours):
if hierarchy[0][i][3] == -1:
contours_poly[i] = cv2.approxPolyDP(c, 3, True)
boundRect.append(cv2.boundingRect(contours_poly[i]))
# Draw the bounding boxes on the (copied) input image:
for i in range(len(boundRect)):
color = (0, 255, 0)
cv2.rectangle(inputCopy, (int(boundRect[i][0]), int(boundRect[i][1])),
(int(boundRect[i][0] boundRect[i][2]), int(boundRect[i][1] boundRect[i][3])), color, 2)
Последний for
цикл в значительной степени необязателен. Он извлекает каждый ограничивающий прямоугольник из списка и рисует его на входном изображении, чтобы вы могли видеть каждый отдельный прямоугольник, например:
Давайте визуализируем это на двоичном изображении:
Кроме того, если вы хотите обрезать каждый символ, используя ограничительные рамки, которые мы только что получили, вы делаете это следующим образом:
# Crop the characters:
for i in range(len(boundRect)):
# Get the roi for each bounding rectangle:
x, y, w, h = boundRect[i]
# Crop the roi:
croppedImg = closingImage[y:y h, x:x w]
cv2.imshow("Cropped Character: " str(i), croppedImg)
cv2.waitKey(0)
Вот как вы можете получить отдельные ограничивающие рамки. Теперь, возможно, вы пытаетесь передать эти изображения в OCR
. Я попытался передать отфильтрованное двоичное изображение (после операции закрытия) в pyocr
(это OCR, который я использую), и я получаю это как выходную строку: 31197402
Код, который я использовал для получения OCR
закрытого изображения, таков:
# Set the OCR libraries:
from PIL import Image
import pyocr
import pyocr.builders
# Set pyocr tools:
tools = pyocr.get_available_tools()
# The tools are returned in the recommended order of usage
tool = tools[0]
# Set OCR language:
langs = tool.get_available_languages()
lang = langs[0]
# Get string from image:
txt = tool.image_to_string(
Image.open(path "closingImage.png"),
lang=lang,
builder=pyocr.builders.TextBuilder()
)
print("Text is:" txt)
Имейте в виду, что OCR
получает черные символы на белом фоне, поэтому сначала необходимо инвертировать изображение.
Комментарии:
1. Большое спасибо за ваш ответ (еще раз) :). Ваш метод отлично работает, но проблема в том, что для него требуется слишком много информации вручную. Мне нужно что-то, что автоматически может считывать числа без какой-либо предварительной обработки вручную. Я думал об алгоритме, чтобы, по крайней мере, найти правильные ограничивающие рамки, чтобы я мог обучить нейронную сеть задаче (вроде как MNIST). Знаете ли вы о каких-либо возможностях здесь?
2. @spadel Извините, в требованиях в вашем вопросе не упоминается полностью автоматическое решение. Вы просили ограничительные рамки для цифр, и я предоставил именно это. Возможно, попробуйте опубликовать новый вопрос и запросить ответ, основанный только на глубоком обучении, и посмотреть, появится ли какое-либо предложение. Удачи, мой друг.
3. Я надеялся найти алгоритм, который можно было бы применять повсеместно с минимальными корректировками. Фактически, при повторении данных ручного ввода, к которым вы подходите, я добиваюсь неплохих результатов. Но это также дорого с точки зрения вычислений. В любом случае я пометил ваш ответ как решение, поскольку это лучшее, что у меня есть до сих пор 🙂 так что еще раз спасибо!