从图像到拼豆图纸:像素化生成与色板匹配全流程解析
发布时间:2026/10/14 6:55:09 锦皓数字建站

做拼豆这几年一个很深的体会是“图好找图纸难画。”尤其当你想把一张心仪的照片、插画甚至游戏截图变成能照着拼的拼豆图纸时手工一个一个像素点去描色号基本是体力活。后来我花了不少时间把整条链路做成了一套能落地的技术流程核心是“像素化生成 色板匹配”既保留了原图的辨识度又把颜色数量控制在拼豆色板可表达的范围内。这篇文章就把我踩过的坑、试过的方法和可以直接抄作业的代码都整理出来给同样想把任意图像转为拼豆图纸的朋友参考。这套方法不只是服务拼豆爱好者做十字绣图纸、像素画、乐高马赛克、甚至串珠图设计底层逻辑都是相通的。核心解决三个问题怎么把连续图像离散成格子、怎么把成百上千种颜色压缩成色板能覆盖的色号、怎么让压缩之后的结果仍然好看且可拼。如果你是第一次接触拼豆图纸生成或者已经在手动画图但效率很低这篇内容应该能帮你把工期从几天压缩到几分钟。1. 整体设计与思路拆解1.1 为什么偏像素化而非矢量描摹很多人第一次想到“把图像变成拼豆图纸”第一反应是用Photoshop或Illustrator的“图像描摹”功能转成矢量再输出色块。这个思路不是不行但实际试过就知道矢量描摹追求的是平滑边缘和色块连续而拼豆图纸恰恰需要离散格子和可数色号。拼豆的物理单位是一颗颗豆子它天然就是像素化的。矢量图里的弧形边缘、串色渐变到了拼豆层面要么需要很细的排线去模拟要么就直接变成不可拼的抽象图形。所以我最终采用的是“先降采样成像素网格再做颜色量化”这条路径。它的好处是第一步就把问题和像素的操作绑定后续所有环节都可以用图像处理的常规手段去解决比如用 OpenCV 或 Pillow 就能完成大部分工作不需要引入复杂的轮廓追踪和区域填充算法。另外像素化天然保留“信息密度均匀”的特征。对于拼豆图我们希望在画面的每个区域豆数大致均衡而不是在细节多的地方塞一堆豆、在背景大色块的地方又空一大片。矢量描摹的结果通常是细节多的区域形状密集背景区域形状巨大这种不均匀到了拼豆尺寸环节会很麻烦要么缩小后细节糊掉要么放大后背景变成一张巨毯。像素化降采样则是一刀切固定格子数每个格子的信息量天然平均。1.2 色板匹配从“眼睛挑色”到“算法算色”色板匹配是整个流程里最有“技术含量”也最容易翻车的环节。市面上的拼豆品牌色号少则二三十个多则上百个但相对于图像里可能出现的上千万种颜色来说还是太少了。直接把每个格子取平均色然后复制到拼豆图纸上会出现两种情况要么平均色在色板里找不到近似色要么找了一个看着差不多的但密集拼出来后整体色差大得离谱。我的做法分两步先对像素化后的图像做颜色量化把每个格子的颜色归并到有限数量的聚类中心再做色板匹配用距离公式计算每个聚类中心和真实豆色之间的距离取最近色号。这两步缺一不可。如果不做量化直接匹配每个格子独立找最近色结果就是颜色数量超多、色号跳变剧烈图纸里全是孤立的不同颜色豆子拼出来视觉效果非常花。更关键的是颜色距离不能简单用 RGB 空间里的欧氏距离。人眼对绿色敏感、对红色次之、对蓝色敏感度低且在暗色区域对亮度差异更敏感所以直接算 RGB 距离经常会出现“算法觉得像人眼觉得不像”的翻车。实际推荐的做法是先把 RGB 转到 Lab 色彩空间或至少做感知加权再计算距离。Lab 空间里欧氏距离与人对色差的感知高度相关效果明显更好。2. 像素化生成的核心参数与原理2.1 格网尺寸决定图纸分辨率和豆量像素化第一步是决定输出多少行、多少列格子。这个参数直接由拼豆底板的尺寸决定。常见的方形拼豆底板有 29×29 格也有 58×58 格的大块还有各种定制尺寸。选择逻辑很简单先定目标长宽比和总豆量上限再反推格网大小。如果原图是 3:4 的竖构图想在 29×29 的方形底板上拼就必须先裁切。我通常先以短边为基准做中心裁切再缩放到目标格子数。比如目标 29×29就把原图裁成正方形然后缩放到 29×29 像素。这里有个细节Pillow 默认的重采样算法是双线性插值BILINEAR直接缩到 29×29 容易丢细节、边缘发虚。我会改成 LANCZOS 重采样然后在缩放后顺手做一次锐化这样得到的格子边缘更清晰。格网尺寸并不是越大越好。格子多意味着豆子多耗时和成本也翻了倍。大多数人第一次玩拼豆28×28 或者 32×32 的图纸已经足够表达一个头像或一只小动物。只有做大幅作品时才会把格网设到 100×100 以上。经验值供参考豆数控制在 2500 以内中等难度5000 以上就得做好拼两三个晚上的准备。2.2 颜色量化把百万色压到几十个色号图像缩到几十上百个格子之后每个格子其实还是一个包含很多颜色的区域得取一个代表色。最简单的是直接取区域内所有像素的平均色。但平均色有一个明显缺陷如果格子里一边是纯白、一边是纯黑平均出来是中性灰而中性灰在拼豆图纸里往往最不讨喜拼出来显得脏。更稳的做法是使用中位数颜色或者用聚类的方法。聚类的逻辑是把图里所有像素颜色当作一群点用 K-Means 聚成 N 个中心每个格子像素的颜色离哪个中心近就用哪个中心的颜色。这样比单纯平均更能还原“这个格子给人的直观颜色感受”。K-Means 在实现上有两个坑。第一K 值怎么定。我一般把 K 设为色板数量的 1.5 到 2 倍比如色板有 40 个色号K 设 60 到 80聚类后再合并到最近的色号。这样能避免一个色号对应的区域内部颜色差异太大。第二K-Means 对初始化敏感同一张图跑两次结果可能不同。我在代码里会固定随机种子并在 OpenCV 里使用 KMEANS_PP_CENTERS 这个初始化策略结果更稳定。另外量化前必须做一步重要处理去除抗锯齿边缘。图像缩放后边缘像素大多是半透明的中间过渡色如果不先处理这些杂色会影响聚类的纯度。具体做法是把图像先缩放到一个低分辨率比如目标格数的 2 倍再用最近邻算法强制去除边缘中间色或者先转成 “P 模式”做颜色索引。这些细节看着小但对最终效果影响非常大。2.3 色彩空间的选择RGB直配还是做色彩感知校正色板匹配这一步我强烈建议不要直接在 RGB 里做。RGB 的颜色距离和人的感知差异完全不匹配。举个例子RGB 里 (50,50,50) 和 (60,60,60) 的欧氏距离约为 17而 (180,0,0) 和 (190,0,0) 的欧氏距离是 10但人眼会觉得前者的亮度差异更明显、后者几乎是同一个红。因为在暗区人对亮度差异的分辨率更高。把颜色转换到 Lab 色彩空间后L 代表亮度a、b 代表两个色度对立轴。在这个空间里欧氏距离和人的色差感知高度一致。具体计算时我会先把 RGB 转成 Lab再计算每个量化后的颜色和所有拼豆色号的 Lab 距离开方取最小值对应的色号。实测下来在 Lab 空间匹配的准确率比 RGB 直接匹配高出不少尤其在紫色、深蓝色、暗红色这几个容易翻车的色系上。Lab 转换在 Python 里可以用 OpenCV 的cv2.cvtColor(pixel, cv2.COLOR_RGB2LAB)也可以用skimage.color.rgb2lab。需要注意 Lab 空间有一定色域限制RGB 里某些极高饱和的颜色无法精确表达所以转换后超出范围的边界值要手动钳制避免出现 NaN 或无穷值。这个小坑我一开始没注意结果一张高饱和红色图片匹配出来的色号全是暗红。3. 实操流程与核心环节实现3.1 图像预处理裁切、缩放、去噪进入正式代码之前先把整条流程串一遍。完整链路是读取并裁切图像统一目标长宽比缩放图像生成像素网格的“原始格子图”对格子图做边缘去抗锯齿处理用颜色量化把每个格子归并到聚类中心用 Lab 距离匹配拼豆色板色号输出带色号和符号的图纸图片以及一份用料清单第一步裁切很关键。很多人跳过去直接缩放结果比例不对图被压扁拼出来的人脸像被横向拉伸过。我习惯先把长边缩放至 2000 像素以内然后以中心为基准裁切到目标长宽比。比如目标是 3:4 的竖版就在原图上裁出一个 3:4 的矩形区域。这个区域可以在代码里用中心裁切也可以做成坐标参数让使用者自己框选。裁切后的图像一般会有压缩痕迹和噪点这时做一个轻微的高斯模糊半径 1-2能有效减少后续聚类的噪声。模糊不是让图像变糊而是让细小纹理的颜色过渡更平滑聚类中心更稳定。我常用高斯核(5,5)sigma 设为 1。噪声严重的手机照片sigma 可以调到 1.5。但不要过分否则边缘信息也跟着糊掉。3.2 像素化核心代码实操下面这段 Python 代码实现了裁切、缩放和初步像素化。我用的依赖是 Pillow、OpenCV、NumPy 和 scikit-image都是常用库直接 pip 安装即可。import cv2 import numpy as np from PIL import Image def center_crop_resize(image_path, grid_w, grid_h, out_path): img Image.open(image_path) # 转 RGB去掉 alpha 通道 img img.convert(RGB) # 中心裁切到目标比例 w, h img.size target_ratio grid_w / grid_h if w / h target_ratio: new_w int(h * target_ratio) x0 (w - new_w) // 2 img img.crop((x0, 0, x0 new_w, h)) else: new_h int(w / target_ratio) y0 (h - new_h) // 2 img img.crop((0, y0, w, y0 new_h)) # 用 LANCZOS 缩放到网格尺寸 img img.resize((grid_w, grid_h), Image.LANCZOS) img.save(out_path) return img这段代码的工作逻辑很清晰按目标宽高比先裁掉多余部分然后用 LANCZOS 缩放到指定格数。得到的结果就是一张grid_w × grid_h的小图每个像素对应一格。我开发时会在这一层输出一张放大预览图用最近邻插值把 29×29 的小图放大 10 倍方便肉眼先检查构图轮廓是否OK再继续往下走。像素化之后的图片不要直接在原图上取值做颜色量化而要做到 2 倍精度。这一步的思路是先放大到grid_w*2 × grid_h*2用更小粒度去聚合最后再降回网格尺寸。实践效果是小图的边缘更干净颜色更接近直觉。3.3 色板匹配实现量化之后算距离先准备好色板数据。每种拼豆有一个名字和 RGB 值我用一个字典存起来。当然实际品牌的色号很多这里只给两个示例色便于跑通逻辑。真实项目里一般是几十个色号的完整列表。palette_rgb { 1: (255, 255, 255), # 白 2: (30, 30, 30), # 黑 3: (200, 30, 30), # 正红 4: (250, 180, 50), # 杏黄 # ... 更多色号 }接下来把上一步生成的网格图读成 NumPy 数组先做 K-Means 聚类再逐格匹配最近色号。中间最长的耗时其实是聚类过程。如果格数只有 50×50数据点也只有 2500 个聚类非常快。但如果想做大幅 150×150就有 2.25 万个点对每个点做几十维颜色的距离计算还是有点压力。所以我一般先对网格图做一次颜色扁平化把每个像素的颜色当作三维坐标用 K-Means 聚成 K 个类K 设置为色板数量的 2 倍左右然后每个像素用聚类中心替代再做 Lab 匹配。from sklearn.cluster import KMeans from skimage import color def extract_palette(img_array, n_clusters): # img_array shape: (h, w, 3) h, w, _ img_array.shape pixels img_array.reshape(-1, 3).astype(np.float32) kmeans KMeans(n_clustersn_clusters, random_state42, n_init10) kmeans.fit(pixels) centers kmeans.cluster_centers_.astype(np.uint8) labels kmeans.labels_.reshape(h, w) return centers, labels def nearest_color_lab(color_rgb, palette_lab): color_lab color.rgb2lab(color_rgb / 255.0) dist np.linalg.norm(palette_lab - color_lab, axis1) return int(np.argmin(dist) 1)这里有个实操细节要注意skimage.color.rgb2lab接受的是[0,1]浮点输入而 OpenCV 读出来的是[0,255]整数所以要先除以 255。然后色板里的 RGB 也都要先转成 Lab 并缓存一次避免每一格都重复转换。整个匹配过程本质上是找最近点每次循环只需要算一个向量对一组向量的距离用 NumPy 广播写很方便也不用写复杂循环。3.4 图纸输出与用料清单匹配完成后输出环节同样重要。单纯输出一张“标了色号”的大图还不够拼豆用户真正需要的是能快速照着拼的图纸以及去买对应颜色豆子的数量参考。我生成的图纸通常包含三部分带颜色填充的格子图每个格子填上匹配后的拼豆色每个颜色对应的符号、编号标在格子角落便于区分相近色一份颜色统计表每个色号用了多少颗豆颜色统计很简单就是遍历所有格子的色号值用字典计数。但我一般会做一个小优化先做连通域分析把零星的单颗色号提示出来。大面积背景中出现一颗孤立的另一种颜色大概率是噪音色号在图纸里我会标注“可替换为背景色”这样拼的时候不用反复换豆效果也不受影响。图纸的美观度也很重要。拼豆图纸的底色最好是浅色格子线要清晰。我常用纯白底格子线用浅灰色RGB 180, 180, 180线宽 1 像素放大后边缘不会太刺眼。每个格子中央或左上角标一个很小的数字色号有人喜欢中央标号有人喜欢角落这个因人而异。建议编号字体大小根据格子尺寸调整原则是放大到 200% 后仍然能看清楚但没放大时不影响看颜色。4. 常见问题与排查技巧实录4.1 匹配出来的颜色总是偏灰、偏暗这是我把 RGB 直接匹配改成 Lab 匹配之前遇到的最大问题。原因是很多像素经过缩放和模糊后颜色值被推向了中间调尤其暗部颜色被平均后更灰。后来排查发现两个叠加因素一是缩放时用了双线性插值暗部的边缘像素和亮部像素互相污染二是匹配时在 RGB 空间算距离算法倾向于选择中间亮度的色号。解决方式分两层。第一层在预处理阶段用 LANCZOS 替代双线性并在量化前增加一步“暗部强化”或者叫“对比度增强”。我常用的是在缩放之后用PIL.ImageEnhance.Contrast增强 1.1 到 1.2 倍同时把亮度微调 0.95 倍让暗部更沉、亮部更亮。第二层在匹配阶段切换到 Lab 并给亮度分量 L 设置一个权重我一般取weights [1.0, 0.8, 0.8]。降低 a、b 的权重能减少因色彩饱和差异导致的误匹配效果更稳定。判断是否偏灰有一个快速方法把匹配后的色号颜色还原成一张图和原像素化图做平均色差对比如果总色差小于某个阈值但视觉观感却明显“脏”那就是亮度分布出了问题需要回到预处理阶段增强对比度而不是继续调匹配逻辑。4.2 大面积同色区域出现零散杂色豆如果你拼出来的成品背景应当是纯色结果零零星星蹦出好几个别的颜色问题通常出在颜色量化阶段。K-Means 是全局聚类它在颜色丰富的区域和颜色单一的区域会平均用力。当图像中存在大面积纯色背景时聚类中心仍然会为背景分出两个甚至多个相近的颜色匹配到不同的色号导致背景不纯净。解决方案是对聚类结果做一次后合并。具体做法是算出所有聚类中心两两之间的 Lab 距离如果距离小于某个阈值通常 8 到 10 Lab 单位就认为这两个颜色人眼无法区分合并为同一类像素标签也一起替换。合并后再做色板匹配背景杂色会明显减少。如果用这个方法后还有零星杂点那就是噪声像素可以用形态学开运算去掉。import cv2 # labels 是二维标签图 kernel np.ones((3, 3), np.uint8) labels_clean cv2.morphologyEx(labels.astype(np.uint8), cv2.MORPH_OPEN, kernel)开运算会把小于 3×3 的孤立色块消除替换为周边的主流色。这个操作对拼豆图效果非常好因为它本质上做的事和拼豆颗粒的视觉整合逻辑一致。4.3 渐变小图变成马赛克灾难照片里的渐变天空、柔和的皮肤阴影在低分辨率像素化后很容易出现颜色跳变。比如天空从左到右由浅蓝渐变到深蓝如果用 K-Means 聚类它会切出好几条颜色带拼出来的效果像一根根彩带很怪。这个问题在很多自动生成工具里都有属于开放性问题没有完美解但有实用的缓解方案。我的经验是引入一个“颜色平滑度约束”。在匹配色号之前先对量化后的网格图做一次中值滤波。中值滤波在去噪的同时可以保留大块结构但会把细碎的过渡色“拉平”。参数取 3×3 即可过大会破坏边缘。另外针对渐变区域我还会在生成用料清单时额外标注“可用双色混豆过渡”比如相邻两颗豆使用深浅两个蓝交替排布这样人眼看到的是渐变效果而图纸层面仍然只有两个色号拼起来也不麻烦。渐变问题是拼豆图纸中少数无法完全靠算法解决的问题因为拼豆本身是离散的物理上的平滑渐变必然需要混色技巧。算法能做的只是让色号分布有序尽量不出现单颗跳变。4.4 图纸符号混乱、对不上号图纸生成了打印出来却看不懂哪里是哪里这是一种很常见的失败。原因一般是色号和格子线对比度不足或者符号标注位置不一致导致拼到一半就要返工检查。我总结了三条实操习惯。第一图纸底色不要用白色以外的深色否则深色豆在黑底上看不清第二相近色号要避免放在相邻格里虽然算法是按最近色号匹配的但人工排图时可以做一个微调如果两个相邻格子的色号它们对应的 Lab 距离很小且其中一个在该图里只出现很少次数就把那个少数色替换为相邻的主流色。第三打印纸质图纸时要确保色号数字字号统一行间距一致最好每 10 格就加一个加粗的坐标标尺。另外我发现把图纸按“分区”输出比一整张更实用。比如一整张 58×58 的图纸打印成 A4 会非常小。我会按 10×10 切块输出生成 36 张小图每张拼完后拼起来不会拼乱。这个方法在手工圈子里叫“分区图纸”实际用起来比一整张大图友好太多。5. 踩坑实录与进阶扩展思路5.1 时间成本估算与预判像素化和匹配本身只需几秒但真正耗时的是“看图微调”。我通常会在生成第一版图纸后手动检查三个地方脸的肤色是否统一、背景是否有孤立杂点、深色区域是否糊成一团。这三个问题如果出现我并不会重新跑全套流程而是通过微调参数局部解决。时间成本有一个比较稳定的规律网格越大检查时间越长。30×30 的图检查加微调大概 10 到 15 分钟。100×100 的图每处细节都要看可能要 1 到 2 小时。所以我建议普通作品控制在 60×60 以内。想做大幅也要先跑小样试验配色方案确定没有颜色灾难再放大这个小样步骤每个人都需要走能省不少返工时间。还有一个细节是豆子亮度差异的问题。不同品牌、不同批次的拼豆同色号之间也会有微小色差尤其是珠光豆和透明豆反光会改变观感。我用算法匹配时会把比较常见的“闪光色”排除在色板之外因为这些颜色在照片和屏幕上很难准确呈现盲拼容易翻车。5.2 从拼豆延伸到更大范围的像素艺术工具这套技术流程修正后完全可以复用到一个更大的工具集合里。我把它做成了一种“可插拔”管线前端接收任意图像中间做像素化和量化后端对接不同的输出格式。拼豆图纸只是其中一个输出端另外我还接了十字绣图纸需要额外把像素格对应到绣布格数并处理 Aida 布的针法密度串珠图把颜色替换成珠子的色号同时输出珠子用量乐高马赛克输出每个乐高颗粒的颜色以及对应的零件编号像素画练习图输出带网格的线稿每次适配一种新输出核心算法几乎不用动只需要更换色板和输出模板。这个思路让我在后续几个项目里省了大量重复开发的时间。如果你也在做类似工具我建议把“图像处理核心”和“输出适配层”严格解耦这样以后想加新玩法非常方便。5.3 一些个人建议和后续可以做的事从图像到拼豆图纸本质上是一个颜色降维的过程这个过程没有标准答案只有最适合你要表达的内容的方案。我个人在做了几十张图之后的体会是技术上要做到“又快又稳”但视觉上一定要保留“人肉微调”这一步。算法负责把 1600 万色压缩到 40 个色号而你要负责告诉算法这款暖黄色是不是比旁边的杏黄更接近你记忆里那个温柔的下午。参数调得再熟练最后敲定的那个色号永远该由你来选。最后分享一个后续可以自己动手扩展的小功能把生成的结果倒推回原图做一次“误差热力图”。每一步匹配都可以得到当前格子颜色与原始对应区域颜色的色差把这个色差按冷暖色渲染成一张热力图能非常直观地看出图纸在哪些区域失真最大。我做这个功能之后发现多数失真集中在毛发、草地这类纹理密集区域属于拼豆物理极限无法避免但在皮肤和天空区域优化空间很大微调几个色号就能让成品提升一个档次。这个思路值得你去试试。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。