1. 程式人生 > >如何識別圖片中的表格資料

如何識別圖片中的表格資料

  在很多時候,我們的資料來源形式是多種多樣的,有時候資料(或表格)也會呈現在圖片中。那麼,我們如何來獲取圖片中的有用資料呢?當一張圖片中含有表格資料的時候,我們可以用OpenCV識別表格中的直線,然後再用OCR技術識別其中的文字。
  本文僅作為如何識別圖片中的表格的一個例子,希望能給讀者一些啟示。筆者用到的工具如下:

  • opencv
  • pyteressact
  • numpy

我們用opencv來識別表格中的直線,用pyteressact來識別單元格文字,用numpy做數值處理。我們要識別的示例圖片(AI.png)如下:

示例圖片 AI.png

我們分以下幾步進行識別:

  1. 識別表格中的橫線,即分割記錄(每一行)的橫線;
  2. 識別表格中的豎線,即每個列的分割線;
  3. 找到資料所在的單元格;
  4. 利用pyteressact識別單元格的文字。

識別表格中的橫線

  識別橫線之前,我們先建立一個圖片表格識別類(ImageTableOCR),如下:

# -*- coding: utf-8 -*-
import cv2
import pytesseract
import numpy as np

class ImageTableOCR(object):

    # 初始化
    def __init__(self, ImagePath):
        # 讀取圖片
        self.image = cv2.imread(ImagePath, 1)
        # 把圖片轉換為灰度模式
        self.gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)

其中self.image為RGB模組的圖片,self.gray為灰度模式的圖片。
  接下來,我們識別圖片中的分割兩條記錄的橫線。注意到,相鄰兩條記錄之間的顏色是不一致的,因此,我們利用圖片灰度化後,每一行畫素的平均值的差的絕對值來作為相鄰兩條記錄的分割線,這樣就能檢測出分割兩條記錄的橫線了。具體的識別橫線的函式的Python程式碼如下:(接以上程式碼)

    # 橫向直線檢測
    def HorizontalLineDetect(self):

        # 影象二值化
        ret, thresh1 = cv2.threshold(self.gray, 240, 255, cv2.THRESH_BINARY)
        # 進行兩次中值濾波
        blur = cv2.medianBlur(thresh1, 3)  # 模板大小3*3
        blur = cv2.medianBlur(blur, 3)  # 模板大小3*3

        h, w = self.gray.shape

        # 橫向直線列表
        horizontal_lines = []
        for i in range(h - 1):
            # 找到兩條記錄的分隔線段,以相鄰兩行的平均畫素差大於120為標準
            if abs(np.mean(blur[i, :]) - np.mean(blur[i + 1, :])) > 120:
                # 在影象上繪製線段
                horizontal_lines.append([0, i, w, i])
                cv2.line(self.image, (0, i), (w, i), (0, 255, 0), 2)

        horizontal_lines = horizontal_lines[1:]
        # print(horizontal_lines)
        return horizontal_lines

首先對圖片進行二值化處理,再進行兩次中值濾波,這樣是為了使相鄰兩條記錄之間的畫素區別儘可能大。然後對該圖片中的每一行的畫素進行檢測, 以相鄰兩行的平均畫素差大於120為標準, 識別出分割兩條記錄的橫線。識別後的橫線如下:(圖片中的綠色線段)

識別橫線後的圖片

識別表格中的豎線

  在這一步中,我們利用opencv中的Hough直線檢測方法來檢測圖片中的豎線。完整的Python程式碼如下:(接以上程式碼)

    #  縱向直線檢測
    def VerticalLineDetect(self):
        # Canny邊緣檢測
        edges = cv2.Canny(self.gray, 30, 240)

        # Hough直線檢測
        minLineLength = 500
        maxLineGap = 30
        lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100, minLineLength, maxLineGap).tolist()
        lines.append([[13, 937, 13, 102]])
        lines.append([[756, 937, 756, 102]])
        sorted_lines = sorted(lines, key=lambda x: x[0])

        # 縱向直線列表
        vertical_lines = []
        for line in sorted_lines:
            for x1, y1, x2, y2 in line:
                # 在圖片上繪製縱向直線
                if x1 == x2:
                    print(line)
                    vertical_lines.append((x1, y1, x2, y2))
                    cv2.line(self.image, (x1, y1), (x2, y2), (0, 0, 255), 2)

        return vertical_lines

首先我們對灰度圖片進行Canny邊緣檢測,在此基礎上再利用Hough直線檢測方法識別圖片中的直線,要求識別的最大間距為30,線段長度最小為500,並且為豎直直線(x1 == x2),當然,也有一些人為的因素,那就是筆者自己添加了兩條豎直直線([[13, 937, 13, 102]],[[756, 937, 756, 102]])。執行上述方法,輸出的結果如下:

[[13, 937, 13, 102]]
[[75, 937, 75, 102]]
[[77, 937, 77, 102]]
[[270, 937, 270, 104]]
[[272, 937, 272, 102]]
[[756, 937, 756, 102]]

識別豎直直線後的圖片如下:(圖片中的紅色線段)

識別豎線後的圖片

可以看到,圖片六條豎直的線段都已經完整標記出來了。

識別圖片中的單元格

  在識別圖片中的單元格之前,我們先來識別每個單元格所在的頂點,也就是上述識別後的橫線與豎線的交點。完整的Python程式碼如下:(接以上程式碼)

    # 頂點檢測
    def VertexDetect(self):
        vertical_lines = self.VerticalLineDetect()
        horizontal_lines = self.HorizontalLineDetect()

        # 頂點列表
        vertex = []
        for v_line in vertical_lines:
            for h_line in horizontal_lines:
                vertex.append((v_line[0], h_line[1]))

        #print(vertex)

        # 繪製頂點
        for point in vertex:
            cv2.circle(self.image, point, 1, (255, 0, 0), 2)

        return vertex

頂點檢測後的圖片如下:(圖片中的藍色點即為每個單元格的頂點)

頂點檢測後的圖片

由此可見,我們識別出來的單元格的頂點是正確的。接著,我們把這些單元格取出來,程式碼如下:(接以上程式碼)

# 尋找單元格區域
    def CellDetect(self):
        vertical_lines = self.VerticalLineDetect()
        horizontal_lines = self.HorizontalLineDetect()

        # 頂點列表
        rects = []
        for i in range(0, len(vertical_lines) - 1, 2):
            for j in range(len(horizontal_lines) - 1):
                rects.append((vertical_lines[i][0], horizontal_lines[j][1], \
                              vertical_lines[i + 1][0], horizontal_lines[j + 1][1]))

        # print(rects)
        return rects

以第一個單元格為例,其影象如下:

第一個單元格的圖片

識別單元格的文字

  在識別出圖片中表格的單元格後,我們可以對該單元格圖片進行文字識別,我們使用的OCR工具為Teressact, 其Python的介面為pyteressact 。具體的Python程式碼如下:(接以上程式碼)

# 識別單元格中的文字
    def OCR(self):
        rects = self.CellDetect()
        thresh = self.gray

        # 特殊字元列表
        special_char_list = ' `[email protected]#$%^&*()-_=+[]{}|\\;:‘’,。《》/?ˇ'
        for i in range(20):
            rect1 = rects[i]
            DetectImage1 = thresh[rect1[1]:rect1[3], rect1[0]:rect1[2]]

            # Tesseract所在的路徑
            pytesseract.pytesseract.tesseract_cmd = 'C://Program Files (x86)/Tesseract-OCR/tesseract.exe'
            # 識別數字(每行第一列)
            text1 = pytesseract.image_to_string(DetectImage1, config="--psm 10")
            print(text1, end='-->')

            # 識別漢字(每行第二列)
            rect2 = rects[i+20]
            DetectImage2 = thresh[rect2[1]:rect2[3], rect2[0]:rect2[2]]
            text2 = pytesseract.image_to_string(DetectImage2, config='--psm 7', lang='chi_sim')
            text2 = ''.join([char for char in text2 if char not in special_char_list])
            print(text2, end='-->')

            # 識別漢字(每行第三列)
            rect3 = rects[i+40]
            DetectImage3 = thresh[rect3[1]:rect3[3], rect3[0]:rect3[2]]
            text3 = pytesseract.image_to_string(DetectImage3, config='--psm 7', lang='chi_sim')
            text3 = ''.join([char for char in text3 if char not in special_char_list])
            print(text3)

識別後的結果如下:

I-->度一-->開放的人一智慧服務平臺
2-->肌訊-->網際網路綜合服務
3-->標為-->人一智慧自動化業務、智慧屹片
4-->阿里巴巴-->網際網路綜合服務
5-->平安集口-->人T智慧金融研發平仄
6-->華大基因-->精準檢測、醫療資料運營服務
d-->搜狗-->綜合人T智慧解決方案平臺
8-->一科大訊飛-->智慧語音技術
9-->一中利創湯-->智慧終端平臺技術
10-->珍山集團-->SaaS級智慧營銷雲平臺
i-->商湯科技-->人工智慧視覺深度學習平臺
12-->神州泰嶽-->綜合類軟體產品及服務
13-->寒武紅科技-->深度學對專用的智慧盂片
14-->漢王科技-->文字識別技術與智慧交工
15-->全志刑技-->智慧晶片設計
16-->face曠視科技-->人T智慧產品和行業解夷方案
17-->創略科技-->智慧客戶資料平臺
18-->海雲資料-->企業級大資料整體運營與分析服務
19-->影渭科技-->視覺技術、智慧影像生產企業
20-->智蹈智慧-->智慧機器人技術提供和平臺運蕭

下面,我們來統計一下識別的準確率。在原來的表格中,一共是20個數字加上280個漢字(包括標點)加上10個英語字母(包括標點),對於識別的結果,其中數字類識別正確17個,漢字正確256個,英語字母8個,因此,總的識別的準確率為90.6% ,識別的結果還是可以的。

總結

  本文僅作為如何識別圖片中的表格的一個例子,希望能給讀者一些啟示。對於不同的圖片表格,需要具體問題具體分析。
  雖然筆者儘可能把整個過程寫得簡單明瞭,但其中的探索過程卻是很複雜的。而且值得注意的是,在本文中的檢測橫線的方法僅適用於相鄰兩條記錄有顏色差別的表格圖片。
  完整的Python程式碼如下,希望能給大家一些思考。

# -*- coding: utf-8 -*-
import cv2
import pytesseract
import numpy as np

class ImageTableOCR(object):

    # 初始化
    def __init__(self, ImagePath):
        # 讀取圖片
        self.image = cv2.imread(ImagePath, 1)
        # 把圖片轉換為灰度模式
        self.gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)

    # 橫向直線檢測
    def HorizontalLineDetect(self):

        # 影象二值化
        ret, thresh1 = cv2.threshold(self.gray, 240, 255, cv2.THRESH_BINARY)
        # 進行兩次中值濾波
        blur = cv2.medianBlur(thresh1, 3)  # 模板大小3*3
        blur = cv2.medianBlur(blur, 3)  # 模板大小3*3

        h, w = self.gray.shape

        # 橫向直線列表
        horizontal_lines = []
        for i in range(h - 1):
            # 找到兩條記錄的分隔線段,以相鄰兩行的平均畫素差大於120為標準
            if abs(np.mean(blur[i, :]) - np.mean(blur[i + 1, :])) > 120:
                # 在影象上繪製線段
                horizontal_lines.append([0, i, w, i])
                # cv2.line(self.image, (0, i), (w, i), (0, 255, 0), 2)

        horizontal_lines = horizontal_lines[1:]
        # print(horizontal_lines)
        return horizontal_lines

    #  縱向直線檢測
    def VerticalLineDetect(self):
        # Canny邊緣檢測
        edges = cv2.Canny(self.gray, 30, 240)

        # Hough直線檢測
        minLineLength = 500
        maxLineGap = 30
        lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100, minLineLength, maxLineGap).tolist()
        lines.append([[13, 937, 13, 102]])
        lines.append([[756, 937, 756, 102]])
        sorted_lines = sorted(lines, key=lambda x: x[0])

        # 縱向直線列表
        vertical_lines = []
        for line in sorted_lines:
            for x1, y1, x2, y2 in line:
                # 在圖片上繪製縱向直線
                if x1 == x2:
                    # print(line)
                    vertical_lines.append((x1, y1, x2, y2))
                    # cv2.line(self.image, (x1, y1), (x2, y2), (0, 0, 255), 2)

        return vertical_lines

    # 頂點檢測
    def VertexDetect(self):
        vertical_lines = self.VerticalLineDetect()
        horizontal_lines = self.HorizontalLineDetect()

        # 頂點列表
        vertex = []
        for v_line in vertical_lines:
            for h_line in horizontal_lines:
                vertex.append((v_line[0], h_line[1]))

        #print(vertex)

        # 繪製頂點
        for point in vertex:
            cv2.circle(self.image, point, 1, (255, 0, 0), 2)

        return vertex

    # 尋找單元格區域
    def CellDetect(self):
        vertical_lines = self.VerticalLineDetect()
        horizontal_lines = self.HorizontalLineDetect()

        # 頂點列表
        rects = []
        for i in range(0, len(vertical_lines) - 1, 2):
            for j in range(len(horizontal_lines) - 1):
                rects.append((vertical_lines[i][0], horizontal_lines[j][1], \
                              vertical_lines[i + 1][0], horizontal_lines[j + 1][1]))

        # print(rects)
        return rects

    # 識別單元格中的文字
    def OCR(self):
        rects = self.CellDetect()
        thresh = self.gray

        # 特殊字元列表
        special_char_list = ' `[email protected]#$%^&*()-_=+[]{}|\\;:‘’,。《》/?ˇ'
        for i in range(20):
            rect1 = rects[i]
            DetectImage1 = thresh[rect1[1]:rect1[3], rect1[0]:rect1[2]]

            # Tesseract所在的路徑
            pytesseract.pytesseract.tesseract_cmd = 'C://Program Files (x86)/Tesseract-OCR/tesseract.exe'
            # 識別數字(每行第一列)
            text1 = pytesseract.image_to_string(DetectImage1, config="--psm 10")
            print(text1, end='-->')

            # 識別漢字(每行第二列)
            rect2 = rects[i+20]
            DetectImage2 = thresh[rect2[1]:rect2[3], rect2[0]:rect2[2]]
            text2 = pytesseract.image_to_string(DetectImage2, config='--psm 7', lang='chi_sim')
            text2 = ''.join([char for char in text2 if char not in special_char_list])
            print(text2, end='-->')

            # 識別漢字(每行第三列)
            rect3 = rects[i+40]
            DetectImage3 = thresh[rect3[1]:rect3[3], rect3[0]:rect3[2]]
            text3 = pytesseract.image_to_string(DetectImage3, config='--psm 7', lang='chi_sim')
            text3 = ''.join([char for char in text3 if char not in special_char_list])
            print(text3)

    # 顯示影象
    def ShowImage(self):
        cv2.imshow('AI', self.image)
        cv2.waitKey(0)
        # cv2.imwrite('E://Horizontal.png', self.image)

ImagePath = 'E://AI.png'
imageOCR = ImageTableOCR(ImagePath)
imageOCR.OCR()

注意:本人現已開通微信公眾號: Python爬蟲與演算法(微訊號為:easy_web_scrape), 歡迎大家關注哦~~