如何使用Python OpenCV查找表格图像中的行数和列数?

3

如何通过OpenCV获取图像表中的行数和列数。

获取表格中方框的代码已经正确获取。

contours, hierarchy = cv2.findContours(img_final_bin, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

def sort_contours(cnts, method="left-to-right"):
# initialize the reverse flag and sort index
reverse = False
i = 0
# handle if we need to sort in reverse
if method == "right-to-left" or method == "bottom-to-top":
    reverse = True
# handle if we are sorting against the y-coordinate rather than
# the x-coordinate of the bounding box
if method == "top-to-bottom" or method == "bottom-to-top":
    i = 1
# construct the list of bounding boxes and sort them from top to
# bottom
boundingBoxes = [cv2.boundingRect(c) for c in cnts]
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
    key=lambda b:b[1][i], reverse=reverse))
# return the list of sorted contours and bounding boxes
return (cnts, boundingBoxes)

(contours, boundingBoxes) = sort_contours(contours, method="top-to-bottom")


请在帖子中添加您的示例图片 :) - nathancy
由于限制,无法上传确切的文件,但这里有示例图像。需要将此图像转换为Pandas数据帧。使用上述代码从图像中提取了框,并使用tesserocr提取了其中的内容。如果我们可以知道确切数量的列和行,我们可以在Pandas中创建空数据帧,然后将框数据放入Pandas表中,从而从图像获取Pandas表。请建议是否有其他方法从此图像获取Pandas表。 - anant
3个回答

7
这里有两种方法:第一种使用表格结构来确定行数和列数,而第二种使用单元格数量。

方法 #1: 表格结构

这个想法是我们可以计算表格的水平线和垂直线的数量来确定行数和列数。对于行,它是 rows = 水平线数 - 1 ,对于列,它是 columns = 垂直线数 - 1

检测到绿色的水平线

enter image description here

检测到绿色垂直线。

enter image description here

结果

Rows: 7
Columns: 4

代码
import cv2

# Load image, convert to grayscale, Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Find number of rows 
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25,1))
horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
rows = 0
for c in cnts:
    cv2.drawContours(image, [c], -1, (36,255,12), 2)
    rows += 1

# Find number of columns
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,25))
vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
columns = 0
for c in cnts:
    cv2.drawContours(image, [c], -1, (36,255,12), 2)
    columns += 1

print('Rows:', rows - 1)
print('Columns:', columns - 1)

cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.waitKey()

方法二:细胞计数

  1. 获取二进制图像。 加载图像,转换为灰度图像,高斯模糊,然后使用大津阈值法进行阈值分割

  2. 删除单元格内的文本。 查找轮廓并使用cv2.contourArea()过滤轮廓,通过使用cv2.drawContours()填充轮廓来删除文本。

  3. 反转图像。 我们反转图像,使单元格为白色,背景为黑色。

  4. 排序单元格并对行/列求和。 我们查找轮廓,然后使用imutils.contours.sort_contours将轮廓从上到下排序。接下来,我们遍历轮廓并找到质心以获取(cX, cY)坐标。这样做的想法是通过比较每个单元格的值来确定它是否是一个新行或同一行中的单元格,使用偏移量。如果值大于某个偏移值,则表示该单元格位于新行中。我们构建一个模型表,其中表的长度给出行数,任何索引的长度都给出列数。


二进制图像

enter image description here

已移除文本轮廓 + 图像反转

enter image description here

这里是迭代每个单元格以计算行数和列数的可视化呈现。

enter image description here

结果

Rows: 7
Columns: 4

代码

import numpy as np
from imutils import contours
import cv2

# Load image, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Find contours and remove text inside cells
cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    area = cv2.contourArea(c)
    if area < 4000:
        cv2.drawContours(thresh, [c], -1, 0, -1)

# Invert image
invert = 255 - thresh
offset, old_cY, first = 10, 0, True
visualize = cv2.cvtColor(invert, cv2.COLOR_GRAY2BGR)

# Find contours, sort from top-to-bottom and then sum up column/rows
cnts = cv2.findContours(invert, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
(cnts, _) = contours.sort_contours(cnts, method="top-to-bottom")
for c in cnts:
    # Find centroid
    M = cv2.moments(c)
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])
    
    # New row
    if (abs(cY) - abs(old_cY)) > offset:
        if first:
            row, table = [], []
            first = False
        old_cY = cY
        table.append(row)
        row = []
    
    # Cell in same row
    if ((abs(cY) - abs(old_cY)) <= offset) or first:
        row.append(1)
   
    # Uncomment to visualize 
    '''
    cv2.circle(visualize, (cX, cY), 10, (36, 255, 12), -1) 
    cv2.imshow('visualize', visualize)
    cv2.waitKey(200)
    '''

print('Rows: {}'.format(len(table)))
print('Columns: {}'.format(len(table[1])))

cv2.imshow('invert', invert)
cv2.imshow('thresh', thresh)
cv2.waitKey()

我们是如何决定这里面积为4000的。 - anant
那可以是任何预定的阈值面积值。它只是过滤掉任何小的噪声颗粒。 - nathancy

0

另一种方法是首先验证它是否是真正的表格,可以使用霍夫线变换,一旦完成后,您可以使用上面同行解释的方法。


0

看起来一个简单的解决方案是先从左到右查看每个像素是否为黑色(这将表明我们已经找到了一列)。然后对行执行相同的操作(如果从上到下每个像素都是黑色,则表示已经找到了一行)。

一个复杂之处在于线条的宽度,这意味着只有在找到白色颜色之前,您才会将其视为仅找到1行/列。

我可以编写此代码,但我现在不在家,所以也许其他人可以编写代码,我稍后将删除我的答案。我知道这可能是一条评论,但我没有50个声望。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接