kazu22002の技術覚書

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

python + opencvでカード認識 精度向上

前回までの記事でカード認識した部分のみの画像を抽出できました。

抽出した結果からなにをしたいか。でどれくらいの精度が必要になるか考える必要があります。

カードを集めて、画像として見れればいい。というレベルであれば今回作った内容で十分使用できると思います。

今回のやりたいことにOCR機能も使えればいいなー。という願望もあったため若干精度が高いものを作る必要がでてきました。

問題点

抽出される画像の線を表示したもの

f:id:kazu22002:20210625040956p:plain

赤い点が抽出した4点になり、緑の線の部分で切り取りをしています。

緑の線を細かくみると少し欠けている部分を取得していることになります。

f:id:kazu22002:20210624074936p:plain

うまく取得できているように見えて実はすこし誤差が出ている状態ですね。

実はカードがすこし斜めの画像で取れてしまいます。

これは今回認識させようとしているカードの角が丸いことが主要な原因になりますが、世の中のカードは大体角が丸いですね。安全性のために丸くしているんですかね。

名刺のようにちゃんとした四角であれば、綺麗に切り抜けるかもしれませんが、カードを認識しようとすると発生する問題だと思います。

原因

なぜこの問題が発生するのか。

輪郭抽出の時点ではカード自体をしっかりと認識できています。

f:id:kazu22002:20210624045618p:plain

4点に絞ることで問題が発生しています。4点に絞るための処理は「輪郭の近似」を使用しています。

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

これは輪郭の点の情報からできるだけ点を減らしても似た輪郭を求めています。そのため、輪郭の点情報にない点からデータは作られないため、丸角のどれかの点を抽出している状態になります。

そのため取得される点によって斜めの画像になってしまいます。

ほとんどのカードで綺麗な認識ができないわけですね。

精度を求める場合はこの問題を解決する必要がありますが、どうインターネットで調べればいいかわかりません。カード部分の切り取りまで行っている人は多いですが、この問題を定義している人があまりいないため試行錯誤しかないですね。

試行錯誤1 参考記事を見つけた

とりあえず参考記事を探し、精度について対応している記事を発見。

vigne-cla.com

内容としては

  • 画像の重心を求める
  • 重心の位置から遠い地点の4点を取得する
  • きれいに画像が取得できる

こういうやり方もあるんですね。同じ仕組みで作ってみます。

labs.eecs.tottori-u.ac.jp

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

card_cnt = max(contours, key=cv2.contourArea)
cnt = card_cnt
M = cv2.moments(cnt)

cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])

重心を求めて表示

f:id:kazu22002:20210625050602p:plain

たしかに中心点っぽいですね。あとは中心点から輪郭の点すべての距離を計算して最大の4点をとればいいのかな。

ということで実装して作ってみたんですが、期待している点が取れないことが多かったです。(試したコードが見つからなかった)

なぜうまくとれないか考えてみると、重心点からの距離って丸角の場合どこが最大になるのかわからないし、一番離れている場所って大体同じ部分に偏るだろうという結論に至り断念。(左下に距離が最大の部分があれば、近い点が次点の距離の点になるんじゃない?綺麗に四角が取れるきがしない)

記事の人はかなりうまく実装ができているらしく、綺麗に画像が取れています。うらやましい。

試行錯誤2

もう背景の部分をちょうどカードと同じ色の四角で作ってしまい、擬似的に認識させる。

運用で乗り切る。という方法。

現実案ではあるが、若干の逃げの姿勢のためもう少し考えることにし、最終手段の候補に。

試行錯誤ではないが、検討したこととして。

試行錯誤3 とりあえず採用

四角でとれればいい。そのことだけを考えてどこの点を取得できると綺麗な画像にできそうかかんがる。

四角の辺の直線部分を伸ばして交差した点から画像ができれば完璧じゃね。とか考えて実現方法を検討し、実装。

実装結果

f:id:kazu22002:20210625055322p:plain

f:id:kazu22002:20210625055349p:plain

カードが曲がっているっぽいが、やろうとしていることを実現。

とりあえずここが自分の限界っぽいので、これを採用。カードの直線がちゃんとしてれば問題ないはず。

まとめ

これでカード認識については大体やれた感じです。

カードの認識まではかなり早くできたため楽しかったのですが、画像が斜めになる現象の原因と対応策でかなり悩みました。

論文とか探せばありそうですが、工夫次第でなんとかできるもんですね。

2,3週間ぐらい悩んで、思い浮かんだ方法をひたらすら試して成果が出た時はうれしいですね。

まぁ、苦しい時間のほうがはるかに長いので、記事にでもしておかねば。と久々にブログを書いている感じです。

たまに頑張ると記事を書きたくなりますね。続くか頑張ってみよう。

前の記事

kazu22002.hatenablog.com

kazu22002.hatenablog.com

kazu22002.hatenablog.com