質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.57%

OCR時に,大きさの違う文字が認識できない。

受付中

回答 0

投稿

  • 評価
  • クリップ 0
  • VIEW 2,575

TsumaW

score 10

 前提・実現したいこと

レシートを読み込み,合計金額を抽出したいです。

python3系でtesseractを用いてOCRし,レシートの内容をテキストファイルにしようとしていたのですが,合計という文字がどうしても認識できません。
文字が潰れない限りで2値化も行っているのですが,そもそも元のレシートにおいて合計という文字が他の文字より大きく横長に書いてあることが多いためだと思います。実際,部首とそれ以外のところが別の文字として認識されてしまいます。

 発生している問題・エラーメッセージ

文字認識した結果が以下のようになります。
""""
小 計 \③⑥0
刊 - が か ト * 社 ざ \①0
畑 言 一 \ ③ ⑤ 0
"""
畑言 一というところが本来,合計,と表示されるところです。

 該当のソースコード

import cv2
import matplotlib.pyplot as plt
import numpy as np
import os

画像を表示させる関数

def show_img(img):
plt.figure()
tmp = np.tile(img.reshape(img.shape[0],img.shape[1], -1),reps=3)
plt.imshow(tmp)
plt.show()

画像をトリミングする関数の定義

def transform_by4(img, points):
""" 4点を指定してトリミングする。 """
points = sorted(points, key=lambda x:x[1])  # yが小さいもの順に並び替え。
top = sorted(points[:2], key=lambda x:x[0])  # 前半二つは四角形の上。xで並び替えると左右も分かる。
bottom = sorted(points[2:], key=lambda x:x[0], reverse=True)  # 後半二つは四角形の下。同じくxで並び替え。
points = numpy.array(top + bottom, dtype='float32')  # 分離した二つを再結合。
width = max(numpy.sqrt(((points[0][0]-points[2][0])2)*2), numpy.sqrt(((points[1][0]-points[3][0])2)*2))
height = max(numpy.sqrt(((points[0][1]-points[2][1])2)*2), numpy.sqrt(((points[1][1]-points[3][1])2)*2))
dst = numpy.array([
numpy.array([0, 0]),
numpy.array([width-1, 0]),
numpy.array([width-1, height-1]),
numpy.array([0, height-1]),
], numpy.float32)

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

def convert(im, filename):
im = cv2.imread(im)
im_size = im.shape[0] * im.shape[1]

輪郭を抽出して切り取るための二値化

im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
cv2.imwrite(filename + "_gray.jpg", im_gray)
print(filename + "_gray.jpg")

im_blur = cv2.fastNlMeansDenoising(im_gray)  #画像のノイズ除去
im_th = cv2.adaptiveThreshold(im_blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, 5) #画像の二値化
show_img(im_th)
cv2.imwrite(filename + "_th.jpg", im_th)
print (filename+'_th.jpg')

輪郭を抽出

img, cnts, hierarchy = cv2.findContours(im_th, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) #輪郭はリスト内にnumpy配列で出力される
cnts.sort(key = cv2.contourArea, reverse=True ) #面積の大きい順のsort
cnt = cnts[1]
img = cv2.drawContours(img, [cnt], -1, (0,255,0), 3)
cv2.imwrite(filename+"_drawcont.jpg", img)
im_line = im.copy()
print("finish")

warp = None
flag = 1
for c in cnts[1:]:
arclen = cv2.arcLength(c, True) #輪郭の長さを検出
approx = cv2.approxPolyDP(c, 0.02*arclen, True) #輪郭を近似

近似した上で四角い輪郭に対して処理

if len(approx) == 4:
cv2.drawContours(im_line, [approx], -1, (0,0,255), 2)
if flag:
print(approx)
warp = approx.copy()
flag = 0 #一番大きな輪郭をwarpに組み込む
else:
cv2.drawContours(im_line, [approx], -1, (0, 255, 0), 2)

for pos in approx:
cv2.circle(im_line, tuple(pos[0]), 4, (255,0,0))

レシートっぽい輪郭の面積を算出

一定以上の大きさならトリミング

area = cv2.contourArea(warp)
print("area = ", area)
if area > im_size//5:
print("now cutting.....")
im_rect = transform_by4(im, warp[:,0,:]) #レシートの輪郭で切り取る。
cv2.imwrite(filename+'_rect.jpg', im_rect)
else:
return im

切り取ったら画像を表示する

plt.figure()
plt.imshow(im_line)
cv2.imwrite(filename+'_line.jpg', im_line)
print("warp = \n",warp[:, 0, :])
print(filename + '_rect.jp')

今度は文字抽出のためのグレースケール化

im_rect_gray = cv2.cvtColor(im_rect, cv2.COLOR_BGR2GRAY)
cv2.imwrite(filename + "_rect_gray.jpg", im_rect_gray)
print(filename + "_rect_gray.jpg")
show_img(im_rect_gray)

2値化

im_rect_blur = cv2.fastNlMeansDenoising(im_rect_gray)
im_rect_th = cv2.adaptiveThreshold(im_rect_blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
rect_th_filename = "{:s}_rect_th.jpg".format(filename)
cv2.imwrite(rect_th_filename, im_rect_th)
print (filename+'_rect_th.jpg')
show_img(im_rect_th)
return (im_rect_th)

実際に文字認識を行う

from PIL import Image
import sys
import pyocr
import pyocr.builders

def detect_txt(file):

tools = pyocr.get_available_tools()
if len(tools) == 0:
print("No OCR tool found")
sys.exit(1)

tool = tools[0]

txt = tool.image_to_string( # ここでOCRの対象や言語,オプションを指定する
Image.open('reciet1_rect_th.jpg'),
lang='jpn',
builder=pyocr.builders.TextBuilder()
)
txt = open("out.txt").read().replace('\\', '¥')

print(txt)

実行

if name == 'main':
detect_txt(convert("./reciet1.jpg", "reciet1"))   

python

 補足情報(FW/ツールのバージョンなど)

独学で勉強している初心者であり,teratailなどの質問場所を利用するのも初めてなので,ルールなど詳しくないのでご無礼ありましたら申し訳ございません。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正の依頼

  • y_waiwai

    2018/11/17 23:29

    現状ではコードが読めません。質門を編集して、<code>ボタン、’’’の枠の中にコードを貼り付けてください

    キャンセル

まだ回答がついていません

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.57%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る