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 07 Python
Python堆排序原理与实现方法详解
May 11 Python
Python之用户输入的实例
Jun 22 Python
Python判断一个文件夹内哪些文件是图片的实例
Dec 07 Python
Python应用领域和就业形势分析总结
May 14 Python
pyqt5 获取显示器的分辨率的方法
Jun 18 Python
python set内置函数的具体使用
Jul 02 Python
python3 使用openpyxl将mysql数据写入xlsx的操作
May 15 Python
Pytorch 使用 nii数据做输入数据的操作
May 26 Python
使用Python判断一个文件是否被占用的方法教程
Dec 16 Python
matplotlib之多边形选区(PolygonSelector)的使用
Feb 24 Python
Python下载商品数据并连接数据库且保存数据
Mar 31 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.ini中文版(2)
2006/10/09 PHP
PHP中的cookie不用刷新就生效的方法
2012/02/04 PHP
PHP基于yii框架实现生成ICO图标
2015/11/13 PHP
php cookie 详解使用实例
2016/11/03 PHP
PHP解析url并得到url参数方法总结
2018/10/11 PHP
最短的IE判断代码
2011/03/13 Javascript
模仿百度三维地图的js数据分享
2011/05/12 Javascript
JavaScript常用全局属性与方法记录积累
2013/07/03 Javascript
Javascript 鼠标移动上去 滑块跟随效果代码分享
2013/11/23 Javascript
js跑步算法的实现代码
2013/12/04 Javascript
JS取request值以及自动执行使用示例
2014/02/24 Javascript
JS和函数式语言的三特性
2014/03/05 Javascript
javascript判断数组内是否重复的方法
2015/04/21 Javascript
浅谈Javascript数组的使用
2015/07/29 Javascript
MVC Ajax Helper或Jquery异步加载部分视图
2015/11/29 Javascript
解决canvas画布使用fillRect()时高度出现双倍效果的问题
2017/08/03 Javascript
使用JS中的Replace()方法遇到的问题小结
2017/10/20 Javascript
JavaScript实现数值自动增加动画
2017/12/28 Javascript
Vue实现内部组件轮播切换效果的示例代码
2018/04/07 Javascript
重学JS 系列:聊聊继承(推荐)
2019/04/11 Javascript
vue配置文件实现代理v2版本的方法
2019/06/21 Javascript
[01:20]DOTA2 2017国际邀请赛冠军之路无止竞
2017/06/19 DOTA
[54:30]Liquid vs Newbee 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/16 DOTA
Python使用新浪微博API发送微博的例子
2014/04/10 Python
Python进阶篇之字典操作总结
2016/11/16 Python
python实现感知器
2017/12/19 Python
python+selenium实现自动抢票功能实例代码
2018/11/23 Python
Python Multiprocessing多进程 使用tqdm显示进度条的实现
2019/08/13 Python
python 利用turtle库绘制笑脸和哭脸的例子
2019/11/23 Python
HTML5中视频音频的使用详解
2017/07/07 HTML / CSS
html5视频常用API接口的实战示例
2020/03/20 HTML / CSS
巴黎欧莱雅法国官网:L’Oreal Paris
2019/04/30 全球购物
委托书的写法
2014/08/30 职场文书
2015年管理人员工作总结
2015/05/13 职场文书
抢劫罪辩护词
2015/05/21 职场文书
2015暑期社会实践通讯稿
2015/07/18 职场文书