Python + OpenCVで画像処理　ドット塗り絵の読み込み [その2]

[その1](https://qiita.com/HERO_Arai/items/64b6d916d9a84fd5d76c) からの続きです。

## 格子の識別
フォーム画像の格子（32x16）は、3D LED CUBEのLEDに対応します。格子ごとに画素を読み込みその領域内にある画素の平均を代表値とする作戦です。その前段階として、フォーム画像から格子の矩形を抽出し、その矩形群をJsonで出力します(=次工程へのインプット)。

格子識別が目的なので、格子抽出に不要なトンボや文字を除いた画像を用意しました。
![test03_form.png](https://qiita-image-store.s3.amazonaws.com/0/203664/5dbbf002-79ec-daf0-f9db-1345d9753ec8.png)


numpyの配列操作に慣れていないため、ソート等でかなり冗長なコードとなっています。。

```python:test03.py
# -*- coding: utf-8 -*-
import numpy as np
import cv2
import codecs, json
from matplotlib import pyplot as plt

# Load an color image in grayscale
img = cv2.imread('3dledcube_02_form2.jpg',cv2.IMREAD_COLOR)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

dst = cv2.cornerHarris(gray,2,3,0.04)

#result is dilated for marking the corners, not important
dst = cv2.dilate(dst,None)

plt.imshow(dst, cmap = 'gray', interpolation = 'None')
plt.show() # 画像1

# FindContours support only 8uC1 and 32sC1 images
# convert gray image to binary image
tmp = np.zeros(dst.shape[:2], np.uint8)
tmp[ dst <= 0.01*dst.max() ] = [255]
tmp[ dst > 0.01*dst.max() ] = [0]

plt.imshow(tmp, cmap = 'gray', interpolation = 'None')
plt.show() # 画像２

# 格子のブロブ（塊）の輪郭を抽出
contours, hierarchy = cv2.findContours(tmp, cv2.RETR_TREE,  cv2.CHAIN_APPROX_SIMPLE) #輪郭の抽出
print "contours={}, ".format(len(contours)),  "hierarchy={}".format(len(hierarchy))

# ブロブ群から、各ブロブの重心＝格子の交点座標を求める
cnts = np.zeros((len(contours)-1, 4), dtype=np.uint32) # 格子の矩形配列 (x, y, width, heght)
for i,contour in enumerate(contours[1:]):# contours[0]は画像全体の輪郭となるので除く
    M = cv2.moments(contour) #モーメント
    cnts[i][0] = int(M['m10']/M['m00']) #重心X1
    cnts[i][1] = int(M['m01']/M['m00']) #重心Y
    cv2.circle(img,(cnts[i][0],cnts[i][1]), 5, (0,0,255), -1)

plt.imshow(img) # 画像3
plt.show()

# findContoursが見つけるブロブの順番は最外行列の頂点を反時計回りに抽出するとのこと。
# https://qiita.com/anyamaru/items/fd3d894966a98098376c
# 格子の幅と高さを求めるために格子を整列させる。まずはY座標でソート
cnts = cnts[cnts[:,1].argsort(), :] # sort by Y

tmp = np.zeros(dst.shape[:2], np.uint8)
tmp[ dst > 0.01*dst.max() ] = [255]
tmp[ dst <= 0.01*dst.max() ] = [0]
cv2.imshow('dst0',tmp)
print cnts[0:20]

# calculate witdh, height
rects = np.zeros((len(contours)-1, 4), dtype=np.uint32)
for row in range(32):
    # 行単位でX座標でソート
    r = cnts[row*17:(row+1)*17] # current row
    r = r[r[:,0].argsort(), :]  # sort by X
    # 次の行もX座標でソート
    r2 = cnts[(row+1)*17:(row+2)*17] # next row
    r2 = r2[r2[:,0].argsort(), :] # sort by X

    rects[row*17:row*17+17] = r[:]

    for col in range(16):
        rects[row*17+col][2] = r[col+1][0] - rects[col][0] 
        rects[row*17+col][3] = r2[col][1] - r[col][1]

print rects[0:20] 

# export as binary file.
np.save('rects.npy', rects)
a_new = np.load('rects.npy')
print "OK" if (rects == a_new).all() else "NG"

# export as json file.
b = rects.tolist() # nested lists with same data, indices
file_path = "rects.json" ## your path variable
json.dump(b, codecs.open(file_path, 'w', 
    encoding='utf-8'), separators=(',', ':'),
    sort_keys=False, indent=4) ### this saves the array in .json format

obj_text = codecs.open(file_path, 'r', encoding='utf-8').read()
b_new = json.loads(obj_text)
c_new = np.array(b_new)
print "OK" if (rects == c_new).all() else "NG"

```

最初にcornerHarrisで交点を検出します。
![画像1 : cornerHarris](https://qiita-image-store.s3.amazonaws.com/0/203664/e036a909-13d6-7e75-374b-adc4b16d58f3.png "画像1　cornerHarris")


輪郭抽出のためにfindContoursの結果を二値化します。交点はいびつな形となっています。
![画像2：cornerHarris rsult to bin](https://qiita-image-store.s3.amazonaws.com/0/203664/37f47786-6d1c-32ff-d242-08501077ca74.png "画像2 cornerHarrisの結果を二値化")

これをもとにFindCoutourで輪郭抽出を行うと、結果は各点のピクセルの塊＝ブロブを取得できます。
これらブロブの重心を求め、それを交点の座標としています。
以下は元画像に交点をマップした結果です。
![画像3：検出結果](https://qiita-image-store.s3.amazonaws.com/0/203664/a9acb81a-e7c1-2e2b-350d-7265a94b331b.png "画像3 検出結果")

うまく交点を検出できました。findContoursの結果はcontours=562となっています。交点の数は561=(16+1)*(32+1)なので1つ多いのですが、これはcontours[0]に画像全体をカウントしているためです。

さてこれら交点を使って矩形を求めます。交点は左上から順番に並んでいるとうれしいのですが、そうではありません。
print cnts[0:10]
[[ 367  217    0    0] # x, y, width, height
 [1347  217    0    0]
 [ 489  219    0    0]
 [ 428  219    0    0]
 [1286  219    0    0]
 [1225  219    0    0]
 [1102  219    0    0]
 [1041  219    0    0]
 [ 980  219    0    0]
 [1164  219    0    0]


格子の点から矩形を抽出するために、まずは列をソートしてから、行をソートしています。最終的に求めた結果です。
print rects[0:10] 
[[ 367  217   61   61] # x, y, width, height
 [ 428  219   61   59]
 [ 489  219   61   59]
 [ 550  219   62   59]
 [ 612  219   61   59]
 [ 673  219   61   59]
 [ 734  219   62   59]
 [ 796  219   61   59]
 [ 857  219   61   59]
 [ 918  219   62   59]
 [ 980  219   61   59]
 [1041  219   61   59]

findContoursの挙動は[こちら](https://qiita.com/anyamaru/items/fd3d894966a98098376c)が参考になります。

矩形情報は[numpyバイナリ形式](https://qiita.com/7of9/items/c730990479687ec2e959)とJsonで出力しています。

さて、これでようやくスキャン塗り絵の読み込む準備ができました。

[その3](https://qiita.com/HERO_Arai/items/3b57406bd81b87654f8f)へ続きます。