python+opencv进行表格识别并写入excel中_opencv 提取图像中的excel-程序员宅基地

技术标签: opencl  cv  

效果图如下:
excel中的表格
原图
对于任意图标都不需要自定义模板,直接程序生成,不过需要注意,图中的表格必须是水平的,无法适配倾斜的表格。

直接上代码:

import cv2
import numpy as np
import math
import xlwt
src='图片路径'

raw = cv2.imread(src, 1)
# 灰度图片
gray = cv2.cvtColor(raw, cv2.COLOR_BGR2GRAY)
binary = cv2.adaptiveThreshold(~gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 35, -5)
# 展示图片

rows, cols = binary.shape
scale2=15
scale = 20
# 自适应获取核值
# 识别横线:
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (cols // scale, 1))
kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (cols // scale2, 1))

eroded = cv2.erode(binary, kernel, iterations=1)
dilated_col = cv2.dilate(eroded, kernel1, iterations=1)
# cv2.imwrite("横线图.jpg", dilated_col)

# 识别竖线:
# scale = 40#scale越大,越能检测出不存在的线
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (1, rows // scale2))

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, rows // scale))
eroded = cv2.erode(binary, kernel, iterations=1)
dilated_row = cv2.dilate(eroded, kernel2, iterations=1)
# cv2.imwrite("竖线图.jpg", dilated_row)

# cv2.imwrite("3.png", dilated_row)

# 将识别出来的横竖线合起来
bitwise_and = cv2.bitwise_and(dilated_col, dilated_row)#对二值图进行与操作
# cv2.imwrite("交点二值图.jpg", bitwise_and)

# 标识表格轮廓
merge = cv2.add(dilated_col, dilated_row)
ret,binary = cv2.threshold(merge, 127, 255, cv2.THRESH_BINARY)
_,contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
area=[]
for k in range(len(contours)):
	area.append(cv2.contourArea(contours[k]))
max_idx = np.argmax(np.array(area))
m_d_r=[]
m_u_l=[]
max_p=0
min_p=1e6
for  l1 in contours[max_idx]:
	for l2 in l1:
		if sum(l2)>max_p:
			max_p=sum(l2)
			d_r=l2
		if sum(l2)<min_p:
			min_p=sum(l2)
			u_l=l2
m_d_r=d_r
m_u_l=u_l
padding=5
x0=max(m_u_l[0]-padding,0)
x1=min(m_d_r[0]+padding,raw.shape[1])
y0=max(m_u_l[1]-padding,0)
y1=min(m_d_r[1]+padding,raw.shape[0])
bitwise_and_crop=bitwise_and[y0:y1,x0:x1]
merge=merge[y0:y1,x0:x1]
raw=raw[y0:y1,x0:x1]
# # 两张图片进行减法运算,去掉表格框线
# merge2 = cv2.subtract(binary, merge)
# cv2.imwrite("去表格图.jpg", merge2)

# new_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
# erode_image = cv2.morphologyEx(merge2, cv2.MORPH_OPEN, new_kernel)#先腐蚀,再膨胀
# cv2.imwrite('腐蚀膨胀后的图.jpg', erode_image)
# merge3 = cv2.add(erode_image, bitwise_and)
# cv2.imwrite('角点带字.jpg', merge3)

# 将焦点标识取出来
ys, xs = np.where(bitwise_and_crop > 0)
# print(xs)
# # print('---------------------------------')
# print(ys)
# 横纵坐标数组
y_point_arr = []
x_point_arr = []
# 通过排序,排除掉相近的像素点,只取相近值的最后一点
# 这个10就是两个像素点的距离,不是固定的,根据不同的图片会有调整,基本上为单元格表格的高度(y坐标跳变)和长度(x坐标跳变)
i = 0
sort_x_point = np.sort(xs)
# print(sort_x_point)
for i in range(len(sort_x_point) - 1):
    if sort_x_point[i + 1] - sort_x_point[i] > 3:
        x_point_arr.append(sort_x_point[i])
    i = i + 1
# 要将最后一个点加入
x_point_arr.append(sort_x_point[i])
i = 0
sort_y_point = np.sort(ys)
for i in range(len(sort_y_point) - 1):
    if sort_y_point[i + 1] - sort_y_point[i] > 3:
        y_point_arr.append(sort_y_point[i])
    i = i + 1
y_point_arr.append(sort_y_point[i])

h_list=[y_point_arr[i+1]-y_point_arr[i] for i in range(len(y_point_arr)-1)]
w_list=[x_point_arr[i+1]-x_point_arr[i] for i in range(len(x_point_arr)-1)]

col_alpha=['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
print(h_list)
print(w_list)

import xlsxwriter
workbook = xlsxwriter.Workbook('chineseQA.xlsx')     #创建工作簿
worksheet = workbook.add_worksheet()
for i in range(len(w_list)):
	worksheet.set_column('{}:{}'.format(col_alpha[i],col_alpha[i]),w_list[i]/6)
for j in range(len(h_list)):
	worksheet.set_row(j,h_list[j])

def islianjie(p1,p2,img):#p的格式是先y后x
	if p1[0]==p2[0]:
		for i in range(min(p1[1],p2[1]),max(p1[1],p2[1])+1):

			if sum([img[j,i] for j in range(max(p1[0]-5,0),min(p1[0]+5,img.shape[0]))])==0:


				return False

		return True
	elif p1[1]==p2[1]:
		for i in range(min(p1[0],p2[0]),max(p1[0],p2[0])+1):
			if sum([img[i,j] for j in range(max(p1[1]-5,0),min(p1[1]+5,img.shape[1]))])==0:

				return False
		return True
	else:
		return False



class cell:
	def __init__(self,lt,rd,belong):
		self.lt=lt
		self.rd=rd
		self.belong=belong


lt_list_x=x_point_arr[:-1]
lt_list_y=y_point_arr[:-1]
rd_list_x=x_point_arr[1:]
rd_list_y=y_point_arr[1:]
d={
    }
for i in range(len(lt_list_x)):
	for j in range(len(lt_list_y)):
		d['cell_{}_{}'.format(i,j)]=cell([lt_list_x[i],lt_list_y[j]],[rd_list_x[i],rd_list_y[j]],[lt_list_x[i],lt_list_y[j]])
for i in range(len(lt_list_x)):
	for j in range(len(lt_list_y)):
		p1=[d['cell_{}_{}'.format(i,j)].rd[1],d['cell_{}_{}'.format(i,j)].lt[0]]#左下点
		p2=[d['cell_{}_{}'.format(i,j)].rd[1],d['cell_{}_{}'.format(i,j)].rd[0]]#右下点
		p3=[d['cell_{}_{}'.format(i,j)].lt[1],d['cell_{}_{}'.format(i,j)].rd[0]]#右上点

		if not islianjie(p1,p2,merge):
			d['cell_{}_{}'.format(i,j+1)].belong=d['cell_{}_{}'.format(i,j)].belong
		if not islianjie(p2,p3,merge):
			d['cell_{}_{}'.format(i+1,j)].belong=d['cell_{}_{}'.format(i,j)].belong

crop_list={
    }
for i in range(len(lt_list_x)):
	for j in range(len(lt_list_y)):
		crop_list['{},{}'.format(d['cell_{}_{}'.format(i,j)].belong[0],d['cell_{}_{}'.format(i,j)].belong[1])]=d['cell_{}_{}'.format(i,j)].rd
w_h_list=[]
zmax=0
zmin=1e6
zlt=[]
zrd=[]
for key in crop_list.keys():
	lt=[int(i) for i in key.split(',')]
	rd=crop_list[key]
	# print(lt,rd)
	if sum(rd)>zmax:
		zrd=rd
		zmax=sum(rd)
	if sum(lt)<zmin:
		zlt=lt
		zmin=sum(lt)
	cv2.imwrite('crop/{}.jpg'.format(key),raw[lt[1]:rd[1],lt[0]:rd[0]])


merge_format = workbook.add_format({
    
    'bold':     True,
    'border':   6,
    'align':    'center',#水平居中
    'valign':   'vcenter',#垂直居中
    'fg_color': '#D7E4BC',#颜色填充
})
for key in crop_list.keys():
	lt=[int(i) for i in key.split(',')]
	rd=crop_list[key]

	lt_=[lt[0]-zlt[0],lt[1]-zlt[1]]
	rd_=[rd[0]-zlt[0],rd[1]-zlt[1]]
	print(lt_)
	print(rd_)
	for i in range(len(w_list)+1):
		if lt_[0]==sum(w_list[:i]):
			lt_col=chr(ord('A')+i)
		if rd_[0]==sum(w_list[:i]):
			rd_col=chr(ord('A')+i-1)
	for i in range(len(h_list)+1):
		if lt_[1]==sum(h_list[:i]):
			lt_row=i+1
		if rd_[1]==sum(h_list[:i]):
			rd_row=i
	if lt_col==rd_col and lt_row==rd_row:
		worksheet.write('{}{}'.format(lt_col,lt_row),'',merge_format)
	else:
		worksheet.merge_range('{}{}:{}{}'.format(lt_col,lt_row,rd_col,rd_row),'',merge_format)



workbook.close()



版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sxl1399504891/article/details/110880866

智能推荐

Oracle JAVAVM 组件 Reload 说明_java vm组件更新-程序员宅基地

文章浏览阅读1.1w次。一.JAVAVM 组件 说明 有关Oracle 所有组件的概述,参考:Oracle 8i/9i/10g/11g 组件(Components) 说明http://blog.csdn.net/tianlesoftware/article/details/5937382 现在我们查看组件的信息:SQL> col comp_id for a15SQL> col version for a15SQL> co_java vm组件更新

Spring Boot Actuator 未授权的测试与利用思路_sb-actuator-程序员宅基地

文章浏览阅读1.9k次,点赞2次,收藏4次。Spring Boot Actuator 未授权的测试与利用思路0x0 前言  最近遇到的一个漏洞,但是因为目标关掉了一些端点导致没利用起来达到RCE的效果,不过在提升漏洞危害的时候,参考了不少文章,也做了一些尝试,所以分享出来自己的经历,希望大家如果遇到跟我一样的情况,可以省下自己调试时间,来做更有意义的事情。0x1 Actuator 简介官方简介: Spring Boot Actuator: Production-ready FeaturesSpring Bo..._sb-actuator

在python3.7+vs2019环境下使用f2py将fortran和c++程序编译为python库_python调用fortran代码-程序员宅基地

文章浏览阅读1.3k次。在python3.7+vs2019环境下使用f2py将fortran和c++程序编译为python库前言步骤1安装Fortran和Visual Studio编译环境2配置vs在python中的路径3测试c++编译环境4测试Fortran编译环境参考链接前言今天务必开贴记录下将Fortran或c++程序编译为python库的方法。起因是老师给了一个Fortran函数,无奈本人看不懂,因而想办法..._python调用fortran代码

uboot模式下怎么备份uboot和uImage_uboot备份固件命令-程序员宅基地

文章浏览阅读1.3k次。uboot中如果支持spi/qspi flash, 那么可以使用sf的erase, read, write命令操作spi flashsf read用来读取flash数据到内存sf write写内存数据到flashsf erase 擦除指定位置,指定长度的flash内容, 擦除后内容全1以备份uboot文件举例:1 、设置环境变量setenv serverip 192.168.230.111setenv ipaddr 192.168.230.124saping 192.168.230.111_uboot备份固件命令

看了此文,Oracle SQL优化文章不必再看!-程序员宅基地

文章浏览阅读965次。看了此文,Oracle SQL优化文章不必再看! 第一章 看了此文,Oracle SQL优化文章不必再看! DBAplus社群 | 2015-11-17 23:44 目录SQL优化的本质 SQL优化Road Map 2.1 制定SQL优化目标 2.2 检查执行计划 2.3 检查统计信息 2.4 检查高效访问结构 2.5 检查影...__optimizer_cost_based_transformation

vue 判断多个input 是否为空_vue form判断input不为空-程序员宅基地

文章浏览阅读9.3k次。vue 判断页面所有input的值不等于空,提交的时候并给出提示,1.html<input placeholder="必填" alt="开户支行" v-model="bank_branch" :value='bank_branch' />2.js//获取页面所有的inputvar els = document.getElementsByTagName("input"..._vue form判断input不为空

随便推点

将链表转换为树_链表node转树形接口-程序员宅基地

文章浏览阅读1.6k次。题目来源今天做了个题:将一个链表里的数据组装树形结构,链表里的数据已经满足树形结构要求这道题描述的很简单,但是有很多种情况。他只说了链表数据满足树形结构要求,并没有说明数据到底是什么样的,也就是题目参数具有多样性,这样其实我们给出一种解决方案就可以。而且也只要求将链表转换为树,并没有说是什么树。所以这道题说难也难,说简单也简单。解题思路最近也将平衡二叉树的原理看了一下,正好借着这..._链表node转树形接口

设计模式六大原则-程序员宅基地

文章浏览阅读151次。单一职责原则(Single Responsibility Principle)定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。解决方案:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责..._b10原则

8天学通MongoDB——第一天 基础入门_传智mongodb-程序员宅基地

文章浏览阅读1.2k次。关于mongodb的好处,优点之类的这里就不说了,唯一要讲的一点就是mongodb中有三元素:数据库,集合,文档,其中“集合”就是对应关系数据库中的“表”,“文档”对应“行”。 一: 下载 上MongoDB官网 ,我们发现有32bit和64bit,这个就要看你系统了,不过这里有两点注意: ①:根据业界规则,偶数为“稳定版”(如:1.6.X,1.8.X_传智mongodb

常用Python第三方库 简介_python有用第三方库-程序员宅基地

文章浏览阅读6.3k次,点赞2次,收藏16次。常用Python第三方库简介 如果说强大的标准库奠定了Python发展的基石,丰富的第三方库则是python不断发展的保证,随着python的发展一些稳定的第三库被加入到了标准库里面,这里有6000多个第三方库的介绍:点这里或者访问:http://pypi.python.org/pypi?%3Aaction=index。下表中加粗并且标红的都是我平时使用较多的一些第三方库。(P.S.C_python有用第三方库

kubelet配置cni插件_第四步:树莓派Kubernetes集群安装-程序员宅基地

文章浏览阅读374次。4. Kubernetes集群安装4.1 master节点部署4.1.1 提前下载所需镜像看一下kubernetes v1.15.1需要哪些镜像:$ kubeadm config images list --kubernetes-version=v1.15.1k8s.gcr.io/kube-apiserver:v1.15.1k8s.gcr.io/kube-controller-manager:v1..._kubelet如何指定cni插件目录

xshell 隧道设置说明_xshell 隧道窗格说明-程序员宅基地

文章浏览阅读1.4w次。###图中仅对部分设备ip进行标注,描述如下场景50.237仅能访问171.7但由于安全策略等原因,无法直接访问171.8,因此利用xshell做隧道进行237与171.8之间的通信。###通讯方向说明192.168.50.237--&gt;192.168.171.7--&gt;192.168.171.81.xshell上在打开192.168.171.7的终端后,文件-&gt;属性-&gt;连接-..._xshell 隧道窗格说明