論文意訳: TableNet: Deep Learning model for end-to-end Table detection and Tabular data extraction from Scanned Document Image
issue: 2020.01 url: https://arxiv.org/pdf/2001.01469.pdf(https://arxiv.org/pdf/2001.01469.pdf)
問題提起
文書の画像データを扱う機会が増えたけど、人力で文字を読んで入力したりするのは大変
テーブル形式の文書データが増えた
テーブルにも色々なレイアウトがあり、そこから文字を機械的に検出するの難題
今ある手法はほとんどが
1) テーブル範囲の検出
2) テーブルの構造の検出
のどちらかを別々にやってるが、それぞれのタスクはそれぞれ補完しうるで、
マルチタスクで解けばいいのではないか
解決案
マルチタスクでEnd2Endにテーブル検出できるモデル「**TableNet** 」を提案
このモデルでは以下を検出
1) テーブルの範囲
2) テーブル内のカラムのセグメンテーション、その後ルールベースで行検出し、セルを取得
エンコーダーとしてImageNet datasetで学習ずみのVGG19を使用
エンコーダーの出力を二種類のデコーダーに渡してテーブル範囲と列を検出
モデル構造

実装
環境
Intel(R) Xeon(R) Silver CPU having 32 cores
and RAM of 128 GB Tesla V100-PCIE-1 GPU with 6GB
of GPU memory.
ライブラリ tensorflow
学習
結果
既存の手法より少しだけ精度向上
ネットワークの構造
参考文献
15) Fully Convolutional Networks for Semantic Segmentation(https://www.cv-foundation.org/openaccess/content_cvpr_2015/papers/Long_Fully_Convolutional_Networks_2015_CVPR_paper.pdf)
CNN:畳み込みニューラルネットワーク
##LeNet
###概要
1998年、Yann LeCun氏が発案
2014年
①VGG16
import keras from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten from keras.models import Model inputs = Input(shape=(224, 224, 3)) x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(inputs) x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='valid', name='block1_pool')(x) x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv1')(x) x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv2')(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='valid', name='block2_pool')(x) x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv1')(x) x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv2')(x) x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv3')(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='valid', name='block3_pool')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv1')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv2')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv3')(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='valid', name='block4_pool')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv1')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv2')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv3')(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='valid', name='block5_pool')(x) flattened = Flatten(name='flatten')(x) x = Dense(4096, activation='relu', name='fc1')(flattened) x = Dense(4096, activation='relu', name='fc2')(x) predictions = Dense(1000, activation='softmax', name='predictions')(x) model = Model(inputs=inputs, outputs=predictions) model.summary()
②AlexNet

import keras from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten, BatchNormalization from keras.models import Model inputs = Input(shape=(227, 227, 3)) # Block 1 x = Conv2D(96, (11, 11), padding='valid', strides=(4, 4), activation='relu', name='conv1')(inputs) #out:55*55*96 x = MaxPooling2D((3, 3), padding='valid', strides=(2, 2), name='pool1')(x) #out:27*27*96 x = BatchNormalization(name='norm1')(x) # Block 2 x = Conv2D(256, (5, 5), padding='same', strides=(1, 1), activation='relu', name='conv2')(x) #out:27*27*256 padding:2 x = MaxPooling2D((3, 3), padding='valid', strides=(2, 2), name='pool2')(x) #out:13*13*256 x = BatchNormalization(name='norm2')(x) # Block 3 x = Conv2D(384, (3, 3), padding='same', strides=(1, 1), activation='relu', name='conv3')(x) #out:13*13*384 padding:1 x = Conv2D(384, (3, 3), padding='same', strides=(1, 1), activation='relu', name='conv4')(x) #out:13*13*384 padding:1 x = Conv2D(256, (3, 3), padding='same', strides=(1, 1), activation='relu', name='conv5')(x) #out:13*13*256 padding:1 x = MaxPooling2D((3, 3), padding='valid', strides=(2, 2), name='pool3')(x) #out:6*6*256 # Classification block flattened = Flatten(name='flatten')(x) # out:9216 x = Dense(4096, activation='relu', name='fc1')(flattened) # out:4096 x = Dense(4096, activation='relu', name='fc2')(x) # out:4096 predictions = Dense(1000, activation='softmax', name='predictions')(x) model = Model(inputs=inputs, outputs=predictions) model.summary()
③GoogLeNet
④ResNet
pandasのread_csvで指定列を読み込む
usecolsオプションを使えばOK
例
pd.read_csv('file_name', usecols=['col1', 'col2'])
openCVで台形補正
opencvを使って台形補正する方法を紹介
台形補正とは?
もともと四角形だったものを斜めから撮影すると遠近によるゆがみで台形に見える。
この遠近による歪みをなくし、真上から見た元の四角形の形に補正する。
やり方
①getPerspectiveTransformで透視変換行列を計算
getPerspectiveTransformでは元画像の変換したい台形の4つの頂点座標と変換後の画像サイズを入力して、変換行列を出力する。
変換行列の定義は以下

引用:
opencv.jp
[変換前のx座標,変換前のy座標,1]
を追加したベクトルの変換行列を掛けると、
[変換後のx座標*t,変換後のy座標*t,t]になる
②warpPerspectiveで補正
warpPerspectiveは、元画像、変換行列、変換後の画像サイズを入力して、変換後の画像を出力する
①で求めた透視変換行列を使う
コード
# ---------------------------------------
# 透視変換行列取得
# ---------------------------------------
def get_transformMatrix(transformArea, transformSize):
"""
Args:
transformArea: 4点座標[top-left,top-right,down-right,down-left]
transformSize: 変換後の画像サイズ(横,縦)
Return:
transformMatrix: 透視変換行列
"""
pts1 = np.float32(transformArea)
pts2 = np.float32([[0, 0], [transformSize[0], 0], [transformSize[0], transformSize[1]], [0, transformSize[1]]])
transformMatrix = cv2.getPerspectiveTransform(pts1, pts2)
return transformMatrix
# --------------------------------------------
# 補正画像出力
# --------------------------------------------
def perspectiveTransform(img, transformMatrix, transformSize):
"""
Args:
img:元画像
transformMatrix: 透視変換行列
transformSize: 変換後の画像サイズ(横,縦)
Return:
transformImage: 変換後の画像
"""
transformImage = cv2.warpPerspective(img, transformMatrix, transformSize)
return transformImage
ニューラルネットの活性化関数
STEP
式
特徴
コード
def step(x):
if x > 0:
return 1
else:
return 0
プロット

relu
式
特徴
- よく使われる
- シンプル
- 勾配消失が防げる
- 計算コストが低い
- 性能がいい
コード
def relu(x): return max(0,x)
プロット

Leaky Relu
式
特徴
- Reluの0以下の場合に0にせず、傾斜をつけて出力
コード
def leaky_relu(x, α):
if x>0:
return x
else:
return α*x
プロット

Sigmoid
式
特徴
- 0から1に正規化する
コード
def sigmoid(x): return 1/(1 + e(-x))
プロット

Tahn
式
特徴
- -1から1で正規化
コード
def tanh(x): return (1-exp(-2*x))/(1+exp(-2*x))
プロット

Hard Tahn
式
特徴
- 0から1の範囲はそのまま
- 1以上は1,0以下は0、
コード
def hard_tanh(x):
if x > 1:
return 1
elif x < -1:
return -1
else:
return x
プロット

Softplus
式
特徴
- あまり使われない
コード
def softplus(x): return log(1+exp(x))
プロット

Softplus
式(例:ガウス関数)
特徴
- あまり使われない
- ある値を中心に左右対称
- 放射基底関数ともいう
- ガウス関数が代表的
コード
def rbf_gaussian(x, α, β): return exp(-α*(β - x)**2)
プロット

プロッまとめ

文字列から数字取得
文字列から数字のみ取り出すコード
よく使うのでメモ
def getNum_fromStr(line):
return ''.join(re.findall('[0-9]', line))
tkinter使ってみた
プログラムの実行をguiにしたいときに手軽に組める。
以下サンプル
よく使う入力枠、ドロップダウンリスト、ラジオボタンの例
import tkinter as tk
from tkinter import messagebox as tkm
from tkinter import ttk
def show(line):
tkm.showinfo('sample', line)
root = tk.Tk()
root.title(u'Sample')
root.geometry('300x300')
# 入力ボックス
label1 = ttk.Label(text=u'入力')
label1.pack()
entry = ttk.Entry(width=20)
entry.pack(pady=(0,5))
# ドロップダウンリスト
label2 = ttk.Label(text=u'ドロップダウンリスト')
label2.pack()
combo = ttk.Combobox(width=15, state='readonly')
combo["values"] = ['ちょえーーーー!!!','くわっ!!!','はーーーーーっ!!!','ふひふふっひいいい!!!']
combo.current(0)
combo.pack(pady=(0,5))
# ラジオボタン
label3 = ttk.Label(text=u'ラジオボタン')
label3.pack()
var = tk.StringVar()
radio1 = ttk.Radiobutton(text=u"1", variable=var, value='キタ━─━─━ヾ(o✪‿✪o)シ━─━─━ッ♪')
radio1.pack()
radio2 = ttk.Radiobutton(text=u"2", variable=var, value='( `)3′)▃▃▃▅▆▇▉ブブブーブォォオ')
radio2.pack(pady=(0,5))
#ボタンを押すと関数呼び出し
button1 = tk.Button(text=u'入力ボックス表示', width=20, height=2, command=lambda: show(entry.get()))
button1.pack(pady=(0,5))
button2 = tk.Button(text=u'ドロップダウンリスト表示', width=20, height=2, command=lambda: show(combo.get()))
button2.pack(pady=(0,5))
button3 = tk.Button(text=u'ラジオボタン選択表示', width=20, height=2, command=lambda: show(var.get()))
button3.pack(pady=(0,5))
root.mainloop()
タブを使う場合
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as tkm
def show(line):
tkm.showinfo('test', line)
class set_tab1():
def __init__(self, tab):
self.var = tk.StringVar()
self.tab = tab
self.set_entry()
self.set_button()
def set_entry(self):
frame = tk.Frame(self.tab)
frame.pack(pady=(10, 20))
label = ttk.Label(frame, text=u'エントリー', width=10)
label.pack(side='left', anchor=tk.W, padx=(0, 10))
entry = ttk.Entry(frame, textvariable=self.var, width=25)
entry.pack(side='left', anchor=tk.W)
def set_button(self):
Button = tk.Button(self.tab, text=u'表示', width=20, height=3, command=lambda: show(self.var.get()))
Button.pack()
class set_tab2():
def __init__(self, tab):
self.var = tk.StringVar()
self.tab = tab
self.set_dropdown()
self.set_button()
def set_dropdown(self):
frame = tk.Frame(self.tab)
frame.pack(pady=(10, 20))
label = ttk.Label(frame, text=u'ドロップダウン', width=10)
label.pack(side='left', anchor=tk.W, padx=(0, 10))
combo = ttk.Combobox(frame, width=20, state='readonly')
combo["values"] = ['ちょえーーーー!!!', 'くわっ!!!', 'はーーーーーっ!!!', 'ふひふふっひいいい!!!']
combo.current(0)
combo.pack(side='left', anchor=tk.W)
self.var = combo
def set_button(self):
Button = tk.Button(self.tab, text=u'表示', width=20, height=3, command=lambda:show(self.var.get()))
Button.pack()
class set_tab3():
def __init__(self, tab):
self.var = tk.StringVar()
self.tab = tab
self.set_radio()
self.set_button()
def set_radio(self):
frame = tk.Frame(self.tab)
frame.pack(pady=(10, 20))
label = ttk.Label(frame, text=u'ラジオボタン', width=10)
label.pack(side='left', anchor=tk.W, padx=(0, 10))
radio1 = ttk.Radiobutton(frame, text=u"1", variable=self.var, value='キタ━─━─━ヾ(o✪‿✪o)シ━─━─━ッ♪')
radio1.pack(side='left', padx=(0, 10))
radio2 = ttk.Radiobutton(frame, text=u"2", variable=self.var, value='( `)3′)▃▃▃▅▆▇▉ブブブーブォォオ')
radio2.pack(side='left')
def set_button(self):
Button = tk.Button(self.tab, text=u'表示', width=20, height=3, command=lambda:show(self.var.get()))
Button.pack()
if __name__ == '__main__':
root = tk.Tk()
root.title(u'サンプル')
root.geometry('400x250')
root.resizable(False, False)
# ノートブック
nb = ttk.Notebook(width=100, height=300)
# タブ作成
tab1 = ttk.Frame(nb)
tab2 = ttk.Frame(nb)
tab3 = ttk.Frame(nb)
nb.add(tab1, text='エントリー', padding=3)
nb.add(tab2, text='ドロップダウン', padding=3)
nb.add(tab3, text='ラジオボタン', padding=3)
nb.pack(expand=1, fill='both')
# タブ配置
set_tab1(tab1)
set_tab2(tab2)
set_tab3(tab3)
root.mainloop()
以下のサイトがめちゃわかりやすい
Introduction · tkinter