導入
OCR が最も読みやすいのは「文字=黒、背景=白」のくっきりした白黒画像です。中間の灰色を無くし、ある明るさ(しきい値)を境に白か黒へ振り分ける処理を 二値化(binarization) と呼びます。前処理の心臓部です。
説明
from PIL import Image, ImageDraw, ImageFont
import numpy as np
import cv2
import matplotlib.pyplot as plt
img = Image.new("RGB", (300, 100), "white")
draw = ImageDraw.Draw(img)
draw.text((15, 25), "Binary", fill=(60, 60, 60), font=ImageFont.load_default(size=48))
gray = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2GRAY)
# 方法1: 固定しきい値(128より明るい→白, 暗い→黒)
_, fixed = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)
# 方法2: 大津(Otsu)の方法(しきい値を自動で決める)
t, otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print("Otsuが選んだしきい値:", t)
fig, ax = plt.subplots(1, 3, figsize=(10, 3))
ax[0].imshow(gray, cmap="gray"); ax[0].set_title("gray"); ax[0].axis("off")
ax[1].imshow(fixed, cmap="gray"); ax[1].set_title("fixed 128"); ax[1].axis("off")
ax[2].imshow(otsu, cmap="gray"); ax[2].set_title("otsu auto"); ax[2].axis("off")
cv2.threshold(画像, しきい値, 最大値, 種類)… 二値化します。戻り値は(使ったしきい値, 二値化画像)の2つ。cv2.THRESH_BINARY… しきい値より明るいピクセルを最大値(255)に、暗いを 0 にします。cv2.THRESH_OTSUを足すと、画像の明るさの分布から最適なしきい値を自動計算してくれます(照明のムラに強い)。このとき最初のしきい値引数(0)は無視されます。
固定しきい値は手軽ですが、写真ごとの明るさの違いに弱いです。まず Otsu を試すのが定石です。
やってみよう
方法1の固定しきい値 128 を 200 や 60 に変えて、白黒の分かれ方がどう変わるか観察しましょう。しきい値が高すぎると文字がかすれ、低すぎると背景が黒くつぶれます。
演習
cv2.THRESH_BINARY を cv2.THRESH_BINARY_INV に変えて、白黒を反転した二値化(文字が白・背景が黒)を作って表示してください。
ヒント1を見る
方法1の行を _, fixed = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY_INV) に変えます。
ヒント2を見る
_INV(inverse=逆)を付けると、白と黒が入れ替わります。輪郭検出では文字を白にすると扱いやすくなります。