资讯动态

ArtCraft:用算法生成与手工精修打造可控的生成艺术工作流

发布时间:2026/10/21 10:01:16 来源:尧图企业网站定制
ArtCraft这套流程我用了挺长时间它不是一个软件也不是什么线上平台而是我自己对一种创作方式的称呼art 负责表达craft 负责落地。简单说就是让程序先生成一堆随机但可控的图案底稿我再拿这些底稿去做手工精修最后变成能印刷、能上墙、能当商品素材的东西。它解决的痛点是大多数创作者都碰到过的两个问题一是面对空白画布不知道从哪下手二是有想法但执行太慢试错成本高。如果你做数字艺术、平面设计或者平时喜欢折腾创意编程下面的内容应该能帮你少走不少弯路。1. ArtCraft是什么把算法生成和手工打磨放在同一工作台1.1 常见误区生成艺术不等于按键出图很多人一听到生成艺术四个字第一反应就是写个脚本点一下运行电脑自己吐出来一堆好看的图。这个理解不能说全错但离真正能用的工作流差了很远。纯随机生成的图看第一张觉得新鲜看到第五十张就会意识到它们只是一堆漂亮的半成品——有纹理、有色彩但没有焦点没有取舍更谈不上完成度。ArtCraft的思路是把流程拆成两半。前半段交给代码负责解决画面元素从哪里来构图可以先生成纹理可以批量算配色也可以让调色板自己说话。后半段留给人负责解决作品感怎么落地哪个局部要留白哪块纹理太密需要压暗哪里应该加一笔手工笔触这些都是审美判断不应该交给随机数。用做饭来打比方算法是备菜的。它帮你把食材洗好、切好、配好盘让你不用从零开始面对一个空灶台。但菜好不好吃火候怎么掌握最后撒不撒葱花这仍然是掌勺人说了算。ArtCraft的本质就是给掌勺人配了一个不知疲倦的切菜工。我现在跟别人推荐这套方式都会先让他们把心态调整过来生成只是起点不是终点。筛图的时间可能比生成还长手工精修更是必不可少。抱着我只要写个脚本就能当艺术家的想法来做大概率会失望把算法当成素材库加速器反而能做出自己都意外的作品。1.2 ArtCraft的创作管线四段式流程这套流程我已经跑得很熟了一共四段生成、筛选、精修、归档。听起来很简单但每一段都有它自己的讲究。第一段是生成。我会写好生成脚本用参数控制尺寸、噪声比例、颜色方案、随机种子。这一段的时间代价主要花在最初写代码上一旦跑通运行一次可能只需要几秒到几分钟。注意是跑一批不是跑一张。我会让脚本一次生成几十张草稿把所有可能性摊开在面前。第二段是筛选。这是最容易被新手跳过、但最锻炼眼力的环节。我会用看图软件快速翻一遍这几十张草稿把有明显构图问题的直接淘汰剩下五六张有潜力的。筛选的标准我没有做得特别复杂主要是看有没有意料之外又结构合理的区域——比如某块纹理突然密集起来、某个角落的配色产生了类似晚霞的效果。这一类意外人工计划未必想得到但它们恰恰是素材最值钱的部分。第三段是手工精修。这一步我会把选中的图导入位图编辑软件做局部调整。可选的操作包括叠加纹理笔刷、增加对比度、局部压暗或提亮、加一点噪点来消除数字感甚至直接在图上画几笔。这里的时间弹性很大快的话十几分钟慢的话可能几个小时。第四段是归档。生成参数、随机种子、调色板定义、源文件、成品图全部放在一起存好。为什么这么做后面第三章会专门讲。简单说没有归档的生成艺术等于一次性艺术出了好看的作品却没法回溯参数是这行里最亏的事。整个流程里时间占比大概是生成百分之二十、筛选百分之三十、精修百分之四十、归档百分之十。精修是大头因为它才是真正让作品区别于算法图的关键。2. 核心算法拆解看似随机的图案背后都有清晰规则2.1 从一个坏主意讲起纯随机像素是没有气质的如果直接用 random.random() 给每个像素生成一个随机灰度值得到的是什么是一张电视机雪花噪点图。你盯着看三秒钟就会觉得眼睛疼因为它缺少一个最基本的东西——连续性。人类美感里说的纹理本质上是相邻区域之间的平滑过渡和结构递进而不是每个点在原地跳变。所以 ArtCraft 生成底稿的第一步就是用噪声替代纯随机。噪声和随机的区别在于随机给每个点独立取值你们各玩各的噪声则会让相邻点的数值比较接近整体看起来像一个连续起伏的曲面。实现噪声的方式很多我最早用的是最直观的一种Value Noise值噪声。思路是先在一张大网格上放置随机值网格点之间用插值函数过渡这样像素之间就有了平滑的变化。核心代码长这样import random def value_noise(width, height, scale, seed): rng random.Random(seed) # 生成网格scale越小网格越密纹理越细碎 grid_w int(width / scale) 2 grid_h int(height / scale) 2 grid [[rng.random() for _ in range(grid_w)] for _ in range(grid_h)] def smoothstep(t): return t * t * (3 - 2 * t) canvas [] for y in range(height): row [] for x in range(width): gx x / scale gy y / scale x0 int(gx) y0 int(gy) fx gx - x0 fy gy - y0 sx smoothstep(fx) sy smoothstep(fy) # 双线性插值让网格值平滑过渡 n00 grid[y0][x0] n10 grid[y0][x0 1] n01 grid[y0 1][x0] n11 grid[y0 1][x0 1] nx0 n00 (n10 - n00) * sx nx1 n01 (n11 - n01) * sx v nx0 (nx1 - nx0) * sy row.append(v) canvas.append(row) return canvas这里 scale 的取值直接决定了图案的观感。scale 偏小网格变密纹理碎、细节多scale 偏大网格稀疏画面会出现大块的流动区域更像水墨染出来的效果。我一般会在 0.02 到 0.15 之间取值具体看想要的气质。这个算法的运行效率不高但胜在容易理解。如果你希望纹理更有自然手绘感可以进一步替换成 Perlin 噪声或 Simplex 噪声核心思想类似只是梯度计算方式更复杂方向性也更强。2.2 多层叠加与分形第二层噪声让画面活起来单层噪声的问题在于它太像云朵或者烟雾了均匀、弥散没有骨架。艺术作品中真正耐看的东西往往同时具备大结构和小细节大结构负责第一眼的冲击小细节负责长久注视后的玩味。单层噪声显然做不到这一点。解决办法叫分形叠加也就是多倍频噪声叠加。你把同一张噪声图按不同频率缩放然后叠加起来。高频变体提供细节低频变体提供轮廓最后再按振幅加权求和。公式写出来是这个意思def fbm(width, height, scale, octaves, seed): total [[0.0 for _ in range(width)] for _ in range(height)] amplitude 1.0 amplitude_sum 0.0 freq 1.0 for i in range(octaves): layer value_noise( width, height, max(scale / freq, 1.0), seed i * 7919 ) for y in range(height): for x in range(width): total[y][x] layer[y][x] * amplitude amplitude_sum amplitude amplitude * 0.5 freq * 2.0 # 归一化到 0~1 for y in range(height): for x in range(width): total[y][x] / amplitude_sum return total这里的 octaves 参数决定了叠加层数。层数越多画面细节越丰富同时计算量也成倍上涨。我个人常用的范围是 4 到 6 层。少于 4 层画面偏光滑大于 6 层则细节太密缩小看时会觉得脏。振幅每次乘 0.5是因为高频组分的贡献应该弱一些否则细节会反过来吞掉大结构。你可以把 fbm 想象成一个模拟自然地貌的过程低频层像连绵的山体中频层像山坡上的岩石堆高频层像石头缝里的草叶。三层合一才是一个有层次的世界。2.3 颜色映射从灰度到有情绪的色彩噪声图算出来以后只是一堆 0 到 1 之间的灰度值离作品还差一个关键步骤颜色映射。这一步是把灰度值映射到调色板上画面的气质自此定型。新手最容易犯的错是直接用 HSV 色轮调色比如把灰度值当作色相彩虹一样拉一圈。这种方案会得到极其塑料感的颜色因为色相变化太均匀缺少色块聚集感。更可控的办法是定义几个锚点色然后让灰度值在锚点之间做插值。def palette_color(value, anchors): anchors: [(position, (r, g, b)), ...] value: 0~1 的灰度 # 找出 value 所在的两个锚点区间 for i in range(len(anchors) - 1): p0, c0 anchors[i] p1, c1 anchors[i 1] if p0 value p1: t (value - p0) / (p1 - p0) return tuple(round(c0[j] (c1[j] - c0[j]) * t) for j in range(3)) return anchors[0][1] if value anchors[0][0] else anchors[-1][1]举个例子一个黄昏感很强的调色板可以是0.0 → 深褐0.35 → 暗橙0.7 → 暖黄1.0 → 苍白。灰度值低的地方是暗部对应夜晚的大地灰度值高的地方是亮部对应天光。调色板的设计是没有捷径的我通常在冷色与暖色间隔分布和相邻锚点色尽量同色系两种策略之间切换。前者让人感到冲突和张力后者让人感到安静和舒适。同一张灰度底图只换个调色板重跑一遍出来的风格会天差地别。这也是批量做系列作品时的作弊利器结构是同一首曲子换音乐器就能出一整张专辑。3. 从零搭建可用工作流生成、精修、归档一条龙3.1 环境准备与项目目录设计实操之前先聊环境。我这里用的是 Python 3图像处理只需要装一个 Pillow 库。不需要 GPU不需要训练模型不需要高配机器这是我刻意为之的——算法生成应该是一件非常平易近人的事情门槛越低越能专注在创作本身。项目目录我建议按功能分四块scripts/放生成脚本所有代码共享一套参数解析逻辑。output/放脚本输出的原始草图和中间产物按日期分子目录。archives/放归档包含参数 JSON、源文件、最终成品这三个部分。projects/放每个手工精修子项目的过程文件给精修步骤留个工作台。目录结构看起来不复杂但它是整套流程里被低估的隐形基础设施。生成艺术做到后期最痛苦的不是不会写算法而是找不到半年前那张图的参数。稍后会细讲归档策略。3.2 生成器实现完整可跑的代码下面是 ArtCraft 最小可用的生成脚本。它会用 fbm 噪声生成一张灰度图再做颜色插值映射保存 PNG并把关键参数写进一个同名 JSON 文件。# scripts/generate_piece.py import argparse import json import random from pathlib import Path from PIL import Image def value_noise(width, height, scale, seed): rng random.Random(seed) grid_w int(width / scale) 2 grid_h int(height / scale) 2 grid [[rng.random() for _ in range(grid_w)] for _ in range(grid_h)] def smoothstep(t): return t * t * (3 - 2 * t) canvas [] for y in range(height): row [] for x in range(width): gx x / scale gy y / scale x0 int(gx) y0 int(gy) fx gx - x0 fy gy - y0 sx smoothstep(fx) sy smoothstep(fy) n00 grid[y0][x0] n10 grid[y0][x0 1] n01 grid[y0 1][x0] n11 grid[y0 1][x0 1] nx0 n00 (n10 - n00) * sx nx1 n01 (n11 - n01) * sx v nx0 (nx1 - nx0) * sy row.append(v) canvas.append(row) return canvas def fbm(width, height, scale, octaves, seed): total [[0.0 for _ in range(width)] for _ in range(height)] amplitude 1.0 amplitude_sum 0.0 freq 1.0 for i in range(octaves): layer value_noise(width, height, max(scale / freq, 1.0), seed i * 7919) for y in range(height): for x in range(width): total[y][x] layer[y][x] * amplitude amplitude_sum amplitude amplitude * 0.5 freq * 2.0 for y in range(height): for x in range(width): total[y][x] / amplitude_sum return total def palette_color(value, anchors): for i in range(len(anchors) - 1): p0, c0 anchors[i] p1, c1 anchors[i 1] if p0 value p1: t (value - p0) / (p1 - p0) return tuple(round(c0[j] (c1[j] - c0[j]) * t) for j in range(3)) return anchors[0][1] if value anchors[0][0] else anchors[-1][1] def main(): parser argparse.ArgumentParser() parser.add_argument(--seed, typeint, default42) parser.add_argument(--size, typeint, default1200) parser.add_argument(--scale, typefloat, default0.08) parser.add_argument(--octaves, typeint, default5) parser.add_argument(--out, typestr, defaultoutput) parser.add_argument(--palette, typestr, defaultdusk) args parser.parse_args() PALETTES { # 黄昏 dusk: [ (0.0, (20, 8, 20)), (0.35, (140, 45, 20)), (0.7, (230, 160, 70)), (1.0, (245, 225, 190)), ], # 深海 ocean: [ (0.0, (5, 15, 40)), (0.4, (10, 60, 90)), (0.75, (70, 140, 160)), (1.0, (210, 240, 230)), ], } anchors PALETTES[args.palette] img Image.new(RGB, (args.size, args.size)) pixels img.load() noise fbm(args.size, args.size, args.scale, args.octaves, args.seed) for y in range(args.size): for x in range(args.size): pixels[x, y] palette_color(noise[y][x], anchors) out_dir Path(args.out) out_dir.mkdir(parentsTrue, exist_okTrue) name fartcraft_seed{args.seed}_scale{args.scale}_oct{args.octaves}_{args.palette} img.save(out_dir / f{name}.png) meta { name: name, algorithm: fbm_value_noise, seed: args.seed, size: args.size, scale: args.scale, octaves: args.octaves, palette: anchors, } (out_dir / f{name}.json).write_text(json.dumps(meta, indent2), encodingutf-8) if __name__ __main__: main()跑起来很简单cd artcraft pip install pillow python scripts/generate_piece.py --seed 42 --size 1600 --scale 0.06 --octaves 5 --palette dusk运行完之后output/目录里会出现一张 PNG 和一个 JSON。我再解释一下为什么保存 JSONPNG 只记录像素结果不记录任何生产过程。你看到一张喜欢的图想知道这个效果是怎么做出来的只能靠 JSON 还原当时的所有参数。参数就是作品的基因JSON 就是那个基因测序报告。3.3 手工精修环节在生成结果上叠加手感生成图本身是干净的甚至过于干净。它的问题是边缘太顺滑、色块过渡太均匀、细节密度到处都一样看起来像展柜里的塑料模型缺少笔触的温度。手工精修就是用来解决这个问题的。我的精修流程通常是五步第一步把 PNG 导入位图编辑软件新建一个覆盖全图的空白图层。第二步在这个图层上用中等透明度的纹理笔刷做大面积扫动。这里的逻辑是给画面添加物理性笔触痕迹打破算法生成的绝对平滑。第三步用局部调整工具压低某些区域的亮度或者增加色温偏移。这一步的目的是制造视觉焦点。算法生成的图往往每个地方都在争抢注意力手工的职责就是分配注意力。第四步加一层轻微的噪点或胶片颗粒把数字感再降一个台阶。噪声幅度控制在 3% 到 5% 的可见度过重会糊过轻没效果。第五步最后做一次整体调色。如果需要打印还要另外做色彩配置。核心原则是程序提供构图手工提供残差。残差是什么意思就是画面里那些不规则、不完美、带有一点痕迹和意外的地方。算法的优点是效率缺点是全知全能式的平均。手工的价值不是把每个像素都改一遍而是打破平均制造呼吸感。3.4 作品归档种子、参数、源文件一起存归档是最不起眼、但长期价值最大的一段。我一开始也偷懒过结果三个月后想在一张早期作品基础上微调对着纯图片完全无从下手只能重新碰运气调参数。踩过那次坑之后我给自己定了规矩每张作品三个文件必须齐全。PNG 或原格式成品用于展示和打印。JSON 参数文件记录尺寸、种子、scale、octaves、调色板锚点以及软件脚本版本。一个可信的复现环境光有参数还不够脚本本身也会迭代所以我还会在 JSON 里追加一行script_version。源文件或过程文件留一份手工精修前的生成原图。很多情况下精修方向是错的或者过几个月想换个思路重新修有原图就能重来。归档目录命名我建议用日期加参数关键项比如20240612_artcraft_s42_scale006_oct5_dusk。收起盘里随便找这种命名方式让你一秒定位想要的作品。4. 常见问题与排查技巧实录4.1 问题一同样的种子两次结果不一样这可能是生成艺术里最让人抓狂的问题。你保存了 seed42过两天再跑一次出来的图跟之前不一样了。这通常不是随机数算法的问题而是你的脚本环境发生了变化。最常见的原因是全局随机状态污染。如果代码里还有别的模块调用了random.random()全局随机流的状态就会被改变即使你给生成器传了同样的种子实际取值序列也会错位。解决办法很简单不要让生成器使用全局 random而是像上面代码那样在每个函数内部创建独立的random.Random(seed)实例。另一个隐蔽原因是浮点运算在不同库版本之间产生了细微偏差。比如 Pillow 换了版本插值精度可能略有变化。这个问题不好根除我能给的建议是在 JSON 归档里记录 Python 版本和 Pillow 版本。需要严格复现时优先使用同版本环境跑。如果你只是要复现一个视觉风格而不是逐像素一致那么版本偏差通常是可以接受的。4.2 问题二图案总是有一种算法味这是初学者问得最多的问题。所谓算法味具体表现出来就是颜色看起来太干净所有区域都有等量的细节画面没有一个明确该被注意的地方。这种图一看就知道是程序生成的。我常用的破局办法有三个减少 octaves不要贪多。5 层以上细节会泛滥画面密度太高缺乏呼吸空间。调低到 3 到 4 层让大块区域保持平坦你的眼睛才有地方休息。调色板里故意加入一些脏颜色。在相邻锚点之间插入一个低饱和度的灰色或者棕灰色整个画面会瞬间多出一种旧照片的质感。纯度高是算法味的来源之一。手工精修时做局部裁切。不是每张图都要完整保留很多最有张力的构图藏在边边角角里。把生成图放大裁掉不需要的区域经常会得到比原图更高级的作品。4.3 问题三大幅面导出内存爆炸与颜色失真如果你要打印大幅面作品直接跑一个 6000x6000 的噪声图用上面那段纯 Python 代码内存会吃得很厉害。因为整个灰度矩阵是 Python float 列表存的一个像素占的内存远超实际需求。解决方案有两个。一是用 NumPy 重写噪声计算把数据切成 uint8 数组内存占用直接降到原来的四分之一以下。二是分块渲染先生成小块算完颜色映射后再拼接。对大多数场景我推荐先用 NumPy 重写这是性价比最高的方案。颜色失真是另一个高频问题。屏幕显示和印刷是完全不同的色彩系统屏幕用的 RGB 色域比印刷用的 CMYK 色域宽。屏幕上看很鲜艳的颜色打印出来会变灰变暗。不要等到打印前才处理生成阶段就应该意识到如果目标是印刷第一步就要选择更保守的调色板避免超高饱和度的纯色尤其是纯红纯绿纯蓝。第二步是在精修完成后做软打样在编辑软件里预览印刷模拟效果再微调输出。这听起来麻烦但总比几大百块钱打样出来了才发现颜色全不对要强。4.4 完整问题速查表现象可能原因排查与对策同一种子两次结果不同全局随机状态污染 / 库版本差异生成器内使用独立 Random 实例归档记录脚本与库版本画面太碎片、眼睛找不到焦点scale 太小 / octaves 过多调大 scale 到 0.06 以上octaves 降到 4 层以内手工局部压暗颜色发闷、看起来不精神调色板锚点全部集中在暗色区间把锚点分布拉开至少有一个接近亮部的颜色过渡带出现明显网格状条纹smoothstep 插值未生效 / 网格值本身跳变检查 smoothstep 是否作用在 x 和 y 两个方向更换 Perlin 噪声运行缓慢、内存飙升纯 Python 两层循环处理大图改用 NumPy 向量化分块渲染后再拼接打印颜色与屏幕差别大RGB 与 CMYK 色彩空间差异生成时降低饱和度打印前做软打样微调5. ArtCraft 的几种实际应用方向5.1 做素材库而不是做单张图ArtCraft 最大的优势不是帮你出单张图而是帮你批量化地搭建素材库。固定一套你觉得好看的核心参数然后让种子在 1 到 100 之间批量跑一遍。这样你会在短时间内获得上百张风格统一的底稿。这些底稿整理好后可以当多种用途新媒介账号的配图背景、商品包装纸的平面纹理、笔记本封面图、电子贺卡的底图。因为风格统一整套素材天然具有系列感比找一推全世界互不相关的图片要专业得多。批量跑的时候我会顺便生成一张索引缩略图把所有候选图拼成网格打印出来贴在桌面边上看。筛选这件事真的不应该在电脑屏幕上一张张点开用物理方式把一百张图摆在一张纸上效率反而高很多。5.2 不同媒介的输出差异同样一张图放到手机屏幕、打印成艺术微喷、做成织物品要求完全不一样。屏幕展示分辨率 150 dpi 足够sRGB 色彩空间即可。如果要做动态背景可以从脚本里额外导出透明通道把灰度层当作动画用蒙版。艺术微喷或普通印刷分辨率至少 300 dpi色彩要经过转换。大幅面时尤其需要注意我在上一节提到的内存与色彩配置问题。织物印花颜色要求跟印刷又不一样有些布料吸色程度高细节会丢失。建议先用小样试印一块确认纹理在布料上的表现后再批量生产。我个人的经验是先用一套参数在一个媒介上跑通全流程不要同时铺开三四种媒介。输出最怕遇到的情况是底稿满意了但导出规格不对、色彩配置错了、细节尺寸不适合目标表面结果从头再调。一次只锁定一个目标媒介杂音会少很多。5.3 从静态图案到交互作品如果觉得静态图已经熟练了可以让 ArtCraft 往前再走一步。比如把 fbm 噪声的层级用作粒子运动的力场或者把灰度值映射成某个装置的亮度信号。我认识的一些做交互装置的朋友就是拿类似的生成方式做 LED 灯光阵列的动态变化。这一步的原理并不复杂你不再输出最终像素图而是把灰度值输出为一个 JSON 数据阵列再由目标设备读取这些数据。同样的噪声在画布上是一张图在灯板上就是一张灯光编舞图。ArtCraft 的底层能力是生成有结构的数据图像只是数据的一种可视化形式。理解到这一层你能做的事情就远不止出图了。我自己的体会是这个项目走到最后真正收获的不是几百张图而是一套稳定的创作习惯生成、筛选、精修、归档四个动作循环往复每循环一轮审美和执行能力都跟着往上走一截。如果你也想试先别急着把参数调到最复杂。跑通最小闭环拿一个种子、一张图、做一次完整手工精修和归档你就能感受到这个流程跟随手画画或随手出图之间那种踏实的差别。最后再分享一个小细节文件命名一定要带关键参数。artcraft_seed42_scale006_oct5_dusk.png比untitled_001.png有用一百倍。积累的素材越多你会发现这项强迫症习惯有多值钱。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑