在做图片文字分割的时候,常用的方法有两种。一种是投影法,适用于排版工整,字间距行间距比较宽裕的图像;还有一种是用OpenCV的轮廓检测,适用于文字不规则排列的图像。

1. 思路

一开始想偷个懒,直接用OpenCV的模型,结果发现效果不佳。文字出现了过度分割的问题,部分文字甚至没有被识别:

于是只好使用传统方法,投影法。对文字图片作横向和纵向投影,即通过统计出每一行像素个数,和每一列像素个数,来分割文字。代码参考https://www.cnblogs.com/zxy-joy/p/10687152.html,但是对于古籍来说,需要做一些修改。比如,古籍文字书写在习惯是从上到下的,所以说在扫描的时候应该扫描列投影,在扫描行投影,搞定这次简单的操作顺序修改以后,分割结果如下:

很显然,虽然说没有出现过度分割的问题,但是由于字体有大有小,有的地方两个字被合起来识别成了一个字。那么很显然,只要把这些地方再进行一次列投影,把它们再度拆分成两个字,问题不就解决了么。添加代码:

# 再进行一次列扫描
DcropImg = cropImg[H_start[pos]:H_end[pos], 0:w]
d_h, d_w = DcropImg.shape
# cv2.imshow("dcrop", DcropImg)
sec_V = getVProjection(DcropImg)
c1, c2 = scan(sec_V, 0)
if len(c1) > len(c2):
c2.append(d_w) # cv2.waitKey(0)
if len(c1) == 1:
Position.append([V_start[i],H_start[pos],V_end[i],H_end[pos]])
else:
for x in range(len(c1)):
Position.append([V_start[i]+c1[x], H_start[pos],V_start[i]+c2[x], H_end[pos]])

2. 优化

对单行文本做列扫描,很容易出现过度分割的问题。因为只有一行,会扫描到很多没有像素点的列,最终就会出现这种情况:

为了避免这种过度分割的情况,可以添加一个检测两个分割之间距离的代码,使距离较近的分割进行合并。

x = 1
while x < len(c1):
if c1[x] - c2[x-1] < 12:
c2.pop(x-1)
c1.pop(x)
x -= 1
x += 1

3. 代码

再通过添加一些属性来限制一个字的最大长度宽度、两个字之间的最小间距,来避免过度分割,最终效果如下:

虽然仍然存在一些小瑕疵,但是总体效果还算不错。

详细代码如下:

import cv2
import numpy as np HIOG = 50
VIOG = 3
Position = [] '''水平投影'''
def getHProjection(image):
hProjection = np.zeros(image.shape,np.uint8)
# 获取图像大小
(h,w)=image.shape
# 统计像素个数
h_ = [0]*h
for y in range(h):
for x in range(w):
if image[y,x] == 255:
h_[y]+=1
#绘制水平投影图像
for y in range(h):
for x in range(h_[y]):
hProjection[y,x] = 255
# cv2.imshow('hProjection2',cv2.resize(hProjection, None, fx=0.3, fy=0.5, interpolation=cv2.INTER_AREA))
# cv2.waitKey(0)
return h_ def getVProjection(image):
vProjection = np.zeros(image.shape,np.uint8);
(h,w) = image.shape
w_ = [0]*w
for x in range(w):
for y in range(h):
if image[y,x] == 255:
w_[x]+=1
for x in range(w):
for y in range(h-w_[x],h):
vProjection[y,x] = 255
# cv2.imshow('vProjection',cv2.resize(vProjection, None, fx=1, fy=0.1, interpolation=cv2.INTER_AREA))
# cv2.waitKey(0)
return w_ def scan(vProjection, iog, pos = 0):
start = 0
V_start = []
V_end = [] for i in range(len(vProjection)):
if vProjection[i] > iog and start == 0:
V_start.append(i)
start = 1
if vProjection[i] <= iog and start == 1:
if i - V_start[-1] < pos:
continue
V_end.append(i)
start = 0
return V_start, V_end def checkSingle(image):
h = getHProjection(image)
start = 0
end = 0 for i in range(h):
pass if __name__ == "__main__":
# 读入原始图像
origineImage = cv2.imread('test_data/test2.jpg')
# 图像灰度化
#image = cv2.imread('test.jpg',0)
image = cv2.cvtColor(origineImage,cv2.COLOR_BGR2GRAY) # cv2.imshow('gray',image)
# 将图片二值化
retval, img = cv2.threshold(image,127,255,cv2.THRESH_BINARY_INV)
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
# img = cv2.erode(img, kernel)
# cv2.imshow('binary',cv2.resize(img, None, fx=0.3, fy=0.3, interpolation=cv2.INTER_AREA))
#图像高与宽
(h,w)=img.shape
#垂直投影
V = getVProjection(img) start = 0
V_start = []
V_end = [] # 对垂直投影水平分割
V_start, V_end = scan(V, HIOG)
if len(V_start) > len(V_end):
V_end.append(w-5) # 分割行,分割之后再进行列分割并保存分割位置
for i in range(len(V_end)):
#获取行图像
if V_end[i] - V_start[i] < 30:
continue cropImg = img[0:h, V_start[i]:V_end[i]]
# cv2.imshow('cropImg',cropImg)
# cv2.waitKey(0)
#对行图像进行垂直投影
H = getHProjection(cropImg)
H_start, H_end = scan(H, VIOG, 40) if len(H_start) > len(H_end):
H_end.append(h-5) for pos in range(len(H_start)):
# 再进行一次列扫描
DcropImg = cropImg[H_start[pos]:H_end[pos], 0:w]
d_h, d_w = DcropImg.shape
# cv2.imshow("dcrop", DcropImg)
sec_V = getVProjection(DcropImg)
c1, c2 = scan(sec_V, 0)
if len(c1) > len(c2):
c2.append(d_w) x = 1
while x < len(c1):
if c1[x] - c2[x-1] < 12:
c2.pop(x-1)
c1.pop(x)
x -= 1
x += 1 # cv2.waitKey(0)
if len(c1) == 1:
Position.append([V_start[i],H_start[pos],V_end[i],H_end[pos]])
else:
for x in range(len(c1)):
Position.append([V_start[i]+c1[x], H_start[pos],V_start[i]+c2[x], H_end[pos]]) #根据确定的位置分割字符
for m in range(len(Position)):
cv2.rectangle(origineImage, (Position[m][0]-5,Position[m][1]-5), (Position[m][2]+5,Position[m][3]+5), (0 ,0 ,255), 2)
cv2.imshow('image',cv2.resize(origineImage, None, fx=0.6, fy=0.6, interpolation=cv2.INTER_AREA))
cv2.waitKey(0)

4. 总结

  果然,在面对具体问题时,一个再优秀的普适模型往往都不如优化的比较好的传统方法。就像调参得当的网络,再具体问题上往往比一些十分优秀的网络模型效果还要好一样。

参考文献:https://www.cnblogs.com/zxy-joy/p/10687152.html

Python+OpenCV竖版古籍文字分割的更多相关文章

  1. Python + opencv 实现图片文字的分割

    实现步骤: 1.通过水平投影对图形进行水平分割,获取每一行的图像: 2.通过垂直投影对分割的每一行图像进行垂直分割,最终确定每一个字符的坐标位置,分割出每一个字符: 先简单介绍一下投影法:分别在水平和 ...

  2. 【Python | opencv+PIL】常见操作(创建、添加帧、绘图、读取等)的效率对比及其优化

    一.背景 本人准备用python做图像和视频编辑的操作,却发现opencv和PIL的效率并不是很理想,并且同样的需求有多种不同的写法并有着不同的效率.见全网并无较完整的效率对比文档,遂决定自己丰衣足食 ...

  3. python opencv识别蓝牌车牌号 之 取出车牌号 (1/3)

    概述 车牌识别是计算机视频图像识别技术在车辆牌照识别中的一种应用,通常来讲如果结合opencv进行车牌识别主要分为四个大步骤,分别为: 图像采集 车牌定位 分割车牌字符 字符识别 当然,如果结合了机器 ...

  4. Python+opencv打开修图的正确方式get

    先逼逼两句: 图像是 Web 应用中除文字外最普遍的媒体格式. 流行的 Web 静态图片有 JPEG.PNG.ICO.BMP 等.动态图片主要是 GIF 格式.为了节省图片传输流量,大型互联网公司还会 ...

  5. 搭建基于python +opencv+Beautifulsoup+Neurolab机器学习平台

    搭建基于python +opencv+Beautifulsoup+Neurolab机器学习平台 By 子敬叔叔 最近在学习麦好的<机器学习实践指南案例应用解析第二版>,在安装学习环境的时候 ...

  6. .NET + OpenCV & Python + OpenCV 配置

    最近需要做一个图像识别的GUI应用,权衡了Opencv+ 1)QT,2)Python GUI,3).NET后选择了.NET... 本文给出C#+Opencv和Python+Opencv的相应参考,节省 ...

  7. RPi 2B python opencv camera demo example

    /************************************************************************************** * RPi 2B pyt ...

  8. OpenCV学习(20) grabcut分割算法

    http://www.cnblogs.com/mikewolf2002/p/3330390.html OpenCV学习(20) grabcut分割算法 在OpenCV中,实现了grabcut分割算法, ...

  9. Python+OpenCV图像处理(一)

    Python+OpenCV图像处理(一): 读取,写入和展示图片 调用摄像头拍照 调用摄像头录制视频 1. 读取.写入和展示图片 图像读入:cv2.imread() 使用函数cv2.imread() ...

随机推荐

  1. 线上调试工具 jvm-sandbox使用

    jvm-sandbox使用 1 快速安装 1.1 下载解压 # 下载最新版本的JVM-SANDBOX wget http://ompc.oss-cn-hangzhou.aliyuncs.com/jvm ...

  2. JVM内存机制与垃圾收集器总结

    本文目录 1. JVM内存组成结构 2. JVM内存回收 3. 垃圾收集器与算法 4. jdk1.6中class文件结构 5. jdk1.6 1.7 1.8比较 1. JVM内存组成结构 JVM栈由堆 ...

  3. effective java 3th item1:考虑静态工厂方法代替构造器

    传统的方式获取一个类的实例,是通过提供一个 public 构造器.这里有技巧,每一个程序员应该记住.一个类可以对外提供一个 public 的 静态工厂方法 ,该方法只是一个朴素的静态方法,不需要有太多 ...

  4. 微信支付之扫码、APP、小程序支付接入详解

    做电商平台的小伙伴都知道,支付服务是必不可少的一部分,今天我们开始就说说支付服务的接入及实现.目前在国内,几乎90%中小公司的支付系统都离不开微信支付和支付宝支付.那么大家要思考了,为什么微信支付和支 ...

  5. SpringCloud(二)- 服务注册与发现Eureka

    离上一篇微服务的基本概念已经过去了几个月,在写那篇博客之前,自己还并未真正的使用微服务架构,很多理解还存在概念上.后面换了公司,新公司既用了SpringCloud也用了Dubbo+Zookeeper, ...

  6. HDU - 4370 0 or 1 最短路

    HDU - 4370 参考:https://www.cnblogs.com/hollowstory/p/5670128.html 题意: 给定一个矩阵C, 构造一个A矩阵,满足条件: 1.X12+X1 ...

  7. HDU 3062 Party 裸 2-sat

    #include <iostream> #include <cstdio> #include <cstring> using namespace std; cons ...

  8. AC自动机 建立nlogn个AC自动机

    String Set Queries 题意:给你3种操作,1.加入一个串到集合中. 2.删除集合中的某一个串 3.查询集合中的字符串在给定的字符串种出现几次.(同一个串可重复) 解法:建立多个AC自动 ...

  9. codeforces 509 D. Restoring Numbers(数学+构造)

    题目链接:http://codeforces.com/problemset/problem/509/D 题意:题目给出公式w[i][j]= (a[i] + b[j])% k; 给出w,要求是否存在这样 ...

  10. CF940B Our Tanya is Crying Out Loud

    Our Tanya is Crying Out Loud time limit per test 1 second memory limit per test 256 megabytes input ...