PaddleOCR数字仪表识别——图像预处理(python)

主要是针对 数字仪表图片进行预处理,所以这里只关注 二值化/去噪(可以不关注文档矫正,因为会限制用户摄像头行为)

主要参考:深度实践OCR 基于深度学习的文字识别(第二章 图像预处理)

1. 二值化

参考:Opencv document Image Thresholding

  • 图像二值化(Image Binarization)是指将像素点的灰度值设为0或255,使图像呈现出明显的黑白效果。
  • 区别于灰度图,灰度图可以有256个值,但是二值化的图只能有两个数,所以二值化的图像一定是灰度图,但是灰度图不一定是二值化的图像
  • 二值化一方面减少了数据维度,另一方面通过排除原图中噪声带来的干扰,可以凸显有效区域的轮廓结构。
  • OCR的效果很大程度上取决于该步骤,高质量的二值化可以显著提升识别的准确率。

目前常见的二值化方法有:全局阈值方法(Global binarization)局部阈值方法(local binarization)以及基于深度学习的方法其他方法,再细分的话,如下图
在这里插入图片描述

1.1 全局阈值方法

1.1.1 cv2.threshhold()

1.1.1.1 cv2.threshhold()代码示例

以前写过,参考 Opencv-python圆盘指针仪表检测 目录:二值化部分

代码和说明参考:OpenCV - 图像阈值(Python实现)

import cv2
import numpy as np
from matplotlib import pyplot as plt
img=cv2.imread('image.jpg',0)#必须为灰度图,单通道
ret,thresh1=cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret,thresh2=cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3=cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret,thresh4=cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret,thresh5=cv2.threshold(img,127,255,cv2.THRESH_TOZERO_INV)
titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(6):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()

在这里插入图片描述
在这里插入图片描述

1.1.1.2 优缺点

  • 优点:简单
  • 缺点:阈值的数值设置比较死,无法针对不同的图片,不好使;场景简单,光线各种比较稳定,能使。

1.1.2 Otsu算法(Opencv python代码实现)

1.1.2.1 代码示例demo

参考
深度实践OCR 基于深度学习的文字识别(第二章 图像预处理)github代码

import cv2
from matplotlib import pyplot as plt

image = cv2.imread("img/2-1.png")
# 将输入图像转为灰度图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 绘制灰度图
# plt.subplot(311), plt.imshow(gray, "gray")
# 绘制原图
plt.subplot(311), plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.title("input image"), plt.xticks([]), plt.yticks([])
# 对灰度图使用 Ostu 算法
ret1, th1 = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
# 绘制灰度直方图
plt.subplot(312), plt.hist(gray.ravel(), 256)
# 标注 Ostu 阈值所在直线
plt.axvline(x=ret1, color='red', label='otsu')
plt.legend(loc='upper right')
plt.title("Histogram"), plt.xticks([]), plt.yticks([])
# 绘制二值化图像
plt.subplot(313), plt.imshow(th1, "gray")
plt.title("output image"), plt.xticks([]), plt.yticks([])
plt.show()

在这里插入图片描述
如果把输出图像再颜色反转一下,就变成:

reverse=cv2.bitwise_not(th1)
color=cv2.cvtColor(reverse,cv2.COLOR_GRAY2BGR)
plt.figure(figsize=(6,16))
plt.imshow(color), plt.xticks([]), plt.yticks([])

在这里插入图片描述
不能再比这更清晰的吧。。。。预处理成这样了都。

如果使用Otsu算法比较费时的话,直接对灰度图做一个颜色反转,效果似乎也不错
在这里插入图片描述

1.1.2.2 优缺点

  • 适用的图片:对于直方图呈现出两个峰值的图像,Otsu算法得到的阈值为峰值间的低谷位置,二值化的效果就比较好。比如上图
  • 不适用的图片:当目标物体与背景大小比例悬殊或灰度级接近,导致直方图呈现三峰或者双峰峰值差距极大时,Otsu算法往往就得不到满意的结果,比如下图:在这里插入图片描述

1.2 局部阈值方法

1.2.1 自适应阈值算法

自适应阈值算法的主要思想是:

  • 以一个像素点为中心设置大小为 s × s s \times s s×s的滑窗,滑窗扫过整张图像,每次扫描均对窗口内的像素求均值并将均值作为局部阈值。
  • 若窗口中的某一像素值低于局部阈值 t / 100 t/100 t/100,赋值为0;高于局部阈值 t / 100 t/100 t/100,赋值为255.
  • 因为涉及多次对有重叠的窗口进行加和计算,因此积分图的使用可以有效降低复杂度和操作次数。

Niblack算法 ,其同样也是根据窗口内的像素值来计算局部阈值的,不同之处在于它不仅考虑到区域内像素点的均值和方差,还考虑到用一个事先设定的修正系数 k k k来决定影响程度。
T ( x , y ) = m ( x , y ) + k s ( x , y ) T(x,y)=m(x,y)+ks(x,y) T(x,y)=m(x,y)+ks(x,y)
其中, T ( x , y ) T(x,y) T(x,y)为阈值, m ( x , y ) , s ( x , y ) m(x,y),s(x,y) m(x,y),s(x,y)分别代表均值与方差。与 m ( x , y ) m(x,y) m(x,y)相近的像素点被判定为背景,反之则判定为前景,而相近的程度则由标准差和修正系数来决定,这样可以保证算法的灵活性。

其缺陷在于:

  1. r × r r \times r r×r的滑窗会导致在边界区域 ( r − 1 ) / 2 (r-1)/2 (r1)/2的像素范围内无法求取阈值
  2. 如果 r × r r \times r r×r的滑窗内全都是背景,则该算法必然会使一部分像素点成为前景,形成伪噪声
  3. 因此, r r r的选择非常关键,窗口太小不能有效抑制噪声,太大则会导致细节丢失

Sauvola算法是针对文档二值化处理,在Niblack算法基础上的改进;
T = m [ 1 + k ( s / R − 1 ) ] T=m[1+k(s/R-1)] T=m[1+k(s/R1)]
其中, R R R是标准方差的动态范围,若当前输入图像是8位灰度图像,则 R = 128 R=128 R=128。Sauvola算法是在处理光线不均匀或染色图像时,比Niblack算法有更好的表现,从上式可以看出:其以自适应的方式放大了标准差 s s s的作用。当处理浅色但有污渍的文档时,乘以 m 系 数 m系数 m会降低背景区域阈值的范围,这可以有效减少染色、污渍等带来的影响。

上述三种算法没有直接对应的Opencv实现,与Opencv自带的自适应阈值方法有出入。

1.2.1.1 cv2.adaptiveThreshold代码示例及函数说明

代码参考:Opencv document Image Thresholding

import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('dave.jpg',0)
img = cv2.medianBlur(img,5)

ret,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
            cv2.THRESH_BINARY,11,2)
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)

titles = ['Original Image', 'Global Thresholding (v = 127)',
            'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]

for i in xrange(4):
    plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()

在这里插入图片描述
可以看到,噪声很多,稍微调节一下:

th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
            cv2.THRESH_BINARY,5,2)
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,5,2)
 # 把blocksize调小点

在这里插入图片描述
看起来会好一些,但是会糊一些

函数说明参考:Opencv document -adaptiveThreshold()

dst=cv.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst])

src:源图 必须是8位的单通道图像(8位指颜色的位深,表明这个图像是由 2 8 2^8 28种颜色表示的)
dst: 和源图有相同大小和类型的目标图像
maxValue :上述公式里满足特定条件时使用的非零值
adaptiveMethod:使用的自适应阈值算法,有两种根据AdaptiveThresholdTypes可知:
在这里插入图片描述
一种是cv2.ADAPTIVE_THRESH_MEAN_C(minus 减去):此时阈值 T ( x , y ) T(x,y) T(x,y)=像素 ( x , y ) (x,y) (x,y) 在blockSize范围内的均值 − C -C C
另一种cv2.ADAPTIVE_THRESH_GAUSSIAN_C: 此时阈值 T ( x , y ) T(x,y) T(x,y)是一个加权和(与高斯窗口互相关),摩恩的sigma(标准差)用于特定的blockSize
另外,还有BORDER_REPLICATE和BORDER_ISOLATED 是用于处理边界的

thresholdType 阈值类型有两种:THRESH_BINARY 或 THRESH_BINARY_INV
在这里插入图片描述
其中 T ( x , y ) T(x,y) T(x,y) 是为每个像素计算出的阈值

blockSize :用于计算像素阈值的像素邻域的大小:3、5、7,依此类推。可以简单的认为就是滑动窗口的大小
C :被从平均值或加权平均值中减去的常数。 通常为正,但也可以为零或负。

1.2.1.2 优缺点

能比固定阈值的方法好一些,但是图像复杂时,二值化效果不是很好,也依赖于参数调节,局部阈值,更关注局部。。。

1.3 基于深度学习的方法

常见的两个数据集 DIBCO(Document image binarization contest)、PLM(Plam leaf Manuscripts)

论文:Document Image Binarization with Fully Convolutional Neural Networks

效果据说很好,论文里看起来很好
在这里插入图片描述

1.4 其他方法

1.4.1 基于形态学和阈值的二值化方法

大致实现方法可以分为四步:

  1. 将RGB图像转换为灰度图
  2. 图像滤波处理
  3. 数学形态学运算
  4. 阈值计算

直接参考书对应的github代码(这书是真的水,哈哈哈,知道有哪些方法之后还是要根据具体情况自己调节的)

import cv2
import numpy as np

img = cv2.imread('img/2-1-4.png', 0)
# 使用 getStructuringElement 定义结构元素,shape 为结构元素的形状,0:矩形;1:十字交
# 叉形;2:椭圆形;ksize 为结构元素的大小;anchor 为原点的位置,默认值(-1,-1)表示原点
# 为结构元素的中心点
k = cv2.getStructuringElement(shape=1, ksize=(3, 3), anchor=(-1, -1))
# k = np.ones((3,3),np.uint8) 也可以自己定义一个结构元素
# erode 函数实现腐蚀运算,src 为输入图像,kernel 为之前定义的结构元素,iterations 为
# 腐蚀操作次数
erosion = cv2.erode(src=img, kernel=k, iterations=1)
cv2.imwrite("Eroded_Image.png", erosion)
# dilate 函数实现膨胀运算,参数同上
dilation = cv2.dilate(img, k, iterations=1)
cv2.imwrite("Dilated_Image.png", dilation)
# morphologyEx 函数实现开闭运算, src 为输入图像,op 为运算类型,cv2.MORPH_OPEN:开
# 运算;cv2.MORPH_CLOSE:闭运算,kernel 为结构元素
opening = cv2.morphologyEx(src=img, op=cv2.MORPH_OPEN, kernel=k)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel=k)
cv2.imwrite("Opening_Image.png", opening)
cv2.imwrite("Closing_Image.png", closing)

2. 平滑去燥

填坑,有空再写。
https://github.com/ocrbook/ocrinaction/blob/master/chapter-2/2.1.2-filter.py
https://github.com/ocrbook/ocrinaction/blob/master/chapter-2/2.4-example.py

猜你喜欢

转载自blog.csdn.net/Castlehe/article/details/109044132