1. wataoka

    No comment

    wataoka
Changes in body
Source | HTML | Preview
@@ -1,403 +1,403 @@
# 月で絵を描く
 最近、月の絵文字で絵を描くツイートがバズっていました。
月の絵文字とは以下の8種類です。
🌑
🌒
🌓
🌔
🌘
🌗
🌖
🌕
 これらの絵文字の濃淡を利用することで何かのロゴとか、オルガさんの「止まるんじゃねぇぞ...」とかを描くというわけです。
 ***これは自動化できる。。。***
 そう思った私は早速emacsとtermimalを立ち上げ、コーディングを開始しました。思っていたより完成は早く、朝の10時にコーディングを開始しお昼12時半には完成していたと思います。それでは手法とその結果を見ていきましょう。
# 手法
-## 流れ
+流れ
1. 画像を読み込む
2. 画像をグレースケールにする
3. 画質を落とす
4. 前処理
5. 画像の一部と月行列でアダマール積を取る(詳細は後で)
6. 5の最大値を取る月行列に対応する月を採用する
7. 5と6を画像の隅々にわたるまで繰り返す
## 1.画像を読み込む
※pythonのバージョンは3.6.4です。
```python
from PIL import Image
img = Image.open(img_file)
```
たったこれだけ。
## 2.画像をグレースケールにする
```python
img = img.convert('L')
```
たったこれだけ。
python最高。
## 3.画質を落とす
```python
width = 300
height = int(width*(img.height/img.width))
height -= height%4
img = img.resize((width, height))
```
さて、少し煩雑なコードになってしまいました。
正確に説明することが難しいので、やりたいことがわかってくれればそれでいいです。
やりたいことは比をできるだけ保ちながら、縦と横のピクセル数を**4の倍数にしたい**ということ。理由は、この後定義する月行列にあります。
### ◆月行列
 当然のことながら、月を何らかの数字に置き換えないとコンピュータは演算することができません。そこで私は次のような月行列を定義しました。
行列*A*を4x4の正方行列とし、
![texclip20180614162253.png](https://qiita-image-store.s3.amazonaws.com/0/221435/e66751e9-fc0d-e6e9-38b8-c954808d3eed.png)
![texclip20180614162223.png](https://qiita-image-store.s3.amazonaws.com/0/221435/22690221-23e9-a070-a34d-7f8bb085247c.png)
明るければ1、暗ければ-1とする。
例えば、
🌒に対応する月行列は、
![texclip20180614162914.png](https://qiita-image-store.s3.amazonaws.com/0/221435/e29b3123-3493-c87e-4c84-e8223e3e0506.png)
🌗に対応する月行列は、
![texclip20180614163002.png](https://qiita-image-store.s3.amazonaws.com/0/221435/16e0a08e-d658-d681-d90e-dd6d580bd498.png)
みたいな感じ。
これを事前に8種類分用意しておく。
```python
tsuki_0000 = np.matrix([[-1, -1, -1, -1],
[-1, -1, -1, -1],
[-1, -1, -1, -1],
[-1, -1, -1, -1]])
tsuki_0001 = np.matrix([[-1, -1, -1, 1],
[-1, -1, -1, 1],
[-1, -1, -1, 1],
[-1, -1, -1, 1]])
tsuki_0011 = np.matrix([[-1, -1, 1, 1],
[-1, -1, 1, 1],
[-1, -1, 1, 1],
[-1, -1, 1, 1]])
tsuki_0111 = np.matrix([[-1, 1, 1, 1],
[-1, 1, 1, 1],
[-1, 1, 1, 1],
[-1, 1, 1, 1]])
tsuki_1000 = np.matrix([[1, -1, -1, -1],
[1, -1, -1, -1],
[1, -1, -1, -1],
[1, -1, -1, -1]])
tsuki_1100 = np.matrix([[1, 1, -1, -1],
[1, 1, -1, -1],
[1, 1, -1, -1],
[1, 1, -1, -1]])
tsuki_1110 = np.matrix([[1, 1, 1, -1],
[1, 1, 1, -1],
[1, 1, 1, -1],
[1, 1, 1, -1]])
tsuki_1111 = np.matrix([[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]])
```
これら月行列を用いて画像から月描画を行います。なので、後々4の倍数となっていた方が特をするのです。
(説明適当ですみません)
そして、月行列のindexを上から0~7としました。index→絵文字とする関数も作っておきます。
```python
def index2tsuki(index):
if index==0:
return emoji.emojize(':new_moon:', use_aliases=True)
if index==1:
return emoji.emojize(':waxing_crescent_moon:', use_aliases=True)
if index==2:
return emoji.emojize(':first_quarter_moon:', use_aliases=True)
if index==3:
return emoji.emojize(':waxing_gibbous_moon:', use_aliases=True)
if index==4:
return emoji.emojize(':waning_crescent_moon:', use_aliases=True)
if index==5:
return emoji.emojize(':last_quarter_moon:', use_aliases=True)
if index==6:
return emoji.emojize(':waning_gibbous_moon:', use_aliases=True)
else:
return emoji.emojize(':full_moon:', use_aliases=True)
```
pythonでは絵文字を扱う時にはemojiというライブラリを使用するのが手っ取り早いかと思います。pipなどお手元のパッケージインストーラーでインストールしましょう。
話を戻します。
## 4.前処理
```python
img = np.matrix(img)
img = ((np.abs(img-255.) / 128.) - 1.0) * (-1)
```
これに関してもやりたいことだけ理解してください。
やりたいことは画像のピクセルに格納されている数値を0~255から-1~1に変換したいということです。あんまり脳みそ使わずに実験的に書いたので、細かいことは放っておいて下さい。。。
さて、ここまでで月描画における準備が完了しました。これらのコードを用いて月描画を行います。上までのコードを全て、`utils.py`というファイルに関数郡としてまとめておきます。
```python
# -*- coding: utf-8 -*- #
import emoji
import numpy as np
from PIL import Image
def load_tsuki_matrix():
"""
月行列を返す関数
Args:
無し
Return:
月行列8個, (numpyの行列のリスト)
"""
tsuki_0000 = np.matrix([[-1, -1, -1, -1],
[-1, -1, -1, -1],
[-1, -1, -1, -1],
[-1, -1, -1, -1]])
tsuki_0001 = np.matrix([[-1, -1, -1, 1],
[-1, -1, -1, 1],
[-1, -1, -1, 1],
[-1, -1, -1, 1]])
tsuki_0011 = np.matrix([[-1, -1, 1, 1],
[-1, -1, 1, 1],
[-1, -1, 1, 1],
[-1, -1, 1, 1]])
tsuki_0111 = np.matrix([[-1, 1, 1, 1],
[-1, 1, 1, 1],
[-1, 1, 1, 1],
[-1, 1, 1, 1]])
tsuki_1000 = np.matrix([[1, -1, -1, -1],
[1, -1, -1, -1],
[1, -1, -1, -1],
[1, -1, -1, -1]])
tsuki_1100 = np.matrix([[1, 1, -1, -1],
[1, 1, -1, -1],
[1, 1, -1, -1],
[1, 1, -1, -1]])
tsuki_1110 = np.matrix([[1, 1, 1, -1],
[1, 1, 1, -1],
[1, 1, 1, -1],
[1, 1, 1, -1]])
tsuki_1111 = np.matrix([[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]])
return [tsuki_0000, tsuki_0001, tsuki_0011, tsuki_0111,
tsuki_1000, tsuki_1100, tsuki_1110, tsuki_1111,]
def index2tsuki(index):
"""
indexから絵文字対応する絵文字を返す関数
Args:
index
Return:
絵文字
"""
if index==0:
return emoji.emojize(':new_moon:', use_aliases=True)
if index==1:
return emoji.emojize(':waxing_crescent_moon:', use_aliases=True)
if index==2:
return emoji.emojize(':first_quarter_moon:', use_aliases=True)
if index==3:
return emoji.emojize(':waxing_gibbous_moon:', use_aliases=True)
if index==4:
return emoji.emojize(':waning_crescent_moon:', use_aliases=True)
if index==5:
return emoji.emojize(':last_quarter_moon:', use_aliases=True)
if index==6:
return emoji.emojize(':waning_gibbous_moon:', use_aliases=True)
else:
return emoji.emojize(':full_moon:', use_aliases=True)
def preprocess(img_file):
"""
画像の前処理。グレスケール、画質下げ、その他前処理。
Args:
img_file: 画像のパス
Return:
img: 前処理が完了したnumpyの行列
"""
img = Image.open(img_file)
img = img.convert('L')
width = 300
height = int(width*(img.height/img.width))
height -= height%4
img = img.resize((width, height))
img = np.matrix(img)
img = ((np.abs(img-255.) / 128.) - 1.0) * (-1)
return img
```
この関数群を`main.py`から呼び出す形式をとります。
## 5.画像の一部と月行列でアダマール積を取る
ここが、今回の月描画の肝です。
アダマール積とは、下のような要素ごとの掛け算です。(Wikiより)
<img width="625" alt="スクリーンショット 2018-06-14 16.44.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/198eb2fe-5cad-8772-579f-986ad9dfa668.png">
```python
if __name__ == "__main__":
tsuki_matrixs = load_tsuki_matrix()
img = preprocess(IMAGE_URL)
```
先ほど定義したutils.pyの中の関数を用いて`tsuki_matrix`と`img`を用意します。ここで重要なことは、どちらの行列も**-1~1の範囲で、明るければ1に近く、暗ければ-1に近い**という性質を持つ行列であるということです。つまりアダマール積と取り、その合計で互いの類似度とうまく測ることができます。`tsuki_matrix`は4x4の行列なので、画像内の4x4の部分のみと類似度を測り、最も類似している月を採用します。それを画像内全ての位置で繰り返せばいいのです。
```python
for n, tk in enumerate(tsuki_matrixs):
hadamard = np.multiply(img[row:row+4, col:col+4], tk)
```
## 6. 5の最大値をとる月行列に対応する月を採用する
```python
max = -10000
max_tk = 0
for n, tk in enumerate(tsuki_matrixs):
hadamard = np.multiply(img[row:row+4, col:col+4], tk)
if max < hadamard.sum():
max_index = n
max = hadamard.sum()
tsuki_list.append(index2tsuki(max_index))
```
これで、画像内の4x4ピクセル部分の月が決定します。あとはこれを画像内全体で繰り返せばいいだけです。
## 7. 5と6を画像の隅々にわたるまで繰り返す
```python
# -*- coding: utf-8 -*- #
import sys
import numpy as np
from PIL import Image
from utils import load_tsuki_matrix, preprocess, index2tsuki
IMAGE_URL = "./images/画像名"
if __name__ == "__main__":
tsuki_matrixs = load_tsuki_matrix()
img = preprocess(IMAGE_URL)
tsuki_list = []
for i in range(int(np.shape(img)[0]/4)):
for j in range(int(np.shape(img)[1]/4)):
row = 4*i
col = 4*j
max = -10000
max_tk = 0
for n, tk in enumerate(tsuki_matrixs):
hadamard = np.multiply(img[row:row+4, col:col+4], tk)
if max < hadamard.sum():
max_index = n
max = hadamard.sum()
tsuki_list.append(index2tsuki(max_index))
tsuki_list.append('\n')
for i in tsuki_list:
sys.stdout.write(i)
```
これで全てのコーティングは終了です。あとは、ソースコード内の`画像名`の部分にお好みの画像を投げてあげれば、python君がせっせこ月で描画を始めてくれます。皆さんも、ぜひ試してみてください。
[ソースコードはこちら](https://github.com/wataoka/tsuki)
# 結果
オルガさん
<img width="1051" alt="スクリーンショット 2018-06-14 15.49.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/eb951a11-43b6-a5b2-ebfb-883bfe6a0112.png">
安倍さん
<img width="1404" alt="abesan.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/2f23ae6e-8131-1996-4f61-1e11b60b60a7.png">
<img width="1048" alt="スクリーンショット 2018-06-14 16.58.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/bb6c774b-5095-a505-6d9c-bfa44f004847.png">
なかなかの描画性能じゃないでしょうか。
これを用いて月クイズなんてものを作ってみましたので、よかったら最後にそれを解いてみて下さい。最後まで読んでいただきありがとうございました。🌚
# 月クイズ
※一応難易度順
## 第一問
<img width="1051" alt="スクリーンショット 2018-06-14 17.08.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/522c017c-cbd9-96f4-c39c-f75d44e15ccc.png">
## 第二問
<img width="1053" alt="スクリーンショット 2018-06-14 17.02.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/8867d8b4-5011-d036-bad7-74e2ce5b1ec6.png">
## 第三問
<img width="1052" alt="スクリーンショット 2018-06-14 17.11.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/e5cab987-0012-d15b-1ee5-820708b9242d.png">
## 第四問
<img width="1052" alt="スクリーンショット 2018-06-14 17.12.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/b6f87feb-74bb-91dd-3427-21fccd655b56.png">
## 第五問
<img width="1050" alt="スクリーンショット 2018-06-14 17.13.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/309f298c-e2e3-d285-f128-f5e8a06e92fd.png">