news 2026/9/7 7:38:10

Python图片转拼豆图纸:像素化与色板映射自动化教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python图片转拼豆图纸:像素化与色板映射自动化教程

拼豆(Perler Beads / Fuse Beads)是一种把彩色塑料豆按图纸排列在钉板上、再加热融合的手工玩法。常见工作流是先在电脑上找图、手动打马赛克、数格子、配颜色,图稍微大一点就要耗尽整个下午。这篇文章要介绍的是一个可以自动完成整条路径的 Python 脚本:输入任意图片,输出一套可用于拼豆的像素化图纸、颜色对照表和用豆数量统计。脚本新增的图片像素化功能,可以直接把 PNG、JPG 甚至手机照片转成钉板网格,不再依赖人工预处理。先给出结果形态:脚本会生成preview.png马赛克预览图、plan.txt文本图纸、plan.html网页图纸和stats.json用豆统计,拿到这四个文件,后面要做的只剩照着图纸往钉板上放豆子。

1. 拼豆图纸脚本的设计思路:图片到钉板的工作流

1.1 手工拼豆流程中的三个耗时环节

手工做拼豆作品,通常要经历三个阶段。

第一个阶段是选图和预处理。找一张喜欢的角色图片,用绘图软件放大、裁切、降分辨率,把它变成一个个方格。方格尺寸必须和钉板数量对应,常见小号钉板是 29×29 颗豆子,大号板可以到 58×58。这个阶段的问题在于,图片缩小之后颜色会糊成一片,边缘也不干净。

第二个阶段是配颜色。拼豆不是任意颜色都有,每个品牌只有几十种标准色。把图片里的每个像素颜色对应到最接近的豆子颜色,靠眼睛逐个看效率很低。人的视觉对颜色差异的判断还会受屏幕色偏影响,经常出现图纸打出来、豆子摆上去才发现颜色不对。

第三个阶段是统计用量。一幅 29×29 的图纸有 841 个格子,手工统计每种颜色需要多少颗豆子非常容易出错,漏算一颗就要跑一趟门店或者等几天快递。

这三个环节正好是脚本能自动化的地方:降采样做像素化,色板匹配做颜色映射,矩阵遍历做数量统计。

1.2 脚本工作流速览

整个脚本的工作流是线性管道,每一步的输入输出都很清楚:

原图 -> [像素化] -> 网格小图 -> [色板映射] -> 代码矩阵 -> [输出] -> 图纸与统计

像素化解决“图变成多少乘多少的格子”的问题,色板映射解决“每一格用哪个颜色的豆子”的问题,输出阶段解决“人怎么照着拼”的问题。

选择 Python 而不是 Node.js 或 Go,主要原因是 Pillow 图像库成熟,Image.resize()一行就能完成高质量缩放,不需要引入浏览器环境。脚本本身是纯命令行工具,Windows、macOS、Linux 都能跑,也和标题里提到的“脚本”定位一致。

1.3 为什么色板才是图纸生成的锚点

很多人做拼豆图纸时先关注像素化,但真正决定成果是否还原的关键是色板。

像素化只决定了“分多少格”,它不会让颜色更准。如果一张图有 24 位真彩色,1624 万种颜色,而手头的豆子只有 24 种,最终每一格到底取哪个颜色,完全由色板匹配逻辑决定。

脚本里必须把色板当作第一等公民来处理。理想流程是先定义色板,再把图片颜色映射到色板。反过来如果先缩小图片、再做颜色匹配,会出现一个常见问题:缩放时产生的中间色在色板里根本不存在,匹配结果就会偏向某个不合理的颜色。更稳妥的做法是先把色板匹配函数准备好,再对缩略图的每个像素做一次匹配。

2. 环境准备:Python 3 与 Pillow

2.1 需要准备什么

运行脚本只需要三个条件:

环境项要求说明
操作系统Windows 10/11、macOS、常见 Linux 发行版脚本使用标准库和 Pillow,不依赖平台特性
Python3.9 及以上建议 3.10 或 3.11
Pillow9.1 及以上脚本使用Image.ResamplingAPI

不需要 GPU,不需要 OpenCV,不需要安装大型图像处理框架。学习环境和开发环境用同样的依赖即可,生产环境额外考虑的是色板校准和批量输出,这部分在最后一章展开。

2.2 安装依赖

先确认 Python 已经装好。在终端执行:

python --version

输出类似Python 3.11.9就说明可用。如果这条命令在 Windows PowerShell 里报错,先跳到第 7 章第 7.1 节处理 PATH 问题。

接着安装 Pillow:

pip install Pillow

如果本机有多个 Python 版本,建议用虚拟环境:

python -m venv venv source venv/bin/activate # macOS / Linux venv\Scripts\activate # Windows PowerShell pip install Pillow

安装完成后验证导入:

python -c "from PIL import Image; print(Image.__version__)"

能输出版本号就说明依赖就绪。

2.3 项目目录结构

建议在一个独立目录下操作:

beads_maker/ ├── beads_maker.py # 脚本本体 ├── demo.png # 测试图片 └── beads_output/ # 输出目录,自动创建

脚本会按用户传入的输出目录自动创建beads_output,不需要手工建目录。图片路径建议用英文文件名,避免个别终端在中文路径上出现编码问题。

3. 第一步:图片像素化,把任意图片变成马赛克

3.1 像素化不是简单打马赛克

图片像素化听起来像美图工具里的马赛克滤镜,但拼豆场景里的像素化有更明确的目标:把原图缩放到一个固定网格,例如 29×29,然后再放大成可视化预览图。

这里的核心不是“模糊”,而是“降采样”。29×29 意味着每颗豆子对应原图里的一块矩形区域,矩形区域内的所有细节都被丢弃,只保留一个代表颜色。

实现时使用两次resize

第一次把原图缩放到目标网格尺寸,使用LANCZOS重采样算法。这个算法会参考周围像素做高质量加权平均,得到的缩略图每个像素都能代表原图中一小块区域的平均颜色。

第二次把缩略图放大到一个容易被肉眼检查的尺寸,例如每格 20 像素,使用NEAREST算法。它不做平滑,直接把一个像素的颜色铺满整个方块,形成马赛克边缘。

3.2 两次 resize 的代码

def pixelate(img, grid_w, grid_h): downsample = Image.Resampling.LANCZOS harden = Image.Resampling.NEAREST grid_img = img.resize((grid_w, grid_h), downsample) preview = grid_img.resize((grid_w * 20, grid_h * 20), harden) return grid_img, preview

grid_img是后续做颜色匹配用的,preview是给人看的预览图。

这里有一个常见的坑:如果只做一次NEAREST缩放,缩略图每个像素只会取自原图某个点的颜色,容易产生噪点;如果只做一次LANCZOS缩放,缩略图颜色是平均了,可放大的预览图边缘会模糊,看不清格子边界。两次 resize 各司其职,才能让图纸既“颜色平均”又“边界清晰”。

如果安装的 Pillow 版本较老,不提供Image.Resampling,可以退回到Image.LANCZOSImage.NEAREST,效果一致。

3.3 尺寸策略:钉板宽度和宽高比

拼豆图纸的尺寸必须和钉板对应。脚本通过宽度参数控制网格:

python beads_maker.py demo.png -w 29

高度默认按原图宽高比自动计算。如果原图是正方形,结果就是 29×29;如果原图很宽,结果可能是 29×12。

也可以同时指定宽高:

python beads_maker.py demo.png -w 29 -H 29

强制指定高度会拉伸图片,导致角色比例变形。除非是有意做拉伸效果,否则建议只指定-w,让高度自动适配。

确定尺寸的代码:

width = args.width if args.height and args.height > 0: height = args.height else: height = max(1, round(img.height / img.width * width))

max(1, ...)是为了防止超宽图计算出高度为 0。

3.4 透明图片与超长图处理

脚本在读取图片时统一转成 RGBA 模式:

img = Image.open(args.image).convert("RGBA")

RGBA 多了一个 alpha 通道。拼豆没有透明豆,所以透明像素必须替换成实际颜色。脚本默认用米白色(255, 250, 240)填充透明区域,因为白色拼豆在多数作品里是底色常用色。

透明区域处理放在颜色匹配阶段:

r, g, b, a = grid_img.getpixel((x, y)) if a < 128: r, g, b = blank_rgb

blank_rgb默认是(255, 250, 240),可以通过--blank参数改成其他 RGB 值。

超长图的问题在高度自动计算时已经解决,但如果原图非常大,例如 8000×6000,LANCZOS缩放会比较慢。可以先在外部把图压缩到长边 2000 像素左右再交给脚本,实际效果几乎没有差别,因为 29×29 网格根本保留不了高分辨率细节。

4. 第二步:色板映射,像素颜色如何变成豆子

4.1 拼豆色板就是颜色的离散集合

拼豆和打印不一样。打印可以把任意颜色渐变换算成 CMYK 网点,拼豆只能从几十种实物颜色里挑一个最接近的。所以色板映射的实质是:在有限颜色集合里,为每个像素找一个距离最近的色板颜色。

如果原图颜色正好落在两个色板颜色的中间,脚本会取距离更小的那个。距离计算方法直接决定像素化后哪个颜色占主导。

4.2 颜色距离:加权 RGB 而不是裸欧氏距离

最直觉的距离计算是 RGB 三维空间里的欧氏距离:

def color_distance(c1, c2): return (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2

这个公式简单,但不符合人眼感知。人眼对绿色最敏感,对蓝色相对不敏感。在 RGB 空间里,绿色通道的小幅度变化比蓝色通道更容易被察觉。

推荐使用加权距离:

def color_distance(c1, c2): dr = (c1[0] - c2[0]) ** 2 dg = (c1[1] - c2[1]) ** 2 * 1.6 db = (c1[2] - c2[2]) ** 2 return dr + dg + db

绿色通道权重 1.6,意味着两个颜色绿色差异会被放大,匹配结果会更贴近人眼感受。

如果追求更严格的颜色差异度量,可以转换到 CIELAB 或 OKLAB 色彩空间再计算 ΔE。那样需要额外实现色彩空间转换或安装colour-science库,这个脚本为了保持轻量,使用加权 RGB 已经够用。

4.3 示例色板定义

色板是唯一需要用户按实际情况调整的部分。下面这组色板是常见的 24 色集合,RGB 值适合作为起点,但不承诺与任意品牌完全一致:

PALETTE = [ {"code": "A", "name": "白色", "rgb": (255, 250, 240)}, {"code": "B", "name": "浅灰", "rgb": (200, 200, 200)}, {"code": "C", "name": "中灰", "rgb": (128, 128, 128)}, {"code": "D", "name": "深灰", "rgb": (64, 64, 64)}, {"code": "E", "name": "黑色", "rgb": (20, 20, 20)}, {"code": "F", "name": "米黄", "rgb": (245, 230, 180)}, {"code": "G", "name": "柠檬黄", "rgb": (255, 240, 0)}, {"code": "H", "name": "橙色", "rgb": (255, 140, 0)}, {"code": "I", "name": "大红", "rgb": (220, 40, 50)}, {"code": "J", "name": "酒红", "rgb": (140, 20, 30)}, {"code": "K", "name": "粉色", "rgb": (255, 180, 190)}, {"code": "L", "name": "桃红", "rgb": (240, 90, 120)}, {"code": "M", "name": "紫色", "rgb": (140, 60, 160)}, {"code": "N", "name": "深紫", "rgb": (80, 30, 100)}, {"code": "O", "name": "天蓝", "rgb": (120, 190, 240)}, {"code": "P", "name": "蓝色", "rgb": (40, 90, 200)}, {"code": "Q", "name": "深蓝", "rgb": (25, 50, 120)}, {"code": "R", "name": "青色", "rgb": (0, 180, 180)}, {"code": "S", "name": "浅绿", "rgb": (160, 220, 120)}, {"code": "T", "name": "翠绿", "rgb": (60, 170, 70)}, {"code": "U", "name": "深绿", "rgb": (30, 100, 40)}, {"code": "V", "name": "浅棕", "rgb": (190, 150, 100)}, {"code": "W", "name": "棕色", "rgb": (140, 90, 50)}, {"code": "X", "name": "深棕", "rgb": (90, 55, 30)}, ]

每个颜色都有一个字母代号,文本图纸就是用这些字母组成的。24 种颜色对应 A 到 X,足够覆盖常见的动漫、像素画和照片类主题。

这里的 RGB 值不一定代表某个真实品牌的官方色卡。实际使用前,建议把手里现有的豆子平铺在自然光下拍照,用取色工具把 RGB 值填进PALETTE,这才是色板校准的正确方式。

4.4 颜色统计:从图纸到采购清单

每个像素匹配到颜色后,脚本边遍历边统计:

def build_grid(grid_img, blank_rgb=(255, 250, 240)): grid = [] stats = {} for y in range(grid_img.height): row = [] for x in range(grid_img.width): r, g, b, a = grid_img.getpixel((x, y)) if a < 128: r, g, b = blank_rgb color = match_color((r, g, b)) row.append(color["code"]) stats.setdefault( color["code"], {"name": color["name"], "rgb": list(color["rgb"]), "count": 0}, ) stats[color["code"]]["count"] += 1 grid.append(row) return grid, stats

grid是二维字符数组,stats是每个颜色代号对应的豆子数。输出stats.json后,可以直接按“颜色->数量”核对库存,形成采购清单。

5. 完整脚本:图片转拼豆图纸生成器

5.1 完整脚本代码

把下面代码保存为beads_maker.py

#!/usr/bin/env python3 # -*- coding: utf-8 -*- """图片转拼豆图纸生成器:像素化 + 色板映射 + 输出图纸与用量统计。""" import argparse import json import math import sys from pathlib import Path try: from PIL import Image except ImportError: sys.exit("缺少 Pillow 依赖,请先执行: pip install Pillow") PALETTE = [ {"code": "A", "name": "白色", "rgb": (255, 250, 240)}, {"code": "B", "name": "浅灰", "rgb": (200, 200, 200)}, {"code": "C", "name": "中灰", "rgb": (128, 128, 128)}, {"code": "D", "name": "深灰", "rgb": (64, 64, 64)}, {"code": "E", "name": "黑色", "rgb": (20, 20, 20)}, {"code": "F", "name": "米黄", "rgb": (245, 230, 180)}, {"code": "G", "name": "柠檬黄", "rgb": (255, 240, 0)}, {"code": "H", "name": "橙色", "rgb": (255, 140, 0)}, {"code": "I", "name": "大红", "rgb": (220, 40, 50)}, {"code": "J", "name": "酒红", "rgb": (140, 20, 30)}, {"code": "K", "name": "粉色", "rgb": (255, 180, 190)}, {"code": "L", "name": "桃红", "rgb": (240, 90, 120)}, {"code": "M", "name": "紫色", "rgb": (140, 60, 160)}, {"code": "N", "name": "深紫", "rgb": (80, 30, 100)}, {"code": "O", "name": "天蓝", "rgb": (120, 190, 240)}, {"code": "P", "name": "蓝色", "rgb": (40, 90, 200)}, {"code": "Q", "name": "深蓝", "rgb": (25, 50, 120)}, {"code": "R", "name": "青色", "rgb": (0, 180, 180)}, {"code": "S", "name": "浅绿", "rgb": (160, 220, 120)}, {"code": "T", "name": "翠绿", "rgb": (60, 170, 70)}, {"code": "U", "name": "深绿", "rgb": (30, 100, 40)}, {"code": "V", "name": "浅棕", "rgb": (190, 150, 100)}, {"code": "W", "name": "棕色", "rgb": (140, 90, 50)}, {"code": "X", "name": "深棕", "rgb": (90, 55, 30)}, ] def color_distance(c1, c2): dr = (c1[0] - c2[0]) ** 2 dg = (c1[1] - c2[1]) ** 2 * 1.6 db = (c1[2] - c2[2]) ** 2 return dr + dg + db def match_color(rgb): best_color = None best_distance = math.inf for item in PALETTE: d = color_distance(rgb, item["rgb"]) if d < best_distance: best_distance = d best_color = item return best_color def pixelate(img, grid_w, grid_h): downsample = Image.Resampling.LANCZOS harden = Image.Resampling.NEAREST grid_img = img.resize((grid_w, grid_h), downsample) preview = grid_img.resize((grid_w * 20, grid_h * 20), harden) return grid_img, preview def build_grid(grid_img, blank_rgb=(255, 250, 240)): grid = [] stats = {} for y in range(grid_img.height): row = [] for x in range(grid_img.width): r, g, b, a = grid_img.getpixel((x, y)) if a < 128: r, g, b = blank_rgb color = match_color((r, g, b)) row.append(color["code"]) stats.setdefault( color["code"], {"name": color["name"], "rgb": list(color["rgb"]), "count": 0}, ) stats[color["code"]]["count"] += 1 grid.append(row) return grid, stats def render_text(grid): lines = [] for row in grid: lines.append("".join(row)) return "\n".join(lines) def render_html(grid, stats, cell_size=20): head = """ <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>拼豆图纸</title> <style> body { font-family: -apple-system, "Microsoft YaHei", sans-serif; padding: 16px; } .board { display: grid; gap: 1px; grid-template-columns: repeat(%d, %dpx); } .cell { width: %dpx; height: %dpx; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: bold; } </style> </head> <body> <h1>拼豆图纸</h1> <div class="board"> """ % (len(grid[0]), cell_size, cell_size, cell_size) code_map = {item["code"]: item for item in PALETTE} cells = [] for row in grid: for code in row: item = code_map[code] r, g, b = item["rgb"] brightness = (r * 299 + g * 587 + b * 114) // 1000 text_color = "#ffffff" if brightness < 140 else "#000000" cells.append( f'<div class="cell" style="background-color:rgb({r},{g},{b});' f'color:{text_color};" title="{item["name"]}">{code}</div>' ) cells_html = "".join(cells) stat_items = [] for code, st in sorted(stats.items(), key=lambda x: -x[1]["count"]): stat_items.append(f"<li>{code} - {st['name']} - {st['count']} 粒</li>") tail = "</div><hr><h2>颜色统计</h2><ul>" + "".join(stat_items) + "</ul></body></html>" return head + cells_html + tail def main(): parser = argparse.ArgumentParser(description="图片转拼豆图纸") parser.add_argument("image", help="输入图片路径") parser.add_argument("-w", "--width", type=int, default=29, help="图纸宽度(豆子个数),默认 29") parser.add_argument("-H", "--height", type=int, default=0, help="图纸高度,默认按原图宽高比计算") parser.add_argument("-o", "--output", default="beads_output", help="输出目录") parser.add_argument("--blank", default="255,250,240", help="透明区域替换颜色,逗号分隔 RGB") args = parser.parse_args() output_dir = Path(args.output) output_dir.mkdir(parents=True, exist_ok=True) img = Image.open(args.image).convert("RGBA") print(f"原图尺寸: {img.width} x {img.height}") width = args.width if args.height and args.height > 0: height = args.height else: height = max(1, round(img.height / img.width * width)) print(f"图纸网格: {width} x {height}") grid_img, preview = pixelate(img, width, height) grid, stats = build_grid(grid_img) preview_path = output_dir / "preview.png" preview.save(preview_path) print(f"像素化预览图: {preview_path}") plan_text = render_text(grid) (output_dir / "plan.txt").write_text(plan_text, encoding="utf-8") print(f"文本图纸: {output_dir / 'plan.txt'}") (output_dir / "stats.json").write_text( json.dumps(stats, ensure_ascii=False, indent=2), encoding="utf-8" ) print(f"颜色统计: {output_dir / 'stats.json'}") plan_html = render_html(grid, stats) (output_dir / "plan.html").write_text(plan_html, encoding="utf-8") print(f"HTML图纸: {output_dir / 'plan.html'}") total = sum(item["count"] for item in stats.values()) print(f"预计用豆: {total} 粒") print("处理完成。") if __name__ == "__main__": main()

代码由四个模块组成:色板定义与颜色匹配、图像像素化、图纸渲染、主流程。PALETTErender_html是后续最可能需要个性化调整的部分。

5.2 命令行参数速查

参数默认值作用
image输入图片路径,必填
-w29图纸宽度(豆子个数)
-H0图纸高度,0 表示按宽高比自动计算
-obeads_output输出目录
--blank255,250,240透明区域替换色,RGB 逗号分隔

实际项目中,-w是最常改的参数。29 对应小号钉板,58 对应大号板。

5.3 运行示例

准备一张测试图片demo.png,然后执行:

python beads_maker.py demo.png -w 29

正常输出结果类似:

原图尺寸: 800 x 600 图纸网格: 29 x 22 像素化预览图: beads_output/preview.png 文本图纸: beads_output/plan.txt 颜色统计: beads_output/stats.json HTML图纸: beads_output/plan.html 预计用豆: 638 粒 处理完成。

如果原图是 800×600,-w 29时高度自动算成 22,总豆数就是 638。到这一步,脚本的完整链路已经跑通。

6. 输出验证:如何确认图纸可以照着拼

6.1 预览图:先看主体轮廓是否保留

preview.png是第一道检查关。打开它,看主体轮廓是否可辨认。拼豆图纸不是照片,不需要看清五官细节,但角色的整体轮廓、主要色块的位置必须清楚。

如果预览图完全看不出原图内容,优先检查两件事:

  • 网格尺寸是否太小。例如一张 1000×800 的多人物图,压到 29×22 后每个人只剩几个格子,自然看不出来。
  • 原图是否主体太小、背景太多。建议先裁掉多余背景再跑脚本。

预览图由grid_img.resize((grid_w * 20, grid_h * 20), NEAREST)生成,所以它的尺寸一定是网格的 20 倍,方便肉眼逐格检查。

6.2 文本图纸与 HTML 图纸的用法

plan.txt是纯文本图纸,每一行代表钉板一行,每个字母代表一种颜色。例如:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAA AABBBBBBBBBBBBBBBBBBBBBBBBAA AABCCCCCCCCCCCCCCCCCCCCCCBAA

这种格式适合直接打印,也适合用文本对比工具检查两个版本之间的差异。

plan.html更适合在手机或平板上看。浏览器打开后,每个格子都有真实颜色背景和字母代号,鼠标悬停可以看到颜色名称。页面底部还有按用量排序的颜色统计列表,可以直接对着列表分配豆子盒。

实际使用中,手机看plan.html比看plan.txt舒服得多,因为颜色和字母是一体的,不用来回对照表头。

6.3 颜色统计 JSON 的正确使用

stats.json的结构如下:

{ "A": { "name": "白色", "rgb": [255, 250, 240], "count": 320 }, "E": { "name": "黑色", "rgb": [20, 20, 20], "count": 180 } }

字段含义:

  • name:颜色名称
  • rgb:色板中的 RGB 值,用于核对颜色
  • count:这种颜色的豆子数量

采购时建议每种颜色多买 5% 到 10% 作为损耗,因为拼豆容易在熔融过程中移位或掉落。

这一节的信息用于说明如何验证脚本输出。如果stats.json中某个颜色数量异常高,例如黑色占了 70%,很可能是原图背景过暗或透明区域被替换成了深色,需要回去看预览图。

7. 常见问题排查:从导入图片到生成图纸

7.1 PowerShell 提示无法将 python 识别为 cmdlet

在 Windows PowerShell 中,如果运行python时出现:

python : 无法将“python”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。

常见原因是 Python 安装时没有勾选“Add Python to PATH”。排查路径:

  1. 打开 PowerShell,执行where.exe python,看是否能找到路径。
  2. 如果找不到,说明 PATH 中确实没有 Python。
  3. 在开始菜单搜索“Edit the system environment variables”,进入“环境变量”。
  4. 在“Path”中加入 Python 安装目录,例如C:\Users\用户名\AppData\Local\Programs\Python\Python311\,同时加入它的Scripts子目录。
  5. 重新打开 PowerShell,执行python --version验证。

也可以使用 Microsoft Store 安装 Python,这种方式通常会自动配置 PATH。

7.2 ModuleNotFoundError: No module named 'PIL'

脚本开头已经捕获了缺少 Pillow 的情况,但如果你在另一个环境里手动导入,可能仍然会遇到:

ModuleNotFoundError: No module named 'PIL'

这个报错表示 Pillow 没有安装到当前 Python 环境。最可能的情况是机器上有多个 Python,pip install Pillow装进了 A 环境,而python beads_maker.py用的是 B 环境。

解决办法是统一使用带-m的安装方式:

python -m pip install Pillow

同样的 Python 解释器执行安装和执行脚本,就不会出现环境错位。

7.3 网格宽度越大越清晰吗

不一定。-w 29已经能把大多数单主体图片的主轮廓表达清楚。继续调大到 58,豆子数量从 841 涨到 3364,手工拼豆时间会成倍增加。

真正决定清晰度的不是网格数量,而是色板与图片颜色的匹配程度。如果色板的棕色系太少,即使网格从 29 调到 58,肤色或者木色区域仍然会大面积映射成错误的灰色系。

遇到图片细节确实很丰富、29 不够用的情况,优先考虑把图片主体裁切居中,让重要部分占更多格子,而不是盲目调大网格。

7.4 图纸颜色发灰、发脏怎么办

颜色发脏通常有两个原因。

第一个原因是色板里面缺少足够的高饱和颜色。原图的亮黄、亮蓝迁到灰色系。

第二个原因是透明背景被替换成了灰色。如果原图是黑底图,透明区域被默认的米白色替换后,匹配出来就是浅灰。

处理方式:

  • 检查PALETTE中颜色是否覆盖常见高饱和色。
  • 调整--blank,将透明背景换成与原图底色接近的颜色。
  • 在图片预处理阶段,直接把背景填成纯白色或纯黑色,再交给脚本。

7.5 透明区域变成黑色

如果 PNG 有透明通道,但输出图纸里透明区域全部变成黑色,说明脚本执行时读取的像素顺序有偏差。检查代码里的解包语句:

r, g, b, a = grid_img.getpixel((x, y))

必须确保图片是真 RGBA 模式。如果用户在外部把图片转成了 RGB 模式,getpixel只返回三个通道,这句解包就会直接抛错或者错位。在脚本入口统一执行convert("RGBA")正是为了防止这种情况。

如果仍然出现黑色区域,可以把--blank显式指定为255,255,255,并确认执行时参数已经传入build_grid

8. 从能跑到好用:实际拼豆项目的最佳实践

8.1 用真实豆子校准色板

任何在博客、网络分享中获得的色板 RGB 都只能作为起点,因为生产批次、拍摄环境、屏幕色偏都会让实物颜色和 RGB 值有出入。

校准方法:把手里所有颜色的豆子每样取一颗,平铺在白色 A4 纸上,在自然光下拍照。用取色工具逐个读取 RGB,替换PALETTE中对应项。

这一步做完,脚本的意义才会真正体现。色板越贴近实物,生成图纸的可用度越高。

8.2 支持大图与多钉板拼接

一幅 29×29 的图纸大约相当于一个小挂件,如果要做海报级作品,需要多块 29×29 钉板拼成 58×58 甚至更大。脚本本身不限制网格尺寸,可以直接用-w 58生成整图。

多钉板拼接的实际问题是分块。拼豆玩家通常希望输出每个 29×29 钉板对应的独立图纸。可以基于现有脚本扩展一个分块参数,按 29 步长把grid切成多个子网格,分别调用渲染函数输出独立 HTML。切块时要保证颜色代码不重新计算,直接用同一个色板映射结果,这样拼起来颜色才连续。

对于新手,不要一上来就挑战 58×58。先完成一个 29×29 的单色块作品,跑通“脚本生成图纸到手工拼豆”的完整闭环,再考虑大图。

8.3 批量生成与自定义输出

如果社团活动或工作室需要一次性生成多张图纸,可以用系统脚本做批量调度。在 Windows 下是 bat 文件,在 Linux 和 macOS 下是 shell 脚本。

一个 shell 批量示例,遍历目录下所有 PNG 并生成 29 宽图纸:

#!/usr/bin/env bash for image in images/*.png; do python beads_maker.py "$image" -w 29 -o "out/$(basename "$image" .png)" done

每条命令都会生成独立的输出目录,不影响彼此的stats.json

生产环境使用前,还要考虑三点:

  • 原图目录是否只有目标图片,避免误处理。
  • 输出目录是否存在同名旧文件,是否需要先清空。
  • 批量跑完是否有汇总表,需要把多个stats.json合并成 Excel 或 CSV 方便采购。

8.4 后续扩展方向

这个脚本的架构足够简单,扩展点也很清晰:

  • 色板外置:把PALETTE抽成palette.json,不同品牌豆子切换配置文件,不需要改代码。
  • 限制颜色数量:当某个作品只需要 12 种豆子时,可以在匹配前做一轮颜色聚类,只保留用量最大的 12 种颜色再映射。
  • 更精确的颜色距离:把像素从 RGB 转换到 CIELAB 空间,计算真正的感知色差,解决深色区域匹配不准的问题。
  • 输出 PDF 图纸:把plan.txt或 HTML 转成适合 A4 打印的 PDF,方便打印后按格子手工标记进度。
  • 反向导入:允许读取现有拼豆作品照片,识别格子颜色后输出数字化图纸,方便整理和分享。

代码本身并不长,读一遍、改一版,比直接复制使用更能理解每一步为什么这么设计。第一次跑通后,建议手动改一下PALETTE的颜色数值,观察preview.pngstats.json的变化,这是理解颜色映射逻辑最直观的练习。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 7:37:35

科研效率翻三倍:十大Agent Skills智能体技能全解析

上周在实验室帮一位师妹改论文&#xff0c;我发现了一个特别扎心的现象&#xff1a;所有人都在用AI&#xff0c;但产出效率能差出三倍。有人已经用Agent把整个科研流程跑成流水线&#xff0c;有人还停留在“把PDF拖进对话框&#xff0c;让它给我总结一下”的阶段。区别就在一个…

作者头像 李华
网站建设 2026/9/7 7:31:59

Samba 4域控运维自动化:备份、批量建号、共享创建与自愈脚本实战

简介&#xff1a;面向Samba 4 AD-DC管理员与Debian运维人员的实用脚本合集&#xff0c;汇集了作者在Debian Jessie/Stretch服务器上日常使用的工具&#xff0c;覆盖Samba域备份、SePrivileges权限查看、sysvol ACL校验与设置、域信息查询等高频运维场景。压缩包共25个文件&…

作者头像 李华
网站建设 2026/9/7 7:31:44

Aspose.Words.Cpp 18.11集成实战:C++文档生成与PDF转换要点

简介&#xff1a;Aspose.Words.Cpp 18.11 是面向C开发者的Word文档处理库&#xff0c;可用于在应用程序中创建、编辑、转换和渲染DOCX/DOC/PDF/HTML等格式&#xff0c;解决无需安装Microsoft Office即可实现文档自动化、报表生成与格式转换等需求。压缩包共1150个文件&#xff…

作者头像 李华
网站建设 2026/9/7 7:29:14

树莓派Python+OpenCV颜色识别、跟随与巡线小车实战解析

简介&#xff1a;这是一份基于树莓派与Python的OpenCV视觉小车项目资源&#xff0c;适合机器人爱好者、树莓派玩家以及学习计算机视觉的初学者。资源围绕颜色识别、巡线行驶与物体跟随三个核心功能展开&#xff0c;利用USB摄像头实时采集图像&#xff0c;通过cv2.inRange设置颜…

作者头像 李华
网站建设 2026/9/7 7:28:48

J-space技术:通过Jacobian空间解读大模型的潜意识思维

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 7:27:22

如何为 FunASR 的 Paraformer 输出时间戳:带时间戳语音识别指南

如何为 FunASR 的 Paraformer 输出时间戳&#xff1a;带时间戳语音识别指南 【免费下载链接】FunASR Open-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.…

作者头像 李华