kazu22002の技術覚書

PHPer, Golang, AWS エンジニアの日々

python + opencvでカード認識 画像抽出

前回までの記事で輪郭の抽出までできました。

あとは抽出した部分のみの画像がほしいので、画像に保存できれば完成です。

画像の保存編です。

f:id:kazu22002:20210624033750p:plain

画像の保存

import cv2
cv2.imwrite('output_card.jpg', img)

opencvで読み込んだ画像インスタンスであればopencvの機能を利用して保存できます。簡単です。

射影変換

def transform_by4(img, points):
    """ 4点を指定してトリミングする。 """
    points = sorted(points, key=lambda x: x[1])  
    top = sorted(points[:2], key=lambda x: x[0])  
    bottom = sorted(points[2:], key=lambda x: x[0], reverse=True)  
    points = np.array(top + bottom, dtype='float32')  

    width = max(np.sqrt(((points[0][0] - points[2][0]) ** 2) * 2), np.sqrt(((points[1][0] - points[3][0]) ** 2) * 2))
    height = max(np.sqrt(((points[0][1] - points[2][1]) ** 2) * 2), np.sqrt(((points[1][1] - points[3][1]) ** 2) * 2))

    dst = np.array([
        np.array([0, 0]),
        np.array([width - 1, 0]),
        np.array([width - 1, height - 1]),
        np.array([0, height - 1]),
    ], np.float32)

    trans = cv2.getPerspectiveTransform(points, dst)  # 変換前の座標と変換後の座標の対応を渡すと、透視変換行列を作ってくれる。
    return cv2.warpPerspective(img, trans, (int(width), int(height)))  # 射影変換。

4点を取得することができれば、画像へ保存できます。

抽出した輪郭が4点にまで減らすことができれば保存することができるようになります。

現在の取得できている輪郭の点情報にポイントを画像に表示してみます。

f:id:kazu22002:20210624070057p:plain

輪郭がかなりの点で作られていることがわかります。この状態では画像に変換することができないため、点の情報を減らす処理を行います。

approxPolyDP 輪郭の近似

labs.eecs.tottori-u.ac.jp

複雑な形状をした輪郭を,より少ない数の点で表現できる単純な形状によって近似する事が出来ます.近似する点の数はユーザが指定できます.

点の絞り方は、数値により変更することが可能で今回は4点ほしいため、値を試して0.01を指定しています。

contours, hierarchy = cv2.findContours(img_gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
card_cnt = max(contours, key=cv2.contourArea)

epsilon = 0.01 * cv2.arcLength(card_cnt, True)
approx = cv2.approxPolyDP(card_cnt, epsilon, True)

点の数を絞った後の画像

f:id:kazu22002:20210624073123p:plain

赤い丸の部分が絞られた点の数になります。4つになっているので、求めている内容になりました。

あとは先ほどの射影変換を利用して切り抜いた画像インスタンスが返却されるため保存し完成です。

epsilon = 0.01 * cv2.arcLength(card_cnt, True)
approx = cv2.approxPolyDP(card_cnt, epsilon, True)

cut_img = react.transform_by4(img, approx[:, 0, :])

plt.imshow(cut_img)
plt.show()

f:id:kazu22002:20210624074936p:plain

かなりちゃんと切り抜けていると思います。opencvの関数を利用するだけでここまでできます。すごいですね。

まとめ

画像からカードの部分のみ抽出できたと思います。

背景と違う色であることは前提となりますが、名刺やカードの色で使い分けてもらう方向でいいと思います。

ユースケースによりますが、大体の枠で綺麗に取得できれば問題ないと思います。

ただ個人的なユースケースとしては次にOCRもやろうとしたことで、この切り抜きだけでは実際に使えそうになく、この後も試行錯誤しています。

問題点と取り組みについては別の記事に書きたいと思います。

インターネットの情報だけで結構なんでもできますね。

前の記事

kazu22002.hatenablog.com

kazu22002.hatenablog.com