最近想做一个关于中文OCR的小系列,也是对之前做的东西的一个总结。作为这个系列的第一篇,我觉得还是有必要说一下关于中文字符图片分割的问题。因为现在开源的OCR代码比较多,相对而言对字符图片的分割的方法提的比较少,尤其是中文字符图片的分割还是有一定困难在里面的。
一、普遍使用的切割方法
现在大部分开源项目上用切割方法还是基于水平、垂直投影字符切割方法。
原理比较简单容易理解,也比较容易实现。先对一个文本图片进行水平投影,得到图片在垂直方向上的像素分布,有像素存在的区域即为文本所在区域;将该行文本切割下来,再对该行文本图片进行垂直投影,得到水平方向的像素分布,同理有像素存在的区域基本字符所在区域。
from PIL import Image
import numpy as np
min_thresh = 2 #字符上最少的像素点
min_range = 5 #字符最小的宽度
def vertical(img_arr):
h,w = img_arr.shape
ver_list = []
for x in range(w):
ver_list.append(h - np.count_nonzero(img_arr[:, x]))
return ver_list
def horizon(img_arr):
h,w = img_arr.shape
hor_list = []
for x in range(h):
hor_list.append(w - np.count_nonzero(img_arr[x, :]))
return hor_list
def OTSU_enhance(img_gray, th_begin=0, th_end=256, th_step=1):
max_g = 0
suitable_th = 0
for threshold in xrange(th_begin, th_end, th_step):
bin_img = img_gray > threshold
bin_img_inv = img_gray <= threshold
fore_pix = np.sum(bin_img)
back_pix = np.sum(bin_img_inv)
if 0 == fore_pix:
break
if 0 == back_pix:
continue
w0 = float(fore_pix) / img_gray.size
u0 = float(np.sum(img_gray * bin_img)) / fore_pix
w1 = float(back_pix) / img_gray.size
u1 = float(np.sum(img_gray * bin_img_inv)) / back_pix
# intra-class variance
g = w0 * w1 * (u0 - u1) * (u0 - u1)
if g > max_g:
max_g = g
suitable_th = threshold
return suitable_th
def cut_line(horz, pic):
begin, end = 0, 0
w, h = pic.size
cuts=[]
for i,count in enumerate(horz):
if count >= min_thresh and begin == 0:
begin = i
elif count >= min_thresh and begin != 0:
continue
elif count <= min_thresh and begin != 0:
end = i
#print (begin, end), count
if end - begin >= 2:
cuts.append((end - begin, begin, end))
begin = 0
end = 0
continue
elif count <= min_thresh or begin == 0:
continue
cuts = sorted(cuts, reverse=True)
if len(cuts) == 0:
return 0, False
else:
if len(cuts) > 1 and cuts[1][0] in range(int(cuts[0][0] * 0.8), cuts[0][0]):
return 0, False
else:
crop_ax = (0, cuts[0][1], w, cuts[0][2])
img_arr = np.array(pic.crop(crop_ax))
return img_arr, True
def simple_cut(vert):
begin, end = 0,0
cuts = []
for i,count in enumerate(vert):
if count >= min_thresh and begin == 0:
begin = i
elif count >= min_thresh and begin != 0:
continue
elif count <= min_thresh and begin != 0:
end = i
#print (begin, end), count
if end - begin >= min_range:
cuts.append((begin, end))
begin = 0
end = 0
continue
elif count <= min_thresh or begin == 0:
continue
return cuts
pic_path = './test.jpg'
save_path = './SplitChar/'
src_pic = Image.open(pic_path).convert('L')
src_arr = np.array(src_pic)
threshold = OTSU_enhance(src_arr)
bin_arr = np.where(src_arr < threshold, 0, 255) #先用大津阈值将图片二值化处理
horz = horizon(bin_arr) #获取到文本 水平 方向的投影
line_arr, flag = cut_line(horz, src_pic) #把文字(行)所在的位置切割下来
if flag == False: #如果flag==False 说明没有切到一行文字
exit()
line_arr = np.where(line_arr < threshold, 0, 255)
line_img = Image.fromarray((255 - line_arr).astype("uint8"))
width, height = line_img.size
vert = vertical(line_arr) #获取到该行的 垂直 方向的投影
cut = simple_cut(vert) #直接对目标行进行切割
for x in range(len(cut)):
ax = (cut[x][0] - 1, 0, cut[x][1] + 1, height)
temp = line_img.crop(ax)
temp = image_unit.resize(temp, save_size)
temp.save('{}/{}.jpg'.format(save_path, x))
因为在切割的时候,根据手动设置的字符宽度和、字符间隙宽度的阈值进行分割,所以存在以下几个的问题:
1、对字符排列密集的图片分割效果差
2、对左右结构的字,且左右结构缝隙比较大如“川”
3、对一部分垂直投影占得像素点比较少的字符,可以会认为是噪声,比如“上”
4、需要手动设置阈值,阈值不能对所有文本适用
5、对中英文、标点、数字的混合文本处理十分不好
二、统计分割
#p#分页标题#e#
上面的方法的缺陷在于它的阈值选择上,如何分割字符的问题就变成了,选择什么样的阈值去判断字符宽度和两个字符的间隙。其实一开始我也想到过用网络直接去定位一块一块字符,但实际操作了一下,精确度着实不高,而且会影响速度。
所以有如下几个任务:
1、自动选择阈值去判断什么样的宽度是一个完整的字,什么样的宽度是一个偏旁部首
2、如果是偏旁,如何与另一个偏旁相连
3、如何是标点或是数字、英文怎么办呢?它们的宽度和偏旁是类似的
那么只需要在上面的基础上稍作一下修改,找出特定的阈值,从而判断是什么部分,作出什么操作就OK
from sklearn.cluster import KMeans
def isnormal_width(w_judge, w_normal_min, w_normal_max):
if w_judge < w_normal_min:
return -1
elif w_judge > w_normal_max:
return 1
else:
return 0
def cut_by_kmeans(pic_path, save_path, save_size):
src_pic = Image.open(pic_path).convert('L') #先把图片转化为灰度
src_arr = np.array(src_pic)
threshold = OTSU_enhance(src_arr) * 0.9 #用大津阈值
bin_arr = np.where(src_arr < threshold, 0, 255) #二值化图片
horz = horizon(bin_arr) #获取到该行的 水平 方向的投影
line_arr, flag = cut_line(horz, src_pic) #把文字(行)所在的位置切割下来
if flag == False:
return flag
line_arr = np.where(line_arr < threshold, 0, 255)
line_img = Image.fromarray((255 - line_arr).astype("uint8"))
width, height = line_img.size
vert = vertical(line_arr) #获取到该行的 垂直 方向的投影
cut = simple_cut(vert) #先进行简单的文字分割(即有空隙就切割)
#cv.line(img,(x1,y1), (x2,y2), (0,0,255),2)
width_data = []
width_data_TooBig = []
width_data_withoutTooBig = []
for i in range(len(cut)):
tmp = (cut[i][1] - cut[i][0], 0)
if tmp[0] > height * 1.8: #比这一行的高度大两倍的肯定是连在一起的
temp = (tmp[0], i)
width_data_TooBig.append(temp)
else:
width_data_withoutTooBig.append(tmp)
width_data.append(tmp)
kmeans = KMeans(n_clusters=2).fit(width_data_withoutTooBig)
#print "聚簇中心点:", kmeans.cluster_centers_
#print 'label:', kmeans.labels_
#print '方差:', kmeans.inertia_
label_tmp = kmeans.labels_
label = []
j = 0
k = 0
for i in range(len(width_data)): #将label整理,2代表大于一个字的
if j != len(width_data_TooBig) and k != len(label_tmp):
if i == width_data_TooBig[j][1]:
label.append(2)
j = j + 1
else:
label.append(label_tmp[k])
k = k + 1
elif j == len(width_data_TooBig) and k != len(label_tmp):
label.append(label_tmp[k])
k = k + 1
elif j != len(width_data_TooBig) and k == len(label_tmp):
label.append(2)
j = j + 1
label0_example = 0
label1_example = 0
for i in range(len(width_data)):
if label[i] == 0:
label0_example = width_data[i][0]
elif label[i] == 1:
label1_example = width_data[i][0]
if label0_example > label1_example: #找到正常字符宽度的label(宽度大的,防止切得太碎导致字符宽度错误)
label_width_normal = 0
else:
label_width_normal = 1
label_width_small = 1 - label_width_normal
cluster_center = []
cluster_center.append(kmeans.cluster_centers_[0][0])
cluster_center.append(kmeans.cluster_centers_[1][0])
for i in range(len(width_data)):
if label[i] == label_width_normal and width_data[i][0] > cluster_center[label_width_normal] * 4 / 3:
label[i] = 2
temp = (width_data[i][0], i)
width_data_TooBig.append(temp)
max_gap = get_max_gap(cut)
for i in range(len(label)):
if i == max_gap[1]:
label[i] = 3
width_normal_data = [] #存正常字符宽度
width_data_TooSmall = []
for i in range(len(width_data)):
if label[i] == label_width_normal:
width_normal_data.append(width_data[i][0])
elif label[i] != label_width_normal and label[i] != 2 and label[i] != 3: #切得太碎的
#box=(cut[i][0],0,cut[i][1],height)
#region=line_img.crop(box) #此时,region是一个新的图像对象。
#region_arr = 255 - np.array(region)
#region = Image.fromarray(region_arr.astype("uint8"))
#name = "single"+str(i)+".jpg"
#region.save(name)
tmp = (width_data[i][0], i)
width_data_TooSmall.append(tmp)
width_normal_max = max(width_normal_data)
width_normal_min = min(width_normal_data) #得到正常字符宽度的上下限
if len(width_data_TooBig) != 0:
for i in range(len(width_data_TooBig)):
index = width_data_TooBig[i][1]
mid = (cut[index][0] + cut[index][1]) / 2
tmp1 = (cut[index][0], int(mid))
tmp2 = (int(mid)+1, cut[index][1])
del cut[index]
cut.insert(index, tmp2)
cut.insert(index, tmp1)
del width_data[index]
tmp1 = (tmp1[1] - tmp1[0], index)
tmp2 = (tmp2[1] - tmp2[0], index+1)
width_data.insert(index, tmp2)
width_data.insert(index, tmp1)
label[index] = label_width_normal
label.insert(index, label_width_normal)
if len(width_data_TooSmall) != 0: #除':'以外有小字符,先找'('、')'label = 4
for i in range(len(width_data_TooSmall)):
index = width_data_TooSmall[i][1]
border_left = cut[index][0] + 1
border_right = cut[index][1]
RoI_data = line_arr[:,border_left:border_right]
#RoI_data = np.where(RoI_data < threshold, 0, 1)
horz = horizon(RoI_data)
up_down = np.sum(np.abs(RoI_data - RoI_data[::-1]))
left_right = np.sum(np.abs(RoI_data - RoI_data[:,::-1]))
vert = vertical(RoI_data)
if up_down <= left_right * 0.6 and np.array(vert).var() < len(vert) * 2:
#print i, up_down, left_right,
#print vert, np.array(vert).var()
label[index] = 4
index_delete = [] #去掉这些index右边的线
cut_final = []
width_untilnow = 0
for i in range(len(width_data)):
if label[i] == label_width_small and width_untilnow == 0:
index_delete.append(i)
cut_left = cut[i][0]
width_untilnow = cut[i][1] - cut[i][0]
#print cut_left,width_untilnow,i
elif label[i] != 3 and label[i] != 4 and width_untilnow != 0:
width_untilnow = cut[i][1] - cut_left
if isnormal_width(width_untilnow, width_normal_min, width_normal_max) == -1: #还不够长
index_delete.append(i)
#print cut_left,width_untilnow,i
elif isnormal_width(width_untilnow, width_normal_min, width_normal_max) == 0: #拼成一个完整的字
width_untilnow = 0
cut_right = cut[i][1]
tmp = (cut_left, cut_right)
cut_final.append(tmp)
#print 'complete',i
elif isnormal_width(width_untilnow, width_normal_min, width_normal_max) == 1: #一下子拼多了
#print 'cut error!!!!',cut_left,width_untilnow,i
width_untilnow = 0
cut_right = cut[i-1][1]
tmp = (cut_left, cut_right)
cut_final.append(tmp)
#print 'cut error!!!!',i
index_delete.append(i)
cut_left = cut[i][0]
width_untilnow = cut[i][1] - cut[i][0]
if i == len(width_data):
tmp = (cut[i][0], cut[i][1])
cut_final.append(tmp)
#print i
else:
tmp = (cut[i][0], cut[i][1])
cut_final.append(tmp)
i1 = len(cut_final) - 1
i2 = len(cut) - 1
if cut_final[i1][1] != cut[i2][1]:
tmp = (cut[i2][0], cut[i2][1])
cut_final.append(tmp)
else:
cut_final = cut
for x in range(len(cut_final)):
ax = (cut_final[x][0] - 1, 0, cut_final[x][1] + 1, height)
temp = line_img.crop(ax)
temp = image_unit.resize(temp, save_size)
temp.save('{}/{}.jpg'.format(save_path, x))
return flag
这里用了一下sklearn这个机器学习库里面的Kmeans来进行分类。
#p#分页标题#e#
#p#分页标题#e#
(a)直接分割的结果 (b)用Kmeans分割的结果
zhangzm0128
关注 关注
-
12
点赞 -
踩 39
评论53
收藏
一键三连
专栏目录
图像字符分割
06-13
本资源以matlab作为开发工具,采用形态学和matlab自带的分割函数,实现对图片中字符的分割和本地存储,同时将分割出的所有字符进行显示,实验效果特别好。自带测试图片,方便调试学习。
ocr识别中的图像分割方法小结
whowhoha的专栏
11-22 7092
ocr识别中的图像分割方法小结 Email:whowhoha@outlook.com ocr 字符图像的特点是:背景复杂,存在如底纹、水印、底线、框线、加盖印章干扰叠加 ,同时存在 光照不匀、对比度小、倾斜、污迹、背景网络 、防伪标识、磨损、打印时着力不均、字的笔画深浅、及受油墨多寡的影等因素的影响,故图像的干扰和噪声大,采用普通的二值化分割方法效果不佳,故针对 OCR图像
字符分割方法1
dudu的博客
11-20 602
非粘连型字符:竖直投影直方图分割 当验证码图片之间没有字符粘连时,在竖直方向进行投影,字符间的的间隙出现投影值为零的区域,根据这些字符为零的区域确定字符分割的位置。 基本步骤如下: 1.对矩阵的每一列,令白点用1表示,黑点用0表示,记录个数,并赋值给marcol 2.用一维矩阵s记录分割点的横坐标 3.根据矩阵s记录的分割点分割图片 处理结果如下:
字符切割
fanfangyu的博客
11-17 1257
字符分割的 任务是把多行或多字符图像中的每个字符从整个图像中分割出来,成为单个字符。对于字符分割的问题常常不被重视,但是字符的正确分割对字符的识别是至关重要 的。由于字符字体存在着多样性,所以在一般的字符识别系统中,字符识别之前要先对图像进行阈值化,然后再进行行字切分,以分割出一个个具体的二值表示的字 符图像点阵,作为单字符识别的输入数据。由于获得的文本图像不但包含了组成文本的一个个字符,而
字符定位与字符分割
小明知道的专栏
05-07 1万+
Abstract:字符串识别最重要的是字符串定位以及字符串分割。例如做车牌识别,车牌字符定位和分割是最难的部分。对于一张字符串的图像,首先必须要定位出字符串的边界,然后分别对字符串进行单个切割,单个分割出来的字符再做识别。
字符分割基础方法
勋勋的博客
12-04 1840
最近这两周看了看字符分割的基础方法,以下做个总结。 @[TOC]平均分割算法 @[TOC]投影分割算法 @[TOC]CFS分割算法 @[TOC]滴水分割算法
图像文字识别之图像分割(待改进)
浮煌的博客
11-16 3212
原图片: 二值化处理: 切割后的图片: 主要问题: 一个字被切位2张图片 从某一位置开始没有进行切割 文字信息缺失即左右两边被多切去一列 代码: import cv2 import numpy as np #读入图片,将图片转化为2值图,最后转化为数组 image = cv2.imread('C:/Users/wang/Desktop/test.jpg') gray...
字符串分割
weixin_44540993的博客
06-27 48
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 开发工具与关键技术:vs· jQuery基础 作者: xqll 撰写时间:2019/6/27 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 下面,我来演示一下字符串中的split() 方法、substring() 方法和substr() 方法 split() 方法的话 是将字符串分...
OCR识别中的字符分割
11-29 6427
OCR识别的一个重要环节是将连续的字符分割为若干个独立的字符区域。 1st 方法: 1) 二值化 2)闭运算 3)计算连通域 4)面积筛选 5)区域排序 不稳定因素: 1)打印机油墨量多时,导致字符间距减少,闭运算效果达不到预期 2)若字符部分像素面积较少,闭运算导致字符完整性被破坏 2nd 方法: 1
字符串的切割
weixin_44552861的博客
05-16 1万+
开发工具与关键技术:MyEclipse 10 作者:杨春桃 撰写时间:2019年05月18日 如果希望将字符串按照指定的标记切分成为若干段,那么可以使用方法: 一、public String[] split(String regex):将regex作为标记进行切刀,返回切分之后的若干字符串(字符串数组)(根据给定正则表达式的匹配拆分此字符串。) 例如: 1、声明一个String字符...
OCR字符切割实例
>点我 ->所有文章目录
06-05 5353
我们来看个字符分割的实例吧 如图我们能看到字符与线粘连 text-based captcha推荐的方法: 1. 水平或垂直投影的直方图分析 对于细小直线 J. Yan and A.S.E. Ahmad. Breaking visual captchas with naive pattern recognition algorithms. In ACSAC 2007, 2007
字符串的分隔方法 split()
热门推荐
奔跑的蜗牛
09-07 2万+
java中的split()的方法: stringObj.split([separator,[limit]]) 参数 stringObj (必选),要被分解的 String 对象或文字。该对象不会被 split 方法修改。 separator :(可选)字符串或 正则表达式对象,它标识了分隔字符串时使用的是一个还是多个字符。如果忽略该选项,返回包含整个字符串的单一元素数组。 li
OCR技术浅探 : 文字定位和文本切割(2)
weixin_30752377的博客
02-06 1638
文字定位 经过前面的特征提取,我们已经较好地提取了图像的文本特征,下面进行文字定位。 主要过程分两步: 1、邻近搜索,目的是圈出单行文字; 2、文本切割,目的是将单行文本切割为单字。 邻近搜索 我们可以对提取的特征图进行连通区域搜索,得到的每个连通区域视为一个汉字。 这对于大多数汉字来说是适用,但是对于一些比较简单的汉字却不适用,比如“小”、“旦”、“八”、“元”这些字,由于不具有连...
字符分割
javatome
10-18 94
public class Test { public static void main(String[] args) { String str = "zifuchun1;zifuchuan2;zifuchuan3;zifuchuan4"; String[] ary = str.split(";");//调用API方法按照逗号分隔字符串 for(String ite...
字符串的分割
qq_49485801的博客
05-07 43
一,字符串的分割 (1)采用正则表达式 使用split()方法切割字符串 String s1 = "2021年5月7日"; String[] s2 = s1.split("\\D");//采用非0~9的数字的方法分割 for (int i =0 ; i < s2.length; i ++) { System.out.println(s2[i]);//输出结果2021 5 7 } 当然,也可以使用别的符号进行分割,例如:逗号,感叹号!空格等等。 Strin..
多种字符分割的方法学习及比较
tango_tori的博客
12-23 447
多种字符分割的方法学习及比较 基于模板匹配的字符分割算法 1.预处理 先将精确定位后的车牌候选区转换为灰度图, 并将白底黑字和黄底黑字候选区灰度取反, 使得所有候选区域字符点灰度高, 背景点灰度低。然后使用局部二值化算法将灰度图转换为二值图。 2. 去除铆钉 为了去除上下边框线和内侧粘连的铆钉对字符分割的影响, 按与公式 (4) 同理的方法计算二值图水平积分投影的平移曲线fH (x) , 再提取fH (x) 的最宽正区间MPI, 将其下、上界作为字符的上下边界。 3.竖直投影谷值分析 首先在去除铆钉后的二
汽车车牌识别系统实现(三)-- 车牌矫正+字符分割+代码实现
奋斗の博客
10-25 5300
阿萨阿萨
字符串的分割方法
最新发布
是木子啦~
06-15 16
©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
zhangzm0128 CSDN认证博客专家 CSDN认证企业博客
码龄5年 暂无认证
- 4
- 原创
- 28万+
- 周排名
- 162万+
- 总排名
- 3万+
- 访问
- 等级
- 486
- 积分
- 33
- 粉丝
- 38
- 获赞
- 88
- 评论
- 104
- 收藏
私信
关注
热门文章
- 【中文OCR】中文字符图片的分割方法
10354
- Windows+VS2015编译caffe+py-faster-rcnn
7987
- py-faster-rcnn在Windows下的end2end训练
7600
- 【数据结构复习】408数据结构中线性表大题整理
5537
- 对于斗地主残局,用python实现solver
3397
分类专栏
胡思乱想 1篇
c/c++
笔记整理 1篇
深度学习 2篇
考研 1篇
最新评论
您愿意向朋友推荐“博客详情页”吗?
-
强烈不推荐 -
不推荐 -
一般般 -
推荐 -
强烈推荐
提交
最新文章
- 【数据结构复习】408数据结构中线性表大题整理
- 对于斗地主残局,用python实现solver
- py-faster-rcnn在Windows下的end2end训练
2019年1篇
2018年1篇
2017年3篇
目录
目录
分类专栏
胡思乱想 1篇
c/c++
笔记整理 1篇
深度学习 2篇
考研 1篇
返回列表python将图片中的文字分割并保存
foxbeing: 博主您好!本人在做OCR相关研究需要分割图片中的汉字,可以的话发一份完整代码,谢谢!foxbeing@hotmail.com
weixin_47608549: 您好,我目前在做汉字图像分割,博主的统计分割部分写得很好,但是代码不全,可以发一份给我吗?邮箱:1321367564@qq.com。不胜感谢
weixin_47608549: 感谢博主!毕设遇到了问题,想要借鉴下你的利用机器学习进行分割的代码,可以发我邮箱吗1321367564@qq.com 谢谢
Chenhy125: 感谢博主!你的代码写的非常好,我的毕设就是做这个的,可以发我邮箱吗 2865343087@qq.com 三连了!
weixin_42949279: 博主你好,我目前也在做OCR识别的毕设,你的统计分割部分写得很好,但是代码不全,可以发一份给我吗?邮箱:1609011944@qq.com。不胜感谢