- 切分思路
- 生成表格文件
- 寻找表格的范围和最小点
- 查找单元格
- 寻找坐标的行像素列像素
- 寻找点周围的单元格
首先通过寻找轮廓线的方法找到最大的轮廓线,然后根据轮廓线确定表格的区域,选择坐标最小的点作为种子点通过泛洪填充得到标记的表格图像,通过横向和纵向的腐蚀和膨胀得到横平竖直的表格,再通过蒙特卡洛算法根据表格的坐标范围生成随机点,通过随机点找行像素和列像素,然后找出最接近的标记颜色的坐标,从而定位出该点四周的表格。为了消除个别多余线段的干扰,采用一次验证,验证方法为,通过找到的表格定位中心点,根据中心点的坐标再寻找一次周围表格的坐标,如果两次坐标相同予以采信。不断寻找直到多次寻找不再增加或者超时为止。
生成表格文件def gen_table_file(imgfile="test.png"): # 路径不能有中文
""" 生成图表框文件 """
print("# 找到最大轮廓")
pt0=getpt0(imgfile)
img_org = cv.imread(imgfile, cv.IMREAD_COLOR)
print("# 获取种子点。。。")
seed_point = pt0 # 坐标
new_val = (0, 0, 255) # 赋新值BGR
lower_diff =(30, 30, 30)# (30, 30, 30) # 下灰阶差
up_diff = (200, 200, 200)#(50, 50, 50)# (30, 30, 30) # 上灰阶差
# mask图片
h, w = img_org.shape[:2]
img_mask = np.zeros([h + 2, w + 2], np.uint8) # 需要大一点
img_copy = img_org.copy() # 会覆盖原图
# 3.执行处理
print("# 标记表格为红色。。。")
cv.floodFill(img_copy, img_mask, seed_point, new_val, lower_diff, up_diff,flags=4 | (255 << 8) | cv.FLOODFILL_FIXED_RANGE)
# flags=4 | (255 << 8) | cv.FLOODFILL_FIXED_RANGE) # 模式 4连通 + 255白色 + 区域计算
# 4.显示结果
#cv.imshow("img_mask", img_mask)
#cv.imshow("img_org", img_org)
#cv.imshow("img_copy", img_copy)
x,y,z=img_copy.shape
res=img_copy.reshape(x*y*z) # 变成一维了
print("# 生成红色蒙板...")
mask=np.zeros(x*y*z).astype("uint8").reshape(x*y,z) # 变成2维
mask=mask+np.array([0,0,255],dtype='uint8') # 生成红色蒙板
#print(mask[:20])
mask=mask.reshape(x*y*z)
print("# 蒙板运算获取红色区域")
new=(res==mask).astype("uint8")
new=new.reshape(x*y,z)
new=np.apply_along_axis(lambda x:x[0]*x[1]*x[2],1,new) # 全1为1
res=res.reshape(x*y,z)
new2=np.vstack((new,new))
new=np.vstack((new,new2)).T # 转置了
#print(res.shape,new.shape)
new=new.reshape(x*y*z)
res=res.reshape(x*y*z)
res=np.multiply(new,res)
#res=np.array([np.array([0,0,255],dtype='uint8') if (x==new_val).all() else np.array([255,255,255],dtype='uint8') for x in res ])
res=res.reshape(x,y,z)
#cv.imshow("img_contour", res)
print("# 保存表格框图片")
#dst = cv.fastNlMeansDenoisingColored(res,None,10,10,7,21)
#res=cv.GaussianBlur(res, (3, 3), 0)#cv.pyrMeanShiftFiltering(res, 20, 100) #
#res=cv.pyrMeanShiftFiltering(res, 30, 100)
res=ero_dialation(res)
cv.imwrite("table.png",res) # 到这里都是不错的
寻找表格的范围和最小点
def get_max_contour(imgfile):
""" 找到最大的轮廓,注意白色被认为是背景色 """
img = cv2.imread(imgfile)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_temp = gray.copy()
ret, dst = cv2.threshold(gray_temp, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 二值化
gray_temp=255-dst # 反色处理了
contours, hierarchy = cv2.findContours(gray_temp, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
area=[]
for i in range(len(contours)):
area.append(cv2.contourArea(contours[i]))
max_idx = np.argmax(area)
return contours[max_idx]
def getpt0(imgfile):
""" 得到最大轮廓的最左上的位置 """
maxcontour=get_max_contour(imgfile)
pt0=process_contour(maxcontour)
return pt0
def get_region(imgfile):
maxcontour=get_max_contour(imgfile)
region=get_range(maxcontour)
return region
查找单元格
def get_rects_in_table(tablepng,rang):
res=[]
x0,y0,x1,y1=rang
timeout=90
t0=time.time()
l=0
cnt=0
while time.time()-t010:
break
return res
寻找坐标的行像素列像素
def get_row_col_at_xy(pt,narray):
""" 得到图片某点处的行像素集和列像素集 """
h,w,z=narray.shape # 高宽通道数,高--行数,宽--x
x,y=pt
narr=narray.reshape(h,w*z)
row=narray[y].reshape(w,z)#行
col=narray[:,x].reshape(h,z)# 列
return row,col
寻找点周围的单元格
def get_rect_around_pt(imgarr,pt,color=[0,0,255]):
""" 如果存在0,那么刚好在线上,数据抛弃
如果存在一正一负,取正的最小,负的最大,加上减去的就是对应的坐标 """
row,col=get_row_col_at_xy(pt,imgarr) # 行像素,列像素
rflag=(row==color).astype(int) # 判断3个通道的值是否一致
rnew=np.apply_along_axis(lambda x:x[0]*x[1]*x[2],axis=1,arr=rflag) # 全部为1才为1
result1=np.where(rnew==1)[0]-pt[0] # 找到等于1的那个标号,就是坐标的值,再和当前点的坐标进行比较,得出距离
if 0 in result1: return None # 刚好在线上抛弃
#print(result1)
try:
x1,x2=(result1[np.where(result1<0)].max()+pt[0],result1[np.where(result1>0)].min()+pt[0]) # x1,x2 取正的最小,负的最大,
except:
return None
#下面操作同上面
cflag=(col==color).astype(int)
cnew=np.apply_along_axis(lambda x:x[0]*x[1]*x[2],axis=1,arr=cflag)
result2=np.where(cnew==1)[0]-pt[1]
if 0 in result2: return None
#print(result2)
try: # 可能出现不规则区域,导致pt在图表外,因此找不到符合要求的两个点
y1,y2=(result2[np.where(result2<0)].max()+pt[1],result2[np.where(result2>0)].min()+pt[1]) # y1,y2
except:
return None
#print(x1,y1,x2,y2)
return [x1,y1,x2,y2]
##主程序
if __name__ == '__main__':
#pytesseract.image_to_string(image,lang="",config="")
"""测试输入为带有表格的imgfile,只考虑了识别最大的那个表格,
首先通过imgfile,生成table.png,
然后根据table.png来找到横线竖线,
得到相应的坐标,并进行切分 """
imgfile="test.png" # 路径不能有中文
gen_table_file(imgfile) # 生成了只含table的文件。
targetfile=r"table.png"
target = cv2.imread(targetfile) # trainImage
region=get_region(imgfile)
#get_rect_around_pt(target,[365,180])
print(f"单元格寻找区域:{region}")
ret=get_rects_in_table(target,region)
print(len(ret))
tmpdir="cropped"
if os.path.exists(tmpdir) and os.path.isdir(tmpdir):
pass
else:
os.mkdir(tmpdir)
result=[]
ret=sorted(ret,key=lambda x:x[0])
ret=sorted(ret,key=lambda x:x[1])
print(ret) # 此处进行了排序!二次排序,先排x1,再排y1
for i,reg in enumerate(ret):
x0,y0,x1,y1=reg
cropped=PIL.Image.open(imgfile).crop([x0+1,y0+1,x1,y1])
fnm=f"{tmpdir}\cropped_{i:03d}.jpg"
cropped.save(fnm)
#img=cv.imread(fnm)
img=np.array(cropped)
dst = cv.fastNlMeansDenoisingColored(img,None,10,10,7,21)
#txt=pytesseract.image_to_string(dst, lang='chi_sim')
txt=get_string(dst)
#print(str(sn)+":",txt.strip())
result.append(str(i)+":"+txt.strip())
print("n".join(result))
具体代码可以见我上传的资源。



