发票识别一:从一张发票照片精确定位出“发票号码”、“发票代码”的数字区域
注:该代码适用于 “国税通用机打发票”。尽量拍摄下正常摆放的完整发票
发票识别一:区域精确定位1 具体步骤1.1 读入发票1.2 获取矩形框:将发票可能的区域定位出来边缘检测-二值化-形态学-轮廓检测-获取多个矩形框hsv颜色空间 -分别提取“红、黑、蓝”的掩膜-形态学--轮廓检测-获取多个矩形框结果:得到上百个候选框1.3 矩形框进行初步筛选。根据矩形框的长高信息、位置信息进行筛选结果:剩下十多个矩形框1.4 矩形框融合若某个矩形框与其他矩形框重叠、交叉度极高在其他矩形框内部,将其删除若两个矩形框在同一水平线上,有一定的交叉,且框内都包含数字,将两个框融合结果:这一步通常还有3-6个矩形框1.5 定位剩下的矩形框从上到下排序。根据矩形框的相对位置、尺寸,定位出“发票号码”、“发票代码”的两个区域。1.6 找了几张发票,地位区域如下。下一步“字符分割”,见发票识别二。
2.源码如下
2.1 main.py
# encoding: utf-8
import cv2
import numpy as np
import roi_merge as roi_
import util_funs as util
from get_rects import *
def main(img):
region = get_rects(img)
roi_solve = roi_.Roi_solve(region)
roi_solve.rm_inside()
roi_solve.rm_overlop()
region = roi_solve.merge_roi()
region = util.sort_region(region)
region = util.get_targetRoi(region)
for i in range(2):
rect2 = region[i]
w1,w2 = rect2[0],rect2[0]+rect2[2]
h1,h2 = rect2[1],rect2[1]+rect2[3]
box = [[w1,h2],[w1,h1],[w2,h1],[w2,h2]]
cv2.drawContours(img, np.array([box]), 0, (0, 255, 0), 1)
if i == 0:
cv2.imwrite('代码'+str(k)+'.jpg', img[h1:h2,w1:w2])
else:
cv2.imwrite('号码'+str(k)+'.jpg', img[h1:h2,w1:w2])
cv2.imshow('img', img)
cv2.waitKey(0)
if __name__ == '__main__':
img = cv2.imread("img_path")
main(img)
2.3 roi_merge.py
#encoding: utf-8
import cv2
import numpy as np
class Roi_solve:
def __init__(self,rect):
self.rect = rect #所有矩形框
self.cursor = -1 #初始化游标位置
self.rect_num = len(rect) #记录rect的实时数量
def next(self):
#将游标的位置前移一步,并返回所在检索位的矩形框
self.cursor = self.cursor+1
return self.rect[self.cursor]
def hasNext(self):
#判断是否已经检查完了所有矩形框
return self.rect_num > self.cursor + 1
def remove(self,flag = -1):
#将非优解从数据集删除
if flag == -1:
del self.rect[self.cursor]
#删除当前游标位置,游标回退一步
self.cursor = self.cursor-1
else:
#删除后面位置的rect,游标不动
del self.rect[flag]
#rect数量,减1
self.rect_num = self.rect_num - 1
def add(self,add_rect):
self.rect.append(add_rect)
self.rect_num = self.rect_num + 1
def get_u_d_l_r(self,rect_):
#获取rect的上下左右边界值
upper_,down_ = rect_[1],rect_[1] + rect_[3]
left_,right_ = rect_[0],rect_[0] + rect_[2]
return upper_,down_,left_,right_
def is_intersect(self,y01, y02 , x01, x02, y11, y12 , x11, x12):
# 判断两个矩形是否相交
lx = abs((x01 + x02) / 2 - (x11 + x12) / 2)
ly = abs((y01 + y02) / 2 - (y11 + y12) / 2)
sax = abs(x01 - x02)
sbx = abs(x11 - x12)
say = abs(y01 - y02)
sby = abs(y11 - y12)
if lx <= (sax + sbx) / 2 and ly <= (say + sby) / 2:
return True
else:
return False
def intersect_area(self,y01, y02 , x01, x02, y11, y12 , x11, x12):
#返回两个rect的交叉面积
col=min(x02,x12)-max(x01,x11)
row=min(y02,y12)-max(y01,y11)
return col*row
def intersect_height(self,y01, y02, y11, y12):
#height轴方向交叉,返回height交叉段占比
row=float(min(y02,y12)-max(y01,y11))
return max(row/float(y02-y01),row/float(y12-y11))
#remove_inside:如果“本rect”被“其他rect”包围了,则删除
def remove_inside(self,rect_curr):
#获取当前rect的上下左右边界信息
u_curr,d_curr,l_curr,r_curr = self.get_u_d_l_r(rect_curr)
#判断当前rect是否在内部
for rect_ in self.rect:
u_,d_,l_,r_ = self.get_u_d_l_r(rect_)
if u_curr>u_ and d_curr<d_ and l_curr>l_ and r_curr<r_:
self.remove()
break
#remove_overlop:如果“本rect”和“其他rect”相交区域达到了95%以上,则删除
def remove_overlop(self,rect_curr):
#获取当前rect的上下左右边界信息
u_curr,d_curr,l_curr,r_curr = self.get_u_d_l_r(rect_curr)
area_curr = rect_curr[2] * rect_curr[3]
#判断当前rect是否在内部
for rect_ in self.rect:
u_,d_,l_,r_ = self.get_u_d_l_r(rect_)
if self.is_intersect(u_curr,d_curr,l_curr,r_curr,u_,d_,l_,r_):
if rect_ == rect_curr:
continue
else :
area_ = self.intersect_area(u_curr,d_curr,l_curr,r_curr,u_,d_,l_,r_)
if float(area_)/float(area_curr) >0.95:
self.remove()
break
#如果“两个rect”在同一水平面上,横向坐标
def merge(self,rect_curr):
#获取当前rect的上下左右边界信息
u_curr,d_curr,l_curr,r_curr = self.get_u_d_l_r(rect_curr)
#判断当前rect是否在内部
for i in range(self.cursor+1,len(self.rect)):
print i,self.cursor+1,len(self.rect)
rect_ = self.rect[i]
u_,d_,l_,r_ = self.get_u_d_l_r(rect_)
#判断是否相交
if self.is_intersect(u_curr,d_curr,l_curr,r_curr,u_,d_,l_,r_):
if self.intersect_height(u_curr,d_curr,u_,d_) > 0.6:
if rect_curr[2] > rect_[2]:
new_rect = np.array(rect_curr)
else:
new_rect = np.array(rect_)
new_l = min(l_curr,l_)
new_r = max(r_curr,r_)
new_rect[0] = new_l
new_rect[2] = new_r-new_l
self.remove(i)
self.remove()
self.add(new_rect)
break
def rm_inside(self):
self.cursor = -1
while(self.hasNext()):
rect_curr = self.next()
self.remove_inside(rect_curr)
return self.rect
def rm_overlop(self):
self.cursor = -1
while(self.hasNext()):
rect_curr = self.next()
self.remove_overlop(rect_curr)
return self.rect
def merge_roi(self):
self.cursor = -1
while(self.hasNext()):
rect_curr = self.next()
self.merge(rect_curr)
return self.rect
2.3 roi_merge.py
#encoding: utf-8
import cv2
import numpy as np
class Roi_solve:
def __init__(self,rect):
self.rect = rect #所有矩形框
self.cursor = -1 #初始化游标位置
self.rect_num = len(rect) #记录rect的实时数量
def next(self):
#将游标的位置前移一步,并返回所在检索位的矩形框
self.cursor = self.cursor+1
return self.rect[self.cursor]
def hasNext(self):
#判断是否已经检查完了所有矩形框
return self.rect_num > self.cursor + 1
def remove(self,flag = -1):
#将非优解从数据集删除
if flag == -1:
del self.rect[self.cursor]
#删除当前游标位置,游标回退一步
self.cursor = self.cursor-1
else:
#删除后面位置的rect,游标不动
del self.rect[flag]
#rect数量,减1
self.rect_num = self.rect_num - 1
def add(self,add_rect):
self.rect.append(add_rect)
self.rect_num = self.rect_num + 1
def get_u_d_l_r(self,rect_):
#获取rect的上下左右边界值
upper_,down_ = rect_[1],rect_[1] + rect_[3]
left_,right_ = rect_[0],rect_[0] + rect_[2]
return upper_,down_,left_,right_
def is_intersect(self,y01, y02 , x01, x02, y11, y12 , x11, x12):
# 判断两个矩形是否相交
lx = abs((x01 + x02) / 2 - (x11 + x12) / 2)
ly = abs((y01 + y02) / 2 - (y11 + y12) / 2)
sax = abs(x01 - x02)
sbx = abs(x11 - x12)
say = abs(y01 - y02)
sby = abs(y11 - y12)
if lx <= (sax + sbx) / 2 and ly <= (say + sby) / 2:
return True
else:
return False
def intersect_area(self,y01, y02 , x01, x02, y11, y12 , x11, x12):
#返回两个rect的交叉面积
col=min(x02,x12)-max(x01,x11)
row=min(y02,y12)-max(y01,y11)
return col*row
def intersect_height(self,y01, y02, y11, y12):
#height轴方向交叉,返回height交叉段占比
row=float(min(y02,y12)-max(y01,y11))
return max(row/float(y02-y01),row/float(y12-y11))
#remove_inside:如果“本rect”被“其他rect”包围了,则删除
def remove_inside(self,rect_curr):
#获取当前rect的上下左右边界信息
u_curr,d_curr,l_curr,r_curr = self.get_u_d_l_r(rect_curr)
#判断当前rect是否在内部
for rect_ in self.rect:
u_,d_,l_,r_ = self.get_u_d_l_r(rect_)
if u_curr>u_ and d_curr<d_ and l_curr>l_ and r_curr<r_:
self.remove()
break
#remove_overlop:如果“本rect”和“其他rect”相交区域达到了95%以上,则删除
def remove_overlop(self,rect_curr):
#获取当前rect的上下左右边界信息
u_curr,d_curr,l_curr,r_curr = self.get_u_d_l_r(rect_curr)
area_curr = rect_curr[2] * rect_curr[3]
#判断当前rect是否在内部
for rect_ in self.rect:
u_,d_,l_,r_ = self.get_u_d_l_r(rect_)
if self.is_intersect(u_curr,d_curr,l_curr,r_curr,u_,d_,l_,r_):
if rect_ == rect_curr:
continue
else :
area_ = self.intersect_area(u_curr,d_curr,l_curr,r_curr,u_,d_,l_,r_)
if float(area_)/float(area_curr) >0.95:
self.remove()
break
#如果“两个rect”在同一水平面上,横向坐标
def merge(self,rect_curr):
#获取当前rect的上下左右边界信息
u_curr,d_curr,l_curr,r_curr = self.get_u_d_l_r(rect_curr)
#判断当前rect是否在内部
for i in range(self.cursor+1,len(self.rect)):
print i,self.cursor+1,len(self.rect)
rect_ = self.rect[i]
u_,d_,l_,r_ = self.get_u_d_l_r(rect_)
#判断是否相交
if self.is_intersect(u_curr,d_curr,l_curr,r_curr,u_,d_,l_,r_):
if self.intersect_height(u_curr,d_curr,u_,d_) > 0.6:
if rect_curr[2] > rect_[2]:
new_rect = np.array(rect_curr)
else:
new_rect = np.array(rect_)
new_l = min(l_curr,l_)
new_r = max(r_curr,r_)
new_rect[0] = new_l
new_rect[2] = new_r-new_l
self.remove(i)
self.remove()
self.add(new_rect)
break
def rm_inside(self):
self.cursor = -1
while(self.hasNext()):
rect_curr = self.next()
self.remove_inside(rect_curr)
return self.rect
def rm_overlop(self):
self.cursor = -1
while(self.hasNext()):
rect_curr = self.next()
self.remove_overlop(rect_curr)
return self.rect
def merge_roi(self):
self.cursor = -1
while(self.hasNext()):
rect_curr = self.next()
self.merge(rect_curr)
return self.rect
2.4 util_funs.py
#encoding:utf-8
import cv2
import numpy as np
def get_u_d_l_r(rect_):
#获取rect的上下左右边界值
upper_,down_ = rect_[1],rect_[1] + rect_[3]
left_,right_ = rect_[0],rect_[0] + rect_[2]
return upper_,down_,left_,right_
#region排序。flag=1时:从上到下;flag=0时:从左到右
def sort_region(region,flag = 1):
temp = []
region_new = []
for rect in region:
temp.append(rect[flag])
temp_sort = sorted(temp)
for height_ in temp_sort:
index_ = temp.index(height_)
region_new.append(region[index_])
return region_new
#判断上下两个相邻框框是否为发票代码、发票号码
def judge_(rect_0,rect_1):
u_d_l_r_0 = get_u_d_l_r(rect_0)
u_d_l_r_1 = get_u_d_l_r(rect_1)
#两个rect上边界之间的距离不超过box_height的四倍
distance_ = rect_1[1] - rect_0[1]
box_height = float(rect_0[3] + rect_1[3])/2
#上边框的右边界值更大
if (distance_ > 0 and distance_ < box_height*3 and rect_0[0]+rect_0[2] > rect_1[0]+rect_1[2]):
return True
return False
#获取 “发票代码”、“发票号码”的区域
def get_targetRoi(region):
if len(region) > 1:
#按照从上到下排序
new_region = sort_region(region)
for i in range(len(new_region)-1):
if judge_(new_region[i],new_region[i+1]):
return [new_region[i],new_region[i+1]]
---------------------
作者:远上寒山
来源:CSDN
原文:https://blog.csdn.net/m0_38097087/article/details/80281835
版权声明:本文为博主原创文章,转载请附上博文链接!