TensorFlowのCNNチュートリアルで自筆の数字を認識させたら位置ズレに弱かったので対処した話

以前、順伝搬型ニューラルネットワーク (FFNN)による手書き数字認識を試しましたが、畳み込み型 (CNN)の威力を試したかったため、TensorFlowのチュートリアル(CNNによる数字認識)を試したところ、99.17%の認識率となりました。

  • 自分の手書き画像をネットワークに入力する

自分の手書き数字を認識させるため、外部画像をネットワークに入力する処理を追加します。
1. PILで画像のロード、サイズ変更
2. numpy列に変換、白黒反転
3. ネットワークに入力

from PIL import Image

img = Image.open(imgpath).convert('L')
img.thumbnail((28, 28))
img = np.array(img, dtype=np.float32)
img = 1 - np.array(img / 255)
img = img.reshape(1, 784)
res = sess.run(self.y_conv, feed_dict = {x : img, y_:[[0.0] * 10], keep_prob:1.0})[0]

※推論時はドロップアウトしないよう、keep_probに1.0を指定します。


早速試したところ、中心からズレると簡単に誤認識する事が解りました。
仕組み上CNNは多少の位置ズレに強いですが、プーリングサイズより大幅にズレると対応できなくなります。

f:id:kuranabe:20180812221515p:plain      f:id:kuranabe:20180812221518p:plain
認識結果:5                      認識結果:7

f:id:kuranabe:20180812221524p:plain      f:id:kuranabe:20180812221521p:plain
認識結果:8                      認識結果:6

  • 位置ズレの対処法

一般的な位置ズレの対処として「意図的に中心をズラした画像を学習させる」方法があります。もともとMNIST画像は文字の位置やサイズを整えた画像群ですので、位置ズレには位置ズレした画像を学習して対処する、といったところですね。
他にも回転や拡大縮小を意図的に入れて学習させる事もあります。

ところで、MNIST画像は黒背景に白文字として数字が描かれているため、背景要素と文字要素を簡単に分離できます。
そのため、「位置ズレ画像を学習させる」のではなく「画像の位置ズレを補正して入力する」方法で対処可能です。

  • 推論画像の位置ズレを補正する

現在の数字がどの程度ズレているのか把握のため、現在の文字の位置を割り出します。
入力画像は白背景に黒文字のため、画像の上下左右方向で白以外のピクセルが初めて登場する四点を結び、画像を枠で囲むイメージです。

img = Image.open(imgpath).convert('L')
__centering_img(img)

def __centering_img(img):
        
    imgW, imgH = img.size
    left, top, right, bottom = imgW, imgH, -1, -1
    imgpix = img.getdata()
    for y in range(imgH):
        yoffset = y*imgW
        for x in range(imgW):
            #if img.getpixel((x,y)) < 255: 遅いので使わない
            if imgpix[yoffset + x] < 255:
                if x < left: left   = x
                if y < top: top     = y
                if x > right: right = x
                if y > bottom: bottom = y
                
    # センタリングのシフト量を計算
    shiftX = int((left + (right - left) / 2) - width / 2)
    shiftY = int((top + (bottom - top) / 2) - height / 2)

    # センタリング(シフト量はマイナスとなる)
    return ImageChops.offset(img, -shiftX, -shiftY)

画素の取得では、非常に遅いgetpixel()に代わりgetdata()で得られた一次元データに対してアクセスします。
文字を囲う枠(left,top,right,bottom)に対して中心からのズレを求め、ズレと逆方向にImageChops.offsetで平行移動させれば完了です。

f:id:kuranabe:20180812234850p:plain  f:id:kuranabe:20180817210404p:plain 
   センタリング前      センタリング後

  • 位置ズレによる誤認識が解消

センタリングにより、最初に示した5,8の画像はもちろん
下記の様な極端なズレでも正しく認識されるようになりました。

f:id:kuranabe:20180817212008p:plain f:id:kuranabe:20180817212012p:plain f:id:kuranabe:20180817212015p:plain


手書き数字の例では決められた背景・枠内に入力するため、入力画像のセンタリングによる効果は絶大です。
自然画像が背景の場合は、文字位置の特定そのものが難しいため、単純に解決は難しいですね。