Обнаружение ограничивающих рамок для символов / цифр

#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 .

Я предлагаю следующие шаги:

  1. Примените адаптивный порог, чтобы получить достаточно хорошую двоичную маску.
  2. Очистите двоичную маску от шума больших двоичных объектов с помощью фильтра области.
  3. Улучшите качество двоичного изображения с помощью морфологии.
  4. Получите внешние контуры каждого символа и установите ограничивающий прямоугольник для каждого символа blob.
  5. Обрезайте каждый символ, используя ранее рассчитанный ограничивающий прямоугольник.

Давайте посмотрим код:

 # 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. Я надеялся найти алгоритм, который можно было бы применять повсеместно с минимальными корректировками. Фактически, при повторении данных ручного ввода, к которым вы подходите, я добиваюсь неплохих результатов. Но это также дорого с точки зрения вычислений. В любом случае я пометил ваш ответ как решение, поскольку это лучшее, что у меня есть до сих пор 🙂 так что еще раз спасибо!