扫描图像按行统计已填充单元格数量的技术实现难题
核心需求
按行统计网格中已填充(hatched)单元格的数量:每个单元格对应5分钟,每行对应一项操作;输出需包含所有行的填充单元格数量(一维/二维列表均可),可通过行号关联操作名称,需适配多份填充情况不同的文档。
当前进展
已完成PDF导入、图像纠偏、裁剪,仅保留目标区域;现有mask处理代码如下:
import cv2 import numpy as np image = cv2.imread('result.jpg') gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) se=cv2.getStructuringElement(cv2.MORPH_RECT , (8,8)) bg=cv2.morphologyEx(gray, cv2.MORPH_DILATE, se) out_gray=cv2.divide(gray, bg, scale=255) thresh = cv2.threshold(out_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] blur = cv2.GaussianBlur(thresh,(5,5),0) kernel = np.ones((3, 3), np.uint8) mask = cv2.erode(blur, kernel, iterations=2)
执行后得到对应mask图像,但仍存在识别问题。
遇到的困难
- 无法准确识别网格结构,无法遍历每个单元格;原计划识别网格后通过轮廓内黑白占比判断填充单元格,但网格识别受阻。
- 尝试过Canny边缘检测、HoughLines直线检测,仅能捕获网格左侧线条或导致图像布满冗余红线;参考常规网格行列统计方案,仍无法忽略擦除痕迹、处理网格不规则问题,遍历易丢失单元格位置。
- 考虑过生成标准网格(如复制第3行并偏移多次)但不知实现方法,请求可行技术方案。
注:单元格上方的手写数字为噪声,可忽略;示例图像中第12行有29个填充单元格,对应2.42小时工时。
可行技术方案
方法1:行分割+列投影统计(绕开网格识别)
适合网格行对齐较好的场景,直接按行分割后统计填充单元格数量:
- 行分割:通过行投影定位每行边界
# 计算每行像素和,过滤空白行 row_proj = np.sum(mask, axis=1) row_threshold = np.max(row_proj) * 0.1 row_indices = np.where(row_proj > row_threshold)[0] # 合并连续行索引,得到每行上下边界 rows = [] current_start = row_indices[0] for i in range(1, len(row_indices)): if row_indices[i] != row_indices[i-1] + 1: rows.append((current_start, row_indices[i-1])) current_start = row_indices[i] rows.append((current_start, row_indices[-1]))
- 每行列投影统计填充数:通过列投影峰值判断填充单元格
fill_counts = [] for (y_start, y_end) in rows: row_img = mask[y_start:y_end+1, :] col_proj = np.sum(row_img, axis=0) col_threshold = np.max(col_proj) * 0.3 col_indices = np.where(col_proj > col_threshold)[0] if len(col_indices) == 0: fill_counts.append(0) continue # 统计连续高投影段的数量(即填充单元格数) count = 1 for i in range(1, len(col_indices)): if col_indices[i] > col_indices[i-1] + 5: # 单元格间距阈值可调整 count += 1 fill_counts.append(count)
方法2:标准网格模板匹配
若某一行网格线清晰,可提取该行网格作为模板,应用到所有行:
- 提取参考行网格分割点(示例取第3行)
# 获取参考行(假设第3行网格清晰,对应rows[2]) ref_y_start, ref_y_end = rows[2] ref_row = mask[ref_y_start:ref_y_end+1, :] ref_col_proj = np.sum(ref_row, axis=0) # 定位网格线列索引(低投影区域为网格线) grid_cols = np.where(ref_col_proj < np.max(ref_col_proj)*0.1)[0] # 去重得到网格分割点 unique_grid_cols = [] current_col = grid_cols[0] for col in grid_cols[1:]: if col - current_col > 2: unique_grid_cols.append((current_col + col)//2) current_col = col unique_grid_cols.append(current_col)
- 应用模板到所有行
fill_counts = [] for (y_start, y_end) in rows: row_img = mask[y_start:y_end+1, :] count = 0 for i in range(len(unique_grid_cols)-1): col_start = unique_grid_cols[i] col_end = unique_grid_cols[i+1] if col_end >= row_img.shape[1]: col_end = row_img.shape[1]-1 cell = row_img[:, col_start:col_end] fill_ratio = np.sum(cell == 255) / (cell.shape[0]*cell.shape[1]) if fill_ratio > 0.2: # 填充占比阈值可调整 count += 1 fill_counts.append(count)
方法3:优化mask减少噪声干扰
在原mask处理后添加两步操作,去除擦除痕迹和手写数字:
# 开运算去除小噪声点 kernel_open = np.ones((5,5), np.uint8) mask_clean = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_open) # 垂直闭运算去除横向手写数字 kernel_vertical = np.ones((15,1), np.uint8) mask_clean = cv2.morphologyEx(mask_clean, cv2.MORPH_CLOSE, kernel_vertical)
内容的提问来源于stack exchange,提问作者Doons
相关产品推荐
相关产品推荐

