PythonとOCRを使って何かできないかということでいろいろと奮闘した記録
(OCRにはTesseract-OCRを使用)
まず、読み取る対象をまるっと1枚読ませてみると
ハンコや写真みたいなものまで頑張って読み上げてくれた結果、意味がわからない
読めてもデータとデータの切れ目がわからない
ということもあって使い物にならない。
そこで必要なところだけを切り出して読ませることにした。
以下のコードを書けば、画像ファイルから任意の場所を
切り出して、切り出した画像を別名で保存することができる。
from PIL import Image
# 画像をPILで開く
img_trim = Image.open('元の画像ファイル名')
# 指定した座標を切り出す
img_trim.crop((x1, y1, x2, y2)).save('切り出した画像の保存名')
このままでは、ほしい部分を切り出すのに何度も座標を調整する必要がある。
こそで座標をしている作業を効率化するためにマウス操作で座標を取得するアプリを作ってみる。
C#やVBなどGUIを作る手段はいろいろとあるが今回はKivyというPythonのGUIを作る仕組みを試してみる。
インストール作業は、pipでKivyをインストールすればよかったはず。。。(詳細は省略)
UI部分の定義(main.kv):
#:import hex_color kivy.utils.get_color_from_hex
<ImageWidget>:
canvas.before:
Color:
rgb: 1,1,1
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
orientation: 'horizontal'
height: root.height
width: root.width
Image:
id: img
allow_stretch: True
source: root.image_src
BoxLayout:
size: root.size
orientation: 'vertical'
width: 200
Label:
id: lbl_file_name
color: 0, 0, 0, 1
font_size: 20
background_color: hex_color('#000000')
Label:
id: lbl_result
color: 0, 0, 0, 1
font_size: 20
HTMLを簡略化したような書き方になっている
続いて本体のソース(main.py):
from kivy.app import App
from kivy.core.text import LabelBase, DEFAULT_FONT # 追加分
from kivy.config import Config
from kivy.resources import resource_add_path # 追加分
from kivy.properties import StringProperty
from kivy.uix.widget import Widget
from kivy.graphics import Line
from kivy.graphics import Color
from kivy.utils import get_color_from_hex
from PIL import Image
import math
import os
import pyocr
import pyocr.builders
resource_add_path('c:/Windows/Fonts') # 追加分
LabelBase.register(DEFAULT_FONT, 'msgothic.ttc') # 追加分
Config.set('graphics', 'width', '1224')
Config.set('graphics', 'height', '768') # 16:9
class ImageWidget(Widget):
image_src = StringProperty('')
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.image_src = 'read_img/0112-3.png'
self.ids.lbl_file_name.text = "ファイル名:\n{}".format(self.image_src)
self.lines = []
def on_touch_down(self, touch):
self.x1 = touch.x
self.y1 = touch.y
self.x2 = None
self.y2 = None
def on_touch_move(self, touch):
img = self.ids.img
if touch.x > img.width:
self.x2 = img.width
else:
self.x2 = touch.x
if touch.y > img.height:
self.y2 = 0
else:
self.y2 = touch.y
for line in self.lines:
self.canvas.remove(line)
self.lines = []
with self.canvas:
# 赤線にするための設定
Color(100, 0, 0)
touch.ud['line'] = Line(points=[self.x1, self.y1, self.x2, self.y1,
self.x2, self.y2, self.x1, self.y2],
close='True')
self.lines.append(touch.ud['line'])
# 破線にするための設定
Color(1, 1, 1)
touch.ud['line'] = Line(points=[self.x1, self.y1, self.x2, self.y1,
self.x2, self.y2, self.x1, self.y2],
dash_offset=5, dash_length=3,
close='True')
self.lines.append(touch.ud['line'])
def on_touch_up(self, touch):
# touch_moveイベントが発生していないときは終了する
if self.x2 is None:
return
# 初期化処理:
# IMGオブジェクトを取得する
img = self.ids.img
# リサイズされた画像のサイズを求める:
vs = img.norm_image_size
# 画像をPILで開く
img_trim = Image.open(self.image_src)
# 画像のサイズを得る
rs = img_trim.size
# 画像の縮尺を算出する
ratio = rs[0] / vs[0]
# かかっているパディングの値を求める:
# MEMO 中央ぞろえを前提としている(画像オブジェクトのサイズ - 表示サイズ) / 2で求めている
px = 0
py = 0
if img.width > vs[0]:
px = (img.width - vs[0]) / 2
if img.height > vs[1]:
py = (img.height - vs[1]) / 2
# IMGオブジェクトからパディングを除く
x1 = (self.x1 - px) * ratio
x2 = (self.x2 - px) * ratio
y1 = (img.height - self.y1 - py) * ratio
y2 = (img.height - self.y2 - py) * ratio
# 切り出し位置の座標のを小→大になるように並び替える
if x1 < x2:
real_x1 = math.floor(x1)
real_x2 = math.ceil(x2)
else:
real_x1 = math.floor(x2)
real_x2 = math.ceil(x1)
if y1 < y2:
real_y1 = math.floor(y1)
real_y2 = math.ceil(y2)
else:
real_y1 = math.floor(y2)
real_y2 = math.ceil(y1)
# 指定した座標を切り出す
img_trim.crop((real_x1, real_y1, real_x2, real_y2)).save('write_img/test.png')
# 画像から文字を読む
self.read_image_to_string()
def read_image_to_string(self):
try:
# 1.インストール済みのTesseractのパスを通す
path_tesseract = r"C:\Program Files\Tesseract-OCR"
if path_tesseract not in os.environ["PATH"].split(os.pathsep):
os.environ["PATH"] += os.pathsep + path_tesseract
# 1.OCRエンジンの取得
tools = pyocr.get_available_tools()
tool = tools[0]
# 2.原稿画像の読み込み
img = Image.open("write_img/test.png")
# 3.OCR実行
builder = pyocr.builders.TextBuilder(tesseract_layout=6)
result = tool.image_to_string(img, lang="jpn", builder=builder)
self.ids.lbl_result.text = f"読み取り結果:\n{result}"
print(result)
except Exception as ex:
print(ex)
self.ids.lbl_result.text = f"読み取り結果:\n失敗"
class MainApp(App):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.title = 'てすと'
def build(self):
return ImageWidget()
if __name__ == '__main__':
app = MainApp()
app.run()
プログラムの流れ:
クリックしたときにクリックしたポイントを得る
ドラッグしている間、四角の枠を描画し続ける
クリックを解除したときに終端の座標を取得し
画像を切り出して、OCRに画像を読ませるという処理を行っている
ポイント:
画面上のクリックした座標は、元の画像に当てはめても使えない
対比を考えて実際の座標の計算を行う必要がある
GUI上の画像オブジェクトにはパディングが入ることを考慮する必要がある
マウスのドラッグ方向が左上に囲むことも考慮する必要がある
OCRの読み取りの結果だが、、、
なんの調整も行わないで実行するとあまり精度がよくない
パラメータを調整したりいろいろとやることがありそう。
そもそもOCRの性能的にムリというのもあるかもしれない
今後は、OCRのエンジン選びなどを行ってみたいと思う