如何基于文本坐标规整DataFrame为表格(解决坐标微小偏差问题)
问题
通过pytesseract.image_to_data(img_pl,lang="eng", output_type='data.frame', config='--psm 11')(psm 11或12结果一致)提取到的OCR数据已整理为关键列的DataFrame:
# 可直接复制测试的数据 data = {'left': [154, 154, 200, 154, 201, 199], 'top': [0, 3, 3, 7, 8, 12], 'width': [576, 168, 162, 168, 155, 157], 'height': [89, 10, 10, 10, 10, 10], 'text': ['text1', 'text2', 'text3', 'text4', 'text5', 'text6']} output_test_min_agg = pd.DataFrame(data)
对应表格:
| left | top | width | height | text |
|---|---|---|---|---|
| 154 | 0 | 576 | 89 | text1 |
| 154 | 3 | 168 | 10 | text2 |
| 200 | 3 | 162 | 10 | text3 |
| 154 | 7 | 168 | 10 | text4 |
| 201 | 8 | 155 | 10 | text5 |
| 199 | 12 | 157 | 10 | text6 |
注意:坐标存在最多3-5像素偏差,需结合width列判断文本覆盖范围。
预期规整后的DataFrame格式:
| index | col 01 | col 02 |
|---|---|---|
| 0 | text1 | |
| 1 | text2 | text3 |
| 2 | text4 | text5 |
| 3 | text6 |
尝试过以下代码,但存在问题:即使top或left仅有1像素偏差,也会生成多余行列:
output_test_min_agg=output_test_min.sort_values('top', ascending=True) output_test_min_agg = output_test_min_agg.groupby(['top', 'left'], sort=False)['text'].sum().unstack('left') output_test_min_agg.reindex(sorted(output_test_min_agg.columns), axis=1).dropna(how='all')
需实现符合预期的表格规整效果。
解决方案
核心思路是对top(行位置)和文本横向位置(结合left+width判断列位置)进行聚类分组,将偏差在允许范围内的坐标归为同一行/列,具体实现如下:
方法1:基于KMeans聚类(依赖sklearn)
import pandas as pd from sklearn.cluster import KMeans # 加载测试数据 df = pd.DataFrame(data) # 1. 对top聚类,识别同一行(偏差≤5像素归为同一行) top_sorted = df['top'].sort_values() row_cluster_num = 1 + sum(top_sorted.diff().dropna() > 5) kmeans_top = KMeans(n_clusters=row_cluster_num, random_state=42) df['row_group'] = kmeans_top.fit_predict(df[['top']]) # 2. 计算文本右边界,取left和right的均值作为列中心,聚类识别同一列 df['right'] = df['left'] + df['width'] col_centers = df[['left', 'right']].mean(axis=1).values.reshape(-1,1) col_sorted = sorted(col_centers.flatten()) col_cluster_num = 1 + sum(col_sorted[i+1]-col_sorted[i] > 10 for i in range(len(col_sorted)-1)) kmeans_col = KMeans(n_clusters=col_cluster_num, random_state=42) df['col_group'] = kmeans_col.fit_predict(col_centers) # 3. 生成规整表格 pivot_df = df.groupby(['row_group', 'col_group'])['text'].first().unstack('col_group') pivot_df.columns = [f'col {str(i+1).zfill(2)}' for i in range(pivot_df.shape[1])] pivot_df = pivot_df.reset_index(drop=True).fillna('') print(pivot_df)
方法2:手动阈值分组(无外部依赖)
import pandas as pd # 加载测试数据 df = pd.DataFrame(data) # 1. 按top排序,手动划分行组(差值>5像素视为不同行) df_sorted = df.sort_values('top').reset_index(drop=True) row_group = 0 df_sorted['row_group'] = row_group for i in range(1, len(df_sorted)): if df_sorted['top'].iloc[i] - df_sorted['top'].iloc[i-1] > 5: row_group +=1 df_sorted.loc[i, 'row_group'] = row_group # 2. 计算列中心,手动划分列组(中心差值>10像素视为不同列) df_sorted['col_center'] = (df_sorted['left'] + df_sorted['left'] + df_sorted['width']) / 2 df_sorted = df_sorted.sort_values(['row_group', 'col_center']) col_group = 0 current_center = df_sorted['col_center'].iloc[0] df_sorted['col_group'] = col_group for i in range(1, len(df_sorted)): if abs(df_sorted['col_center'].iloc[i] - current_center) > 10: col_group +=1 current_center = df_sorted['col_center'].iloc[i] df_sorted.loc[i, 'col_group'] = col_group # 3. 生成规整表格 pivot_df = df_sorted.groupby(['row_group', 'col_group'])['text'].first().unstack('col_group') pivot_df.columns = [f'col {str(i+1).zfill(2)}' for i in range(pivot_df.shape[1])] pivot_df = pivot_df.reset_index(drop=True).fillna('') print(pivot_df)
输出结果
两种方法均会得到符合预期的表格:
col 01 col 02 0 text1 1 text2 text3 2 text4 text5 3 text6
内容的提问来源于stack exchange,提问作者Dolev Mitz
相关产品推荐
相关产品推荐

