如何用Python通用提取无全边框表格为文本及DataFrame
通用无全边框表格提取方案(支持多行拆分字段自动合并)
这套方案不需要针对单张表格手动指定坐标,可适配绝大多数无外边框、存在字段折行的业务表格,输出结果支持直接转pandas DataFrame或纯文本。
依赖安装
先安装所需依赖包,OCR引擎默认用Tesseract,需要提前在本地安装对应程序并配置好需要的语言识别包:pip install opencv-python numpy pandas pytesseract
核心实现逻辑
整套流程不需要单表配置坐标,所有结构识别全为自适应逻辑:
- 自动识别表格结构:通过自适应阈值做图像二值化,结合形态学运算自动提取水平/垂直分隔线,推算行列边界;如果表格没有内部竖线,自动切换为列投影模式,按列方向的空白间隙切分列
- 单元格内容识别:按自动计算的单元格边界切分图块,做OTSU二值化提升识别率后调用OCR提取文字
- 折行字段自动合并:通过通用规则识别折行:如果某一行的首列内容不含数字、计量单位等指标类特征,判定为上一行名称字段的拆分内容,自动拼接到上一行对应单元格
- 结构化输出:清洗空行后将二维表数据转换为DataFrame,支持直接导出为CSV、文本等格式
可直接运行的代码
import cv2 import numpy as np import pandas as pd import pytesseract # 若tesseract不在系统环境变量,取消下面一行注释改成本地安装路径 # pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe' def extract_borderless_table(img_path, ocr_lang='chi_sim+eng'): # 读取图片自动预处理 img = cv2.imread(img_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) binary = cv2.adaptiveThreshold(~gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -2) # 自动检测水平、垂直分隔线,阈值根据图片尺寸自适应计算 h, w = binary.shape horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (w//10, 1)) vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, h//10)) horizontal_lines = cv2.erode(binary, horizontal_kernel, iterations=2) horizontal_lines = cv2.dilate(horizontal_lines, horizontal_kernel, iterations=2) vertical_lines = cv2.erode(binary, vertical_kernel, iterations=2) vertical_lines = cv2.dilate(vertical_lines, vertical_kernel, iterations=2) # 提取所有线的坐标,自动计算行列边界 h_y_coords = [] contours_h, _ = cv2.findContours(horizontal_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours_h: x,y,w_cnt,h_cnt = cv2.boundingRect(cnt) h_y_coords.append(y) h_y_coords = sorted(list(set(h_y_coords))) v_x_coords = [] contours_v, _ = cv2.findContours(vertical_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours_v: x,y,w_cnt,h_cnt = cv2.boundingRect(cnt) v_x_coords.append(x) v_x_coords = sorted(list(set(v_x_coords))) # 处理无线条靠间距区分的场景:如果检测到的竖线少于2条,自动按列投影切分 if len(v_x_coords) <2: col_projection = np.sum(binary, axis=0) col_break_points = np.where(col_projection < 10)[0] v_x_coords = [] for i in range(len(col_break_points)-1): if col_break_points[i+1] - col_break_points[i] > 20: v_x_coords.append(col_break_points[i]) v_x_coords = sorted(list(set(v_x_coords))) # 切分单元格+OCR识别 rows = [] for i in range(len(h_y_coords)-1): current_row = [] y1, y2 = h_y_coords[i], h_y_coords[i+1] for j in range(len(v_x_coords)-1): x1, x2 = v_x_coords[j], v_x_coords[j+1] cell_roi = gray[y1:y2, x1:x2] # 预处理单元格提升识别率 _, cell_binary = cv2.threshold(cell_roi, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU) text = pytesseract.image_to_string(cell_binary, lang=ocr_lang).strip().replace('\n', '') current_row.append(text) rows.append(current_row) # 自动合并折行的名称字段 cleaned_rows = [] for row in rows: # 跳过全空行 if not any([t.strip() for t in row]): continue # 判断当前行是不是上一行的折行:第一列内容非空且没有数字/指标特征,且上一行存在 is_wrap_line = False if cleaned_rows: first_col_text = row[0].strip() # 折行判断规则:第一列有内容,且不含数字、百分号、货币单位这类指标特征 if first_col_text and not any(c.isdigit() for c in first_col_text) and not any(u in first_col_text for u in ['%','元','万元','吨']): # 把当前行所有内容拼接到上一行对应单元格 for idx, cell_text in enumerate(row): if cell_text.strip(): cleaned_rows[-1][idx] += cell_text.strip() is_wrap_line = True if not is_wrap_line: cleaned_rows.append([t.strip() for t in row]) # 转成DataFrame,默认第一行为表头,不符合的话可以手动调整 df = pd.DataFrame(cleaned_rows[1:], columns=cleaned_rows[0]) return df # 调用示例 if __name__ == '__main__': table_df = extract_borderless_table('your_table_image.png') print(table_df) # 导出文本/CSV table_df.to_csv('table_output.csv', index=False, encoding='utf-8-sig')
适配说明
- 如果对OCR识别精度要求更高,可以把代码里的pytesseract替换为PaddleOCR,表格结构检测、折行合并的逻辑完全不需要改动
- 折行判断规则可根据自身业务场景全局调整,比如新增指标类关键词、修改特征判断逻辑,调整后可适配同类型所有表格,不需要单表改配置
- 如果是PDF里的表格,只需要在读取图片环节加一步
pdf2image把PDF页转成OpenCV可读取的图片数组,后续逻辑完全复用
内容的提问来源于stack exchange,提问作者stephsmith
相关产品推荐
相关产品推荐

