传统文字检测方法+代码实现

文章目录

  • 前言
  • 传统文字检测方法
    • 1、基于最大稳定极值区域(MSER)的文字检测
      • 1.1 MSER(MSER-Maximally Stable Extremal Regions)
        • 基本原理
        • 代码实现——使用Opencv中的`cv2.MSER_create()`接口
    • 2、基于笔画宽度变换(Stroke Width Transform,SWT)的场景文字检测
      • SWTloc库
      • 代码实现
  • 总结


前言

尽管现在基于深度学习的文字检测方法精度高,速度快,加速了 O C R OCR OCR 技术的发展,但是传统的检测方法的思想和方法也是非常精妙,依旧有值得学习的地方。本文主要介绍两种传统的场景文件检测方法,分别是基于最大稳定极值区域( M S E R MSER MSER)的文字检测和基于笔画宽度变换( S W T SWT SWT)的场景文字检测方法,介绍了其基本思想和步骤,并在车牌数据集上简单的实现了他们,得到了较不错的效果。🧐


传统文字检测方法

1、基于最大稳定极值区域(MSER)的文字检测

1.1 MSER(MSER-Maximally Stable Extremal Regions)

基本原理
  • 一种用于检测图像中稳定的区域的算法。它的主要思想是在图像中寻找极值区域,这些区域在不同尺度下保持稳定性。
  • 主要步骤:
    • 将一幅图像进行转换为灰度图像
    • 取阈值对图像进行二值化处理,阈值从 0 − 255 0-255 0255依次递增,图像逐渐变成纯黑的图像或纯白的图像(如下图所示),每一幅二值化图像中的黑色/白色的连通域就是一个极值区域 Q Q Q。任选两个极值区域,只有两种关系,一种是没有交集,一种是包含。
      在相邻阈值的二值化图像中,极值区域 Q 1 , Q 2 , Q 3 , Q 4 . . . Q_{1},Q_{2},Q_{3},Q_{4}... Q1Q2Q3Q4...构成嵌套关系,即 Q i ∈ Q i + 1 Q_{i}\in Q_{i+1} QiQi+1

    极值区域::定义与灰度阈值有关。设定好灰度阈值后,在图像中的某个区域能够成为极值区域的条件是无法再找到一个不大于所设定的灰度阈值的像素点去扩大当前区域。
    在这里插入图片描述

    • 当且仅当下面公式中的 q ( i ) q(i) q(i)取最小值时,对应的 Q ( i ) 成为 M S E R Q(i)成为MSER Q(i)成为MSER,式中 Δ \Delta Δ表示灰度阈值的微小增加量

在这里插入图片描述

代码实现——使用Opencv中的cv2.MSER_create()接口
  • cv2.MSER_create()函数参数
    在这里插入图片描述
  • 代码:
import cv2

# 读取图像
image = cv2.imread('car.png')

# 转换为灰度图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 创建 MSER 对象
mser = cv2.MSER_create()

# 检测图像中的稳定区域
regions, _ = mser.detectRegions(gray)

# 绘制检测到的区域
for region in regions:
    x, y, w, h = cv2.boundingRect(region)
    cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 1)

cv2.imshow('MSER Regions', image)
cv2.waitKey(0)

结果:
在这里插入图片描述

很明显中间的车牌区域是我们的目标区域,但是现在得到的检测框太多冗余和非目标区域的了,所以接下来需要进行一些微调,直至得到想要的结果。

import cv2

# 读取图像
image = cv2.imread('car.png')

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 先得到车牌的大致区域
mser = cv2.MSER_create(delta=15)
regions, _ = mser.detectRegions(gray)
max_area = 1
result = None
for region in regions:

    x, y, w, h = cv2.boundingRect(region)
    if w*h > max_area:
        max_area = w*h 
        result = [x, y, w, h]

# 裁剪出车牌
cropped_image = image[result[1]:result[1]+result[3], result[0]:result[0]+result[2],:]
cropped_image_gray = cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)

mser1 = cv2.MSER_create(delta = 5)
regions, _ = mser.detectRegions(cropped_image_gray)

for region in regions:
    x, y, w, h = cv2.boundingRect(region)
    # 去除长宽比差异较大的区域 以及 面积过大的区域
    if w/h > 3 or w/h < 1/3 or h*w > (cropped_image.shape[0]*cropped_image.shape[1]*0.8):
        continue
    cv2.rectangle(cropped_image, (x, y), (x + w, y + h), (0 ,0 ,255), 1)

cv2.namedWindow('MSER Regions', cv2.WINDOW_NORMAL)
cv2.imshow('MSER Regions', cropped_image)
cv2.waitKey(0)

结果
在这里插入图片描述
大部分的字符都得到了比较好的检测结果,但是依旧存在一些问题,如·漏检、误检

  • 为了应用于更加一般的车牌识别(假设已经抠出了车牌所在的区域),可以使用以下代码,当然如果结合更多的一些先验条件和判断方法,将会得到更好的检测效果。👀
import cv2
image = cv2.imread('car_plate_rec\car_3001.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
height,width = gray.shape[0],gray.shape[1]
area = height * width                       # 计算图像面积,用于后续的判断

mser = cv2.MSER_create()
regions, _ = mser.detectRegions(gray)


# 定义函数,用于筛选多余框
def judge_box(w,h):
    if w * h >  (area / 7):                                  # 车牌一共有7个字符,因此可以根据面积过滤掉一些大的区域
        return False
    if  w > width/7 or h > 0.95*height or h < (height/4):    # 太窄和太宽的区域可以过滤掉
        return False
    else :
        return True
    
li : list = [cv2.boundingRect(regions[0])]
max_H : int = 0             # 所有检测框中最高的框的高度,然后按照这个长度去裁剪单个字符所在的区域


def is_effect(li:list,x,y,w,h):     # 对于部分重叠的框,只保留靠最左上角的框
    n = 1
    for i in li:
        if abs(x - i[0]) >= 13:
            continue
        elif abs(x - i[0]) <= 7: 
            n = 0
            if x > i[0]:
                li[li.index(i)] = (x,y,w,h)
    if n == 1 :
        li.append((x,y,w,h))

for region in regions:
    x, y, w, h = cv2.boundingRect(region)
    if judge_box(w,h):
        is_effect(li,x,y,w,h)
        if h > max_H:
            max_H = h

# 去掉重复框
li = list(set(li)) 

for region in li:
    x, y, w, h = region
    if judge_box(w,h):
        print("x,y,w,h:",x,y,w,h)
        cv2.rectangle(image, (x, y), (x + w, y + max_H), (0, 255, 0), 1)

# 显示结果
cv2.namedWindow('MSER Regions', cv2.WINDOW_NORMAL)
cv2.imshow('MSER Regions', image)
cv2.waitKey(0)

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

2、基于笔画宽度变换(Stroke Width Transform,SWT)的场景文字检测

  • 基本原理:利用笔画宽度,也就是平行字符边缘的点对之间的距离,来区分文本和非文本的区域。在一般的文本图像中,由于字符的笔画宽度值比较稳定,而非字符笔画宽度值变化程度较大,据此就可以将文本区域和非文本区域进行区分。
  • 主要步骤:参考文章:基于残差网络及笔画宽度变换的场景文本检测
    • 首先使用Canny算子对图像进行边缘检测,得到每个边缘像素点 p p p ;然后使用Sobel算子获取每个边缘像素点的梯度值 d p d_{p} dp
    • 对于像素点 p p p,根据梯度方向 d p d_{p} dp 沿着路径 r = p + n ∗ d p ( n ≥ 0 ) r=p+n*d_{p}(n\geq0) r=p+ndp(n0)寻找对应边缘上的一个像素点 q q q,其梯度方向为 d q d_{q} dq
    • d p d_{p} dp d q d_{q} dq 方向相反,即满足 d q = − d p ± π 6 ( π 6 是可接受的角度偏差,也自设定其他的角度 ) d_{q}=-d_{p}±\frac{\pi}{6}(\frac{\pi}{6}是可接受的角度偏差,也自设定其他的角度) dq=dp±6π(6π是可接受的角度偏差,也自设定其他的角度),则匹配成功, [ p , q ] [p,q] [pq]路径上的所有像素点标记其笔画宽度值为 p p p q q q间的距离 ∣ ∣ p − q → ∣ ∣ ||\overrightarrow{p-q}|| ∣∣pq ∣∣;若不满足条件,则废弃此路径。如下图所示,左边字符中的路径 p q pq pq 属于匹配成功,右边字符中的路径 p q pq pq 就属于匹配失败,废弃掉该路径。
    • 重读步骤2,3,直至标记该图像上所有没被废弃掉的路径上像素的笔画宽度值。若有像素点被多次赋值,则保留最小值。
    • 矫正拐点。计算每一条路径上的像素的笔画宽度的中值,若该路径上像素点的笔画宽度值大于中值,则对该像素点赋值为该路径上笔画宽度的中值。
    • 由于存在亮底暗字背景和暗底亮字背景两种情况,从梯度相反方向重复 步骤2 至 步骤5 进行二次搜索。
    • 若8邻域内像素与中心像素的笔画宽度之比在 [ 1 / 3 , 3 ] [1/3,3] [1/3,3]内,则聚合成连通域。计算每个有效连通域的笔画宽度的均值和方差,滤除方差大于阈值的连通域。

在这里插入图片描述

  • 因为源码是使用C++写的,所以想自己动手使用 p y t h o n python python实现这个算法,可惜能力不够,费了很长时间也没有预期效果。😫不过发现已经有大佬在 22 22 22 年直接将该算法集成到 p y t h o n python python单独的一个库了:SWTloc,不过目前网上基本没有对这个库的详细介绍,所以干脆详细了解一下这个库,以此来快速实现一下 S W T SWT SWT算法。

SWTloc库

✔安装库:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple swtloc
  • SWTLocalizer类充当对图像执行转换的入口点,是一个初始化方法。它可以接受

    • 单图像路径
    • 多个图像路径
    • 单个预加载图像
    • 多个预加载图像
      在这里插入图片描述
  • SWTImage.transformImage:笔画宽度变换

输入参数参数说明
text_mode文本模式参数: { d b _ l f : 深色背景上的浅色文本 l b _ d f : 浅色背景上的深色文本 \begin{cases}db\_lf:深色背景上的浅色文本\\lb\_df:浅色背景上的深色文本 \end{cases} {db_lf:深色背景上的浅色文本lb_df:浅色背景上的深色文本
engine引擎参数: { “ n u m b a ” : 默认参数 “ p y t h o n ” \begin{cases}“numba”:默认参数\\“python” \end{cases} {numba:默认参数python
gaussian_blurr是否应用高斯模糊器,default = True
gaussian_blurr_kernel高斯模糊的核大小
edge_function边缘函数: { ′ a c ′ : 默认值。 A u t o − C a n n y 函数 , 一个内置函数 , 生成 C a n n y 图像 自定义函数 \begin{cases}'ac':默认值。Auto-Canny函数,一个内置函数,生成 Canny 图像\\自定义函数 \end{cases} {ac:默认值。AutoCanny函数,一个内置函数,生成Canny图像自定义函数
auto_canny_sigma A u t o − C a n n y Auto -Canny AutoCanny函数的 S i g m a Sigma Sigma值,default=0.33
minimum_stroke_width最小允许行程长度参数 [默认 = 3];在寻找从图像中任何边坐标发出的合格描边时,此参数控制最小描边长度,低于该描边将被取消资格。
maximum_stroke_width最大允许行程长度参数 [默认 = 200];在寻找从图像中任何边坐标发出的合格描边时,此参数控制了描边将被取消资格的最大描边长度。
check_angle_deviation是否检查角度偏差 [default = True]
maximum_angle_deviation入射边缘点梯度矢量中可接受的角度偏差[default = np.pi/6]
display控制是否显示该特定阶段的进程

运行后得到4幅图像:原始图像、灰度图像、边缘图像和SWT变换图像
看一下具体效果:

import swtloc as swt
imgpath1 = 'car.png'
imgpath2 = 'car1.png'

swtl = swt.SWTLocalizer(image_paths=[imgpath1, imgpath2]) # Initializing the SWTLocalizer class with the image path
swtImgObj = swtl.swtimages               # Accessing the SWTImage Object which is housing this image
swtimg0 = swtImgObj[1]
swt_mat = swtimg0.transformImage(text_mode='db_lf', maximum_stroke_width=40)

在这里插入图片描述

  • SWTImage.localizeLetters:根据相关参数定位字符。
输入参数参数说明
minimum_pixels_per_cc区域中的最小像素数,低于该值的区域被视为非字符区域
maximum_pixels_per_cc区域中的最大像素数,高于该值的区域被视为非字符区域
acceptable_aspect_ratio纵横比(宽度/高度)小于的所有连接组件都将被修剪
localize_by可以为此函数提供三个可能的参数 { m i n _ b b o x :最小边界框边界计算和标注 e x t _ b b o x :极端边界框边界计算和标注 o u t l i n e :轮廓(等高线)边界计算和标注 \begin{cases}min\_bbox : 最小边界框边界计算和标注\\ext\_bbox : 极端边界框边界计算和标注\\outline:轮廓(等高线)边界计算和标注 \end{cases} min_bbox:最小边界框边界计算和标注ext_bbox:极端边界框边界计算和标注outline:轮廓(等高线)边界计算和标注
padding_pct在标注过程中,有时标注会紧密地贴合在组件本身上,这会导致计算出的边界被扩大。该参数仅适用于和定位。
比较关键的就是前三个参数,下面通过设置前三个参数看一下具体检测字符的效果:
import swtloc as swt
imgpath1 = 'car.png'
imgpath2 = 'car1.png'

swtl = swt.SWTLocalizer(image_paths=[imgpath1, imgpath2]) # Initializing the SWTLocalizer class with the image path
swtImgObj = swtl.swtimages               # Accessing the SWTImage Object which is housing this image
swtimg0 = swtImgObj[1]
swt_mat = swtimg0.transformImage(text_mode='db_lf', maximum_stroke_width=40)
local = swtimg0.localizeLetters()

在这里插入图片描述

local = swtimg0.localizeLetters(minimum_pixels_per_cc=800)

在这里插入图片描述

local = swtimg0.localizeLetters(minimum_pixels_per_cc=800,maximum_pixels_per_cc=5000)

在这里插入图片描述

local = swtimg0.localizeLetters(minimum_pixels_per_cc=800,maximum_pixels_per_cc=5000,acceptable_aspect_ratio=0.5)

在这里插入图片描述

  • 还有其他的一些高级用法, 这里不过多介绍,有需要的可以直接看官方文档 → \xrightarrow{} 📌swtloc

代码实现

  • 👀 看看在车牌数据集上的大致效果:也基本很快速的得到了较好的效果。不过两幅图中都绘制了一个异常的较大的矩形框,可以加上前面MSER算法中对矩形框做些筛选处理的代码即可滤除掉。
# 针对car_plate_rec这个车牌数据集
import swtloc as swt
import cv2

imgpath = 'car_plate_rec/car_1901.jpg'

swtl = swt.SWTLocalizer(image_paths=imgpath) # Initializing the SWTLocalizer class with the image path
swtImgObj = swtl.swtimages               # Accessing the SWTImage Object which is housing this image
swtimg0 = swtImgObj[0]

swt_mat = swtimg0.transformImage(text_mode='db_lf', maximum_stroke_width=40)  # SWT算法
local = swtimg0.localizeLetters(minimum_pixels_per_cc=100,maximum_pixels_per_cc=2000,acceptable_aspect_ratio = 0.1,localize_by='ext_bbox') #检测单个字段区域

li = []         # 存储绘制的矩形框
for i in local.values():
    li.append(i.ext_bbox)
    
def draw_rect(image, li):
    for region in li:
        cv2.polylines(image, [region], isClosed=True, color=(0, 255, 0), thickness=1)
            
img = cv2.imread(imgpath)
draw_rect(img,li)
cv2.namedWindow('img', cv2.WINDOW_NORMAL)
cv2.imshow('img',img)
cv2.waitKey(0)

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

总结

使用python简单实现了基于最大稳定极值区域( M S E R MSER MSER)的 和基于笔画宽度变换( S W T SWT SWT)的文字检测方法,希望对不太了解该算法的伙伴们有所帮助。👍👍👍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/538494.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【示例】MySQL-SQL语句优化

前言 本文主要讲述不同SQL语句的优化策略。 SQL | DML语句 insert语句 插入数据的时候&#xff0c;改为批量插入 插入数据的时候&#xff0c;按照主键顺序插入 大批量插入数据的时候&#xff08;百万&#xff09;&#xff0c;用load指令&#xff0c;从本地文件载入&#x…

基于深度学习的人脸表情识别系统(PyQT+代码+训练数据集)

基于深度学习的人脸表情识别系统&#xff08;PyQT代码训练数据集&#xff09; 前言一、数据集1.1 数据集介绍1.2 数据预处理 二、模型搭建三、训练与测试3.1 模型训练3.2 模型测试 四、PyQt界面实现 前言 本项目是基于mini_Xception深度学习网络模型的人脸表情识别系统&#x…

关于nvm node.js的按照

说明&#xff1a;部分但不全面的记录 因为过程中没有截图&#xff0c;仅用于自己的学习与总结 过程中借鉴的优秀博客 可以参考 1,npm install 或者npm init vuelatest报错 2&#xff0c;了解后 发现是nvm使用的版本较低&#xff0c;于是涉及nvm卸载 重新下载最新版本的nvm 2…

4月12日重新安排行程

332.重新安排行程 332. 重新安排行程 - 力扣&#xff08;LeetCode&#xff09; 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机…

Linux网络 基础概念

目录 背景知识 互联网的发展 局域网和广域网 网络拓扑 网络协议栈 协议的概念 网络协议的分层 网络与操作系统的联系 网络传输的基本流程 IP地址和MAC地址 以太网通信 数据包的封装和分用 跨网段传输 背景知识 互联网的发展 计算机网络是计算机技术和通信技术相…

循环新蓝海,“新”从“旧”中来

浙江安吉&#xff0c;是“两山”理念——“绿水青山就是金山银山”的发源地&#xff0c;也是众多循环经济和绿色产业的根据地。这里汇集了大批已上市和待上市的相关公司的总部&#xff0c;年初刚递表港交所的闪回科技&#xff0c;就是其中之一。 主营二手手机回收和销售的闪回…

卫星图像10个开源数据集资源汇总

文章目录 1、UC Merced Land-Use 2、Indian Pines 3、KSC 4、Washington DC 5、BigEarthNet 6、水体卫星图像的图像 7、城市航拍图像分割数据集 8、游泳池和汽车卫星图像检测 9、人工月球景观数据集 10、马萨诸塞州道路数据集 1、UC Merced Land-Use 数据集下载地址&am…

一文看懂交易主机托管!(此篇足矣)

什么是主机托管&#xff1f; 主机托管的类型&#xff1f; 如何开通主机托管&#xff1f; 主机托管的费用? 日常大家关心最多的就是这几个问题&#xff01;小编今天我们全面一次型解答&#xff01;帮助我们跟多了解&#xff01;建议收藏&#xff0c;避免下次找不到了哦&#x…

VulnHub靶机-easy_cloudantivirus 打靶

easy_cloudantivirus 靶机 目录 easy_cloudantivirus 靶机一、导入虚拟机配置二、攻击方式主机发现端口扫描web渗透-SQL注入命令注入反弹shellssh爆破提权 一、导入虚拟机配置 靶机地址&#xff1a; https://www.vulnhub.com/entry/boredhackerblog-cloud-av,453/下载完成&am…

DOTS Instancing合批:如何针对单个渲染实体修改材质参数

最近在做DOTS的教程,由于DOTS(版本1.0.16)目前不支持角色的骨骼动画&#xff0c;我们是将角色的所有动画数据Baker到一个纹理里面&#xff0c;通过修改材质中的参数AnimBegin,AnimEnd来决定动画播放的起点和终点&#xff0c;材质参数AnimTime记录当前过去的动画时间。但是在做大…

【Super数据结构】二叉搜索树与二叉树的非递归遍历(含前/中/后序)

&#x1f3e0;关于此专栏&#xff1a;Super数据结构专栏将使用C/C语言介绍顺序表、链表、栈、队列等数据结构&#xff0c;每篇博文会使用尽可能多的代码片段图片的方式。 &#x1f6aa;归属专栏&#xff1a;Super数据结构 &#x1f3af;每日努力一点点&#xff0c;技术累计看得…

洛谷P1209 [USACO1.3] 修理牛棚 Barn Repair

#先看题目 题目描述 在一个月黑风高的暴风雨夜&#xff0c;Farmer John 的牛棚的屋顶、门被吹飞了 好在许多牛正在度假&#xff0c;所以牛棚没有住满。 牛棚一个紧挨着另一个被排成一行&#xff0c;牛就住在里面过夜。有些牛棚里有牛&#xff0c;有些没有。 所有的牛棚有相同…

策略为王股票软件源代码-----如何修改为自己软件06

本主播的下载栏目提供了数据&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c; 策略为王股票软件如何导入历史数据&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;

Okhttp全链路监控

目标&#xff1a; 1&#xff09;.监控网络请求的各个阶段 2&#xff09;获取每一个阶段的耗时和性能&#xff0c;用于性能分析。包括dns解析&#xff0c;socket连接时间&#xff0c;tls连接时间&#xff0c;请求发送时间&#xff0c;服务器接口处理时间&#xff0c;应答传输时…

C++设计模式:享元模式(十一)

1、定义与动机 概述&#xff1a;享元模式和单例模式一样&#xff0c;都是为了解决程序的性能问题。面向对象很好地解决了"抽象"的问题&#xff0c;但是必不可免得要付出一定的代价。对于通常情况来讲&#xff0c;面向对象的成本大豆可以忽略不计。但是某些情况&#…

程序“猿”自动化脚本(一)

1.剪贴板管理器&#x1f4cb; 您是否曾经发现自己在处理多个文本片段时忘记了复制的内容&#xff1f;有没有想过有一个工具可以跟踪您一天内复制的所有内容&#xff1f; 该自动化脚本会监视您复制的所有内容&#xff0c;将每个复制的文本无缝存储在时尚的图形界面中&#xff0c…

Salient Object Detection 探索经历

概述 显著性目标检测也被称为显著性检测&#xff0c;旨在通过模拟人类视觉感知系统来检测自然场景图像中最显著的目标和区域。虽然&#xff0c;显著性目标检测听名字是一个检测任务&#xff0c;但是实际上是一个图像分割任务&#xff0c;即一个像素级分类任务&#xff0c;是一…

【数组】5螺旋矩阵

这里写自定义目录标题 一、题目二、解题精髓-循环不变量三、代码 一、题目 给定⼀个正整数 n&#xff0c;⽣成⼀个包含 1 到 n^2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的正⽅形矩阵。 示例: 输⼊: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ] 二、解题精髓…

java包目录命名

包目录命名 config controller exception model common entity enums reponse request repository security service util

权限修饰符,代码块,抽象类,接口.Java

1&#xff0c;权限修饰符 权限修饰符&#xff1a;用来控制一个成员能够被访问的范围可以修饰成员变量&#xff0c;方法&#xff0c;构造方法&#xff0c;内部类 &#x1f47b;&#x1f457;&#x1f451;权限修饰符的分类 &#x1f9e3;四种作用范围由小到大(private<空着…