【OpenCV】手写字符分割

OpenCV 是一个开源的计算机视觉(Computer Vision)与机器学习软件库,提供了多种图像处理算法与接口。在 OCR 技术中,字符分割用于提取图像中的文字信息,可以应用于车牌识别、身份证识别、文档扫描等场景。本文主要记录如何使用 OpenCV 实现手写字符分割。

目录

1 工作原理

1.1 图像预处理

1.2 字符检测

1.3 字符提取

2 程序设计


1 工作原理

        手写字符分割的主要目标是将连续的手写文本图像进行分割,得到单字符的图像。这里考虑字符按照水平方向书写的情况,使用 OpenCV 实现手写字符分割,主要包括以下几个步骤:

        1)图像预处理:将图像转化为二值图,并进行图像去噪,使字符更容易被识别;

        2)字符检测:使用轮廓检测函数,识别可能包含字符的区域;

        3)字符提取:找到所有字符区域之后,从每个字符区域中提取字符。

1.1 图像预处理

        在手写字符分割中,图像预处理过程包括:灰度图转换、二值化和中值滤波。其中,灰度图转换和二值化处理,使字符与背景区域之间的对比度更大,便于寻找可能的字符区域;中值滤波用于去除图像中的噪点。

        使用 cv2.cvtColor() 和 cv2.threshold() 函数实现图像灰度图转换与二值化。

# 灰度图转换
gray = cv2.cvtColor(src_img, cv2.COLOR_BGR2GRAY)# 二值化
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

        使用 cv2.medianBlur() 函数实现中值滤波。

# 中值滤波
filter_size = 3
binary_f = cv2.medianBlur(binary, filter_size)

1.2 字符检测

        图像预处理完成后,就可以使用 cv2.findContours() 函数检测图像的轮廓信息,进一步寻找字符区域。

# 查找字符区域
contours, _ = cv2.findContours(binary_f, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        cv2.findContours() 函数会返回多个轮廓信息,考虑到一些特殊字符(例如 %,÷),这些字符存在多个不连接的部分,因此需要合并位置接近的轮廓,得到字符整体区域。

        首先遍历所有的轮廓区域,获取最大宽度。然后计算每个区域的中点位置,若两个区域的中点位置距离小于最大宽度的一半,则拼接这两个区域。

# 遍历所有区域,寻找最大宽度
w_max = 0
for cnt in contours:_, _, w, _ = cv2.boundingRect(cnt)if w > w_max:w_max = w# 遍历所有区域,拼接x坐标接近的区域
char_dict = {}
for cnt in contours:x, y, w, h = cv2.boundingRect(cnt)x_mid = x + w//2 # 计算中点位置if not char_dict.keys() or all(np.abs(z - x_mid) > w_max//2 for z in char_dict.keys()):char_dict[x_mid] = cntelse:for z in char_dict.keys():if np.abs(z - x_mid) <= w_max//2:char_dict[z] = np.concatenate((char_dict[z], cnt), axis=0) # 拼接两个区域

1.3 字符提取

        字符区域查找完成之后,遍历所有字符区域,使用 cv2.boundingRect() 函数获取端点位置和宽高信息,就可以提取字符了。

# 遍历所有区域,提取字符
dst_img = []
for _, cnt in char_dict.items():x, y, w, h = cv2.boundingRect(cnt)roi = binary[y:y+h, x:x+w]dst_img.append(roi)

2 程序设计

        使用 Gradio 实现交互式界面,中值滤波大小可选 3 × 3, 5 × 5 或 7 × 7。以下是 Python 实现代码:

#-*- Coding: utf-8 -*-import cv2
import numpy as np
import gradio as grdef charSeperate(src_img, filter_size):"""函数功能:字符分割@param src_img@param filter_size@return dst_img"""# 灰度图gray = cv2.cvtColor(src_img, cv2.COLOR_BGR2GRAY)# 二值化_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)binary_inv = cv2.bitwise_not(binary)# 中值滤波filter_size = int(filter_size[0][0]) if filter_size else 3binary_f = cv2.medianBlur(binary_inv, filter_size)# 查找字符区域contours, _ = cv2.findContours(binary_f, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 遍历所有区域,寻找最大宽度w_max = 0for cnt in contours:_, _, w, _ = cv2.boundingRect(cnt)if w > w_max:w_max = w# 遍历所有区域,拼接x坐标接近的区域char_dict = {}for cnt in contours:x, y, w, h = cv2.boundingRect(cnt)x_mid = x + w//2 # 计算中点位置if not char_dict.keys() or all(np.abs(z - x_mid) > w_max//2 for z in char_dict.keys()):char_dict[x_mid] = cntelse:for z in char_dict.keys():if np.abs(z - x_mid) <= w_max//2:char_dict[z] = np.concatenate((char_dict[z], cnt), axis=0) # 拼接两个区域# 按照中点坐标,对字符进行排序char_dict = dict(sorted(char_dict.items(), key=lambda item: item[0]))# 遍历所有区域,提取字符dst_img = []for _, cnt in char_dict.items():x, y, w, h = cv2.boundingRect(cnt)roi = binary[y:y+h, x:x+w]dst_img.append(roi)return dst_imgif __name__ == "__main__":demo = gr.Interface(fn=charSeperate,inputs=[gr.Image(label="input image"), gr.Radio(['3x3', '5x5', '7x7'], value='3x3')],outputs=[gr.Gallery(label="charset", columns=[3], object_fit="contain", height="auto")],live=True)demo.launch()

以下是代码运行效果:

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Flutter学习笔记】9.6 动画切换组件(AnimatedSwitcher)

参考资料&#xff1a;《Flutter实战第二版》9.6 动画切换组件&#xff08;AnimatedSwitcher&#xff09; 9.6.1 AnimatedSwitcher AnimatedSwitcher 可以同时对其新、旧子元素添加显示、隐藏动画&#xff0c;在需要切换新旧元素的场景广泛使用。也就是说在AnimatedSwitcher的子…

Java操作Sql语句 出现迭代死循环 (Bug排查)

目录 1. 问题所示2. 原理分析3. 解决方法4. 彩蛋1. 问题所示 Java执行Sql语句来查询一些数据的时候 虽说数据量很大,但是查询过程中一直迭代查询 截图如下所示: 2. 原理分析 至于迭代死循环,可能是不满足的条件也进入查询(本身我的数据量就很大) 主要可能引起的两个原…

Python数学建模-2.3函数(下)

2.3.2模块的导入与使用 模块在Python中是一个包含Python定义和语句的文件&#xff0c;通常用来组织代码&#xff0c;使得代码更易于管理和复用。下面&#xff0c;我将详细讲解Python函数中模块的使用知识。 1. 模块的导入 在Python中&#xff0c;你可以使用import语句来导入…

【新书推荐】29.1 32位汇编基本概念

第二十九章 32处理器体系结构 这一章我们将讲述32处理器体系结构。包括32位计算机的一些基本概念&#xff0c;32位处理器&#xff0c;程序加载执行的过程&#xff0c;32位计算机的硬件组成和输入输出系统。 29.1 基本概念 本节内容&#xff1a; ■实模式与保护模式 ■操作系…

【华为 ICT HCIA eNSP 习题汇总】——题目集16

1、下面哪一个最适合使用室内分布方式部署 WLAN&#xff1f; A、运动场 B、办公室 C、高校单排宿舍 D、广场 考点&#xff1a;无线局域网 解析&#xff1a;&#xff08;C&#xff09; 室内分布方式部署 WLAN 一般适用于需要大面积、高密度、高质量无线覆盖的场所&#xff0c;从…

读算法的陷阱:超级平台、算法垄断与场景欺骗笔记10_中间人

1. 中间人 1.1. 从积极的意义上讲&#xff0c;比价网站与搜索引擎这些“网络中间人”的存在有效提高了市场透明度&#xff0c;看似打造出了一片阻绝价格歧视、改善社会福利的乐土 1.2. 类似于“网络聚合器”的互联网巨头已经成为线上市场的重要中介 1.2.1. 网络聚合器实际上…

[Kali] 安装Nessus及使用

在官方网站下载对应的 Nessus 版本:Download Tenable Nessus | TenableDownload Nessus and Nessus Managerhttp://www.tenable.com/products/nessus/select-your-operating-system这里选择 Kali 对应的版本 一、安装 Nessus 1、下载得到的是 deb 文件,与

第四百零一回

文章目录 知识回顾示例代码经验总结 我们在上一章回中介绍了MethodChannel的使用方法&#xff0c;本章回中将介绍EventChannel的使用方法.闲话休提&#xff0c;让我们一起Talk Flutter吧。 知识回顾 我们在前面章回中介绍了通道的概念和作用&#xff0c;并且提到了通道有不同的…

centos7 install rocketmq 宿主机快速搭建RocketMQ单机开发环境

为什么采用宿主机而不采用 Docker 方式快速搭建 在搭建 RocketMQ 测试环境时&#xff0c;我们可以选择在宿主机上直接安装和配置&#xff0c;也可以使用 Docker 容器来快速搭建。然而&#xff0c;为什么我们选择了在宿主机上安装而不是使用 Docker 方式呢&#xff1f; 调整配置…

Android14之报错:error:add its name to the whitelist(一百九十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

Vue手写模拟步骤条

效果图&#xff1a; 如果要使用element的步骤条就需要强行修改样式&#xff0c;参考之前的那篇步骤条。这里我采用手写div 代码&#xff1a; 思路是给最外层的div一个左边框&#xff0c;给里面的step-item设置左边框为图片&#xff0c;通过定位来移动。 <div class"m…

代码训练LeetCode(11)删除有序数组中的重复项II

代码训练(11)LeetCode之删除有序数组中的重复项II Author: Once Day Date: 2024年3月14日 漫漫长路&#xff0c;才刚刚开始… 全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客 参考文章: 80. 删除有序数组中的重复项 II - 力扣&#xff08;LeetCode&#xff…