artcraft是我在业余时间搭起来的一个参数化设计小项目,名字就是把art和craft拼在一起。做它的初衷很简单:我一直觉得数字设计和传统手工艺之间隔了条沟。一边是精确到像素的算法和屏幕色,另一边是讲究触感、染色和痕迹的手作,两边好像天然不搭。但我把第一张照片跑成十字绣图纸,再真的拿针线绣出来之后,这个想法彻底变了——数字工具不是来取代手工艺的,它只是帮你在下针之前想清楚很多事。
这个项目能做什么?简单说,它用Python把任意一张图片拆成网格、做颜色量化,再映射成十字绣、拼豆、马赛克或者编织配色能直接用的图案稿。你不需要很会画画,也不需要精通软件,只要给出一张参考图,设定网格大小和颜色数量,它就能自动出一套带图例的图纸。如果你经历过把图片贴到像素软件里一格一格点颜色的日子,应该能立刻get到这是个什么样的解放。
文章适合谁看?我猜至少三类人会觉得有用:一是想尝试算法工具但不知道从哪入手的手工艺爱好者;二是做过图片处理、想把能力延伸到实物创作的开发者;三是沉迷于“做一个自己的小工具”的独立创作者。下面我会把整个项目的定位、核心算法、实操流程和踩坑记录都摊开讲,希望能给你一点参考。
1. artcraft到底做了什么:定位和设计逻辑
1.1 为什么把编程和手工艺放一起
十字绣这个手艺本身是很“数字化”的。成品是一格一格的像素网格,每一格是一个颜色,这不就是数字图像的低分辨率降采样吗?我第一次意识到这件事的时候,手腕上还挂着绣绷子。既然绣图本身就是像素画,那我完全可以用程序来生成它——把图片读进来,缩到目标针数,然后统计颜色,配上色号,再输出一张符号图。这就是artcraft最早的雏形。
当时我手上有一张自己拍的风景照,想绣成一幅宽度约80格的装饰画。以前我用过一些在线转图工具,结果是:颜色数量没控制,打出来像马赛克又不够像马赛克;符号密密麻麻,绣到一半根本分不清哪个是哪个;最崩溃的是它并没有告诉我每种颜色要用多少线量。很多免费工具只给你一张“能看”的图,不给你“能直接用”的图纸。artcraft从设计上就要解决三件事:颜色可控、符号可读、用料可算。
这里面其实藏着一个选型逻辑。我没有选择直接使用现成的刺绣软件,因为它们大多数偏向商业工作流,学习成本高,也没法按我的规则来定制。而用Python自己做,等于把整个流程的每个旋钮都暴露在自己手里。想控制输出尺寸?改一个参数就行。想限定色板?换一个线色库文件就行。这种自由度对于个人项目很重要,因为你总会在第三天发现,工具里那个“不重要的选项”恰恰是你最需要的功能。
1.2 技术选型:Python + Pillow + OpenCV + scikit-learn
我选Python不是因为它是“最强的图像处理语言”,而是因为它的库生态让这个项目的复杂度断崖式下降。Pillow负责常规图片读写和缩图,OpenCV用来做更好的预处理,比如降噪和边缘感知的缩放,scikit-learn里现成的KMeans直接接管颜色聚类,matplotlib用来排版和输出PDF。这四个库几乎把一个“完整图像处理+可视化”流程的所有脏活累活都包圆了。
这里也说一下为什么选KMeans而不是别的颜色压缩方法。图像有上百万种颜色,但绣线色号最多也就几百种。如果直接取图片里出现频率最高的颜色,画面里会有很多近似色,每个色块都很碎;如果单纯减少位深,又会丢失整体层次感。KMeans聚类处理的是“把颜色空间切成K个代表色”,每个像素认领离自己最近的代表色,这样既控制了颜色总数,又能在“像照片”和“像绣图”之间调平衡。实测中,K在8到24之间是最常用的范围,K值越大图案越接近原照片,但工艺成本和颜色的区分难度也会上升。
有些朋友会问为什么不用最新潮的深度学习方法。我的回答很简单:深度模型确实能在某些场景下生成看起来更“聪明”的风格化结果,比如自动补全细节、模拟笔触,但它的问题是不可控——你要它保持绣图颜色严格限定在预置线色里,它往往做不到,而且跑一轮推理的时间是KMeans的几十倍。手工艺图纸的本质是“严谨的生产文档”,不是“艺术滤镜”,稳定可控比什么都重要。
1.3 它能覆盖哪些手工艺场景
artcraft虽然是从十字绣长出来的,但核心逻辑并不依赖十字绣。只要把最后的“符号映射”换掉,同一套颜色量化和网格化流程可以输出很多种手工设计稿。
- 像素拼豆:网格当作拼豆矩阵,颜色映射到对应色号的胶珠,成品是挂件或杯垫。
- 串珠图:输出单元格的颜色和对应米珠色号,适合做斜纹或方格串珠包。
- 马赛克拼贴:要看马赛克颗粒的规格,把图像映射为固定尺寸的作品。
- 毛线编织的像素图案:比如提花图案、刺绣贴,核心同样是网格和配色。
也就是说,artcraft本质上是一个“统一的前端:图片到网格加颜色标记”,后端接什么样的手工艺,完全取决于你想让它接什么。这个抽象在我后续迭代中帮了大忙,我甚至只换了一个配置文件,就让它能输出串珠配色清单。所以你现在看到的技术路线里有大量参数化设计的思想:代码不绑定某个具体成品,而是生成一份可被不同工艺流程消费的规范文档。
2. 核心算法拆解:从照片到手工图纸的关键步骤
2.1 读图与预处理:别让噪点毁了下针
读图这步看起来最简单,实际上最影响后续结果。如果你把一张原本是1200x800像素的照片直接缩小到80x100网格,很多细节会以一种很糟糕的方式混成一团。为什么?因为普通缩图算法会做平均采样,边缘会发灰,细小的高光会被“稀稀拉拉”地涂成噪声。更好的做法是先在原始分辨率下进行少量降噪,比如5x5的高斯模糊,把颗粒感磨平,再缩图,这样网格上的每个格子得到的是更稳定的颜色。
当然,降噪力度也要看用途。如果原图是光滑渐变多的照片,模糊多一点没关系;如果原图本身就是像素画或插画,基本不需要模糊,不然会把锐利的边界弄糊。我现在的管线里默认是低强度模糊,同时开放了模糊半径参数,让使用者自己判断。
除了模糊,还有一个常见问题是图片比例。十字绣网格通常不是正方形,比如我的常用规格是80x100格。如果直接resize到80x100,图片会被强行拉变形。所以我在resize之前会先做中心裁剪,按目标宽高比裁好,再resize。这一步别省,不然绣了半天发现脸是歪的。
预处理的另一个隐藏收益是“减少无效计算”。一张大图的边缘区域如果只是渐变天空,对绣图而言信息量很低。裁掉多余比例后,网格里的每一格都承载有效内容,同样的网格数能表达更多有效主体。
2.2 颜色量化:怎么把上百万颜色压缩到二十种线色
这是整个项目最核心的部分。我把颜色量化拆成两段:
第一段,用KMeans把图片压缩成K个代表颜色。输入是一个[高x宽, 3]的二维数组,每个元素是RGB值。KMeans会找到K个聚类中心,然后把每个像素归到最近的中心。我们最后要的不是像素本身,而是每个聚类的中心颜色和它所占的像素数量。像素数量可以换算成线量:如果某个颜色占了四分之一个图案,那它需要的绣线量大约是总针数的四分之一,乘上单针用线长度就能估算耗材。
第二段,把聚类中心映射到实际绣线色号。照片里的颜色很自由,线色却是一堆离散的色块,所以我们要在预设颜色库里为每个聚类中心找“最近”的颜色。这里有个容易踩的坑:如果直接用RGB欧氏距离来比,结果经常偏。比如RGB空间里,暗红色和暗棕色之间的距离可能比暗红色和红色之间还近,那映射出来就会出现绣图整体偏棕。更可靠的方案是在Lab色彩空间算色差。我用了scikit-image的deltaE_ciede2000函数,它是CIE的推荐公式,考虑了人眼的非线性感知。虽然慢一点,但颜色判定的准确度好很多,尤其对肤色、天空这类颜色非常敏感的场景。
这里我特别建议保留“聚类中心颜色”和“映射后线色”的对比步骤。拿两个小色条并排打印出来,肉眼扫一眼就知道是量化损失还是映射错误。很多人直接跳到出图,结果颜色不对,还要反过来猜是哪个环节的问题。
2.3 网格映射与符号映射:让图纸“可读”
颜色处理完之后,每一个网格单元都对应一个颜色标签。接下来要做的,是给每个颜色分配一个独立符号,然后把网格画成“符号图”。
符号图的意义在于,十字绣图纸讲究的是“一眼能分清”,而不是“好看”。同一块色区里如果全是碎碎点点的符号,绣起来眼睛很累。我采用的做法是:先给颜色按像素数量从多到少排序,用简单的符号去匹配大面积色块,例如大块底色用“空白带点”或“横线”,小面积细节用“X”或“三角”这类明显符号。这个设计规律看起来不起眼,但实际使用中能省很多眼力。
生成符号图我用matplotlib绘制。每格画一个符号,同时保留网格线;图纸边上附一个图例,列出“符号、颜色名、线号、所需线束数”。导出PDF的DPI设为300,一张A4纸恰好容纳80x100格。这里有一个细节:直接导出PDF时会发现符号很小,打印出来容易糊。我的解决办法是加大fontsize,并适当减少网格线宽度,让符号成为画面的绝对主角。
如果你想让图纸更像专业刺绣杂志的风格,还可以引入“半格”和“特殊针法”概念。但这会显著增加生成逻辑的复杂度。我个人认为对业余手工爱好者来说,保持纯一格一色、一格一符反而更容易上手。
2.4 风格化控制:让参数成为艺术表达的一部分
很多人觉得“生成式工具做出来的东西千篇一律”,这其实是参数没调好。artcraft里有两个最能影响风格的旋钮:一个是grid_size,也就是网格数。网格越多,图案越写实,但手工程量也会成倍上升;网格越少,成品越像抽象的“像素画”,反而能突出大色块的冲击力。
第二个旋钮是quantize_k,也就是最终的颜色类别数。K低时,画面会变成高对比度的海报风格;K高时,照片的细腻层次会出现,但线色采购成本上升。我在一套作品里曾经用同一个原图生成过三个不同K的版本:K=8像一个复古游戏截图,K=24几乎能看清照片的光影过渡。两版放在一起,气质完全不同。
还有一个容易被忽略的控制项:色彩平衡的预处理。比如给原图先做一点色相偏移或饱和度拉伸,出来的绣图气质会变化很大。换句话说,artcraft不能替你决定什么是“好看的图案”,但它可以让你在不同方案之间快速做A/B测试——这比真绣完一个版本才发现不对要省钱得多。
3. 实操记录:完整跑通一套artcraft流程
3.1 环境准备与依赖安装
先说明一下我的运行环境:Python 3.10,虚拟环境。安装依赖很简单:
python -m venv .venv source .venv/bin/activate pip install numpy pillow opencv-python scikit-learn scikit-image matplotlib如果面向全新使用者,这套依赖里最重的是scikit-learn和opencv-python,但现代机器装起来基本也就一两分钟。我还是推荐用虚拟环境,因为matplotlib和opencv对版本比较敏感,直接装在全局环境里容易出依赖地狱。
3.2 生成第一个十字绣样稿
下面这段是我最初版本的核心代码,做了精简但流程完整。我拿一张风景插画做测试,目标网格80x100,目标颜色12种。需要注意,我这里为了展示逻辑省略了符号排序和线量统计,但那两步在生产代码里不能省。
import numpy as np from PIL import Image, ImageFilter from sklearn.cluster import KMeans import matplotlib.pyplot as plt from skimage import color from skimage.color import deltaE_ciede2000 # 1. 读图并预处理 img = Image.open("input.jpg").convert("RGB") img = img.filter(ImageFilter.GaussianBlur(2)) scale_w, scale_h = 80, 100 img = img.resize((scale_w, scale_h), Image.LANCZOS) pixels = np.array(img).reshape(-1, 3).astype(np.float32) # 2. KMeans颜色聚类 K = 12 kmeans = KMeans(n_clusters=K, n_init=10, random_state=42, n_jobs=-1) labels = kmeans.fit_predict(pixels) centers = kmeans.cluster_centers_.astype(np.uint8) # 3. 映射到绣线色板 thread_colors = np.array([ [238, 233, 233], [220, 224, 225], [216, 178, 163], # 简化的示例色板 [190, 193, 180], [137, 153, 173], [148, 177, 200], [117, 146, 170], [166, 198, 190], [211, 203, 179], [222, 120, 129], [112, 71, 72], [57, 48, 49], ], dtype=np.float32) lab_centers = color.rgb2lab(centers[None]) lab_threads = color.rgb2lab(thread_colors) mapped = [] for center in lab_centers[0]: dists = deltaE_ciede2000(center, lab_threads) mapped.append(np.argmin(dists)) # 4. 重建网格图 grid = labels.reshape(scale_h, scale_w) # 5. 用matplotlib绘制符号图 symbols = ["x", "o", "*", ".", "+", "s", "^", "v", "D", "P", "X", "h"] fig, ax = plt.subplots(figsize=(8, 10)) for y in range(scale_h): for x in range(scale_w): sym = symbols[grid[y, x]] ax.text(x + 0.5, y + 0.5, sym, ha="center", va="center", fontsize=6) ax.set_xlim(0, scale_w) ax.set_ylim(scale_h, 0) ax.set_aspect(1) # 6. 保存PDF plt.savefig("pattern_output.pdf", bbox_inches="tight", dpi=300)这段代码跑完之后,你会得到一份PDF,打开以后是一页带网格的符号图。不过我强调一句,这只是“最简可用版”,我第一次跑出来的图谱面非常拥挤,因为我没有按颜色像素数量重新排序符号,导致同一个符号频繁来回跳。后面我在绘制前增加了一步:统计每个簇的像素数量并排序,面积大的簇分配到阅读性最好的符号。
3.3 导出打印清样与排版
打印这一步有很多细节决定成败。首先,图纸不是直接按屏幕尺寸打印的,它必须带物理尺寸标注。对十字绣来说,绣布的规格通常是14ct,意思是每英寸14个格子。80x100格的图案,绣布宽度大概是80/14约等于5.7英寸,不到15厘米,所以其实是个很迷你的装饰画。
在打印排版时,我用matplotlib一次性生成三个元素:网格符号主图、图例表、耗线量统计表。图例不是简单列颜色,而是包含“线号、色名、每个符号、估算线束”。估算线束的公式是:针数乘单针线长再除以每束线长。经验上,14ct绣布每一针大约需要2.5厘米的线;线束长度按8米计算。所以某一颜色的针数是1200针,估算线束就是1200乘0.025除以8约等于3.75束。
有一个坑我特别提醒:这一步的图例表很容易超出页边距。你要么把整张图的字体调小,要么把图例放到第二页。我后来干脆单独做了一页“材料清单”,主图和图例分开,这个改动极大减少了打印时的大量废纸。
3.4 从数字图案回到实物:一次真实的刺绣验证
软件流程跑通之后,我建议一定要做一次实物验证,因为只有实物会把程序问题反馈得明明白白。我拿一张80x100格的风景样稿,买好相应颜色的绣线,选了14ct的白色绣布,按程序给出的数量配线。实际绣的过程中出现了三件事:
第一,大面积底色区域的符号确实好认,但小面积高光区域因为符号太小,几乎看得眼花。我当时把符号字体调大了两档,问题才缓解。所以不要迷信默认参数,打印出来对着光看,如果一张A4纸上符号难分辨,就直接加大字体或者减少网格数。
第二,电脑上的颜色匹配和真实绣线颜色相比,总会有冷暖偏差。我的做法是把真实线拿到屏幕前比较,给线色库重新校正了一遍RGB参考值。很多软件自带的色库,由于色板印刷批次不同,根本不是你所购买的实线颜色。手动的“屏幕对线”校准一次,之后所有生成结果都会准很多。
第三,我意识到“自动生成的图案”不等于“好看的图案”。照片转出来的绣图,局部细节很多,但缺少工艺美术上讲的那种“色块规划”。后来我在流程里加了一个后处理步骤:把小于某个阈值的孤立色块合并到邻近的大色块里。这一步能在很大程度上提升成品整洁度,有点像图像处理里的“形态学闭运算”。效果很明显,绣出来的画面不会到处都是零星的跳线。
4. 高频问题与我的排查思路
4.1 颜色怎么调都不对,问题出在哪
一个最常被问的问题:“为什么转出来的颜色和我看到的照片差这么多?”排查路径一般是三条:
- 原图有色偏,比如从网上保存的照片可能带sRGB和Display P3的差异,Python读出来就是偏的。可以先做一个白平衡或者手动调整通道增益。
- 聚类数量不合适。如果颜色总数量小于目标,增加K值,但也要注意增加K值后线色映射环节经常把两个很接近的聚类映射成同一个线号,等于白加。这时要检查“有效映射色数”而不是单纯的簇数。
- 色板本身和真实绣线不一致。这是常见原因,色板RGB值来自厂商官方文档,实际绣线在不同灯光下看完全不一样。校准方法是按实线暗调。
我还建议在代码里把“聚类中心颜色”和“映射后的线色”并排列出来,拉一张对比图。立刻就能看出是量化损失还是映射错误。
4.2 图片一大就卡,内存和速度怎么平衡
KMeans对数据量很敏感。如果你的原图有2000x2000像素,直接输入KMeans,就是四百万个样本点。在我的旧笔记本上跑一次K值16的聚类,差不多要半分钟,而且内存占用明显。
解决思路很简单:别拿原图做颜色量化,先缩到目标网格尺寸。网格最大通常也就120x120,也就是14400个像素,这个数量级对KMeans毫无压力。我后来在代码里明确分了两条路径:在缩到网格尺寸后聚类,然后再用同一个模型把原图缩略图做颜色替换,用于生成预览。整个过程最多两三秒。
如果你确实需要在缩图之前做更精细的颜色统计,可以先随机采样10万个像素做聚类。用numpy.random.choice取索引就行,没必要全量计算。聚类模型本质上只关心颜色分布,不关心像素空间位置,采样不会带来明显偏差。
4.3 图纸打印出来和屏幕有色差怎么办
打印机色域比显示器窄,颜色偏是不可避免的。我这里说的是“图案符号”层面,不是打印色彩精度本身。如果你是打印出来给刺绣当参考,实际上并不要求颜色完全一致,只要求图例里的线号能对上。所以更实用的做法是:把图例设计成黑白灰版本,只靠符号和线号区分,不依赖色块填充颜色。这样即使打印机颜色失调,只要符号清楚,线号列表清楚,制作过程就不会出错。
如果坚持要打印彩色预览图,建议在导出时关闭色彩管理或选择“相对色度”渲染意图,可以减少颜色压缩带来的偏差。另外,用铜版纸和喷墨纸打出来的效果差很多,普通家用喷墨纸吸墨太厉害,颜色会变灰。
4.4 符号图太大,眼睛看花了怎么办
我自己遇到过的最现实问题就是一张A4纸打印的80x100符号图,符号又多又小,绣到第20行就开始眼花。解决方向有几个:
- 增大打印尺寸:A3甚至A2。网格线宽调细,符号字体调大,但总尺寸变大后,绣的时候要频繁抬头看图,其实也累。
- 分块打印:把图纸切成四个象限,每一块单独打印成A4,旁边标注拼接对齐点。这个方案对手工艺人最友好。
- 降低符号复杂度:把相近的颜色合并,让图纸里的符号类别变少,辨识度提升。
- 电子图纸配合放大:在屏幕或平板上看图,能任意放大,配合图例使用非常顺。我现在实际制作时就很少用整张纸,而是把PNG传给平板,放大到能看清每个格子。
从我的个人经验而言,“好看”和“好用”的图纸,差别就在细节。很多在线生成的图案只考虑美观,根本不管你在绣绷上连续操作时的感受。artcraft最大的价值,是让我重新理解了“工艺图纸”不是装饰画,而是一份严谨的生产文档。
最后再分享一个我现在还在用的习惯:每次生成一套图纸,我都会把原图、网格大小、颜色数量、线色映射结果存成一个带参数记录的JSON。这样几个月后想复刻或者调整方案时,不用重新捣鼓参数,直接改一个种子值就能得到新的变体。这种“参数化留存”的思维方式,或许才是这类项目给我带来的最大帮助。