#暇だったので…
漢字って割とバランス整ってると思いませんか?
(これは重心が関係してるのでは????暇だしちょっと調べてみるか〜)
と思い立ったのが運の尽き。
暇すぎて常用漢字全部の重心求めた
— 15種のヘルシーサラダ (@R01_G03_Y08) August 30, 2019
ほとんど真ん中でおもんない pic.twitter.com/wzoxsxvlfb
予想外に伸びてしまい後に引けなくなったのでちゃんと書くことにしました。
#方針
- PILで文字入りの画像を作成
- 2値のnuppy.arrayに変換
- 文字が中心に来るように調整する
- 重心を計算して点を打つ
#1. PILで文字入りの画像を作成
from PIL import Image, ImageDraw, ImageFont
def generate_char_img(char, fontname, size=(300, 300)):
img = Image.new('L', size, 255)
draw = ImageDraw.Draw(img)
fontsize = int(size[0] * 0.8)
font = ImageFont.truetype(fontname, fontsize)
# adjust character position.
char_displaysize = font.getsize(char)
offset = tuple((si - sc) // 2 for si, sc in zip(size, char_displaysize))
assert all(o >= 0 for o in offset)
# adjust offset, half value is right size for height axis.
draw.text((offset[0], offset[1] // 2), char, font=font, fill=0)
return img
PIL.ImageFontでフォントが扱えるみたいです。
ImageFont.truetype.getsizeでフォントサイズを取得してだいたい真ん中に描画します。
#2. 2値のnuppy.arrayに変換
重心を計算するために、
黒(グレースケールで0)→1,その他の色(1〜255)→0
のnumpy.arrayに変換します。
※計算しやすくするためここでは色が0以外のピクセルを無視します。誤差は気にしません。暇つぶしなので。
import numpy as np
def im_to_np(im):
im_array = np.asarray(im)
return np.array([[[0 if x else 1] for x in y] for y in im_array]).reshape(im_array.shape)
#3. 文字が中心に来るように調整する
文字を真ん中に配置していても、フォントサイズと文字の本当の大きさは違う(上下左右に空白部分が存在する)ので中心に来るように調整する必要があります。
any(list)はlistの中身が一つでもTrueの時Trueなのでx軸,y軸にスライスしたベクトルを代入することで文字の端っこを見つけて、マージンを計算して0行列の該当箇所に代入することで中心に配置しました。
def adjust_array(mat):
Y, X = mat.shape
top, left = mat.shape
bottom = 0
right = 0
for i, x in enumerate(mat):
if any(x):
top = i if top > i else top
bottom = i if bottom < i else bottom
for i, y in enumerate(mat.T):
if any(y):
left = i if left > i else left
right = i if right < i else right
char_area = mat[top:bottom, left:right]
height, width = char_area.shape
margin = ((Y - height) // 2, (X - width) // 2)
matrix = np.full(mat.shape, 0)
matrix[margin[0]:margin[0] + height, margin[1]:margin[1] + width] = char_area
return matrix
#4. 重心を計算して点を打つ
重心の計算は
M_{nm}=\Sigma^{画像全体}\left(x座標^n×y座標^m×画素値\right)
とすると、重心の座標 $G$ は
G=\left(\frac{M_{10}}{M_{00}}, \frac{M_{01}}{M_{00}}\right)
で与えられます。
def edit_image(im):
mat = adjust_array(im_to_np(im))
x = np.array([np.full(mat.shape[1], i) for i in range(mat.shape[0])])
y = np.array([range(mat.shape[1]) for i in range(mat.shape[0])])
M = {}
for n, m in [(0, 0), (1, 0), (0, 1)]:
M_nm = np.sum(np.power(x, n) * np.power(y, m) * mat)
M[str(n) + str(m)] = M_nm
G = (int(round(M["10"] / M["00"])), int(round(M["01"] / M["00"])))
im_adjusted = Image.fromarray(np.uint8(210 - mat * 210)).convert("RGB")
im_G = Image.fromarray(np.uint8(210 - mat * 210)).convert("RGB")
draw = ImageDraw.Draw(im_G)
r = im_G.size[0] / 25
draw.ellipse(((G[1] - r, G[0] - r), (G[1] + r, G[0] + r)), fill=(210, 50, 50))
return im_G, im_adjusted, G, M["00"]
調整したnumpy配列をRGB画像に変換→重心の位置に円を描画しました。
#Characterクラス
使いやすくするためclass定義
import os
import re
import gravity
class Character:
def __init__(self, char, font, size=(200, 200)):
self.char = char
self.font = font
self.size = size
self.font_name = re.findall(r'(.*/)?(.+)(\.[a-z]{3})', font)[0][-2]
self.image_raw = None
self.image_G = None
self.G = None
self.M = 0
def search_g(self):
im = gravity.generate_char_img(char=self.char, fontname=self.font, size=self.size)
edited = gravity.edit_image(im)
self.image_G, self.image_raw, self.G, self.M = edited
def save_images(self):
raw_directly = "output_png/{}".format(self.font_name)
if not os.path.exists(raw_directly):
os.makedirs(raw_directly, exist_ok=True)
self.image_raw.save("{}/{}_{}.png".format(raw_directly, self.font_name, self.char), 'png')
edit_directly = "output_G/{}".format(self.font_name)
if not os.path.exists(edit_directly):
os.makedirs(edit_directly, exist_ok=True)
self.image_G.save("{}/{}_{}_G.png".format(edit_directly, self.font_name, self.char), 'png')
def main(self):
self.search_g()
self.save_images()
#いざ実行
漢字は常用漢字,人名漢字,JIS第1,第2水準漢字から重複を除いたリストを用いました。(6735文字!)
フォントはヒラギノ明朝,ヒラギノ角ゴシック,Kleeの3種類です。
(当初は、ヒラギノ明朝,Osaka,KleeだったのですがOsakaは一部の漢字が豆腐化したのでやめました。)
from character import Character
from tqdm import tqdm
import re
import csv
with open("kanji.txt") as f:
characters = f.readlines()[0]
font_list = ["/System/Library/Fonts/ヒラギノ明朝 ProN.ttc",
"/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc",
"/System/Library/Assets/com_apple_MobileAsset_Font5/1765bae07bcefebc499ababa7ecdef963e4f151e.asset"
"/AssetData/Klee.ttc"]
if __name__ == '__main__':
for font in font_list:
with open("output/{}.csv".format(re.findall(r'(.*/)?(.+)(\.[a-z]{3})', font)[0][-2]), 'w') as f:
writer = csv.writer(f, lineterminator='\n')
writer.writerow(("character", "G_x", "G_y", "M", "Euclid"))
with tqdm(characters) as pbar:
for i, char in enumerate(pbar):
pbar.set_description("[{}:{}]" .format(i, char))
c = Character(char=char, font=font, size=(500, 500))
c.main()
writer.writerow((char, c.G[0], c.G[1], c.M, c.euclid))
各columnの内容は以下の通り
G_x | G_y | M | Euclid |
---|---|---|---|
重心のx座標 | 重心のy座標 | $M_{00}$(画像の重さ) | 中心との距離 |
#結果発表
やっと結果発表です、お疲れ様です。
###重心が偏ってる漢字top5
####全漢字
#####ヒラギノ明朝
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
3303 | 厂 | 176 | 194 | 11919 | 92.800862 |
3714 | 广 | 192 | 195 | 12383 | 79.931220 |
3284 | 匚 | 179 | 253 | 16545 | 71.063352 |
3734 | 廴 | 193 | 285 | 19819 | 66.887966 |
3624 | 尸 | 233 | 189 | 16871 | 63.324561 |
#####ヒラギノ角ゴシック
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
3303 | 厂 | 197 | 183 | 15587 | 85.428333 |
3734 | 廴 | 188 | 284 | 21880 | 70.710678 |
3624 | 尸 | 236 | 182 | 21906 | 69.426220 |
130 | 下 | 260 | 184 | 20691 | 66.753277 |
3714 | 广 | 205 | 204 | 15824 | 64.350602 |
#####Klee
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
3303 | 厂 | 204 | 182 | 13166 | 82.097503 |
3714 | 广 | 213 | 200 | 14470 | 62.201286 |
3624 | 尸 | 255 | 188 | 20860 | 62.201286 |
3734 | 廴 | 197 | 276 | 20071 | 59.033889 |
130 | 下 | 258 | 194 | 18921 | 56.568542 |
####常用漢字
#####ヒラギノ明朝
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
1073 | 仁 | 206 | 253 | 18033 | 44.102154 |
1042 | 心 | 243 | 292 | 21276 | 42.579338 |
245 | 刈 | 292 | 243 | 30032 | 42.579338 |
1499 | 刀 | 290 | 241 | 21582 | 41.000000 |
594 | 甲 | 243 | 210 | 27352 | 40.607881 |
#####ヒラギノ角ゴシック
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
130 | 下 | 260 | 184 | 20691 | 66.753277 |
246 | 干 | 248 | 195 | 24481 | 55.036352 |
1010 | 上 | 258 | 304 | 21260 | 54.589376 |
761 | 止 | 243 | 300 | 26012 | 50.487622 |
557 | 戸 | 243 | 200 | 29006 | 50.487622 |
#####Klee
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
130 | 下 | 258 | 194 | 18921 | 56.568542 |
1010 | 上 | 255 | 305 | 19259 | 55.226805 |
761 | 止 | 244 | 304 | 24055 | 54.332311 |
557 | 戸 | 245 | 197 | 26077 | 53.235327 |
594 | 甲 | 248 | 197 | 29236 | 53.037722 |
###重心が偏ってない漢字top5
####全漢字
#####ヒラギノ明朝
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
3001 | 粕 | 250 | 250 | 36773 | 0.0 |
4249 | 樶 | 250 | 250 | 44476 | 0.0 |
6279 | 鵝 | 250 | 250 | 45208 | 0.0 |
3986 | 攪 | 250 | 250 | 44978 | 0.0 |
2606 | 菖 | 250 | 250 | 33132 | 0.0 |
#####ヒラギノ角ゴシック
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
1420 | 勅 | 250 | 250 | 44996 | 0.0 |
1371 | 築 | 250 | 250 | 47119 | 0.0 |
3436 | 囓 | 250 | 250 | 55474 | 0.0 |
274 | 感 | 250 | 250 | 43700 | 0.0 |
6203 | 魍 | 250 | 250 | 55238 | 0.0 |
#####Klee
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
717 | 材 | 250 | 250 | 31767 | 0.0 |
3757 | 彷 | 250 | 250 | 31734 | 0.0 |
491 | 慶 | 250 | 250 | 46851 | 0.0 |
4997 | 紮 | 250 | 250 | 33211 | 0.0 |
6248 | 鰰 | 250 | 250 | 49405 | 0.0 |
####常用漢字
#####ヒラギノ明朝
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
2128 | 麓 | 250 | 250 | 45407 | 0.0 |
694 | 災 | 250 | 250 | 25713 | 0.0 |
1571 | 内 | 250 | 251 | 29141 | 1.0 |
47 | 因 | 250 | 249 | 35050 | 1.0 |
193 | 懐 | 250 | 249 | 42986 | 1.0 |
#####ヒラギノ角ゴシック
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
1504 | 豆 | 250 | 250 | 36011 | 0.0 |
1455 | 提 | 250 | 250 | 45763 | 0.0 |
1420 | 勅 | 250 | 250 | 44996 | 0.0 |
1371 | 築 | 250 | 250 | 47119 | 0.0 |
274 | 感 | 250 | 250 | 43700 | 0.0 |
#####Klee
character | G_x | G_y | M | Euclid | |
---|---|---|---|---|---|
491 | 慶 | 250 | 250 | 46851 | 0.0 |
159 | 箇 | 250 | 250 | 41478 | 0.0 |
1558 | 栃 | 250 | 250 | 37857 | 0.0 |
717 | 材 | 250 | 250 | 31767 | 0.0 |
816 | 持 | 249 | 250 | 36551 | 1.0 |
#感想
へぇ。って感じですね。
JIS水準漢字とかあまり知らなかったので知れて良かったです。暇は潰せました。では。
ソースコードと結果(CSV)