欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

opencv+paddle orc 识别图片提取表格信息

发布时间:2023/12/18 编程问答 35 豆豆
生活随笔 收集整理的这篇文章主要介绍了 opencv+paddle orc 识别图片提取表格信息 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

参考:
python-opencv表格识别

思路:
1.提取出横线
2.提取出纵线
3.得到交叉点,删除错误的交叉点,两个交叉点距离小于10,取坐标值小的那个交叉点,得到表格行列
4.对每个单元格使用paddle ocr提取文字

在原文代码基础上修改了2点
1.pytesseract识别准确率不高,使用paddle ocr代替 pytesseract
2.识别出的表格交叉点有些并非真实交叉点,通过判断该行像素点个数,丢掉错误横纵坐标

import cv2 import numpy as np import pandas as pd# import pytesseract # import re from paddleocr import PaddleOCRsrc = 'image.png'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) # src:灰度化的图片 # maxValue:满足条件的像素点需要设置的灰度值 # adaptiveMethod:自适应方法。有2种:ADAPTIVE_THRESH_MEAN_C 或 ADAPTIVE_THRESH_GAUSSIAN_C # thresholdType:二值化方法,可以设置为THRESH_BINARY或者THRESH_BINARY_INV # blockSize:分割计算的区域大小,取奇数 # C:常数,每个区域计算出的阈值的基础上在减去这个常数作为这个区域的最终阈值,可以为负数 # dst:输出图像,可选 # 展示图片 # cv2.imshow("binary_picture", binary) # cv2.waitKey()def recognize_bgkx(binary):rows, cols = binary.shapescale = 30 # 值越小 横线越少 40# 自适应获取核值# 识别横线:kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (cols // scale, 1))# 矩形eroded = cv2.erode(binary, kernel, iterations=1) # 腐蚀dilated_col = cv2.dilate(eroded, kernel, iterations=1) # 膨胀# cv2.imshow("excel_horizontal_line", dilated_col)# cv2.waitKey()# 识别竖线:scale = 20kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, rows // scale))eroded = cv2.erode(binary, kernel, iterations=1)dilated_row = cv2.dilate(eroded, kernel, iterations=1)# cv2.imshow("excel_vertical_line:", dilated_row)# cv2.waitKey()# 将识别出来的横竖线合起来 对二进制数据进行“与”操作bitwise_and = cv2.bitwise_and(dilated_col, dilated_row)cv2.imshow("excel_bitwise_and", bitwise_and)cv2.waitKey()# 标识表格轮廓# merge = cv2.add(dilated_col, dilated_row) # 进行图片的加和# cv2.imshow("entire_excel_contour:", merge)# cv2.waitKey()# 两张图片进行减法运算,去掉表格框线# merge2 = cv2.subtract(binary, merge)# cv2.imshow("binary_sub_excel_rect", merge2)# cv2.waitKey()# new_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))# erode_image = cv2.morphologyEx(merge2, cv2.MORPH_OPEN, new_kernel)# cv2.imshow('erode_image2', erode_image)# cv2.waitKey()# merge3 = cv2.add(erode_image, bitwise_and)# cv2.imshow('merge3', merge3)# cv2.waitKey()# 将焦点标识取出来ys, xs = np.where(bitwise_and > 0)# 横纵坐标数组y_point_arr = []x_point_arr = []# 通过排序,排除掉相近的像素点,只取相近值的最后一点# 这个10就是两个像素点的距离,不是固定的,根据不同的图片会有调整,基本上为单元格表格的高度(y坐标跳变)和长度(x坐标跳变)i = 0sort_x_point = np.sort(xs)for i in range(len(sort_x_point) - 1):if sort_x_point[i + 1] - sort_x_point[i] > 10:x_point_arr.append(sort_x_point[i])i = i + 1# 要将最后一个点加入x_point_arr.append(sort_x_point[i])i = 0sort_y_point = np.sort(ys)# print(np.sort(ys))for i in range(len(sort_y_point) - 1):if (sort_y_point[i + 1] - sort_y_point[i] > 10):y_point_arr.append(sort_y_point[i])i = i + 1y_point_arr.append(sort_y_point[i])# 横纵坐标超过3个 代表点对应的行、列超过2data = pd.DataFrame(bitwise_and)drop_y_list = []for i in y_point_arr:# for j in x_point_arr: data[(data.loc[i-1,:]>0)].index.tolist()# y_dot_num = [x for x in data.loc[i-1,:] if x!=0]y_dot_num = 0y_dot_num += len(data.loc[(data.loc[i, :] > 0)].index.tolist())for j in range(1, 5):y_dot_num += len(data.loc[(data.loc[i + j, :] > 0)].index.tolist())y_dot_num += len(data.loc[(data.loc[i - j, :] > 0)].index.tolist())if y_dot_num < 5:print('纵坐标%s并不在框线上删除,该行只有%s个像素' % (i, y_dot_num))drop_y_list.append(i)for y in drop_y_list:y_point_arr.remove(y)drop_x_list = []for m in x_point_arr:# for j in x_point_arr: data[(data.loc[i-1,:]>0)].index.tolist()# y_dot_num = [x for x in data.loc[i-1,:] if x!=0]x_dot_num = 0x_dot_num += len(data.loc[(data.loc[:, m] > 0)].index.tolist())print('检测', m)for n in range(1, 5):x_dot_num += len(data.loc[(data.loc[:, m + n] > 0)].index.tolist())x_dot_num += len(data.loc[(data.loc[:, m - n] > 0)].index.tolist())if x_dot_num < 5:print('横坐标坐标%s并不在框线上删除 ,该列只有%s个像素' % (m, x_dot_num))drop_x_list.append(m)for x in drop_x_list:x_point_arr.remove(x)print('该表格有%s行%s列 ' % (len(y_point_arr) - 1, len(x_point_arr) - 1))return x_point_arr, y_point_arrx_point_arr, y_point_arr = recognize_bgkx(binary) # 退后ocr = PaddleOCR(use_angle_cls=True, lang="ch")def recognize_text_by_loop():y_point_arr = [x - 3 for x in y_point_arr]# 循环y坐标,x坐标分割表格data = [[] for i in range(len(y_point_arr))]for i in range(len(y_point_arr) - 1):# if i==0:# continuefor j in range(len(x_point_arr) - 1):# 在分割时,第一个参数为y坐标,第二个参数为x坐标cell = gray[y_point_arr[i]:y_point_arr[i + 1],x_point_arr[j]:x_point_arr[j + 1]]# cv2.imshow("sub_pic" + str(i) + str(j), cell)# cv2.waitKey()# cv2.destroyAllWindows()img_path = "cell_image_"+str(i)+'_'+str(j)+".png"cv2.imwrite(img_path,cell)# 输入待识别图片路径# 输出结果保存路径result = ocr.ocr(img_path, cls=True)text1 = ''.join([x[1][0] for x in result])print(text1)data[i].append(text1)j = j + 1i = i + 1print(data)table_coordinate = pd.DataFrame(data[1:-1],columns=data[0])table_coordinate.to_excel('saomiaojian.xlsx',index=False)

待优化:

  • 真实图片的单元格并不在一条横坐标或纵坐标下,使用框线交叉点坐标确定单元格,实际图像不够完整,待后续优化,获取每个单元格坐标

添加优化代码:
1.找到表格四个角点
2.做平移变换

因为已经平移变换,就删除了一些代码,完整优化代码如下:

import cv2 import pandas as pd import numpy as npfrom paddleocr import PaddleOCRocr = PaddleOCR(use_angle_cls=True, lang="ch")class TableOCR(object):def __init__(self,result_file=''):self.img_file = ''if not result_file:self.result_file = 'result.xlsx'def get_sorted_rect(self, rect):'''获取排序的四个坐标@param rect:@return:按照左上 右上 右下 左下排列返回'''mid_x = (max([x[1] for x in rect]) - min([x[1] for x in rect])) * 0.5 + min([x[1] for x in rect]) # 中间点坐标left_rect = [x for x in rect if x[1] < mid_x]left_rect.sort(key=lambda x: (x[0], x[1]))right_rect = [x for x in rect if x[1] > mid_x]right_rect.sort(key=lambda x: (x[0], x[1]))sorted_rect = left_rect[0], left_rect[1], right_rect[1], right_rect[0]return sorted_rectdef get_table(self, gray, min_table_area=0):'''从灰度图获取表格坐标,[[右下→左下→左上→右上],..]边缘检测+膨胀---》找最外围轮廓点,根据面积筛选---》根据纵坐标排序---》计算轮廓的四个点,再次筛选@param gray:灰度图 如果是二值图会报错@return:'''canny = cv2.Canny(gray, 200, 255) # 第一个阈值和第二个阈值kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))canny = cv2.dilate(canny, kernel)_, contours, HIERARCHY = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)if not min_table_area:min_table_area = gray.shape[0] * gray.shape[1] * 0.01 # 50000 # 最小的矩形面积阈值candidate_table = [cnt for cnt in contours if cv2.contourArea(cnt) > min_table_area] # 计算该轮廓的面积candidate_table = sorted(candidate_table, key=cv2.contourArea, reverse=True)area_list = [cv2.contourArea(cnt) for cnt in candidate_table]table = []for i in range(len(candidate_table)):# 遍历所有轮廓# cnt是一个点集cnt = candidate_table[i]# 找到最小的矩形,该矩形可能有方向rect = cv2.minAreaRect(cnt)# box是四个点的坐标box = cv2.boxPoints(rect)box = np.int0(box)sorted_box = self.get_sorted_rect(box)result = [sorted_box[2], sorted_box[3], sorted_box[0], sorted_box[1]] # 右下 左下 左上 右上result = [x.tolist() for x in result]table.append(result)return tabledef perTran(self, image, rect):'''做透视变换image 图像rect 四个顶点位置:左上 右上 右下 左下'''tl, tr, br, bl = rect # 左下 右下 左上 右上 || topleft topright 左上 右上 右下 左下# 计算宽度widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))maxWidth = max(int(widthA), int(widthB))# 计算高度heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))maxHeight = max(int(heightA), int(heightB))# 定义变换后新图像的尺寸dst = np.array([[0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1],[0, maxHeight - 1]], dtype='float32')# 变换矩阵rect = np.array(rect, dtype=np.float32)dst = np.array(dst, dtype=np.float32)M = cv2.getPerspectiveTransform(rect, dst)# 透视变换warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))return warpeddef recognize_bgkx(self,binary):rows, cols = binary.shapescale = 30 # 值越小 横线越少 40# 自适应获取核值# 识别横线:kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (cols // scale, 1))# 矩形eroded = cv2.erode(binary, kernel, iterations=1) # 腐蚀dilated_col = cv2.dilate(eroded, kernel, iterations=1) # 膨胀# cv2.imshow("excel_horizontal_line", dilated_col)# cv2.waitKey()# 识别竖线:scale = 20kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, rows // scale))eroded = cv2.erode(binary, kernel, iterations=1)dilated_row = cv2.dilate(eroded, kernel, iterations=1)# cv2.imshow("excel_vertical_line:", dilated_row)# cv2.waitKey()# 将识别出来的横竖线合起来 对二进制数据进行“与”操作bitwise_and = cv2.bitwise_and(dilated_col, dilated_row)# cv2.imshow("excel_bitwise_and", bitwise_and)# cv2.waitKey()# 标识表格轮廓# merge = cv2.add(dilated_col, dilated_row) # 进行图片的加和# cv2.imshow("entire_excel_contour:", merge)# cv2.waitKey()# 两张图片进行减法运算,去掉表格框线# merge2 = cv2.subtract(binary, merge)# cv2.imshow("binary_sub_excel_rect", merge2)# cv2.waitKey()# new_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))# erode_image = cv2.morphologyEx(merge2, cv2.MORPH_OPEN, new_kernel)# cv2.imshow('erode_image2', erode_image)# cv2.waitKey()# merge3 = cv2.add(erode_image, bitwise_and)# cv2.imshow('merge3', merge3)# cv2.waitKey()# 将焦点标识取出来ys, xs = np.where(bitwise_and > 0)# 横纵坐标数组y_point_arr = []x_point_arr = []# 通过排序,排除掉相近的像素点,只取相近值的最后一点# 这个10就是两个像素点的距离,不是固定的,根据不同的图片会有调整,基本上为单元格表格的高度(y坐标跳变)和长度(x坐标跳变)i = 0sort_x_point = np.sort(list(set(xs)))for i in range(len(sort_x_point) - 1):if sort_x_point[i + 1] - sort_x_point[i] > 10:x_point_arr.append(sort_x_point[i])i = i + 1# 要将最后一个点加入x_point_arr.append(sort_x_point[i])i = 0sort_y_point = np.sort(list(set(ys)))# print(np.sort(ys))for i in range(len(sort_y_point) - 1):if (sort_y_point[i + 1] - sort_y_point[i] > 10):y_point_arr.append(sort_y_point[i])i = i + 1y_point_arr.append(sort_y_point[i])return x_point_arr, y_point_arrdef recognize_text_by_loop(self,gray,x_point_arr,y_point_arr):# y_point_arr = [x - 3 for x in y_point_arr]# 循环y坐标,x坐标分割表格data = [[] for i in range(len(y_point_arr))]for i in range(len(y_point_arr) - 1):# if i==0:# continuefor j in range(len(x_point_arr) - 1):# 在分割时,第一个参数为y坐标,第二个参数为x坐标cell = gray[y_point_arr[i]:y_point_arr[i + 1],x_point_arr[j]:x_point_arr[j + 1]]# cv2.imshow("sub_pic" + str(i) + str(j), cell)# cv2.waitKey()# cv2.destroyAllWindows()# img_path = "cell_image_" + str(i) + '_' + str(j) + ".png"# cv2.imwrite(img_path, cell)# 输入待识别图片路径# 输出结果保存路径result = ocr.ocr(cell)text1 = ''.join([x[1][0] for x in result])print(text1)data[i].append(text1)print(data)df = pd.DataFrame(data[1:-1], columns=data[0])return dfdef ocr(self,img_file):self.img_file = img_fileimg = cv2.imread(img_file)gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)rect = self.get_table(gray)sorted_rect = self.get_sorted_rect(rect[0])gray_z = self.perTran(gray, sorted_rect) #如果有多个table 可以循环cv2.imwrite('gray_z.jpg', gray_z)binary_z = cv2.adaptiveThreshold(~gray_z, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY, 15, -5)x_point_arr, y_point_arr = self.recognize_bgkx(binary_z)df = self.recognize_text_by_loop(gray_z,x_point_arr, y_point_arr)df.to_excel(self.result_file, index=False)tableOCR = TableOCR() tableOCR.ocr('table.png')

效果:

结果:

总结

以上是生活随笔为你收集整理的opencv+paddle orc 识别图片提取表格信息的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。