具體見代碼。
#!python3
# -*- coding: utf-8 -*-
import os
import math
from PIL import Image
class ProcessBar(object):
"""一個打印進度條的類"""
def __init__(self, total): # 初始化傳入總數
self.shape = ['▏', '▎', '▍', '▋', '▊', '▉']
self.shape_num = len(self.shape)
self.row_num = 30
self.now = 0
self.total = total
def print_next(self, now=-1): # 默認+1
if now == -1:
self.now += 1
else:
self.now = now
rate = math.ceil((self.now / self.total) * (self.row_num * self.shape_num))
head = rate // self.shape_num
tail = rate % self.shape_num
info = self.shape[-1] * head
if tail != 0:
info += self.shape[tail-1]
full_info = '[%s%s] [%.2f%%]' % (info, (self.row_num-len(info)) * ' ', 100 * self.now / self.total)
print("\r", end='', flush=True)
print(full_info, end='', flush=True)
if self.now == self.total:
print('')
class MosaicImg(object):
'''
用於將源圖像當成一個像素點合成目標圖像
'''
def __init__(self,
target, # 目標圖像的路徑,圖像的透明區域會被忽略
img_path, # 源圖片的目錄,裏面的圖片將被拿來作爲像素點
pixel_size=50, # 每個像素圖片的寬高
size=(0, 0), # 最後生成的圖片的尺寸,格式是 (width, height)
scale_factor=-1, # 縮放因子,0~1之間的浮點數,如果設定了這個,則 size 無效
):
path = os.path.split(target)
name, ext = os.path.splitext(path[1])
self.save_img = os.path.join(path[0], "mosaic_" + name + '.png') # 生成的圖像名字
self.pixel_size = pixel_size
# 處理目標圖像
self.target = Image.open(target).convert('RGBA')
if scale_factor == -1: # 使用 size 參數
self.target.thumbnail(size)
else: # 使用 scale_factor 參數
new_size = (scale_factor * self.target.size[0], scale_factor * self.target.size[1])
self.target.thumbnail(new_size)
# 處理源圖像
self.images = {} # key是一張圖片的主顏色,value是該圖片的 PIL.Image 對象
self.images_count = {}
file_list = os.listdir(img_path)
print("正在讀取源圖像:")
pb = ProcessBar(len(file_list))
for file in file_list: # 遍歷目錄
img = Image.open(os.path.join(img_path, file)).convert('RGB')
img.thumbnail((pixel_size, pixel_size)) # 重設圖片大小
color = self.main_color(img) # 計算主顏色
self.images[color] = img
self.images_count[color] = 0
pb.print_next()
# 每個圖像最多使用的次數
self.max_img_use = 3 * self.target.size[0] * self.target.size[1] / len(self.images)
def gen_mosaic_image(self):
'''
使用初始化的設置生成像素圖。
其具體做法是遍歷目標圖像,對每個像素點的顏色,從源圖像
的主顏色中找到一個最接近的,然後將該圖像當成像素點
'''
# 最後生成的圖像的大小
size = (self.pixel_size * self.target.size[0], self.pixel_size * self.target.size[1])
self.mosaic_img = Image.new('RGBA', size)
# 開始生成圖像
print("正在生成圖像: ")
# 遍歷每一個像素
pb = ProcessBar(self.target.size[0])
for x in range(self.target.size[0]):
for y in range(self.target.size[1]):
r, g, b, a = self.target.getpixel((x, y)) # 得到該像素的顏色
if a == 0: # 跳過透明區域
continue
min_score = 1000
min_color = None
for color in self.images: # 找到最接近的顏色
score = self.color_distance((r, g, b), color)
if score < min_score:
min_score = score
min_color = color
# 將圖片貼上去
self.mosaic_img.paste(self.images[min_color], (x*self.pixel_size, y*self.pixel_size))
self.images_count[min_color] += 1 # 使用次數+1
# 超過了最大使用次數就刪除
if self.images_count[min_color] > self.max_img_use:
self.images.pop(min_color)
pb.print_next()
print('正在保存圖像,請稍候。')
self.mosaic_img.save(self.save_img, format='PNG')
return self.mosaic_img
def main_color(self, image):
'''
得到一張圖片的主顏色,這裏使用了偷懶的做法,
將圖片resize到一個像素點大小,取該點的像素值
'''
img = image.resize((1, 1), Image.BICUBIC)
return img.getpixel((0, 0))
def color_distance(self, rgb_1, rgb_2):
'''
兩個RGB顏色的相似度計算,使用LAB顏色空間
'''
R_1, G_1, B_1 = rgb_1
R_2, G_2, B_2 = rgb_2
rmean = (R_1 + R_2) / 2
R = R_1 - R_2
G = G_1 - G_2
B = B_1 - B_2
return math.sqrt((2+rmean/256)*(R**2)+4*(G**2)+(2+(255-rmean)/256)*(B**2))
if __name__ == '__main__':
current_path = os.path.dirname(os.path.abspath(__file__)) # 當前目錄的完整路徑
img_path = os.path.join(current_path, 'dog') # 存放源圖像的目錄
target = os.path.join(current_path, 'xinxin.png') # 目標圖像
ma = MosaicImg(target, img_path, 70, scale_factor=0.1) # 初始化
img = ma.gen_mosaic_image() # 生成圖像