OCR图片预处理之去除印章(一)

article/2025/6/23 22:04:28

导读

在做OCR票据类识别的时候经常会遇到一些票据上会有印章,而对于的文字检测文字识别模型而言,印章的存在一定会影响模型识别的准确率,所以通常我们都是先将图片去除印章之后,再将图片送入到文字检测和文字识别模型中。

本篇文章就介绍一个比较简单的方法用来去除红色印章

移除红色印章

我们通过分离图片的通道,提取图片的红色通道,然后再通过阈值来去除红色的印章

import cv2
import numpy as npdef remove_red_seal(input_img):# 分离图片的通道blue_c, green_c, red_c = cv2.split(input_img)#利用大津法自动选择阈值thresh, ret = cv2.threshold(red_c, 0, 255,cv2.THRESH_OTSU)#对阈值进行调整filter_condition = int(thresh * 0.90)#移除红色的印章_, red_thresh = cv2.threshold(red_c, filter_condition, 255, cv2.THRESH_BINARY)# 把图片转回3通道result_img = np.expand_dims(red_thresh, axis=2)result_img = np.concatenate((result_img, result_img, result_img), axis=-1)return result_imginput_img = cv2.imread("1.jpg")
remove_seal = remove_red_seal(input_img)
cv2.imwrite("remove_seal.jpg",remove_seal)

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

注意:对于不同的场景,你可能需要对阈值进行微调(百分比),以获取你认为的最佳阈值,百分比越小红色印章移除的越干净,同时也有可能会移除部分文字信息。

threshold函数

threshold(src, thresh, maxval, type[, dst])->ret,dst

  • src::灰度图或单通道图片
  • thresh:阈值
  • maxval:最大值
  • type:阈值类型

这里重点介绍一下type参数的取值,它的取值如下图所示
在这里插入图片描述
在对图片做二值化处理的时候需要设置一个阈值来对图片进行二值化处理,然而在部分复杂的场景下,如果采用固定的阈值可能在某些场景下效果不错,换到有些场景时效果就不行了。

这时候我们就会想要用自动的阈值,这时候就可以用到THRESH_OTSUTHRESH_TRIANGLE这两个参数,它们会根据图片的灰度直方图来计算出一个阈值将图片分为前景和背景,下面我们来介绍一下它们是如何实现的。

THRESH_OTSU

大津法(OTSU):也被称为是最大类间差法,被认为是图像分割阈值选择的最佳算法,计算简单,鲁棒性较好,不受图像亮度和和对比度的影响,因此在数字图像处理中被广泛的使用。

它根据图像的灰度直方图,将图像分为前景背景两个部分。因为方差是度量图像灰度分布是否均匀,如果图像的背景和前景之间的差别越大,那么它们之间的类间方差差距也会越大。所以,如果我们能够保证图像前景和背景的灰度直方图方差差距最大时,就能让前景和背景分离的效果达到最佳,实际效果还是取决于具体的场景,可能需要根据不同的需求对阈值进行微调。

  • 公式推导

其实只要抓住大津法的核心思想最大化前景和背景的方差要推导公式用代码来实现并不难,接下来我们来推导一下这个公式。

假设灰度 T T T是图像分割前景和背景的最佳阈值,图像上任意一点属于前景的概率 ω 1 \omega_1 ω1,属于背景的概率 ω 2 \omega_2 ω2。图像前景的平均灰度值 μ 1 \mu_1 μ1背景的平均灰度值 μ 2 \mu_2 μ2,所以图像的平均灰度值 μ \mu μ
μ = ω 1 μ 1 + ω 2 μ 2 \mu = \omega_1 \mu_1 + \omega_2\mu_2 μ=ω1μ1+ω2μ2
根据类间的方差计算公式,前景和背景的类间方差计算如下
δ 2 = ω 1 ( μ 1 − μ ) 2 + ω 2 ( μ 2 − μ ) \delta^2=\omega_1(\mu_1-\mu)^2+\omega_2(\mu_2-\mu) δ2=ω1(μ1μ)2+ω2(μ2μ)
因为
ω 1 + ω 2 = 1 \omega_1+\omega_2 = 1 ω1+ω2=1
结合上面3个式子可得
δ 2 = ω 1 ( μ 1 − ( ω 1 μ 1 + ω 2 μ 2 ) ) 2 + ω 2 ( μ 2 − ( ω 1 μ 1 + ω 2 μ 2 ) ) 2 = ω 1 ( ( 1 − ω 1 ) μ 1 − ω 2 μ 2 ) 2 + ω 2 ( ( 1 − ω 2 ) μ 2 − ω 1 μ 1 ) 2 = ω 1 ( ω 2 μ 1 − ω 2 μ 2 ) 2 + ω 2 ( ω 1 μ 2 − ω 1 μ 1 ) 2 = ω 1 ω 2 2 ( μ 1 − μ 2 ) 2 + ω 2 ω 1 2 ( μ 1 − μ 2 ) = ω 1 ω 2 ( μ 1 − μ 2 ) 2 ( ω 2 + ω 1 ) = ω 1 ω 2 ( μ 1 − μ 2 ) 2 \begin{aligned} \delta^2&=\omega_1(\mu_1-(\omega_1\mu_1+\omega_2\mu_2))^2+\omega_2(\mu_2-(\omega_1\mu_1+\omega_2\mu_2))^2 \\ &= \omega_1((1-\omega_1)\mu_1-\omega_2\mu_2)^2+\omega_2((1-\omega_2)\mu_2-\omega_1\mu_1)^2 \\ &=\omega_1(\omega_2\mu_1-\omega_2\mu_2)^2+\omega_2(\omega_1\mu_2-\omega_1\mu_1)^2 \\ &=\omega_1\omega_2^2(\mu_1-\mu_2)^2+\omega_2\omega_1^2(\mu_1-\mu_2) \\ &=\omega_1\omega_2(\mu_1-\mu_2)^2(\omega_2+\omega_1)\\ &=\omega_1\omega_2(\mu_1-\mu_2)^2 \end{aligned} δ2=ω1(μ1(ω1μ1+ω2μ2))2+ω2(μ2(ω1μ1+ω2μ2))2=ω1((1ω1)μ1ω2μ2)2+ω2((1ω2)μ2ω1μ1)2=ω1(ω2μ1ω2μ2)2+ω2(ω1μ2ω1μ1)2=ω1ω22(μ1μ2)2+ω2ω12(μ1μ2)=ω1ω2(μ1μ2)2(ω2+ω1)=ω1ω2(μ1μ2)2
为了方便我们后面编程来实现,还需要对上式做一些调整,这里引入几个参数 p i p_i pi表示灰度值等于 i i i的概率,图像的灰度取值在 [ 0 , 255 ] [0,255] [0,255]范围内取整数。假设灰度值 t t t可以使图像前景和背景的方差最大, m 1 m_1 m1为灰度级 t t t的累加均值, m m m为图像的灰度级 L L L的均值累加
ω 1 = ∑ i = 0 t p i m 1 = ∑ i = 0 t i p i m = ∑ i = 0 L i p i \begin{aligned} \omega_1=& \sum_{i=0}^{t}p_i \\ m_1=&\sum_{i=0}^{t}ip_i\\ m=&\sum_{i=0}^{L}ip_i\\ \end{aligned} ω1=m1=m=i=0tpii=0tipii=0Lipi
可得 μ 1 \mu_1 μ1 μ 2 \mu_2 μ2
μ 1 = ∑ i = 0 t i p i ω 1 = m 1 ω 1 μ 2 = ∑ i = t + 1 L i p i ω 2 = ∑ i = 0 L i p i − ∑ i = 0 t i p i ω 2 = m − m 1 ω 2 \begin{aligned} \mu_1 &=\frac{\sum_{i=0}^{t}ip_i}{\omega_1}=\frac{m_1}{\omega_1}\\ \mu_2 &=\frac{\sum_{i=t+1}^{L}ip_i}{\omega_2}=\frac{\sum_{i=0}^{L}ip_i-\sum_{i=0}^{t}ip_i}{\omega_2}=\frac{m-m_1}{\omega_2}\\ \end{aligned} μ1μ2=ω1i=0tipi=ω1m1=ω2i=t+1Lipi=ω2i=0Lipii=0tipi=ω2mm1
接下来我们对 δ 2 \delta^2 δ2结合上面的式子做个变换
δ 2 = ω 1 ω 2 ( μ 1 − μ 2 ) 2 = ω 1 ω 2 ( m 1 ω 1 − m − m 1 ω 2 ) 2 = ω 1 ω 2 1 ω 1 2 ω 2 2 ( m 1 ω 2 − m ω 1 + m 1 ω 1 ) 2 = ( m 1 − m ω 1 ) 2 ω 1 ω 2 = ( m 1 − m ω 1 ) 2 ω 1 ( 1 − ω 1 ) \begin{aligned} \delta^2 &=\omega_1\omega_2(\mu_1-\mu_2)^2\\ &=\omega_1\omega_2(\frac{m_1}{\omega_1}-\frac{m-m_1}{\omega_2})^2\\ &=\omega_1\omega_2\frac{1}{\omega_1^2\omega_2^2}(m_1\omega_2-m\omega_1+m_1\omega_1)^2\\ &=\frac{(m_1-m\omega_1)^2}{\omega_1\omega_2}\\ &=\frac{(m_1-m\omega_1)^2}{\omega_1(1-\omega_1)} \end{aligned} δ2=ω1ω2(μ1μ2)2=ω1ω2(ω1m1ω2mm1)2=ω1ω2ω12ω221(m1ω2mω1+m1ω1)2=ω1ω2(m1mω1)2=ω1(1ω1)(m1mω1)2
我们只需要使上式最大化即可

  • 代码实现OTSU

上面我们推导了大津法的公式,以及如何来求解阈值划分前景和背景,下面我们用python来实现这个算法

import numpy as npdef Otsu(gray_img,L=256):#只处理二维数组assert len(gray_img.shape) == 2#创建一个灰度级数组gray_array = np.arange(0,L)#用来统计灰度级数组中每个灰度出现的次数gray_counts = np.zeros(shape=L,dtype=np.int32)#统计灰度图中每个灰度值出现的次数img_gray_value,img_gray_counts = np.unique(gray_img,return_counts=True)#将图片的灰度级信息拷贝到灰度级数组中gray_counts[img_gray_value] = img_gray_counts#计算每个灰度值出现的频率gray_frequency = gray_counts / np.sum(gray_counts)#计算频率的累加,也就是前景或背景类的概率p_array = np.cumsum(gray_frequency)#灰度级的均值累加m_array = np.cumsum(gray_array * gray_frequency)#计算以每个[0,255]灰度作为阈值计算方差gray_var = (m_array - m_array[-1]*p_array)**2 / (p_array*(1-p_array)+1e-6)#计算方差最大的下标值也就是最终的阈值return np.argmax(gray_var)

比较一下我们自己实现的大津法opencv内置的函数

def remove_red_seal(input_img):# 分离图片的通道blue_c, green_c, red_c = cv2.split(input_img)#利用大津算法自动选择阈值t1 = time.time()thresh, ret = cv2.threshold(red_c, 0, 255,cv2.THRESH_OTSU)t2 = time.time()print(t2 - t1)print(thresh)print(Otsu(red_c))print(time.time()-t2)input_img = cv2.imread("2.jpg")
remove_seal = remove_red_seal(input_img)

最终两者输出的阈值都是160,不过python实现的代码是opencv时间的25倍左右,所以python在这方面对比c确实是硬伤。

THRESH_TRIANGLE

三角法(TRIANGLE):是基于直方图利用几何的方法来求分割的最佳阈值,假设的成立条件是直方图的最大波峰在靠近最亮的一侧,然后再通过三角形来求解最大的距离找到最佳阈值。
在这里插入图片描述
如图所示,在灰度直方图上,从最高峰 b m a x b_{max} bmax到最暗对应直方图的 b m i n b_{min} bmin构造一条直线,然后从 b m i n b_{min} bmin b m a x b_{max} bmax开始计算到直线的垂直距离 d d d,当 d d d达到最大时,此时所对应的灰度值 t t t就是分割图像的最佳阈值

接下来我们看看,使用三角法求解阈值值的整个流程,这里引入两个参数灰度级 L L L和频率 f f f

  1. 将图片转换为灰度图,通过OpenCV可以很容易实现
  2. 计算灰度图的灰度直方图,也就是每个灰度级 L L L对应的频率 f f f
  3. 对灰度直方图进行排序,按灰度级进行排序,由小到大
  4. 确定直方图最大值(也就是 f f f)所对应灰度级 L L L的位置,如果在左侧(灰度值小)就需要对灰度直方图进行翻转
  5. 根据左侧边界的灰度级点 ( L m i n , f m i n ) (L_{min},f_{min}) (Lmin,fmin)和最亮部分频率最大对应的灰度级点 ( L m a x , f m a x ) (L_{max},f_{max}) (Lmax,fmax),由两点式我们可以确定这条直线
  6. 计算 L m i n L_{min} Lmin L m a x {L_{max}} Lmax的任意一点 ( L , f ) (L,f) (L,f)到直线的距离 d d d,当 d d d最大时所对应的 L L L就是我们要求的最佳阈值
    在这里插入图片描述
  • 代码实现
def Triangle(gray_img,L=256):assert len(gray_img.shape) == 2# 用来统计灰度级数组中每个灰度出现的次数gray_counts = np.zeros(shape=L, dtype=np.int32)# 统计灰度图中每个灰度值出现的次数img_gray_value, img_gray_counts = np.unique(gray_img, return_counts=True)# 将图片的灰度级信息拷贝到灰度级数组中gray_counts[img_gray_value] = img_gray_counts#找到左侧和右侧的边界left_bound = img_gray_value[0]if left_bound > 0:left_bound -= 1right_bound = img_gray_value[-1]if right_bound < L - 1:right_bound += 1#获取频率最大对应的灰度值max_gray = np.argmax(gray_counts)#计算最大灰度值对应的频率大小max_fre = gray_counts[max_gray]#用来记录是否翻转flip_flag = False#如果最大频率的灰度值在靠近左侧位置对齐进行翻转if (max_gray - left_bound) < (right_bound - max_gray):gray_counts = gray_counts[::-1]max_gray = L - 1 - max_grayleft_bound = L - 1 - right_boundflip_flag = True#用来记录最大的距离max_dist = 0#记录最终的阈值th = 0#直方图最大值对应的点point1 = np.array([max_gray,max_fre])#直方图最小值对应的点point2 = np.array([left_bound,gray_counts[left_bound]])#找到距离最大对应的灰度值for i in range(left_bound+1,max_gray+1):point3 = np.array([i,gray_counts[i]])vec1 = point3 - point2vec2 = point1 - point2#计算点到直线的距离,实际上分母可以不要dist = abs(np.cross(vec1,vec2)) / np.linalg.norm(point1 - point2)if dist > max_dist:max_dist = distth = ith -= 1if flip_flag:th = L - 1 - threturn th

大津法和三角法的对比

  • 共同点:两者都是算法自动计算出阈值,不需要指定阈值
  • 不同点:大津法适合双波峰的灰度直方图,三角法适合单波峰的灰度直方图

opencv显示灰度直方图

from matplotlib import pyplot as plt
input_img = cv2.imread("1.jpg")
blue_c, green_c, red_c = cv2.split(input_img)
hist = cv2.calcHist([red_c],[0],None,[256],[0,256])
plt.plot(hist)
plt.show()

在这里插入图片描述

参考:

  1. https://blog.csdn.net/weixin_40647819/article/details/90179953
  2. https://www.cnblogs.com/ZFJ1094038955/p/12027836.html
  3. https://blog.csdn.net/qq_45769063/article/details/107102117

http://chatgpt.dhexx.cn/article/4vvviNp8.shtml

相关文章

CSS垂直居中的几种方法

目录 1.设置行高line-height 2.内边距法&#xff08;padding&#xff09; 3.模拟表格法 4.绝对定位 5.使用flex布局 1.设置行高line-height 如果要垂直居中的只有一行或几个文字&#xff0c;那它的制作最为简单&#xff0c;只要让文字的行高和容器的高度相同即可 div {hei…

CSS中垂直居中的七种方法

前言&#xff1a; 我们在编辑一个版面&#xff0c;通常都会用到水平居中和垂直居中来设计&#xff0c;而水平居中很好处理&#xff0c;不外乎就是设定margin:0 auto&#xff1b;或是text-align:center&#xff1b;&#xff0c;就可以轻松解决掉水平居中的问题&#xff0c;但一…

CSS文本垂直居中的几种方法

观看更佳-点击前往 一、单行文本的居中 1.文字水平居中 <div classbox style"text-align: center;">hello world</div>2.文本垂直水平居中 <div class"box2" style"width:150px;height:100px;line-height: 100px;">文本垂直…

css如何设置文本垂直居中显示,css中怎么设置文本居中?css文本垂直居中的设置方法...

在网页设计的过程中&#xff0c;有时候可能为了布局美观可能需要让文本居中&#xff0c;那么&#xff0c;怎么设置文本居中呢&#xff1f;本篇文章将给大家介绍关于css设置文本垂直居中的方法。 首先我们要知道通过css实现元素的水平居中较为简单&#xff1a;对文本&#xff0c…

css表格怎么垂直居中对齐,css表格垂直居中怎么设置?

css设置表格垂直居中的方法&#xff1a;1、在表格外面嵌套一层table&#xff0c;用table中的td来控制居中。2、将表格放在div标签中&#xff0c;通过为div设置css样式display:table-cell实现垂直居中。 css设置表格垂直居中的方法&#xff1a;1、在表格外面嵌套一层table&#…

竖排文字垂直居中css样式

竖排文字垂直居中css样式 <div class"item"><p class"bgcolor-green">A0001</p><p class"bgcolor-yellow">A0002</p><p class"bgcolor-red">A0003</p><p class"bgcolor-green"…

CSS实现水平垂直居中的五种方法

前言 今天来看看一个之前困扰我很久的问题&#xff0c;在CSS中&#xff0c;水平垂直居中&#xff0c;能有几种写法。 方法一&#xff1a;margin:auto 子绝父相&#xff0c;当元素绝对定位的时候&#xff0c;会根据最近父元素进行定位&#xff0c;利用这个特性&#xff0c;我…

CSS 元素垂直居中的 6种方法

转自&#xff1a;http://blog.zhourunsheng.com/2012/03/css-%E5%85%83%E7%B4%A0%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD%E7%9A%84-6%E7%A7%8D%E6%96%B9%E6%B3%95/ 利用CSS进行元素的水平居中&#xff0c;比较简单&#xff0c;行级元素设置其父元素的text-align center&#xff…

使用 CSS 实现垂直居中的8种方法

​​​​​​CSS垂直居中的8种方法 目录 ​​​​​​CSS垂直居中的8种方法 1、通过vertical-align:middle实现CSS垂直居中。 2、通过display:flex实现CSS垂直居中。 3、通过伪元素:before实现CSS垂直居中。 4、通过display:table-cell实现CSS垂直居中。 5、通过隐藏节点…

css垂直居中的6种方式

在线demo演示地址&#xff1a;https://rondsjinhuajin.github.io/demo/index.html 个人博客主页&#xff1a;KinHKin的博客_CSDN博客-vue,中秋活动,性能优化领域博主 使用人群&#xff1a;前端面试&#xff0c;日常开发小技巧 目录 1、效果演示如下 ​编辑 1、使用display:…

css 居中问题的总结

1.css让浮动的盒子水平居中 解决方法&#xff1a; 给浮动的盒子加一个父盒子&#xff0c;设置宽度跟浮动盒子一样大小&#xff0c;并且overflow:hidden; 设置该盒子为margin: 0 auto; <!DOCTYPE html> <html lang"en"> <head><meta charset&qu…

CSS实现垂直居中的十五种方法

情景一&#xff1a;单行文字垂直居中 1、line-height 原理是在于将单行文字的行高设定后&#xff0c;文字会位于行高的垂直中间位置 <div class"text-con">单行文字垂直居中</div><style>.text-con {width: 200px;height: 60px;line-height: 60px…

CSS垂直居中的10种实现姿势

前言 前端开发中元素居中是最常见和最经常使用到的css技巧&#xff0c;不仅开发中经常会用到&#xff0c;面试官出题考核基础时有时候也会问道这类问题。本文主要介绍10种垂直居中的方法。希望对你我都有帮组。 1、line-heightheight实现 如果子元素是行内文本元素的话&…

CSS 垂直居中的七种方法——史上最详细总结

博主目前在蚂蚁集团-体验技术部&#xff0c;AntV/S2 是博主所在团队的开源项目——多维交叉分析表格&#xff0c;欢迎使用&#xff0c;感谢到 S2 github 仓库点赞 star&#xff0c;有任何关于前端面试、就业、技术问题都可给在文章后留言。 我们在写页面时&#xff0c;通常会用…

严题集6.65//Leetcode105 已知二叉树前序序列和中序序列分别存放在两个一位数组中,建立该二叉树的二叉链表

一、问题描述 注意可以假设数组中没有重复元素&#xff0c;这位我们判断子树是否为空时提供了便利。 二、DVC版本 先是在DVEC上编译的&#xff0c;供读者参考。 后面有LeetCode版的。 BiTree Resume_BiTree(TElemType *pre,TElemType *mid,int prelen,int midlen) //6-65 前…

二叉树的前序序列、中序序列、后序序列、层次序列

前序序列根 左子树 右子树&#xff0c;中序序列左子树 根 右子树&#xff0c;后序序列左子树 右子树 根&#xff0c;层次序列根 第二层从左到右 第三层从左到右 以此类推。中序序列又称为对称序列。 前序序列第一个节点为根节点&#xff0c;后序序列最后一个节点为根节点&…

331.验证二叉树的前序序列化

如果只给一个前序遍历&#xff0c;是不能构造出二叉树的&#xff0c;但是把空节点也加上&#xff0c;就可以唯一构造一个二叉树&#xff0c;按要求模拟一遍&#xff1a; 代码的执行过程被唯一限制住&#xff0c;还有一类是最优化问题&#xff0c;用算法解决该类问题。 class …

【数据结构】二叉树的链式存储结构(通过前序序列和中序序列构造二叉树

说明&#xff1a;需要分别输入要二叉树的前序序列和中序序列才能构建二叉树。如果构建失败&#xff0c;程序会报错。 比如我们给定一个二叉树&#xff0c;容易知道 前序序列为&#xff1a;GDAFEMHZ 中序序列为&#xff1a;ADEFGHMZ 程序运行结果&#xff1a; 源代码 #include&…

二叉树:已知前序序列与后序序列建树

二叉树&#xff1a;已知前序与后序建树 已知前序与中序、后序与中序建树是常遇到的算法问题。若已知前序序列与后序序列&#xff0c;要求输出满足条件的树的个数或者输出所有可能的树的中序序列&#xff0c;该怎么解决&#xff1f;下面我们一步步讨论这个问题。 首先&#xf…

LeetCode 每日一题331. 验证二叉树的前序序列化

331. 验证二叉树的前序序列化 序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时&#xff0c;我们可以记录下这个节点的值。如果它是一个空节点&#xff0c;我们可以使用一个标记值记录&#xff0c;例如 #。 _9_/ \3 2/ \ / \4 1 # 6 / \ / \ / …