一年前写过一个图片转乐高积木风格的代码,
没想到派上用场了,前一段时间有一个读者关注,说想要给女朋友做一个乐高的画。
当时我直接给它推荐了工具,不知道是没用对,还是工具怎么了,说效果不行。
后面给我发了好几张照片,我想既然这样,我就用我之前的工具给它试试,结果说生成的像素太多了,买积木可能要很多,中间断断续续找我很多次,最后没办法,重新改了一下之前的缩放比例,保证宽不超过60个积木片,效果很满意。
互联网真的很奇妙,没想到我的代码还有这样的功效,有相同需求的可以留言,毕竟做好事我还是愿意的。
没有需求,那就五一快乐吧。
源代码:
from PIL import Image
import matplotlib.pyplot as plt
import collections
import json
from PIL import Image
import numpy as np
import openpyxl
from openpyxl.drawing.image import Image as xl_img
from openpyxl.styles import Alignment
# 忽略numpy 版本警告
np.warnings.filterwarnings('ignore', category=np.VisibleDeprecationWarning)
# 读取颜色数据
with open("color.json","r",encoding="utf-8") as f:
data = json.load(f)
def img_to_pixel(img_name, block_size):
"""
img:image path
block_size:pixel size
"""
pic = Image.open(img_name)
level = 10 #pic.width//100
# 将原图缩小level倍
pic.thumbnail((int(pic.size[0]/level),int(pic.size[1]/level)))
width, height = pic.size
board = Image.new(
"RGB", (width//block_size*block_size, height//block_size*block_size))
# 循环处理图片
for y in range(0, height, block_size):
for x in range(0, width, block_size):
# 每隔一段距离切一块图片
temp = pic.crop((x, y, x+block_size, y+block_size))
temp_list = list(temp.getdata())
# 获取数量最多的像素值
max_color = collections.Counter(temp_list).most_common()[0][0]
# 创建该像素值图片
temp_past = Image.new("RGB", temp.size, max_color)
# 粘贴图片
board.paste(temp_past, (x, y))
# 再缩小两倍
board = board.resize((int(board.size[0]/2),int(board.size[1]/2)))
# 保存到本地
board.save(f"{img_name[0:-4]}_pixel.jpg")
# 返回原图和像素图
return pic, board
# 查找最接近的颜色
def find_approach(pixel):
# 获取颜色名
colors = np.array(data)[:,0]
# 获取RGB值
colors_pixel = np.array(data)[:,1:].tolist()
# 计算乐高图片与图片RGB值差值
compare_value = colors_pixel-np.array(pixel)
# 计算差值绝对值
compare_abs = np.abs(compare_value)
# 将RGB三者的绝对值相加
compare_add = np.sum(compare_abs,axis=2)
# 计算相差最小的值
min_value = np.min(compare_add)
# 确定最小值所在数据中的索引值
min_value_index = np.where(compare_add == min_value)[0]
# 如果有多个相似值
if len(min_value_index)>1:
# 选取第一个
min_value_index=min_value_index[0]
# 获取对应的颜色名
color_name = colors[min_value_index]
else:
# 获取颜色名,需要转换一下numpy数据到string
approach_color = colors[min_value_index]
color_name = np.array2string(approach_color).replace("['","").replace("']","").replace("'","")
return color_name
# 生成乐高图片
def generate_lego_img(board):
# 创建一个画板,用来贴图
new_board = Image.new("RGB", (board.size[0]*20, board.size[1]*20), "white")
# 创建积木清单字典
block_list = {}
# 循环处理图片
for row in range(board.size[0]):
for col in range(board.size[1]):
# 获取像素值
r, g, b = board.getpixel((row, col))
# 找到最接近的颜色
color_name=find_approach([r,g,b])
# 将积木数量加 1
if color_name in block_list:
block_list[color_name] += 1
else:
block_list[color_name] = 1
# 打开最接近颜色的积木图片
img_temp = Image.open(f"pic/{color_name}.jpg").resize((20,20))
# 粘贴到画板上
new_board.paste(img_temp, (20*row, 20*col))
# 保存乐高图片到本地
new_board.save(f"{img_name[0:-4]}_lego.jpg")
# 返回零件清单
return block_list
# 生成零件清单
def generate_block_list(block_list):
wb = openpyxl.Workbook()
ws = wb.active
# 设置居中样式
align = Alignment(horizontal="center",vertical="center")
# 添加表头
ws.append(["颜色","积木块","数量(块)"])
ws[f"A1"].alignment = align
ws[f"B1"].alignment = align
ws[f"C1"].alignment = align
# 读取数据添加到表格中,并设置表格样式
for i in range(len(block_list.keys())):
color = list(block_list.keys())[i]
img = xl_img(f"pic/{color}.jpg")
num = block_list[color]
ws.append([color,"",num])
ws.column_dimensions["A"].width = 20
ws.column_dimensions["B"].width = img.width*0.14
ws.add_image(img,f"B{i+2}")
ws.row_dimensions[i+2].height = img.height*0.75
ws[f"A{i+2}"].alignment = align
ws[f"B{i+2}"].alignment = align
ws[f"C{i+2}"].alignment = align
# 保存清单文件
wb.save(f"{img_name[0:-4]}_积木清单.xlsx")
if __name__ == "__main__":
img_name = "51.png"
# 生成像素图
_,board = img_to_pixel(img_name,1)
# 生成乐高图,并返回零件数据
block_list = generate_lego_img(board)
# 生成零件清单
generate_block_list(block_list)