漢字シャッフルクイズ
前回、漢字シャッフルクイズ作成プログラムでシャッフルする前の画像を出力する部分を作成しました。
今回はこの漢字シャッフルクイズのキモの部分。
画像をバラバラにして、シャッフルするプログラムを作成していきます。
まずおさらいですが、前回作成した部分はこんな感じ。
<セル1>
import random
import json
from matplotlib import pyplot as plt
import matplotlib.font_manager as fm
%matplotlib notebook
with open('./question.json') as f_in:
question_list = json.load(f_in)
question = question_list[random.choice(list(question_list))]
print(question)
with open('./fonts.json', 'r') as f_in:
fonts_list = json.load(f_in)
font_path = fonts_list[random.choice(list(fonts_list))]
print(font_path)
fp = fm.FontProperties(fname=font_path)
fig = plt.figure(figsize=(6,6))
plt.clf()
plt.text(0.5, 0.4, question, horizontalalignment='center', verticalalignment='center', fontsize=400, fontproperties=fp)
plt.axis('off')
plt.show()
plt.savefig(f'./question/test.png', pad_inches = 0)
実行結果
画像をバラバラにする
まずは画像をバラバラにしていきます。
画像を扱うライブラリといえば、前に紹介したPIL(Python Image Library)です。
ということでこんな感じ。
<セル2>
from PIL import Image
divide_num = 3
im = Image.open('./question/test.png')
w = im.size[0]
divide_size = int(w/divide_num)
print(divide_size)
left_upper = []
for i in range(divide_num):
for j in range(divide_num):
left_upper.append([i*divide_size,j*divide_size])
print(left_upper)
right_bottom = []
for i in range(1, divide_num+1):
for j in range(1, divide_num+1):
right_bottom.append([i*divide_size,j*divide_size])
print(right_bottom)
crop_list = []
for lu, rb in zip(left_upper, right_bottom):
crop_list.append([lu[0], lu[1], rb[0], rb[1]])
print(crop_list)
img_num = []
for i in range(len(crop_list)):
crop_im = im.crop(crop_list[i])
crop_im.save(f"./question/{i}.png")
img_num.append(i)
print(img_num)
PILのインポートから画像の読み込み、サイズの取得まで
まずはPILで画像を扱う準備ということで、PILをインポートして、画像の読み込み、サイズの取得を行っていきます。
from PIL import Image
divide_num = 3
im = Image.open('./question/test.png')
w = im.size[0]
divide_size = int(w/divide_num)
print(divide_size)
「from PIL import Image」がPILのインポート。
「divide_num = 3」は分割数です。
今回は縦横同じ分割数にするため、数字を一つだけ指定しています。
ちなみに3つ以上にすると難易度がグッと上がるので、3が一番いい難易度かなと思います。
「im = Image.open(‘./question/test.png’)」で前回作成したシャッフル前の画像を開いています。
「w = im.size[0]」でシャッフル前の画像の横のサイズを取得しています。
今回は縦横のサイズが同じなので、横幅だけ取得し、後の計算に用いています。
「divide_size = int(w/divide_num)」で分割した後のサイズ(ピクセル数)を計算しています。
画像を抽出する領域を取得
次に画像を分割する領域を取得していきます。
実は分割するといっても、実際に切って保存していくというよりも、元の画像から特定の部分を抜き出して保存するという方が感覚的に近いです。
最終的に使用するコマンドは「.crop([‘左の座標’,’上の座標’,’右の座標’,’下の座標’])」です。
ただ今回複雑なのは、複数の画像に分割する際、抜き出す領域をどうやって取得するかということでした。
元の画像に対して、抜き出す領域の点の座標を示してみるとこんな感じです。
例えば左上の領域を抜き出すときは[0, 0, 200, 200]という抜き出す左上の点と右下の点の座標が必要になります。
となると真ん中上の領域を抜き出すためには[0, 200, 200, 400]、右上の領域なら[0, 400, 200, 600]となるわけです。
ここが結構苦戦しまして、計算式にできなかったので、こんな感じのプログラムにしました。
left_upper = []
for i in range(divide_num):
for j in range(divide_num):
left_upper.append([i*divide_size,j*divide_size])
print(left_upper)
right_bottom = []
for i in range(1, divide_num+1):
for j in range(1, divide_num+1):
right_bottom.append([i*divide_size,j*divide_size])
print(right_bottom)
crop_list = []
for lu, rb in zip(left_upper, right_bottom):
crop_list.append([lu[0], lu[1], rb[0], rb[1]])
print(crop_list)
まず抜き出す画像の左上の座標を取得していきます。
つまりこの点。
プログラムだとこの部分です。
left_upper = []
for i in range(divide_num):
for j in range(divide_num):
left_upper.append([i*divide_size,j*divide_size])
変数divide_numが縦横それぞれで分割する数なので、横を分割するという意味で「for i in range(divide_num):」、さらに縦を分割するという意味で「for j in range(divide_num):」としています。
rangeの場合、数字を一つか指定しないと、0からその数字の一つ前までの整数のリストが返ってきます。
つまり「divide_num=3」の場合、[0, 1, 2]というリストが返ってくるわけです。
それを分割するサイズであるdivide_sizeと掛け算することで座標にしています。
次に抜き出す画像の右下の座標を取得していきます。
right_bottom = []
for i in range(1, divide_num+1):
for j in range(1, divide_num+1):
right_bottom.append([i*divide_size,j*divide_size])
座標の取得方法は先ほどの左上の座標を取得した時と同じですが、数値の範囲を「range(1, divide_num+1)」とすることで全体として右下に一つずつずれた座標を取得しています。
このように取得した左上、右下の座標を新しいリストに格納していっているのがこの部分。
crop_list = []
for lu, rb in zip(left_upper, right_bottom):
crop_list.append([lu[0], lu[1], rb[0], rb[1]])
print(crop_list)
これで「[[座標1の左,座標1の上,座標1の右下,座標1の左下], [座標2の左,座標2の上,座標2の右下,座標2の左下],…]」というリストを取得できます。
画像を抽出する
次に元の画像と先ほど取得した抜き出す領域の座標から、画像を抽出していきます。
もうすでに変数crop_listの中には座標がリストとして入っていますので、for文で一つずつ取得し、「im.crop(crop_list[i])」とすることで特定の領域の画像を取得できます。
img_num = []
for i in range(len(crop_list)):
crop_im = im.crop(crop_list[i])
crop_im.save(f"./question/{i}.png")
img_num.append(i)
print(img_num)
ここでわざわざ「range(len(crop_list))」としているのは、画像の番号を左上から1、2、3…として、その数字を画像のファイル名にするため、そして数字をリストに格納して、のちのちシャッフルするのに使うためです。
これを実行すると数字がファイル名になったこんな画像のファイルが生成されます。
これが先ほどの漢字の一部分というわけです。
画像をシャッフルして、結合させる
最後に画像をシャッフルして結合させていきます。
<セル3>
random.shuffle(img_num)
print(img_num)
new_im = Image.new('RGB', (w, w))
for i, size in zip(img_num, crop_list):
paste_img = Image.open(f'./question/{i}.png')
new_im.paste(paste_img, (size[0], size[1]))
new_im.save(f"./question/question.png")
まず先ほど取得した画像の番号のリストを「random.shuffle(‘リスト’)」でシャッフルします。
これにより元々は[0, 1, 2, 3, 4, 5, 6, 7, 8]と数字が並んでいたリストが例えば[1, 5, 0, 6, 3, 7, 8, 2, 4]という感じにシャッフルされます。
次にシャッフルした画像をつなげていくのですが、「つなげていく」というイメージではなく、新しいキャンバスを用意して、貼り付けていくというイメージの方が近いです。
そのためまず新しいイメージを「Image.new(‘カラーモデル’, (横のサイズ, 縦のサイズ)」で作成しています。
new_im = Image.new('RGB', (w, w))
wは元の画像の横幅のサイズで、今回は縦も同じです。
そしてシャッフルした画像の番号のリスト「img_num」を順番を変えていない抜き出した領域の座標「crop_list」をfor文で繰り返すことで、元とは違う場所に配置していきます。
for i, size in zip(img_num, crop_list):
paste_img = Image.open(f'./question/{i}.png')
new_im.paste(paste_img, (size[0], size[1]))
その場合もまずは貼り付ける画像ファイルを「Image.open(‘画像ファイルのパス’)」で読み込みます。
そして「.paste(貼り付ける画像, (貼り付ける場所の左の座標, 貼り付ける場所の上の座標))」で貼り付けます。
貼り付ける際には座標は「左」と「上」しか使いません。
最後に「new_im.save(f”./question/question.png”)」でquestion.pngとして保存しています。
ちなみにここまででできた画像はこんな感じです。
なかなかいい感じです。
次回はこのプログラムをもとにサーバーにアップロードするためのプログラムに変換していきます。
ではでは今回はこんな感じで。
コメント