python 如何做一个识别率百分百的OCR


Posted in Python onMay 29, 2021

写在前面

当然这里说的百分百可能有点夸张,但其实想象一下,游戏里面的某个窗口的字符就是那种样子,不会变化的。而且识别的字符可能也不需要太多。中文有大几千个常用字,还有各种符号,其实都不需要。

这里针对的场景很简单,主要是有以下几点:

  • 识别的字符不多:只要识别几十个常用字符即可,比如说26个字母,数字,还有一些中文。
  • 背景统一,字体一致:我们不是做验证码识别,我们要识别的字符都是清晰可见的。
  • 字符和背景易分割:一般来说就是对图片灰度化之后,黑底白字或者白底黑字这种。

技术栈

这里用到的主要就是python+opencv了。

  • python3
  • opencv-python

环境主要是以下的库:

pip install opencv-python
pip install imutils
pip install matplotlib

实现思路

首先看下图片的灰度图。

python 如何做一个识别率百分百的OCR

第一步:二值化,将灰度转换为只有黑白两种颜色。

python 如何做一个识别率百分百的OCR

第二步:图像膨胀,因为我们要通过找轮廓算法找到每个字符的轮廓然后分割,如果是字符还好,中文有很多左右偏旁,三点水这种无法将一个整体进行分割,这里通过膨胀将中文都黏在一起。

python 如何做一个识别率百分百的OCR

第三步:找轮廓。

python 如何做一个识别率百分百的OCR

第四步:外接矩形。我们需要的字符是一个矩形框,而不是无规则的。

python 如何做一个识别率百分百的OCR

第五步:过滤字符,这里比如说标点符号对我来说没用,我通过矩形框大小把它过滤掉。

python 如何做一个识别率百分百的OCR

第六步:字符分割,根据矩形框分割字符。

python 如何做一个识别率百分百的OCR

第七步:构造数据集,每一类基本上放一两张图片就可以。

python 如何做一个识别率百分百的OCR

第八步:向量搜索+生成结果,根据数据集的图片,进行向量搜索得到识别的标签。然后根据图片分割的位置,对识别结果进行排序。

具体实现

读取图片

首先先读取待识别的图片。

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画图结果如下:

python 如何做一个识别率百分百的OCR

二值化

在进行二值化之前,首先进行灰度分析。

python 如何做一个识别率百分百的OCR

灰度值是在0到255之间,0代表黑色,255代表白色。可以看到这里背景色偏黑的,基本集中在灰度值30,40附近。而字符偏白,大概在180灰度这里。

这里选择100作为分割的阈值。

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

2值化后效果如下:

python 如何做一个识别率百分百的OCR

图像膨胀

接下来进行一个图像的纵向膨胀,选择一个膨胀的维度,这里选择的是7。

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

python 如何做一个识别率百分百的OCR

找轮廓

接下来调用opencv找一下轮廓,

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

接下来我们再读取一下原图,绘制轮廓看下轮廓的样子。

python 如何做一个识别率百分百的OCR

外接矩形

对于轮廓我们可以做外接矩形,这里可以看下外接矩形的效果。

python 如何做一个识别率百分百的OCR

过滤字符

这里过滤字符的原理其实就是将轮廓内的颜色填充成黑色。下面的代码是将高度小于15的轮廓填充成黑色。

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

填充后可以看到标点符号就没了。

python 如何做一个识别率百分百的OCR

字符分割

因为图像是个矩阵,最后字符分割就是使用切片进行分割。

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"))

以上就是python 如何做一个识别率百分百的OCR的详细内容,更多关于python 做一个OCR的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python 数据加密代码
Dec 24 Python
小结Python用fork来创建子进程注意事项
Jul 03 Python
Python中itertools模块用法详解
Sep 25 Python
python查找目录下指定扩展名的文件实例
Apr 01 Python
Python实现合并两个有序链表的方法示例
Jan 31 Python
python os模块简单应用示例
May 23 Python
Python Django 简单分页的实现代码解析
Aug 21 Python
在python中计算ssim的方法(与Matlab结果一致)
Dec 19 Python
为什么黑客都用python(123个黑客必备的Python工具)
Jan 31 Python
如何基于python实现年会抽奖工具
Oct 20 Python
python 检测图片是否有马赛克
Dec 01 Python
Jupyter notebook 输出部分显示不全的解决方案
Apr 24 Python
基于PyTorch实现一个简单的CNN图像分类器
May 29 #Python
python 爬取华为应用市场评论
python 开心网和豆瓣日记爬取的小爬虫
May 29 #Python
Python趣味挑战之实现简易版音乐播放器
新手必备Python开发环境搭建教程
Keras多线程机制与flask多线程冲突的解决方案
May 28 #Python
pytorch 6 batch_train 批训练操作
May 28 #Python
You might like
php 友好URL的实现(吐血推荐)
2008/10/04 PHP
PHP正则表达式之定界符和原子介绍
2012/10/05 PHP
laravel unique验证、确认密码confirmed验证以及密码修改验证的方法
2019/10/16 PHP
javascript中类的定义及其方式(《javascript高级程序设计》学习笔记)
2011/07/04 Javascript
js控制web打印(局部打印)方法整理
2013/05/29 Javascript
javascript中cookie对象用法实例分析
2015/01/30 Javascript
javascript实现dom元素可拖动
2016/03/21 Javascript
jquery实现上传文件大小类型的验证例子(推荐)
2016/06/25 Javascript
JS实现显示带倒影的图片横排居中放大展示特效实例【测试可用】
2016/08/23 Javascript
原生node.js案例--前后台交互
2017/02/20 Javascript
老生常谈javascript中逻辑运算符&amp;&amp;和||的返回值问题
2017/04/13 Javascript
Ionic2调用本地SQlite实例
2017/04/22 Javascript
jquery ajax异步提交表单数据的方法
2017/10/27 jQuery
详解vue移动端日期选择组件
2018/02/22 Javascript
jQuery实现checkbox全选、反选及删除等操作的方法详解
2019/08/02 jQuery
VUE 动态组件的应用案例分析
2019/12/02 Javascript
JS实现容器模块左右拖动效果
2020/01/14 Javascript
在Mac OS系统上安装Python的Pillow库的教程
2015/11/20 Python
Ubuntu安装Jupyter Notebook教程
2017/10/18 Python
Python面向对象之继承和组合用法实例分析
2018/08/27 Python
python实现遍历文件夹修改文件后缀
2018/08/28 Python
在Python中调用Ping命令,批量IP的方法
2019/01/26 Python
Django Form 实时从数据库中获取数据的操作方法
2019/07/25 Python
使用Python和百度语音识别生成视频字幕的实现
2020/04/09 Python
Django自带的用户验证系统实现
2020/12/18 Python
css 如何让背景图片拉伸填充避免重复显示
2013/07/11 HTML / CSS
米兰网婚纱礼服法国网上商店:Milanoo法国
2016/08/20 全球购物
德国运动鞋网上商店:Afew Store
2018/01/05 全球购物
Java程序员面试题
2013/07/15 面试题
篝火晚会主持词
2014/03/25 职场文书
企业党员一句话承诺
2014/05/30 职场文书
国际贸易实训报告
2014/11/05 职场文书
运动会800米赞词
2015/07/22 职场文书
分析mysql中一条SQL查询语句是如何执行的
2021/06/21 MySQL
Python可变与不可变数据和深拷贝与浅拷贝
2022/04/06 Python
阿里云k8s服务升级时502错误 springboot项目应用
2022/04/09 Servers