在做图片文字分割的时候,常用的方法有两种。一种是投影法,适用于排版工整,字间距行间距比较宽裕的图像;还有一种是用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. python 17 内置模块

    目录 1. 序列化模块 1.1 json 模块 1.2 pickle 模块 2. os 模块 3. sys 模块 4. hashlib 加密.摘要 4.1 加密 4.2 加盐 4.3 文件一致性校验 ...

  2. ionic 删除返回按钮文字,及自定义nav两侧的按钮和title格式

    1.删除返回按钮文字 1)可以在app.js进行配置 .config(function($stateProvider, $urlRouterProvider,$ionicConfigProvider) ...

  3. ionic App 解决android端在真机上 tab处于顶部的Bug

    在app.js 页面中添加以下代码 .config(function($stateProvider, $urlRouterProvider,$ionicConfigProvider) { $ionic ...

  4. 【Linux命令】lsmod命令

    lsmod(list modules)命令 lsmod命令用来显示已被内核加载的模块的状态 1)语法:lsmod 2)功能: lsmod命令可以美观地显示/prco/module中的内容,这些内容是被 ...

  5. Keras(五)LSTM 长短期记忆模型 原理及实例

    LSTM 是 long-short term memory 的简称, 中文叫做 长短期记忆. 是当下最流行的 RNN 形式之一 RNN 的弊端 RNN没有长久的记忆,比如一个句子太长时开头部分可能会忘 ...

  6. CodeForces 592D Super M DP

    Super M 题解: 定义 dp[u][0] 为遍历完u中的所有节点, 但不回到u点的路径花费值. 定义 dp[u][1] 为遍历完u中的所有节点, 且要回到u点的路径花费值. 转移方程. dp[u ...

  7. 左偏树 P3377【模板】左偏树(可并堆)

    题目传送门 代码: /* code by: zstu wxk time: 2019/03/01 */ #include<bits/stdc++.h> using namespace std ...

  8. Atcoder D - A or...or B Problem(思维)

    题目链接:http://agc015.contest.atcoder.jp/tasks/agc015_d 题意:给出两个数b,a(a>=b)问{a,a+1,....,b}的集合内取任意数求或运算 ...

  9. hdu 3038 How Many Answers Are Wrong(并查集的思想利用)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3038 题意:就是给出n个数和依次m个问题,每个问题都是一个区间的和,然后问你这些问题中有几个有问题,有 ...

  10. java学习笔记之基础篇

    java选择语句之switch   //switch可以用于等值判断 switch (e) //int ,或则可以自动转化成int 的类型,(byte char short)枚举jdk 7中可以防止字 ...