近一个半月时间没更了,在这段时间里针对OCR业务进行了深入研究,业务也已上线,谨以此篇作为OCR系列的开篇。

目前NLP+OCR的落地应用在市场上愈加火热,如金融领域的研报分析、司法领域的合同审核甚至知识图谱的信息抽取,无不显示着NLP与OCR融合的巨大魅力。

本文将针对OCR的前序-“预处理”从理论实战两方面进行详细论述,当然,不会涉及过多的公式,网上对于公式解析已经很全面,若感兴趣可自行查找。

本项目完整代码:

https://github.com/Vincent131499/Chinese-OCR3/tree/master/preprocess

1.理论篇

光学字符识别(Optical Character Recognition,OCR)一般包括文本检测(主要用于定位文本的位置)和文本识别(主要用于识别文本的具体内容)两个步骤。而图像质量的好坏对于检测率与识别率的高低影响很大,不容忽视。下面将重点介绍图像预处理中的二值化、去噪和倾斜角检测校正的常用算法。

1.1 二值化方法

图像二值化,Image Binarization,即通过将像素点的灰度值设为0或255使得图像呈现明显的黑白效果。在传统方法甚至是现在的流行方法中,高质量的二值化图像仍然可以显著提升OCR效果,一方面减少了数据维度,另一方面排除噪声凸显有效区域。目前,二值化方法主要分为四种:

• 全局阈值方法

• 局部阈值方法

• 基于深度学习的方法

• 基于形态学和阈值的文档图像二值化方法

1.1.1 全局阈值方法

(1)固定阈值方法

该方法是对于输入图像中的所有像素点统一使用同一个固定阈值,类似于NLP中相似度计算的阈值选择方法。其基本思想就是个分段函数:

公式中的T就是选择的固定全局阈值。

在NLP领域的相似度计算中,不同领域的文本阈值不同,而在图像领域也是一样,固定阈值方法存在一个致命缺陷:很难为不同的输入图像确定最佳阈值。因此提出了接下来的计算方法。

(2)Ostu方法

Ostu方法又被称为最大类间方差法,是一种自适应的阈值确定方法。

对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为U,类间方差记为G。 假设图像的背景较暗,并且图像的大小为M×N,图像中像素的灰度值小于阈值T的像素个数记作N0,像素灰度大于阈值T的像素个数记作N1,则有:

将式(5)代入式(6),得到等价公式:

采用遍历的方法得到使类间方差G最大的阈值T。

注:opencv可以直接调用这种算法,threshold(gray, dst, 0, 255, CV_THRESH_OTSU);

• 优点:算法简单,当目标与背景的面积相差不大时,能够有效地对图像进行分割。

• 缺点:当图像中的目标与背景的面积相差很大时,表现为直方图没有明显的双峰,或者两个峰的大小相差很大,分割效果不佳,或者目标与背景的灰度有较大的重叠时也不能准确的将目标与背景分开。

• 原因:该方法忽略了图像的空间信息,同时将图像的灰度分布作为分割图像的依据,对噪声也相当敏感。

1.1.2 局部阈值方法

(1)自适应阈值算法

自适应阈值算法用到了积分图,是一个快速且有效地对网格的矩形子区域计算和的算法。积分图中任意一点(x,y)的值是从图左上角到该点形成的矩形区域内所有值之和。

(2)Niblack算法

Niblack算法同样是根据窗口内的像素值来计算局部阈值的,不同之处在于它不仅考虑到区域内像素点的均值和方差,还考虑到用一个事先设定的修正系数k来决定影响程度。

(3)Sauvola算法

Sauvola是针对文档二值化进行处理,在Niblack算法基础上进一步改进。在处理光线不均匀或染色图像时,比Niblack算法拥有更好的表现。

1.1.3 基于深度学习的方法

2017年提出了一种使用全卷积的二值化方法(Multi-Scale Fully Convolutional Neural Network),利用多尺度全卷积神经网络对文档图像进行二值化,可以从训练数据中学习并挖掘出像素点在空间上的联系,而不是依赖于在局部形状上人工设置的偏置。

1.1.4 基于形态学和阈值的文档图像二值化方法

该方法大体分为四步操作:

• 1)将RGB图像转化为灰度图;

• 2)图像滤波处理;

• 3)数学形态学运算;

• 4)阈值计算。

其中,数学形态学运算包括腐蚀、膨胀、开运算和闭运算。

1.2 图像去噪

在图像的采集、量化或传输过程中会导致图像噪声的出现,这对图像的后处理、分析会产生极大的影响。目前去噪方法分为4种:

• 空间滤波

• 小波阈值去噪

• 非局部方法

• 基于神经网络的方法

(1)空间滤波

空间滤波由一个邻域和对该邻域内像素执行的预定义操作组成,滤波器中心遍历输入图像的每个像素点之后就得到了处理后的图像。其中线性空间滤波器指的是在图像像素上执行的是线性操作,非线性空间滤波器的执行操作则与之相反。

线性空间滤波器包括平滑线性滤波、高斯滤波器。

非线性空间滤波器包括中值滤波、双边滤波。

(2)小波阈值去噪

基本思路包括3个步骤:1)二维图像的小波分解;2)对高频系数进行阈值量化;3)二维小波重构。

(3)非局部方法

该类型方法包括NL-means和BM3D。其中BM3D是当前效果最好的算法之一,具体可参考this repo。

(4)基于神经网络的方法

目前已经逐渐流行使用这种方法进行降噪处理,从简单的MLP发展到LLNet。

1.3 倾斜角检测校正

在扫描过程中,很容易出现文档旋转和位移的情况,因此后续的OCR处理与倾斜角检测校正步骤密不可分。常见的方法有:霍夫变换、Randon变换以及基于PCA的方法。

针对霍夫变换的使用一般分为3个步骤:

1)用霍夫变换探测出图像中的所有直线;

2)计算出每条直线的倾斜角,求它们的平均值;

3)根据倾斜角旋转矫正图片。

2.实战篇

接下来本文将针对图像二值化、去噪、水平矫正三个模块进行实战演示。

2.1 图像二值化

以Ostu算法为例,展示实际效果,核心代码如下:

#1.将图像转换为灰度图gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#2.对灰度图使用ostu算法ret1, th1 = cv2.threshold(gray_img, 0, 255, cv2.THRESH_OTSU)

输出效果如下:

2.2 图像去噪

以图像处理领域广为人知的Lena图片为例,展示高斯滤波、NL-means非局部均值、小波阈值以及BM3D四种方法的效果。

核心代码如下所示:

#1.读取噪声图像noisy_img = np.float32(imread('../img/lena_noise.bmp'))noisy_img = cv2.cvtColor(noisy_img, cv2.COLOR_BGR2GRAY) / 255#高斯滤波img_Guassian = cv2.GaussianBlur(noisy_img, (5, 5), 0)# NL-means(非局部均值)def nlm(X, N, K, sigma):    H, W = X.shape    pad_len = N + K    Xpad = np.pad(X, pad_len, 'constant', constant_values=0)    yy = np.zeros(X.shape)    B = np.zeros([H, W])    for ny in range(-N, N + 1):        for nx in range(-N, N + 1):            ssd = np.zeros((H, W))            # 根据邻域内像素间相似性确定权重            for ky in range(-K, K + 1):                for kx in range(-K, K + 1):                    ssd += np.square(                        Xpad[pad_len + ny + ky:H + pad_len + ny + ky, pad_len + nx + kx:W + pad_len + nx + kx] - Xpad[                                                                                                                 pad_len + ky:H + pad_len + ky,                                                                                                                 pad_len + kx:W + pad_len + kx])            ex = np.exp(-ssd / (2 * sigma ** 2))            B += ex            yy += ex * Xpad[pad_len + ny:H + pad_len + ny, pad_len + nx:W + pad_len + nx]    return yy / Bimg_nlm = nlm(noisy_img, 10, 4, 0.6)# 小波阈值def wavelet(X, levels, lmain):    def im2wv(img, nLev):        # pyr array        pyr = []        h_mat = np.array([[1, 1, 1, 1],                          [-1, 1, -1, 1],                          [-1, -1, 1, 1],                          [1, -1, -1, 1]])        for i in range(nLev):            n, mid = len(img), len(img) // 2            # split image up for HWT            a = img[:n:2, :n:2]            b = img[1:n:2, :n:2]            c = img[:n:2, 1:n:2]            d = img[1:n:2, 1:n:2]            vec = np.array([a, b, c, d])            # reshape vector to perform mat mult            D = 1 / 2 * np.dot(h_mat, vec.reshape(4, mid * mid))            L, H1, H2, H3 = D.reshape([4, mid, mid])            pyr.append([H1, H2, H3])            img = L        pyr.append(L)        return pyr    def wv2im(pyr):        h_mat = np.array([[1, 1, 1, 1],                          [-1, 1, -1, 1],                          [-1, -1, 1, 1],                          [1, -1, -1, 1]])        h_mat_inv = np.linalg.inv(h_mat)        L = pyr[-1]        for [H1, H2, H3] in reversed(pyr[:-1]):            n, n2 = len(L), len(L) * 2            vec = np.array([L, H1, H2, H3])            D = 2 * np.dot(h_mat_inv, vec.reshape(4, n * n))            a, b, c, d = D.reshape([4, n, n])            img = np.empty((n2, n2))            img[:n2:2, :n2:2] = a            img[1:n2:2, :n2:2] = b            img[:n2:2, 1:n2:2] = c            img[1:n2:2, 1:n2:2] = d            L = img        return L    def denoise_coeff(y, lmbda):        x = np.copy(y)        x[np.where(y > lmbda / 2.0)] -= lmbda / 2.0        x[np.where(y < -lmbda / 2.0)] += lmbda / 2.0        x[np.where(np.logical_and(y > -lmbda / 2.0, y < lmbda / 2.0))] = 0        return x    pyr = im2wv(X, levels)    for i in range(len(pyr) - 1):        for j in range(2):            pyr[i][j] = denoise_coeff(pyr[i][j], lmain / (2 ** i))        pyr[i][2] = denoise_coeff(pyr[i][2], np.sqrt(2) * lmain / (2 ** i))    im = wv2im(pyr)    return im# BM3D算法def run_bm3d(noisy_im, sigma,             n_H, k_H, N_H, p_H, tauMatch_H, useSD_H, tau_2D_H, lambda3D_H,             n_W, k_W, N_W, p_W, tauMatch_W, useSD_W, tau_2D_W):    k_H = 8 if (tau_2D_H == 'BIOR' or sigma < 40.) else 12    k_W = 8 if (tau_2D_W == 'BIOR' or sigma < 40.) else 12    noisy_im_p = symetrize(noisy_im, n_H)    img_basic = bm3d_1st_step(sigma, noisy_im_p, n_H, k_H, N_H, p_H, lambda3D_H, tauMatch_H, useSD_H, tau_2D_H)    img_basic = img_basic[n_H: -n_H, n_H: -n_H]    assert not np.any(np.isnan(img_basic))    img_basic_p = symetrize(img_basic, n_W)    noisy_im_p = symetrize(noisy_im, n_W)    img_denoised = bm3d_2nd_step(sigma, noisy_im_p, img_basic_p, n_W, k_W, N_W, p_W, tauMatch_W, useSD_W, tau_2D_W)    img_denoised = img_denoised[n_W: -n_W, n_W: -n_W]    return img_basic, img_denoised

输出效果如下:

高斯滤波输出

NL-means输出

小波阈值输出

BM3D输出

从以上效果中可以看出,BM3D算法去噪效果最好。

2.3 水平矫正

演示以霍夫变换为例的算法,核心代码:

# 度数转换def DegreeTrans(theta):    res = theta / np.pi * 180    return res# 逆时针旋转图像degree角度(原尺寸)def rotateImage(src, degree):    # 旋转中心为图像中心    h, w = src.shape[:2]    # 计算二维旋转的仿射变换矩阵    RotateMatrix = cv2.getRotationMatrix2D((w / 2.0, h / 2.0), degree, 1)    print(RotateMatrix)    # 仿射变换,背景色填充为白色    rotate = cv2.warpAffine(src, RotateMatrix, (w, h), borderValue=(255, 255, 255))    return rotate# 通过霍夫变换计算角度def CalcDegree(srcImage):    midImage = cv2.cvtColor(srcImage, cv2.COLOR_BGR2GRAY)    dstImage = cv2.Canny(midImage, 50, 200, 3)    lineimage = srcImage.copy()    # 通过霍夫变换检测直线    # 第4个参数就是阈值,阈值越大,检测精度越高    lines = cv2.HoughLines(dstImage, 1, np.pi / 180, 200)    # 由于图像不同,阈值不好设定,因为阈值设定过高导致无法检测直线,阈值过低直线太多,速度很慢    sum = 0    # 依次画出每条线段    for i in range(len(lines)):        for rho, theta in lines[i]:            # print("theta:", theta, " rho:", rho)            a = np.cos(theta)            b = np.sin(theta)            x0 = a * rho            y0 = b * rho            x1 = int(round(x0 + 1000 * (-b)))            y1 = int(round(y0 + 1000 * a))            x2 = int(round(x0 - 1000 * (-b)))            y2 = int(round(y0 - 1000 * a))            # 只选角度最小的作为旋转角度            sum += theta            cv2.line(lineimage, (x1, y1), (x2, y2), (0, 0, 255), 1, cv2.LINE_AA)            cv2.imshow("Imagelines", lineimage)    # 对所有角度求平均,这样做旋转效果会更好    average = sum / len(lines)    angle = DegreeTrans(average) - 90    return angle

输出效果如下:

由于微信平台算法改版,公号内容将不再以时间排序展示,如果大家想第一时间看到我们的推送,强烈建议星标我们和给我们多点点【在看】。星标具体步骤为:

(1)点击页面最上方"AINLP",进入公众号主页。

(2)点击右上角的小点点,在弹出页面点击“设为星标”,就可以啦。

感谢支持,比心

欢迎加入AINLP技术交流群进群请添加AINLP小助手微信 AINLPer(id: ainlper),备注NLP技术交流

推荐阅读

这个NLP工具,玩得根本停不下来

征稿启示| 200元稿费+5000DBC(价值20个小时GPU算力)

完结撒花!李宏毅老师深度学习与人类语言处理课程视频及课件(附下载)

从数据到模型,你可能需要1篇详实的pytorch踩坑指南

如何让Bert在finetune小数据集时更“稳”一点

模型压缩实践系列之——bert-of-theseus,一个非常亲民的bert压缩方法

文本自动摘要任务的“不完全”心得总结番外篇——submodular函数优化

Node2Vec 论文+代码笔记

模型压缩实践收尾篇——模型蒸馏以及其他一些技巧实践小结

中文命名实体识别工具(NER)哪家强?

学自然语言处理,其实更应该学好英语

斯坦福大学NLP组Python深度学习自然语言处理工具Stanza试用

关于AINLP

AINLP 是一个有趣有AI的自然语言处理社区,专注于 AI、NLP、机器学习、深度学习、推荐算法等相关技术的分享,主题包括文本摘要、智能问答、聊天机器人、机器翻译、自动生成、知识图谱、预训练模型、推荐系统、计算广告、招聘信息、求职经验分享等,欢迎关注!加技术交流群请添加AINLPer(id:ainlper),备注工作/研究方向+加群目的。

阅读至此了,分享、点赞、在看三选一吧?

opencv ocr字符识别_OCR深度实践系列:图像预处理相关推荐

  1. opencv生成3d模型_OCR深度实践系列:数据生成

    OCR深度实践系列:(一)图像预处理这篇为OCR深度实践系列的第二篇:数据生成.深度学习依赖大量的数据,然而在真实的业务场景中无法获取足够多且真实的打标数据,因此人们希望通过图像增强.语义理解.生成对 ...

  2. python opencv 录制视频_如何使用OpenCV、Python和深度学习在图像和视频中实现面部识别?...

    Face ID 的兴起带动了一波面部识别技术热潮.本文将介绍如何使用 OpenCV.Python 和深度学习在图像和视频中实现面部识别,以基于深度识别的面部嵌入,实时执行且达到高准确度. 以下内容由 ...

  3. 深度学习中图像预处理均值

    图像中的均值,均方差等名字解释 均值 表示信号中直流分量的大小,一般用E(x)表示,其公式为: 对于高斯白噪声而言,其均值为0. 均方值 表示信号平方后的均值,一般用E(x^2)表示,一般是用来表示信 ...

  4. 如何用 OpenCV、Python 和深度学习实现面部识别?

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达本文转自|新机器视觉 Face ID 的兴起带动了一波面部识别技术热 ...

  5. python模块cv2人脸识别_手把手教你使用OpenCV,Python和深度学习进行人脸识别

    使用OpenCV,Python和深度学习进行人脸识别 在本教程中,你将学习如何使用OpenCV,Python和深度学习进行面部识别.首先,我们将简要讨论基于深度学习的面部识别,包括"深度度量 ...

  6. 图像预处理 mean=[0.485, 0.456, 0.406] std=[0.229, 0.224, 0.225] 的由来以及使用

    为什么一些深度学习的图像预处理使用mean=[0.485, 0.456, 0.406] and std=[0.229, 0.224, 0.225]来正则化? Using the mean and st ...

  7. 文末送书 | 阿里资深员工撰写:深度实践OCR

    一直以来没有一本特别好的,针对 OCR 方向的既前沿又兼具实践应用的书籍. 今天,为大家推荐一本上个月刚刚出版的新书<深度实践 OCR --基于深度学习的文字识别>. 由阿里巴巴本地生活研 ...

  8. 银行卡号识别python_银行卡号识别 基于 OpenCV 光学字符识别(OCR)

    银行卡号识别 基于 OpenCV 光学字符识别(OCR) 今天的博客文章是我们最近关于光学字符识别(OCR)和计算机视觉的系列的延续. 在之前的博客文章中,我们学习了如何安装Tesseract二进制文 ...

  9. 深度学习系列:深度学习在腾讯的平台化和应用实践

    深度学习系列:深度学习在腾讯的平台化和应用实践(一) 莫扎特 2015-01-04 6:05:13 大数据技术 评论(0) 深度学习是近年机器学习领域的重大突破,有着广泛的应用前景.随着Google公 ...

最新文章

  1. 4月《程序员》上我讲HTML5的文章---激动人心的HTML5之美
  2. 机器学习流程,以及实践应用
  3. 解决虚拟机提示VMware Workstation cannot connect to the virtual machine的问题
  4. instanceof与typeof 运算符
  5. 从 2017 OpenStack Days China 看国内云计算的发展现状
  6. 对于python 3.x与python2.x中新型类的继承特性总结
  7. Flink从入门到精通100篇(二十二)- Flink应用实战案例:如何实现网络流控与反压机制
  8. python os.walk_Python os.walk() 简介
  9. linux 网口名称变了_CentOS7修改网卡名称为eth0及一些基本设置
  10. git commit撤销_Git 实用操作:撤销 Commit 提交
  11. 常用的redis命令
  12. 【php】mysql全局ID生成方案
  13. DockOne微信分享(七十七):用Harbor实现容器镜像仓库的管理和运维
  14. 阶段1 语言基础+高级_1-3-Java语言高级_05-异常与多线程_第6节 Lambda表达式_2_冗余的Runnable代码...
  15. Linux vmstat命令实战详解
  16. 重庆万州公交车坠江原因公布:乘客与司机激烈争执互殴致车辆失控
  17. Vmware安装MacOS提示请选择您要安装的macOS的磁盘
  18. js date日期格式化
  19. python随机函数random、画、星轨_如何使用 NVIDIA StyleGAN 生成自己的动漫(老婆)头像...
  20. 获取元素到body顶部的距离,offsetTop和offsetParent,getBoundingClientRect

热门文章

  1. LeetCode 简要日记 455 104
  2. 并查集 HDOJ 5441 Travel
  3. nginx防止高负载的解决方案(sysgurad模块)
  4. 【Vegas原创】SQL case when 用法
  5. 基于SSM实现健康食品零售网站
  6. java诡异的String.split()方法
  7. Design Pattern----06.Creational.Singleton.Pattern (Delphi Sample)
  8. UOJ37. 【清华集训2014】主旋律
  9. Unity3D_(游戏)贪吃蛇
  10. git merge 和 git rebase详解