Pythonで碁盤目を画像から認識する(数独)



この記事は[Pythonで数独を解く](https://qiita.com/hatt_takumi/items/37a9c15b4e0ad2119ac6)の続きです
https://twitter.com/hatt_takumi/status/1230424166546886663?s=20
#碁盤目の画像認識
近年PythonでのAI開発がブームで、将棋などのボードゲームに関してはソースもよく見るようになった。
これらのソースに関しては共通して、"碁盤目の認識"というのが含まれているので、今回それを使わせていただいた。
偶然にも、将棋盤も数独も9x9の碁盤目なので、うまく流用できたと思う。


#流用したソース
今回はこちらを使わせていただいた。まずは感謝。

[将棋盤を画像認識する](https://analytics.livesense.co.jp/entry/2017/12/21/090000)

[こちらのGitHub](https://github.com/na-o-ys/shogi-camera)の`shogi-camera/shogicam/preprocess/`を、数独用に書き換えてimportして使用する。

#書き換え個所
_board_detection.py 37行目のcontoursを変更

```_board_detection.py
    # contours = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[1]
    contours = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[0]
```

_trim_board.py 5行目のBASE_SIZEを32に変更

```_trim_board.py
# BASE_SIZE = 64
BASE_SIZE = 32
```

_trim_board.py 7行目のwをBASE_SIZE * 15に変更

```_trim_board.py
# w = BASE_SIZE * 14
w = BASE_SIZE * 15
```

#ソースコード
```board_detection.py
import cv2
import numpy as np
from _detect_corners import *
from _trim_board import *

def show_fitted(img, x):
    cntr = np.int32(x.reshape((4, 2)))
    blank = np.copy(img)
    cv2.drawContours(blank, [cntr], -1, (0,255,0), 2)
    return blank

def split_image(img, x, y, line):
    height, width, channels = img.shape
    h = height//y
    w = width//x
    line_h = round(h*line)
    line_w = round(w*line)
    counter = 0
    for split_y in range(1, y+1):
        for split_x in range(1, x+1):
            counter += 1
            clp = img[ (h*(split_y-1))+line_h:(h*(split_y))-line_h, (w*(split_x-1))+line_w:(w*(split_x))-line_w]
            cv2.imwrite("./raw_img/{}.png".format(counter), clp)
    counter = 0

def draw_ruled_line(img, show=True):
    base_size = 32
    w = base_size * 15
    h = base_size * 15
    img = img.copy()
    for i in range(10):
        x = int((w / 9) * i)
        y = int((h / 9) * i)
        cv2.line(img, (x, 0), (x, h), (0, 0, 255), 1)
        cv2.line(img, (0, y), (w, y), (0, 0, 255), 1)
    if show:
        display_cv_image(img)
    return img

if __name__ == "__main__":
    raw_img = cv2.imread("./sudoku.png")
    fit_img = fit_size(raw_img, 500, 500)

    polies = convex_poly(fit_img, False)
    poly = select_corners(fit_img, polies)
    x0 = poly.flatten()
    img = show_fitted(fit_img, x0)

    rect, score = convex_poly_fitted(img)

    trimed = trim_board(raw_img, normalize_corners(rect) * (raw_img.shape[0] / img.shape[0]))
    lined = draw_ruled_line(trimed, False)

    cv2.imshow('image', fit_img)
    cv2.waitKey(0)
    cv2.imshow('image', img)
    cv2.waitKey(0)
    cv2.imshow('image', trimed)
    cv2.waitKey(0)
    cv2.imshow('image', lined)
    cv2.waitKey(0)

    split_image(trimed, 9, 9, 0)
```
'sudoku.png'として同階層に保存したpngファイルに対して操作する。
輪郭の検出で四角形を検出した後、ホモグラフィーで正面からの画像に変換している。([参考](https://cvtech.cc/homography/))
検出した後の操作は簡単で、等間隔に縦横9分割して、切り分けて保存するのみ。後でこれらの画像一枚一枚に対してOCRを行う。
(この後のフェースでまた述べるが、この処理だと罫線が邪魔でうまく認識できないため、少し工夫が必要)

#実行結果
こういった画像にたいして
![sudoku5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467051/a5d64458-b2b9-054c-1e1d-9777ca7ec9e6.png)
問題の輪郭を検出する
![num2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467051/b182d1f2-cfb5-e7c7-6a0f-4cbb649ac9a3.png)
検出した領域をホモグラフィーで正方形に変換
![sudoku7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467051/6b38003b-e6fb-2b76-e0e2-c84f59efc632.png)
縦横9x9に分割する(画像は線を引いて表示しただけ)
![sudoku8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467051/d676e7e2-fd16-1a3b-74d5-8e18825597a2.png)
raw_imgフォルダ以下に、切り離した画像が81枚保存される。
![num5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/467051/19e15ea6-c6e3-69d6-be83-b8b1078e9287.png)

#最後に
今回はほぼ流用させてもらってしまったので、あまり細かい中身まで踏み込めていない、、、
とりあえず、Pythonで数独を認識する、解く、ところまでできたので、時価はOCRで文字認識し、画像からデータ化までもっていく。


<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">そして<br>Pythonで数独を認識する。<br>これらを繋ぐと、、、<a href="https://twitter.com/hashtag/Python?src=hash&amp;ref_src=twsrc%5Etfw">#Python</a> <a href="https://twitter.com/hashtag/%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A8%E7%B9%8B%E3%81%8C%E3%82%8A%E3%81%9F%E3%81%84?src=hash&amp;ref_src=twsrc%5Etfw">#エンジニアと繋がりたい</a><a href="https://twitter.com/hashtag/%E9%A7%86%E3%81%91%E5%87%BA%E3%81%97%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A8%E7%B9%8B%E3%81%8C%E3%82%8A%E3%81%9F%E3%81%84?src=hash&amp;ref_src=twsrc%5Etfw">#駆け出しエンジニアと繋がりたい</a> <a href="https://t.co/nwSqmgjVVQ">pic.twitter.com/nwSqmgjVVQ</a></p>&mdash; たくみ@スカジャンのエンジニア (@hatt_takumi) <a href="https://twitter.com/hatt_takumi/status/1230424166546886663?ref_src=twsrc%5Etfw">February 20, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>