その1 からの続きです。
格子の識別
フォーム画像の格子(32x16)は、3D LED CUBEのLEDに対応します。格子ごとに画素を読み込みその領域内にある画素の平均を代表値とする作戦です。その前段階として、フォーム画像から格子の矩形を抽出し、その矩形群をJsonで出力します(=次工程へのインプット)。
格子識別が目的なので、格子抽出に不要なトンボや文字を除いた画像を用意しました。
numpyの配列操作に慣れていないため、ソート等でかなり冗長なコードとなっています。。
# -*- 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() # 画像2
# 格子のブロブ(塊)の輪郭を抽出
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"
輪郭抽出のためにfindContoursの結果を二値化します。交点はいびつな形となっています。
これをもとにFindCoutourで輪郭抽出を行うと、結果は各点のピクセルの塊=ブロブを取得できます。
これらブロブの重心を求め、それを交点の座標としています。
以下は元画像に交点をマップした結果です。
うまく交点を検出できました。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の挙動はこちらが参考になります。
矩形情報はnumpyバイナリ形式とJsonで出力しています。
さて、これでようやくスキャン塗り絵の読み込む準備ができました。
その3へ続きます。