python中opencv实现文字分割的实践
作者:告白少年 发布时间:2022-08-02 20:42:48
图片文字分割的时候,常用的方法有两种。一种是投影法,适用于排版工整,字间距行间距比较宽裕的图像;还有一种是用OpenCV的轮廓检测,适用于文字不规则排列的图像。
投影法
对文字图片作横向和纵向投影,即通过统计出每一行像素个数,和每一列像素个数,来分割文字。
分别在水平和垂直方向对预处理(二值化)的图像某一种像素进行统计,对于二值化图像非黑即白,我们通过对其中的白点或者黑点进行统计,根据统计结果就可以判断出每一行的上下边界以及每一列的左右边界,从而实现分割的目的。
算法步骤:
使用水平投影和垂直投影的方式进行图像分割,根据投影的区域大小尺寸分割每行和每块的区域,对原始图像进行二值化处理。
投影之前进行图像灰度学调整做膨胀操作
分别进行水平投影和垂直投影
根据投影的长度和高度求取完整行和块信息
横板文字-小票文字分割
#小票水平分割
import cv2
import numpy as np
img = cv2.imread(r"C:\Users\An\Pictures\1.jpg")
cv2.imshow("Orig Image", img)
# 输出图像尺寸和通道信息
sp = img.shape
print("图像信息:", sp)
sz1 = sp[0] # height(rows) of image
sz2 = sp[1] # width(columns) of image
sz3 = sp[2] # the pixels value is made up of three primary colors
print('width: %d \n height: %d \n number: %d' % (sz2, sz1, sz3))
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
retval, threshold_img = cv2.threshold(gray_img, 120, 255, cv2.THRESH_BINARY_INV)
cv2.imshow("threshold_img", threshold_img)
# 水平投影分割图像
gray_value_x = []
for i in range(sz1):
white_value = 0
for j in range(sz2):
if threshold_img[i, j] == 255:
white_value += 1
gray_value_x.append(white_value)
print("", gray_value_x)
# 创建图像显示水平投影分割图像结果
hori_projection_img = np.zeros((sp[0], sp[1], 1), np.uint8)
for i in range(sz1):
for j in range(gray_value_x[i]):
hori_projection_img[i, j] = 255
cv2.imshow("hori_projection_img", hori_projection_img)
text_rect = []
# 根据水平投影分割识别行
inline_x = 0
start_x = 0
text_rect_x = []
for i in range(len(gray_value_x)):
if inline_x == 0 and gray_value_x[i] > 10:
inline_x = 1
start_x = i
elif inline_x == 1 and gray_value_x[i] < 10 and (i - start_x) > 5:
inline_x = 0
if i - start_x > 10:
rect = [start_x - 1, i + 1]
text_rect_x.append(rect)
print("分行区域,每行数据起始位置Y:", text_rect_x)
# 每行数据分段
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (13, 3))
dilate_img = cv2.dilate(threshold_img, kernel)
cv2.imshow("dilate_img", dilate_img)
for rect in text_rect_x:
cropImg = dilate_img[rect[0]:rect[1],0:sp[1]] # 裁剪图像y-start:y-end,x-start:x-end
sp_y = cropImg.shape
# 垂直投影分割图像
gray_value_y = []
for i in range(sp_y[1]):
white_value = 0
for j in range(sp_y[0]):
if cropImg[j, i] == 255:
white_value += 1
gray_value_y.append(white_value)
# 创建图像显示水平投影分割图像结果
veri_projection_img = np.zeros((sp_y[0], sp_y[1], 1), np.uint8)
for i in range(sp_y[1]):
for j in range(gray_value_y[i]):
veri_projection_img[j, i] = 255
cv2.imshow("veri_projection_img", veri_projection_img)
# 根据垂直投影分割识别行
inline_y = 0
start_y = 0
text_rect_y = []
for i in range(len(gray_value_y)):
if inline_y == 0 and gray_value_y[i] > 2:
inline_y = 1
start_y = i
elif inline_y == 1 and gray_value_y[i] < 2 and (i - start_y) > 5:
inline_y = 0
if i - start_y > 10:
rect_y = [start_y - 1, i + 1]
text_rect_y.append(rect_y)
text_rect.append([rect[0], rect[1], start_y - 1, i + 1])
cropImg_rect = threshold_img[rect[0]:rect[1], start_y - 1:i + 1] # 裁剪图像
cv2.imshow("cropImg_rect", cropImg_rect)
# cv2.imwrite("C:/Users/ThinkPad/Desktop/cropImg_rect.jpg",cropImg_rect)
# break
# break
# 在原图上绘制截图矩形区域
print("截取矩形区域(y-start:y-end,x-start:x-end):", text_rect)
rectangle_img = cv2.rectangle(img, (text_rect[0][2], text_rect[0][0]), (text_rect[0][3], text_rect[0][1]),
(255, 0, 0), thickness=1)
for rect_roi in text_rect:
rectangle_img = cv2.rectangle(img, (rect_roi[2], rect_roi[0]), (rect_roi[3], rect_roi[1]), (255, 0, 0), thickness=1)
cv2.imshow("Rectangle Image", rectangle_img)
key = cv2.waitKey(0)
if key == 27:
print(key)
cv2.destroyAllWindows()
小票图像二值化结果如下:
小票图像结果分割如下:
竖版-古文文字分割
对于古籍来说,古籍文字书写在习惯是从上到下的,所以说在扫描的时候应该扫描列投影,在扫描行投影。
1.原始图像进行二值化
使用水平投影和垂直投影的方式进行图像分割,根据投影的区域大小尺寸分割每行和每块的区域,对原始图像进行二值化处理。
原始图像:
二值化后的图像:
2.图像膨胀
投影之前进行图像灰度学调整做膨胀操作,选取适当的核,对图像进行膨胀处理。
3.垂直投影
定位该行文字区域:
数值不为0的区域就是文字存在的地方(即二值化后白色部分的区域),为0的区域就是每行之间相隔的距离。
1、如果前一个数为0,则记录第一个不为0的坐标。
2、如果前一个数不为0,则记录第一个为0的坐标。形象的说就是从出现第一个非空白列到出现第一个空白列这段区域就是文字存在的区域。
通过以上规则就可以找出每一列文字的起始点和终止点,从而确定每一列的位置信息。
垂直投影结果:
通过上面的垂直投影,根据其白色小山峰的起始位置就可以界定出每一列的起始位置,从而把每一列分割出来。
4.水平投影
根据投影的长度和高度求取完整行和块信息
通过水平投影可以获得每一个字符左右的起始位置,这样也就可以获得到每一个字符的具体坐标位置,即一个矩形框的位置。
import cv2
import numpy as np
import os
img = cv2.imread(r"C:\Users\An\Pictures\3.jpg")
save_path=r"E:\crop_img\result" #图像分解的每一步保存的地址
crop_path=r"E:\crop_img\img" #图像切割保存的地址
cv2.imshow("Orig Image", img)
# 输出图像尺寸和通道信息
sp = img.shape
print("图像信息:", sp)
sz1 = sp[0] # height(rows) of image
sz2 = sp[1] # width(columns) of image
sz3 = sp[2] # the pixels value is made up of three primary colors
print('width: %d \n height: %d \n number: %d' % (sz2, sz1, sz3))
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
retval, threshold_img = cv2.threshold(gray_img, 120, 255, cv2.THRESH_BINARY_INV)
cv2.imshow("threshold_img", threshold_img)
cv2.imwrite(os.path.join(save_path,"threshold_img.jpg"),threshold_img)
# 垂直投影分割图像
gray_value_y = []
for i in range(sz2):
white_value = 0
for j in range(sz1):
if threshold_img[j, i] == 255:
white_value += 1
gray_value_y.append(white_value)
print("", gray_value_y)
#创建图像显示垂直投影分割图像结果
veri_projection_img = np.zeros((sp[0], sp[1], 1), np.uint8)
for i in range(sz2):
for j in range(gray_value_y[i]):
veri_projection_img[j, i] = 255
cv2.imshow("veri_projection_img", veri_projection_img)
cv2.imwrite(os.path.join(save_path,"veri_projection_img.jpg"),veri_projection_img)
text_rect = []
# 根据垂直投影分割识别列
inline_y = 0
start_y = 0
text_rect_y = []
for i in range(len(gray_value_y)):
if inline_y == 0 and gray_value_y[i]> 30:
inline_y = 1
start_y = i
elif inline_y == 1 and gray_value_y[i] < 30 and (i - start_y) > 5:
inline_y = 0
if i - start_y > 10:
rect = [start_y - 1, i + 1]
text_rect_y.append(rect)
print("分列区域,每列数据起始位置Y:", text_rect_y)
# 每列数据分段
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (13, 3))
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
dilate_img = cv2.dilate(threshold_img, kernel)
cv2.imshow("dilate_img", dilate_img)
cv2.imwrite(os.path.join(save_path,"dilate_img.jpg"),dilate_img)
for rect in text_rect_y:
cropImg = dilate_img[0:sp[0],rect[0]:rect[1]] # 裁剪图像y-start:y-end,x-start:x-end
sp_x = cropImg.shape
# 垂直投影分割图像
gray_value_x = []
for i in range(sp_x[0]):
white_value = 0
for j in range(sp_x[1]):
if cropImg[i, j] == 255:
white_value += 1
gray_value_x.append(white_value)
# 创建图像显示水平投影分割图像结果
hori_projection_img = np.zeros((sp_x[0], sp_x[1], 1), np.uint8)
for i in range(sp_x[0]):
for j in range(gray_value_x[i]):
veri_projection_img[i, j] = 255
# cv2.imshow("hori_projection_img", hori_projection_img)
# 根据水平投影分割识别行
inline_x = 0
start_x = 0
text_rect_x = []
ind=0
for i in range(len(gray_value_x)):
ind+=1
if inline_x == 0 and gray_value_x[i] > 2:
inline_x = 1
start_x = i
elif inline_x == 1 and gray_value_x[i] < 2 and (i - start_x) > 5:
inline_x = 0
if i - start_x > 10:
rect_x = [start_x - 1, i + 1]
text_rect_x.append(rect_x)
text_rect.append([start_x - 1, i + 1,rect[0], rect[1]])
cropImg_rect = threshold_img[start_x - 1:i + 1,rect[0]:rect[1]] # 裁剪二值化图像
crop_img=img[start_x - 1:i + 1,rect[0]:rect[1]] #裁剪原图像
# cv2.imshow("cropImg_rect", cropImg_rect)
# cv2.imwrite(os.path.join(crop_path,str(ind)+".jpg"),crop_img)
# break
# break
# 在原图上绘制截图矩形区域
print("截取矩形区域(y-start:y-end,x-start:x-end):", text_rect)
rectangle_img = cv2.rectangle(img, (text_rect[0][2], text_rect[0][0]), (text_rect[0][3], text_rect[0][1]),
(255, 0, 0), thickness=1)
for rect_roi in text_rect:
rectangle_img = cv2.rectangle(img, (rect_roi[2], rect_roi[0]), (rect_roi[3], rect_roi[1]), (255, 0, 0), thickness=1)
cv2.imshow("Rectangle Image", rectangle_img)
cv2.imwrite(os.path.join(save_path,"rectangle_img.jpg"),rectangle_img)
key = cv2.waitKey(0)
if key == 27:
print(key)
cv2.destroyAllWindows()
分割结果如下:
从分割的结果上看,基本上实现了图片中文字的分割。但由于中文结构复杂性,对于一些文字的分割并不理想,字会出现过度分割、有粘连的两个字会出现分割不够的现象。可以从图像预处理(图像腐蚀膨胀),边界判断阈值的调整等方面进行优化。
来源:https://blog.csdn.net/qq_43555843/article/details/117412056


猜你喜欢
- 继续分享pygame有趣的技术知识,欢迎往下看。一、先搭个架子(一)黏贴背景图:实现代码如下:import pygamepygame.ini
- 前言本文大概内容:例如:随着ElementUI前后端交互的技术的更新,用户的的体验越来越好。本文主要针对用户在保持原页面结构,再添加另一个页
- 问题描述:在画时间序列ACF时,调用from statsmodels.graphics.tsaplots import plot_acf,
- 我们先看一下相关数据结构的知识。 在学习线性表的时候,曾有这样一个例题。 已知一个存储整数的顺序表La,试构造顺序表Lb,要求顺序表Lb中只
- python操作mongodb数据库# !/usr/bin/env python# -*- coding:utf-8 -*-"&q
- 本文实例讲述了Python打开文件、文件读写操作、with方式、文件常用函数。分享给大家供大家参考,具体如下:打开文件:在python3中,
- 1. 背景:最近写了一篇CSDN博客需要上传gif图,发现大小超过了5M,无法上传。文件大小:本想自己找个免费的压缩工具,结果下载下来的工具
- 本文实例讲述了python实现读取excel文件中所有sheet操作。分享给大家供大家参考,具体如下:表格是这样的 实现把此文件所
- SQL SERVER数据库用datetime类型的时候 如果插入NULL,没有问题 如果插入空串"",就会默认1900-
- 如何在Typescript中使用for...in ?本人在TS中用for...in出现了些问题,也想到了一些解决方法。那么先来看看下面报错的
- 简介在这篇文章中我将介绍如何写一个简短(200行)的 Python 脚本,来自动地将一幅图片的脸替换为另一幅图片的脸。这个过程分四步:检测脸
- 1.作用域在python中,作用域分为两种:全局作用域和局部作用域。全局作用域是定义在文件级别的变量,函数名。而局部作用域,则是定义函数内部
- 你好,我是林骥。斜率图,可以快速展现两组数据之间各维度的变化,特别适合用于对比两个时间点的数据。比如说,为了对比分析某产品不同功能的用户满意
- 我的工作内容可以说是五花八门,策划、文案、设计(包括平面设计、网站设计、VIS设计)、前端代码、交互、测试、摄影、项目管理等。所有这些工作最
- 今天给大家分享的主题是用百度的接口实现图片的文字识别。1.环境和配置要求整体是用Python实现,所需要使用的第三方库包括aip、PIL、k
- 引言第一次了解python的WSGI,可能描述的不太准确。本篇文章所依赖的python环境为:什么是WSGIWSGI也称之为web服务器通用
- 直接执行这两个命令即可:sudo update-alternatives --install /usr/bin/python python
- Chrome Options常用的行为一般有以下几种:禁止图片和视频的加载:提升网页加载速度。添加代理:用于 * 某些页面,或者应对IP访
- 一、摘要Python使用被称为异常 的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象。
- 一、前端代码<el-upload class="step_content" drag