原理
轮廓检测:寻找连续的像素闭合区域,通过轮廓大小和形状判断是否为单个单元格或者整个表格区域。
结构
表格由横线、纵线构成,线与线交叉形成单元格
效果
步骤
1、读取图像
img = cv2.imread(imagePath)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
2、识别表格轮廓(canny算法)
canny = cv2.Canny(gray, 200, 255)
# 只要最外层轮廓
_, contours, HIERARCHY = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 去除大小不合适的轮廓
candidate_table = [cnt for cnt in contours if cv2.contourArea(cnt) > = gray.shape[0] * gray.shape[1] * 0.01 ]
3、对表格顶点排序
def get_sorted_rect(rect):
'''
获取矩阵排序的四个坐标,方便透视变换使用
rect包含坐标点为负数时,left_rect包含三个 right_rect包含1个坐标点,或者反之
@param rect:
@return:按照左上 右上 右下 左下排列返回
'''
try:
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] # 左上 右上 右下 左下
except:
# np.array_equal(order_points(rect), sorted_rect):
sorted_rect = order_points(rect)
return sorted_rect
for i in range(len(candidate_table)):
cnt = candidate_table[i]
area = cv2.contourArea(cnt)
# 找到最小的矩形,该矩形可能有方向
rect = cv2.minAreaRect(cnt)
# box是四个点的坐标
box = cv2.boxPoints(rect) # boxPoints返回四个点顺序:右下→左下→左上→右上(实际上不定
box = np.int0(box)
sorted_box = 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)
4、切割表格
def get_standard_table_image(gray, table):
'''
获取表格图片
Args:
gray:
table:
Returns:
'''
sorted_rect = get_sorted_rect(table)
gray_z = perTran(gray, sorted_rect)
binary_z = cv2.adaptiveThreshold(~gray_z, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 15, -5)
return gray_z, binary_z
def perTran(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 warped
def get_muti_tables_images( gray, tables):
'''
获取多个table结果
@param gray: 灰度图
@param table:表格四个点坐标
@return:返回解析结果
'''
gray_z_list = []
binary_z_list = []
for index,table in enumerate(tables):
gray_z, binary_z = get_standard_table_image(gray, table)
gray_z_list.append(gray_z)
binary_z_list.append(binary_z)
return gray_z_list,binary_z_list
get_muti_tables_images( gray, tables):
完整代码
见github项目:https://github.com/dirac472/tableOCR
优化
待补充无框线表格的识别