Как сделать OCR со 100% распознаванием? (для указанной сцены)

Python OpenCV

написать впереди

В последнее время я занимаюсь автоматизацией игр (тестированием), то есть написанием игровых сценариев. Основные требования следующие:

  • 100% уровень распознавания
  • Быстрее
  • модель меньше

Конечно, утверждение 100% здесь может быть несколько преувеличенным, но на самом деле представьте, что персонажи определенного окна в игре такие и не изменятся. Да и распознаваемым персонажам, наверное, тоже многого не надо. Китайский язык имеет тысячи часто используемых иероглифов и различных символов, которые на самом деле не нужны.

Сценарий здесь очень простой, в основном в следующих моментах:

  • Распознается не так много символов: достаточно распознать десятки общих символов, таких как 26 букв, цифры и некоторые китайские символы.
  • Фон однородный и шрифт тот же: мы не занимаемся распознаванием проверочного кода, и символы, которые хотим распознать, хорошо видны.
  • Символы и фон легко отделить: вообще говоря, после полутоновых изображений используются белые символы на черном фоне или черные символы на белом фоне.

стек технологий

В основном здесь используется python+opencv.

  • python3
  • opencv-python

Среда — это в основном следующие библиотеки:

pip install opencv-python
pip install imutils
pip install matplotlib

Реализовать идеи

Сначала посмотрите на изображение в градациях серого.image.png

Первый шаг: бинаризация, которая преобразует оттенки серого только в черный и белый.image.png

Шаг 2: Расширение изображения, потому что нам нужно найти контур каждого символа с помощью алгоритма контура, а затем сегментировать его. Если это символ, это нормально. В китайском языке много левых и правых радикалов. Три точки воды не могут делить целое.вот через разложение склеить все китайское вместе.image.png

Шаг 3: Найдите контур.image.png

Шаг 4: Опишите прямоугольник. Нужный нам персонаж — прямоугольная коробка, а не случайная.image.png

Шаг 5: Отфильтруйте символы, здесь, например, знаки препинания для меня бесполезны, я отфильтровываю их по размеру прямоугольника.image.png

Шестой шаг: сегментация символов, в соответствии с символами сегментации прямоугольной рамки.image.png

Шаг 7: Создайте набор данных, в основном поместите одно или два изображения в каждую категорию.image.png

Восьмой шаг: векторный поиск + генерация результатов по картинкам набора данных, выполнение векторного поиска для получения идентифицированных тегов. Затем результаты распознавания сортируются в соответствии с положением сегментации изображения.

Выполнение

читать изображение

Сначала прочитайте изображение, которое нужно распознать.

import cv2
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.colors import NoNorm
import imutils
from PIL import Image


img_file = "test.png"
im = cv2.imread(img_file, 0)

Результат рисования с помощью matplotlib выглядит следующим образом:image.png

Бинаризация

Перед бинаризацией сначала выполняется анализ оттенков серого.image.png

Значение оттенков серого находится в диапазоне от 0 до 255, где 0 соответствует черному цвету, а 255 — белому. Видно, что цвет фона здесь темный, в основном сконцентрированный вокруг значения серого 30 и 40. Символы белые, вероятно, в градациях серого 180.

Здесь 100 выбрано в качестве порога для сегментации.

thresh = cv2.threshold(im, 100, 255, cv2.THRESH_BINARY)[1]

После 2-значного эффекта эффект следующий:image.png

инфляция изображения

Затем выполните вертикальное расширение изображения и выберите размер расширения, который здесь равен 7.

kernel = np.ones((7,1),np.uint8) 
dilation = cv2.dilate(thresh, kernel, iterations=1)

image.png

найти схему

Затем вызовите opencv, чтобы найти контур,

# 找轮廓
cnts = cv2.findContours(dilation.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

Далее, давайте прочитаем исходное изображение и нарисуем контур, чтобы увидеть, как он выглядит.image.png

описанный прямоугольник

Для контура мы можем сделать описанный прямоугольник, и здесь вы можете увидеть эффект описанного прямоугольника.image.png

символ фильтра

Принцип фильтрации символов здесь фактически состоит в том, чтобы заполнить цвет контура черным. Следующий код предназначен для заполнения контура высотой менее 15 черных.

for i, c in enumerate(cnts): 
    x, y, w, h = cv2.boundingRect(c) 
    if (h < 15):
        cv2.fillPoly(thresh, pts=[c], color=(0))

После заполнения видно, что знаки препинания исчезли.image.png

сегментация символов

Поскольку изображение представляет собой матрицу, окончательная сегментация символов заключается в использовании срезов для сегментации.

for c in cnts: 
    x, y, w, h = cv2.boundingRect(c)
    if (h < 15):
        continue
    cropImg = thresh[y:y+h, x:x+w]
    plt.imshow(cropImg)
    plt.show()

Построить набор данных

Наконец, мы создаем набор данных для маркировки, который должен объединить все вышеперечисленное, а затем сохранить сегментированные изображения в папку и завершить маркировку.

import cv2
import numpy as np
import imutils
from matplotlib import pyplot as plt
import uuid


def split_letters(im):
    # 2值化
    thresh = cv2.threshold(im, 100, 255, cv2.THRESH_BINARY)[1]
    # 纵向膨胀
    kernel = np.ones((7, 1), np.uint8)
    dilation = cv2.dilate(thresh, kernel, iterations=1)
    # 找轮廓
    cnts = cv2.findContours(dilation.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)

    # 过滤太小的
    for i, c in enumerate(cnts):
        x, y, w, h = cv2.boundingRect(c)
        if h < 15:
            cv2.fillPoly(thresh, pts=[c], color=(0))

    # 分割
    char_list = []
    for c in cnts:
        x, y, w, h = cv2.boundingRect(c)
        if h < 15:
            continue
        cropImg = thresh[y:y + h, x:x + w]
        char_list.append((x, cropImg))
    return char_list


for i in range(1, 10):
    im = cv2.imread(f"test{i}.png", 0)

    for ch in split_letters(im):
        print(ch[0])
        filename = f"ocr_datas/{str(uuid.uuid4())}.png"
        cv2.imwrite(filename, ch[1])

Векторный поиск (классификация)

Векторный поиск на самом деле является задачей поиска ближайшего соседа, мы можем использовать sklearn вKNeighborsClassifier.

Код обучающей модели выглядит следующим образом:

import os
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
import cv2
import pickle
import json

max_height = 30
max_width = 30


def make_im_template(im):
    template = np.zeros((max_height, max_width))
    offset_height = int((max_height - im.shape[0]) / 2)
    offset_width = int((max_width - im.shape[1]) / 2)
    template[offset_height:offset_height + im.shape[0], offset_width:offset_width + im.shape[1]] = im
    return template

label2index = {}
index2label = {}
X = []
y = []
index = 0
for _dir in os.listdir("ocr_datas"):
    new_dir = "ocr_datas/" + _dir
    if os.path.isdir(new_dir):
        label2index[_dir] = index
        index2label[index] = _dir
        for filename in os.listdir(new_dir):
            if filename.endswith("png"):
                im = cv2.imread(new_dir + "/" + filename, 0)
                tpl = make_im_template(im)  # 生成固定模板
                tpl = tpl / 255  # 归一化
                X.append(tpl.reshape(max_height*max_width))
                y.append(index)
        index += 1

print(label2index)
print(index2label)

model = KNeighborsClassifier(n_neighbors=1)
model.fit(X, y)

with open("simple_ocr.pickle", "wb") as f:
    pickle.dump(model, f)


with open("simple_index2label.json", "w") as f:
    json.dump(index2label, f)

Здесь стоит упомянуть одну вещь: как построить вектор изображения. Длина и ширина изображений, которые мы разделяем, не фиксированы. Здесь нам сначала нужно использовать модель, чтобы поместить разделенное изображение в центр шаблона. Затем преобразуйте модель в одномерный вектор и, конечно же, выполните нормализацию.

генерировать результаты

Окончательный результат состоит в том, чтобы сначала разбить его, затем преобразовать в вектор и вызватьKNeighborsClassifierсмоделировать и в результате найти наилучшее совпадение. Разумеется, это результат идентификации персонажа, нам же нужно отсортировать по положению сегментации, чтобы получить окончательный результат.

import cv2
import numpy as np
import imutils
from sklearn.neighbors import KNeighborsClassifier
import pickle
import json


with open("simple_ocr.pickle", "rb") as f:
    model = pickle.load(f)

with open("simple_ocr_index2label.json", "r") as f:
    index2label = json.load(f)

max_height = 30
max_width = 30


def make_im_template(im):
    template = np.zeros((max_height, max_width))
    offset_height = int((max_height - im.shape[0]) / 2)
    offset_width = int((max_width - im.shape[1]) / 2)
    template[offset_height:offset_height + im.shape[0], offset_width:offset_width + im.shape[1]] = im
    return template.reshape(max_height*max_width)


def split_letters(im):
    # 2值化
    thresh = cv2.threshold(im, 100, 255, cv2.THRESH_BINARY)[1]
    # 纵向膨胀
    kernel = np.ones((7, 1), np.uint8)
    dilation = cv2.dilate(thresh, kernel, iterations=1)
    # 找轮廓
    cnts = cv2.findContours(dilation.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)

    # 过滤太小的
    for i, c in enumerate(cnts):
        x, y, w, h = cv2.boundingRect(c)
        if h < 15:
            cv2.fillPoly(thresh, pts=[c], color=(0))

    # 分割
    char_list = []
    for c in cnts:
        x, y, w, h = cv2.boundingRect(c)
        if h < 15:
            continue
        cropImg = thresh[y:y + h, x:x + w]
        char_list.append((x, cropImg))
    return char_list


def ocr_recognize(fname):
    im = cv2.imread(fname, 0)
    char_list = split_letters(im)

    result = []
    for ch in char_list:
        res = model.predict([make_im_template(ch[1])])[0]  # 识别单个结果
        result.append({
            "x": ch[0],
            "label": index2label[str(res)]
        })
    result.sort(key=lambda k: (k.get('x', 0)), reverse=False) # 因为是单行的,所以只需要通过x坐标进行排序。

    return "".join([it["label"] for it in result])


print(ocr_recognize("test1.png"))