画像の表をPythonで分割してみた

2020-10-31

概要

PDFファイルにOCRを使ってみた
↑これの続き。

画像の表を行で分割するというのをやっていたのでそれを書きます。
分割した画像をOCRにかけるっていうのはやってないです。
https://github.com/shlia34/imageTableDivider

画像について

そもそも画像というのは、縦×横ごとに色の情報が入ってます。
https://your-3d.com/python-rgb/

例えば添付したpdfをpngに変換した画像データは

    pdfs = convert_from_path(pdf_path)
    img: PpmImagePlugin.PpmImageFile = pdfs[0]
    #画像に変換
    img_as_arr= np.asarray(img)
    # numpy形式
    print(img_as_arr.shape)

(2339, 1654, 3)と出力されます。これで2339行*1654列のデータということが確認できます。

さらに

print(img_as_arr[0][0])

[255 255 255]と出力され、0行目の0列目のマスはRGBの指定で白色ってことがわかります。
https://qiita.com/secang0/items/1229212a37d8c9922901

本題

本題ですが、表を行で画像を分割するために、線を捉える必要がありますが、
2339行*1654列をforでグルグル回して、横向きに黒色が一定数連続したら、それを線とする作戦をとってみます。

いったん、以下の条件でやってみます。

  • 黒が横に300続いたら線とする
  • 縦に50以内にある線は一本の線とする(pdfファイルの画像は傾いているので、実際の線が複数行に途切れ途切れまたいで存在してしまうため)

実行したコード↓

from PIL import PpmImagePlugin
# 型をつけるため
from pdf2image import convert_from_path
# pdfをimageに変換
import numpy as np
# numpy
import cv2
# openCV

pdf_path = 'pdf/test.pdf'

def main():
    pdfs = convert_from_path(pdf_path)
    # pdfをimageに変換

    for page in range(len(pdfs)):
        img: PpmImagePlugin.PpmImageFile = pdfs[page]
        grayscale_img_as_arr = convert_gray(img)
        # グレースケールに変換
        line_list = get_line_list(grayscale_img_as_arr)
        # 線があるy座標のリスト
        line_list = bundle_line(line_list)
        # 線がいつくかのy軸をまたいでしまってる。故にばらつきをなくす
        output_divided_img(page,line_list,grayscale_img_as_arr)
        # 出力

def convert_gray(img):
    img_as_arr= np.asarray(img)
    # 画像をnumpy形式に変換
    return cv2.cvtColor(img_as_arr, cv2.COLOR_BGR2GRAY)
    # RGB→グレースケールに変換(白黒のグラデーションは残ってる。昔の写真見たいな)

def get_line_list(grayscale_img_as_arr):
    line_list = []
    white_num = 255
    # y軸
    for y in range(len(grayscale_img_as_arr)):
        line_length_list = []
        # x軸
        line_length = 0
        for x in range(len(grayscale_img_as_arr[y])):
            if grayscale_img_as_arr[y][x] < white_num:
                line_length += 1
            else:
                line_length = 0
            line_length_list.append(line_length)

        line_length_list.sort()
        max_length = line_length_list[-1]

        # 黒が300以上続いてたら線と認定する
        if max_length  > 300:
            line_list.append(y)

    return line_list

def bundle_line(line_list):
    bundled_line_list = []
    # 線とする場所
    for i in range(len(line_list)):
        if i == 0:
            bundled_line_list.append(line_list[i])
        if line_list[i] - bundled_line_list[-1] > 50:
            # line_list[i]が現在のループ
            # bundled_line_list[-1]は前回線として認定した場所。
            # それが50より大きかった場合
            bundled_line_list.append(line_list[i])
    return bundled_line_list

def output_divided_img(page,line_list,grayscale_img_as_arr):
    img_width = grayscale_img_as_arr.shape[1]
    for i in range(len(line_list)):
        if i == 0:
            divided_image = grayscale_img_as_arr[0:line_list[i], 0:img_width]
            # 第一引数はY軸,第二引数はX軸。y軸は0から1個目の線までっていう意味。
            # pythonのスライスっていう機能
        else:
            divided_image = grayscale_img_as_arr[line_list[i-1]:line_list[i], 0:img_width]
            # y = 前回の線から今回の線
        cv2.imwrite(f'result/pdf{page}-{i}.png', divided_image)
        # pngとして出力。

if __name__ == '__main__':
    main()

全ページ以下みたいになったのでいい感じ。