Python

[Python]表の位置情報をOpenCVで読み取る!

みなさん、こんにちは。どんぶラッコです。

今回は、OpenCVを用いて 表の場所を認識するプログラムを書いてみましょう!

と言ってもどういうことをするのかいまいちイメージがつきませんよね?

つまり、

このような表があったら…

このように、表の形になっている位置を取得することができるサンプルです。

x座標, y座標, 幅, 高さ を 辞書型でエクスポートしてあげると、より何をしているのかわかりやすいかもしれません。

今回は、このように表の位置を取得できるプログラムを一緒に書いていこうと思います!

今回参考にしたサイト

この記事を執筆するに当たって多くのサイトを参考にしたので、予めご紹介しておきます。

特にteratailのサイトのanswersには非常に助けられました!

ライブラリのインストール・インポート

まずopencvをインストールしていない場合は予めインストールしておきましょう。

# opencvをインストールしていない場合
pip install opencv-python

続いて今回使用するライブラリです。 cv2 というのが opencvのライブラリです。pip installした時の名前と違うので注意が必要です。

import cv2
from IPython.display import Image, display
import numpy as np
import copy
import json

関数の作成

後ほど使うことになる関数を予め作成しておきます。

# cf.) https://pystyle.info/opencv-canny/
def convert_nparr_to_image(img):
    ret, encoded = cv2.imencode(".jpg", img)
    display(Image(encoded))

後ほど説明しますが、今回は画像データをnumpy配列で取り扱います。この関すはjpgに配列をencodeして表示してくれるプログラムです。 jupyterlab ( notebook )でコーディングしている方は確認用に使いましょう。

def create_pos_data(contour):
    x, y, w, h = cv2.boundingRect(contour)
    return {'x': x, 'y': y, 'width': w, 'height': h}

contour ( 輪郭 ) 情報から x, y, width, height の情報を取り出します。 JSON形式にエクスポートするときに使います。

イメージを読み込む

下準備が終わったところで、画像を読み込みましょう。

img = cv2.imread('./assets/sample.jpg')
height, width, channels = img.shape

gray_img = cv2.imread('./assets/sample.jpg', cv2.IMREAD_GRAYSCALE)

cv2.imread() が画像を読み込むためのメソッドです。

img.shape には 高さ, 幅, チャンネル情報が格納されているので、 それぞれ height, width, channels に分割代入しています。

3行目ではcv2.IMREAD_GRAYSCALEオプションを指定して同じ画像を読み込んでいますね。このオプションを渡すことで、渡した画像を白黒にしてくれます。

どんぶラッコ
どんぶラッコ
今回の画像は元々白黒だからあんまり意味ないけど、普通の写真から表を認識するときには必要だよ!

読み込んだ img 変数を確認してみると、1ピクセル毎のRGB情報が多重配列で格納されている様子が確認できます。

どんぶラッコ
どんぶラッコ
gray_img 変数を試しに覗いてみると、 imgとは違って、輝度情報のみが格納されていることがわかるね!
グランくん
グランくん
ほんとだ、img変数の中身は [255,255,255], … のように3要素ある配列が複数並んでいたけれど、gray_imgの中身は255, …. のように数字だけだね!

Cannyを使ってエッジを検出する

threshold1 = 800
threshold2 = 800
edges = cv2.Canny(gray_img, threshold1, threshold2)

cv2.Canny() はエッジ検出をするためのメソッドです。

公式によると、こんな使い分けがあるようです。

Python:cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) → edgesC:void cvCanny(const CvArr* image, CvArr* edges, double threshold1,

Parameters:image – single-channel 8-bit input image.edges – output edge map; it has the same size and type as image .threshold1 – first threshold for the hysteresis procedure.threshold2 – second threshold for the hysteresis procedure.
どんぶラッコ
どんぶラッコ
公式も threshhold1, threshold2 って名前なんだね

こちらのQiita記事の言葉をお借りすると、thresholdが大きいほどエッジが検出されにくく、小さいほどエッジが検出されやすくなります。

違いを確認してみましょう。

threshhold を 10に設定してみた場合:

threshhold を 1000 に設定してみた場合:

今回は500で設定してみました。

膨張処理

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
dilates = cv2.dilate(edges, kernel)

次に膨張処理をかけます。

平たく言うと、線を太くします。

こうすることで境界線をわかりやすくしているんですね。

輪郭検出

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

さあ、いよいよ輪郭検出のロジックをかけていきます。

輪郭検出には cv2.findContours() を使います。

引数にはそれぞれ、 元画像, 輪郭の抽出モード, 輪郭検出方法 を指定します。

この関数の挙動については、参考サイトでもご紹介したこちらのサイトがわかりやすかったです。

元画像に検出結果を描画 & JSONエクスポート

# エクスポート用イメージ配列を作成しておく
export_img = copy.deepcopy(img)

# JSON用の辞書型配列
export_array = {
    'width': width,
    'height': height,
    'results': []
}

for i in range(len(contours)):
    # 色を指定する
    color = np.random.randint(0, 255, 3).tolist()
    
    if cv2.contourArea(contours[i]) < 3000:
        continue  # 面積が小さいものは除く
    
    # 階層が第1じゃなかったら ... 
    if hierarchy[0][i] != -1:
        # 配列に追加
        export_array['results'].append(create_pos_data(contours[i]))
        # 画像に当該の枠線を追加
        cv2.drawContours(export_img, contours, i, color, 3)  

ここでのポイントは cv2.contourArea()cv2.drawContours() です。

cv2.contourArea() では、検出した輪郭の大きさを算出してくれます。ある程度の大きさのものに絞ることで、ノイズを除去しています。

cv2.drawContours() を使うことで、画像に検出した輪郭の枠線を描画していっています。

どんぶラッコ
どんぶラッコ
cv2.drawContours() を使ったから、冒頭の画像のようなカラフルな枠線が描画されたんだね!

あとは、export_array 変数に地道に検出結果を append しています。

jsonにエクスポートする場合は、 json.dump() を使えば完了です。

fw = open('assets/output.json','w')
json.dump(export_array,fw,indent=2)
fw.close()

駆け足ではありますが、表検出のロジックでした!みなさんも試してみてください♪

ABOUT ME
どんぶラッコ
ECコンサルタント、システムエンジニアを経て、quintet株式会社CTOに就任。普段はNuxt.jsやLaravelを使用しています。

\面白いと思ったら/

記事のシェア & Twitter のフォロー をお願いします!

@proglearn
RELATED POST

COMMENT

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です