#!/usr/bin/env python # coding: utf-8 # # @ftnext (nikkie) # # Pillowで画像処理してモザイクアートを作る # In[9]: color_data = materials_list_from_file('average_color.csv') icon_im = Image.open('my_icon.png') display_image(icon_im) icon_im_width, icon_im_height = icon_im.size mosaic_icon_im = Image.new('RGBA', (1600, 1600)) for left in range(0, icon_im_width, DOT_AREA_ONE_SIDE): for top in range(0, icon_im_height, DOT_AREA_ONE_SIDE): average_color = calc.average_color_in_range(icon_im, left, top, left+DOT_AREA_ONE_SIDE, top+DOT_AREA_ONE_SIDE) if len(average_color) != 3: continue filename = similar_color_filename(average_color, color_data) # 距離最小のファイルを縮小して1600×1600の画像に貼り付け area_im = Image.open('image/euph_part_icon/'+filename) area_im.thumbnail((THUMBNAIL_ONE_SIDE, THUMBNAIL_ONE_SIDE)) mosaic_icon_im.paste(area_im, (left//DOT_AREA_ONE_SIDE * THUMBNAIL_ONE_SIDE, top//DOT_AREA_ONE_SIDE * THUMBNAIL_ONE_SIDE)) save_path = 'product/my_icon_mosaic_mean.png' mosaic_icon_im.save(save_path) # Display Image ##### @@@@@ ##### #画像の読み込み im = Image.open(save_path) display_image(im) # # #rettypy #7 (2018/02/10) でやったこと # # - モザイクアートの前処理のプログラムをクラスを使って書き直し # - Slackのアイコンでモザイクアートが作れなかった件の原因調査 # ## モザイクアートの前処理のプログラムをクラスを使って書き直し # ### before # calculate_average_color.py # ```python # import os # import csv # # from PIL import Image # # from mosaic_art import calc # # # data_list = [] # for image_name in os.listdir('image/euph_part_icon'): # if not image_name.endswith('.png'): # continue # im = Image.open('image/euph_part_icon/'+image_name) # im_width, im_height = im.size # red, green, blue = calc.average_color_in_range(im, 0, 0, im_width, im_height) # data_list.append([image_name, red, green, blue]) # # with open('average_color.csv', 'w', newline='') as csv_file: # csv_writer = csv.writer(csv_file) # csv_writer.writerows(data_list) # ``` # calculate_mode_color.py # ```python # import os # import csv # # from PIL import Image # # from mosaic_art import calc # # # data_list = [] # for image_name in os.listdir('image/euph_part_icon'): # if not image_name.endswith('.png'): # continue # im = Image.open('image/euph_part_icon/'+image_name) # im_width, im_height = im.size # red, green, blue = calc.mode_color_in_range(im, 0, 0, im_width, im_height) # data_list.append([image_name, red, green, blue]) # # with open('mode_color.csv', 'w', newline='') as csv_file: # csv_writer = csv.writer(csv_file) # csv_writer.writerows(data_list) # ``` # 差分は2箇所 # # `$ diff calculate_average_color.py calculate_mode_color.py ` # # 15c15 # # < red, green, blue = calc.average_color_in_range(im, 0, 0, im_width, im_height) # # \--- # # \> red, green, blue = calc.mode_color_in_range(im, 0, 0, im_width, im_height) # # 18c18 # # < with open('average_color.csv', 'w', newline='') as csv_file: # # \--- # # \> with open('mode_color.csv', 'w', newline='') as csv_file: # - 15行目: 素材画像を代表する色を計算するメソッドを呼び分けている # - 18行目: 素材画像を代表する色の計算結果記入ファイルを切り替えている # # →クラスのプロパティとして持たせる。 # #  インスタンス作成時の引数で適切なプロパティを設定する # ### after # ```python # class ColorCalculator: # def __init__(self, calc_type): # self.calc_func = ColorCalculator.calculate_function(calc_type) # self.csv_name = ColorCalculator.color_csv_name(calc_type) # # def calculate(self): # data_list = [] # for image_name in os.listdir('image/euph_part_icon'): # if not image_name.endswith('.png'): # continue # im = Image.open('image/euph_part_icon/'+image_name) # im_width, im_height = im.size # red, green, blue = self.calc_func(im, 0, 0, im_width, im_height) # data_list.append([image_name, red, green, blue]) # # with open(self.csv_name, 'w', newline='') as csv_file: # csv_writer = csv.writer(csv_file) # csv_writer.writerows(data_list) # ``` # - shinyorkeさんから: クラスを持ち込む必要はなかったかもしれない(オブジェクト指向は設計をしっかり考えないと後々苦しむことになる) # # →[今後] 関数を使って実装して比較してみる # ## Slackのアイコンでモザイクアートが作れなかった件の原因調査 # In[11]: icon_im = Image.open('slack.png') icon_im_width, icon_im_height = icon_im.size # In[12]: icon_im # In[13]: # 元の画像の平均色の可視化 dot_icon_im = icon_im.copy() for left in range(0, icon_im_width, DOT_AREA_ONE_SIDE): for top in range(0, icon_im_height, DOT_AREA_ONE_SIDE): red, green, blue = calc.average_color_in_range(icon_im, left, top, left+DOT_AREA_ONE_SIDE, top+DOT_AREA_ONE_SIDE) average_color_im = Image.new('RGBA', (DOT_AREA_ONE_SIDE, DOT_AREA_ONE_SIDE), (red, green, blue, 255)) # a=0だと透明で何も見えない dot_icon_im.paste(average_color_im, (left, top)) display_image(dot_icon_im) # (R, G, B, A)のタプルじゃない。。 # In[14]: from PIL import ImageStat mean_color = ImageStat.Stat(icon_im).mean mean_color # In[15]: icon_im.getpixel((100,100)) # (R, G, B, A)のタプルを想定 # In[16]: normal_im = Image.open('my_icon.png') display_image(normal_im) normal_im_width, normal_im_height = normal_im.size print(normal_im_width, normal_im_height) # In[17]: normal_mean_color = ImageStat.Stat(normal_im).mean normal_mean_color # In[18]: normal_im.getpixel((100,100)) # ## convertしたら解決した # In[19]: icon_im2 = icon_im.convert('RGBA') icon_im2 # In[20]: icon_im2.getpixel((100,100)) # In[21]: ImageStat.Stat(icon_im2).mean # ## 原因はImageのmodeらしい # # https://pillow.readthedocs.io/en/5.0.0/handbook/concepts.html#concept-modes # # >P (8-bit pixels, mapped to any other mode using a color palette) # # >RGBA (4x8-bit pixels, true color with transparency mask) # In[22]: icon_im.mode # In[23]: normal_im.mode # In[24]: icon_im2.mode # In[27]: color_data = materials_list_from_file('average_color.csv') # icon_im = Image.open('my_icon.png') display_image(icon_im2) icon_im_width, icon_im_height = icon_im2.size mosaic_icon_im = Image.new('RGBA', (1600, 1600)) for left in range(0, icon_im_width, DOT_AREA_ONE_SIDE): for top in range(0, icon_im_height, DOT_AREA_ONE_SIDE): average_color = calc.average_color_in_range(icon_im2, left, top, left+DOT_AREA_ONE_SIDE, top+DOT_AREA_ONE_SIDE) if len(average_color) != 3: continue filename = similar_color_filename(average_color, color_data) # 距離最小のファイルを縮小して1600×1600の画像に貼り付け area_im = Image.open('image/euph_part_icon/'+filename) area_im.thumbnail((THUMBNAIL_ONE_SIDE, THUMBNAIL_ONE_SIDE)) mosaic_icon_im.paste(area_im, (left//DOT_AREA_ONE_SIDE * THUMBNAIL_ONE_SIDE, top//DOT_AREA_ONE_SIDE * THUMBNAIL_ONE_SIDE)) save_path = 'product/slack_mosaic_mean.png' mosaic_icon_im.save(save_path) # Display Image ##### @@@@@ ##### #画像の読み込み im = Image.open(save_path) display_image(im) # In[ ]: →[今後] ソースコードにconvertを導入する # # 以下、モザイクアートのプログラム # In[3]: import csv import matplotlib.pyplot as plt import numpy as np from PIL import Image from mosaic_art import calc #Jupyterでインライン表示するための宣言 get_ipython().run_line_magic('matplotlib', 'inline') # ## 定数宣言 # In[4]: DOT_AREA_ONE_SIDE = 10 THUMBNAIL_ONE_SIDE = 40 # 2つの色(R,G,B)の間の最大の距離 MAX_COLOR_DISTANCE = 255**2 * 3 # CSVファイル中のカラムの意味づけ POS_NAME = 0 POS_RED = 1 POS_GREEN = 2 POS_BLUE = 3 # ## 関数定義 # In[5]: def materials_list_from_file(filename): """Returns a list which contains material image information. Args: filename: File name such as "foo.csv" The file contains information on average color of image. (Average color maens the average of the values of R of all pixels, the average of the values of G of all pixels, and the average of the values of B of all pixels) A row is as follows: image_name, R_average, G_average, B_average Returns: A list of tuples Tuple is like ( image_name : str (such as "bar.png"), red_average : int, green_average: int, blue_average : int ) """ color_data = [] with open(filename, 'r', newline='') as csvfile: reader = csv.reader(csvfile) for row in reader: image_info = (row[POS_NAME], int(row[POS_RED]), int(row[POS_GREEN]), int(row[POS_BLUE])) color_data.append(image_info) return color_data # In[6]: def color_distance(RGB1, RGB2): """Returns color distance Considering the distance between two points (x1, y1, z1) and (x2, y2, z2) in three dimensions Args: RGB1: A tuple which means (R, G, B) RGB2: A tuple which means (R, G, B) Returns: color distance(:int) """ d2_r = (RGB1[0] - RGB2[0]) ** 2 d2_g = (RGB1[1] - RGB2[1]) ** 2 d2_b = (RGB1[2] - RGB2[2]) ** 2 return d2_r + d2_g + d2_b # In[7]: def similar_color_filename(average_color, color_data): """Returns name of file similar to average color Find the image with average color closest to `average_color` from `color_data` Args: average_color: a tuple which means (R, G, B) of average color of a certain range color_data: A list of tuples Tuple is like (image_name, red_average, green_average, blue_average) Returns: A name of file such as 'foo.png' (NOT path) """ distance = MAX_COLOR_DISTANCE filename = '' # 色の差が最小になるファイルを決定(距離に見立てている) for color in color_data: sample_color = (color[POS_RED], color[POS_GREEN], color[POS_BLUE]) d = color_distance(average_color, sample_color) if d < distance: distance = d filename = color[POS_NAME] return filename # In[8]: def display_image(image): #画像をarrayに変換 im_list = np.asarray(image) #貼り付け plt.imshow(im_list) #表示 plt.show()