关于 image-to-svg
一个 Python 图像处理流程,通过基于像素的颜色量化和轮廓提取,将栅格图像复现为 SVG。它提供填充与线稿组合流程、调色板预设及批量生成,适用于创作者和开发者。
主要功能
- 01将栅格图像转换为 SVG
- 02按图像类型选择处理模式
- 03将线稿提取为 SVG 描边
- 04使用调色板预设重新映射颜色
- 05批量生成多个变体
适用场景
- 01矢量化徽标和图标
- 02将绘画或照片复现为 SVG
- 03创建波普艺术调色板变体
- 04从一张图像批量处理多种风格
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ oaustegard/ claude-skills --skill image-to-svg安装后在 Claude Code 中输入/image-to-svg确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CHANGELOG.mdscripts/lines.pynn_assign.cpipeline.py详细介绍
将图像复现为 SVG
使用数据驱动的颜色量化和轮廓提取,将栅格图像转换为逼真的 SVG 复现图。绝不要根据视觉解读手绘形状——始终从实际像素数据中提取几何信息。
核心原则
相信数据,而不是想象。 Claude 对图像的视觉解读在精确匹配颜色、定位形状和判断空间关系方面并不可靠。每个形状、颜色和位置都必须通过对源像素的计算分析得出。
快速开始
pip install opencv-python-headless scikit-image scipy scikit-learn --break-system-packages -q
apt-get install -y librsvg2-bin -qq
import sys
sys.path.insert(0, '/mnt/skills/user/image-to-svg/scripts')
from pipeline import image_to_svg
svg, flow = image_to_svg("source.jpg", mode="painting")
with open("output.svg", "w") as f:
f.write(svg)
flow.summary() # timing + status per step
模式选择
观察图像并思考:“它是否有平滑渐变或硬边缘?”渐变 → 更高的 K 值。硬边缘 → 更低的 K 值。
| 模式 | K | 最适合 | 深色形状筛选 |
|---|---|---|---|
"graphic" | 28 | Logo、图标、康定斯基风格作品、扁平设计 | 宽松(保留细线) |
"illustration" | 40 | 漫画、编辑插画、数字艺术 | 适中 |
"painting" | 56 | 文艺复兴风格、印象派、水彩画 | 标准 |
"photo" | 64 | 肖像、风景、静物 | 标准(防止产生木刻伪影) |
默认值为 "painting"。不确定时,从这里开始。
权衡:K=64 会生成约 2300 个形状(约 1.2MB SVG),而 K=28 会生成约 1000 个形状(约 550KB)。这些数据是在 v1.9.0 的紧凑输出格式推出前测得的;新格式体积约缩小至原来的 2.6 倍(参见输出格式)。K 值增加时,处理时间大约翻倍。对于照片,色调渐变质量的提升非常显著;对于图形艺术,这种提升则毫无用处。
所有模式默认值(K、dark_lum、compactness_min 等)都可以通过 **kwargs 覆盖:
svg, flow = image_to_svg("source.jpg", mode="graphic", K=12, min_area=20)
合成式处理管线(线稿)
对于以线条、笔画和几何形状为主的图像(康定斯基风格作品、建筑图纸、技术插画、漫画墨线作品),标准的纯填充处理管线会生成锯齿状的填充多边形,而不是干净的笔画。合成式处理管线通过两个阶段解决这个问题:
阶段 1 — 线条提取:通过形态学腐蚀分离细小特征 → 骨架化为 1px 中心线 → 使用霍夫直线检测 → 合并共线线段 → 测量笔画宽度 → 采样颜色。输出带有 stroke-width 的 SVG <line> 元素。
阶段 2 — 填充提取:从图像中抑制线条区域(使用中值模糊生成的局部背景估计替换)→ 对清理后的图像运行标准 K 均值量化 → 提取轮廓 → 使用 <path> 填充。
合成:填充绘制在笔画下方,并置于分层的 <g> 组中。
# Auto-detect: classifies input and routes automatically
svg, flow = image_to_svg("kandinsky.jpg", mode="graphic")
# Force compositional pipeline
svg, flow = image_to_svg("technical_drawing.png", mode="graphic", pipeline="compositional")
# Force fill-only (previous default behavior)
svg, flow = image_to_svg("photo.jpg", mode="painting", pipeline="fill")
处理管线选择(pipeline 参数):
| 值 | 行为 |
|---|---|
"auto"(默认) | 根据边缘密度、亮度双峰性和霍夫直线数量对输入进行分类。图形艺术使用合成式处理管线,照片使用纯填充处理管线。 |
"fill" | 强制使用纯填充处理管线。适用于照片、绘画,或合成式处理产生不理想结果的情况。 |
"compositional" | 强制使用两阶段处理管线。适用于线稿、技术图纸或已知包含线条的墨线作品。 |
自动分类启发式规则:边缘密度较高(>5% 的像素为边缘像素)且亮度分布呈双峰(>0.35 的双峰系数),或直线密度较高(每 10k 像素有 >3 条霍夫直线)时,图像会被分类为图形。
SVG 输出结构(合成式处理管线):
<svg ...>
<rect ... />
<g id="fills" stroke-width="1" stroke-linejoin="round">
<path class="c3" d="m12 40l5-2..z"/>
</g>
<g id="strokes">
<line x1="..." y1="..." x2="..." y2="..." stroke="#000" stroke-width="2.5" stroke-linecap="round"/>
</g>
</svg>
笔画宽度控制:垂直于检测到的每条直线进行测量,然后缩放至 0.65 倍,并限制在 4.5 个 SVG 单位以内。这可以防止较粗的特征渲染成臃肿的笔画,同时保持细线清晰。
当前限制——仅支持直线:霍夫变换可检测直线线段。弧线、螺旋线等曲线笔画目前不会被提取为笔画,而会进入填充阶段。后续工作:在骨架分支上使用 cv2.fitEllipse 或样条拟合。
调色板重映射(Warhol 效果)
将结构与颜色分离:K-means 用于查找区域,调色板重映射用于分配鲜明的颜色。由此可生成丝网印刷/波普艺术效果。
# Named preset
svg, flow = image_to_svg("photo.jpg", mode="graphic", K=4, palette="pop")
# Custom hex list (darkest → lightest mapping order)
svg, flow = image_to_svg("photo.jpg", mode="graphic", K=8,
palette=["#000", "#dc143c", "#ff69b4", "#ffd700", "#32cd32", "#00bfff", "#ff8c00", "#f5f5f5"])
# Override background separately
svg, flow = image_to_svg("photo.jpg", mode="graphic", K=4, palette="ocean", bg_color="#000000")
内置预设:bw、mono3、mono4、pop、pop2、neon、warhol4、warhol6、warhol8、sunset、ocean
工作原理:按亮度对不同的形状颜色排序。按比例映射调色板条目:palette[0] 替换最暗的簇,palette[-1] 替换最亮的簇。除非设置了 bg_color,否则背景默认为调色板中最亮的条目。调色板长度无需与 K 完全一致;颜色会按比例分箱。
人像:即使使用鲜明的调色板,也应将 K 设为 16–24。面部特征(眼镜、胡须、眉毛)需要低 K 会消除的色调范围。经验法则:调色板长度 ≈ K/3,以实现干净的亮度分箱。K=8 且使用 4 色调色板时,面部会变成一团无法区分的色块。
对比度预处理警告:外部增强对比度(contrast-stretch、sigmoidal-contrast)可能会干扰背景检测。该管线的边缘接触启发式假设亮度分布未经改动——激进的色调映射会将主体色调推入与背景相邻的分箱,导致误分类(例如,将深色夹克区域分类为背景,并映射为调色板中最亮的颜色)。如果看到主体区域被替换成背景色,请先尝试不进行预处理。管线自带的双边模糊和可选的 kuwahara/oilpaint 会处理色调分离。
背景检测覆盖(bg_clusters)
控制哪些簇被视为背景:
# Auto-detect (default) — edge-contact heuristic
svg, flow = image_to_svg("photo.jpg", mode="illustration", K=20, palette="warhol6")
# Disable — no clusters removed, no background rect color override
svg, flow = image_to_svg("photo.jpg", mode="illustration", K=20, palette="warhol6", bg_clusters=0)
# Force specific cluster indices (from quantize step's sorted_clusters output)
svg, flow = image_to_svg("photo.jpg", mode="illustration", K=20, palette="warhol6", bg_clusters=[2, 5])
当调色板重映射已明确控制所有颜色,且背景检测造成干扰时,请使用 bg_clusters=0。当你知道哪些簇属于背景,但启发式识别错误时,请使用 bg_clusters=[list]。
人像波普艺术配方(Warhol 风格)
# Key: enough K for facial features, palette length ~K/3, modest smoothing
# Do NOT apply contrast preprocessing — it breaks background detection.
results = image_to_svg_batch("portrait.jpg", [
{"name": "hot", "mode": "illustration", "K": 20, "smooth": "kuwahara:6",
"palette": ["#000", "#D4145A", "#FF6B9D", "#FF85C0", "#FFD700", "#FFEF82", "#FFF8DC"]},
{"name": "cool", "mode": "illustration", "K": 20, "smooth": "kuwahara:6",
"palette": ["#0D0035", "#4A00E0", "#7B68EE", "#00D4FF", "#7FFFD4", "#B0FFE0", "#E0FFFF"]},
{"name": "earth", "mode": "illustration", "K": 20, "smooth": "kuwahara:6",
"palette": ["#1a0a00", "#8B4513", "#CD853F", "#DEB887", "#F5DEB3", "#FAEBD7", "#FFF8DC"]},
{"name": "neon", "mode": "illustration", "K": 20, "smooth": "kuwahara:6",
"palette": ["#0d0d0d", "#ff00ff", "#00ff00", "#ffff00", "#00ffff", "#ff69b4", "#f5f5f5"]},
], svg_width=700)
此配方有效的原因:K=20 能保留足够多的色调簇来呈现面部结构(眼镜、胡须、眉脊)。7 色调色板可提供约 K/3 个亮度分箱,足以区分面部特征,又不会显得浑浊。kuwahara:6 可以平滑纹理而不抹除边缘(:12 会抹掉眼镜)。使用原始图像作为输入,只进行管线内的平滑处理;不进行外部对比度调整。
ImageMagick 预处理(smooth)
在量化前使用 ImageMagick 保留边缘的滤镜,可减少 20–45% 的形状数量和 SVG 文件大小。需要在 PATH 中安装 ImageMagick(Claude.ai 容器已预装)。
# Oilpaint: bold, painterly smoothing (default strength=8)
svg, flow = image_to_svg("photo.jpg", mode="photo", smooth="oilpaint")
# Stronger smoothing = fewer shapes, more stylized
svg, flow = image_to_svg("photo.jpg", mode="illustration", K=32, smooth="oilpaint:12")
# Kuwahara: subtler, preserves more structure (default strength=5)
svg, flow = image_to_svg("photo.jpg", mode="painting", smooth="kuwahara:7")
# Works with batch API too
results = image_to_svg_batch("photo.jpg", [
{"name": "raw", "mode": "photo"},
{"name": "smooth", "mode": "photo", "smooth": "oilpaint"},
{"name": "stylized", "mode": "illustration", "K": 32, "smooth": "oilpaint:12", "palette": "pop"},
])
可用滤镜:oilpaint(ImageMagick -paint)、kuwahara(ImageMagick -kuwahara)。追加 :N 可设置自定义强度。
工作原理:IM 滤镜在管线的双边模糊和高斯模糊之前运行。二者是在不同尺度上保留边缘的平滑器:IM 处理粗糙纹理,双边模糊处理精细细节。结果是 K-means 区域更加干净,碎片化形状更少。
测量结果(1206×1597 照片,K=32):
| smooth | 形状数 | SVG 大小 | 减少幅度 |
|---|---|---|---|
| none | 3381 | 1868KB | — |
| oilpaint (8) | 2385 | 1329KB | -29% |
| oilpaint:12 | 1842 | 1065KB | -43% |
| kuwahara (5) | 2719 | 1453KB | -22% |
| kuwahara:7 | 2000 | 1152KB | -38% |
管线架构
使用 flowing DAG 运行器。输入相互独立的步骤会并行运行。
仅填充管线(pipeline="fill")
preprocess → quantize → ┬─ detect_background ─┬─ extract_contours → assemble_svg
└─ edge_map ─┘
组合式流水线(pipeline="compositional")
classify_input ──→ extract_lines ──→ suppress_line_regions ──→ [fill pipeline on cleaned image]
│ │
└──────────── lines ───────────────────→ assemble_compositional ←── fills
步骤(仅填充):
- preprocess — 双边滤波 + 高斯模糊(保留边缘地去除纹理)
- quantize — 按选定的 K 值进行 K-means 颜色量化
- detect_background — 根据边缘接触情况识别背景聚类(与 edge_map 并行)
- edge_map — 使用
cv2.Sobel进行 Sobel 边缘检测(与 detect_background 并行) - extract_contours — 按聚类提取轮廓,感知深色区域并防止木刻效果(膨胀 d=1;描边负责填补间隙)
- assemble_svg — 使用按 Z 顺序排列的画家算法进行组装,并通过描边覆盖填充间隙
附加步骤(组合式):
- classify_input — 分析边缘密度、双峰性和 Hough 直线数量
- extract_lines — 形态学分离细特征 → 骨架化 → Hough 检测 → 合并共线特征 → 测量描边宽度 → 采样颜色
- suppress_line_regions — 用中值模糊估算的背景替换线条像素
- assemble_compositional — 在描边后方放置填充图层,并在 SVG 中分组
失败时恢复
svg, flow = image_to_svg("source.jpg", mode="photo")
# If extract_contours failed:
flow.override(extract_contours, corrected_shapes)
flow.resume() # quantize, detect_background, edge_map stay cached
批处理 API
从一张图像生成多个变体,并在 K 值相同的运行之间共享计算:
from pipeline import image_to_svg_batch
results = image_to_svg_batch("photo.jpg", [
{"name": "photo", "mode": "photo"},
{"name": "warhol", "mode": "graphic", "K": 12, "palette": "warhol4"},
{"name": "neon", "mode": "graphic", "K": 12, "palette": "neon"},
{"name": "sunset", "mode": "graphic", "K": 12, "palette": "sunset"},
{"name": "bw", "mode": "graphic", "K": 16, "palette": "bw"},
], svg_width=1400)
for name, svg in results.items():
with open(f"{name}.svg", "w") as f:
f.write(svg)
K 值相同的变体只运行一次流水线(preprocess → quantize → edge_map → extract_contours),然后在组装阶段分支处理调色板映射。这样可确保不同调色板变体的结构一致(形状和路径相同),并为每组共享 K 值节省约 20–60 秒。
批处理模式仍需进行验证。 批处理带来的便利容易让人跳过并排比较——不要跳过。每组 K 值至少渲染一个变体,并在交付前验证。背景检测失败和调色板映射问题如果不渲染就无法发现。
验证流程
每次运行后,都要渲染并进行并排目视比较。 这是必须遵守的要求。
import subprocess
from PIL import Image
subprocess.run(['rsvg-convert', '-w', '1400', 'output.svg', '-o', 'output.png'])
orig = Image.open('source.jpg')
rendered = Image.open('output.png')
target_h = 800
orig_r = orig.resize((int(orig.width * target_h / orig.height), target_h))
rend_r = rendered.resize((int(rendered.width * target_h / rendered.height), target_h))
gap = 20
comp = Image.new('RGB', (orig_r.width + rend_r.width + gap, target_h), (255,255,255))
comp.paste(orig_r, (0, 0))
comp.paste(rend_r, (orig_r.width + gap, 0))
comp.save('comparison.png')
# LOOK AT comparison.png BEFORE claiming success
手动后处理
处理细微的颜色差异
当两个区域的亮度相近、但色相/饱和度不同时,RGB 空间中的 K-means 会将它们合并。请使用HSV 多光谱分析:
hsv = cv2.cvtColor(rgb, cv2.COLOR_RGB2HSV)
h_ch, s_ch, v_ch = hsv[:,:,0], hsv[:,:,1], hsv[:,:,2]
# Separate gray (low saturation) from red (high saturation) at similar brightness
red_mask = ((h_ch < 12) | (h_ch > 168)) & (s_ch > 120) & (v_ch > 80)
gray_mask = (s_ch < 80) & (v_ch > 40) & (v_ch < 120) & spatial_constraint
对于在灰度图中看起来相似、但视觉上有明显区别的颜色,饱和度是关键判别因素。
定位叠加图形
添加未被量化捕获的形状时,请根据 SVG 渲染结果推导坐标,而不是根据源图像。提取流水线会因轮廓简化而改变位置。
# WRONG: extract from source, insert into SVG (coordinate mismatch)
# RIGHT: render SVG → detect gap in render → create shape in render coords → insert
svg_render = cv2.imread('rendered_svg.png')
输出格式
从 v1.9.0 起,填充内容会以紧凑格式写入。在《星夜》中,绘画输出从 3.64 MB 减少到 1.35 MB(gzip 压缩后从 825 KB 减少到 293 KB),在 Chromium 和 librsvg 中的渲染效果一致:
<svg xmlns="http://www.w3.org/2000/svg" id="s1a2b3c" viewBox="0 0 1000 625">
<style>#s1a2b3c .c0{fill:#3e85bb;stroke:#3e85bb}#s1a2b3c .c1{...}</style>
<rect width="1000" height="625" fill="#16151e"/>
<g stroke-width="1" stroke-linejoin="round">
<path class="c0" d="m793 0l-10.8 2.9-1 14.7..z"/>
</g>
</svg>
- 每种调色板颜色使用一个 CSS 类,并限定在每个文档各自的 id 下,因此可以在同一页面内联多个 SVG,而不会发生类名冲突。
- 连续的同色形状会合并到一个
<path>中(保持绘制顺序;evenodd 形状仍单独保留),因此元素数量少于形状数量。 - 路径数据使用相对
m/l命令,坐标以10^-precision为单位的网格表示。precision=1(默认值)与轮廓网格一致,不会造成精度损失;precision=0会将坐标四舍五入到整数 viewBox 单位,体积约减少 20%,平均像素差异约为 1%。 - 形状在内部保留绝对的
"M x,y L x,y Z"路径字符串(flow.value(extract_contours)["shapes"]);只有输出的 SVG 会采用紧凑格式。 - 若要为颜色添加动画,请指定其类:
@keyframes k{50%{fill:#f0f;stroke:#f0f}} #s1a2b3c .c0{animation:k 4s infinite}。
间隙覆盖:stroke=fill
每个填充 <path> 都会使用自身的填充色描边,stroke-width="{gap_stroke}",并采用圆角连接(在 fills 组上设置一次)。这会让每个形状向外扩展自身的填充色,用局部正确的颜色覆盖簇间隙。
自动缩放:gap_stroke 的计算方式为 max(1.0, round(svg_width / source_width))。svg_width=1000 时,500px 的源图得到 gap_stroke=2;1000px 的源图得到 gap_stroke=1。这可以防止“雪花”伪影:形状数量较多的 SVG 在缩小显示时,因描边扩展过度而出现明显的浅色光晕。可显式覆盖:image_to_svg("img.jpg", gap_stroke=3)。
为什么描边比膨胀更适合填补间隙:膨胀作用于轮廓简化之前的二值掩码——会模糊细节。描边作用于 approxPolyDP 之后的最终多边形——能填补所有间隙,包括简化引入的间隙。完全基于矢量;从 v1.9.0 起,描边的开销仅为每种颜色的类中增加一条 stroke 声明。
背景回退:当 detect_background 找不到任何簇时,背景矩形使用 #000000(黑色)而非白色。黑色看起来像阴影;白色看起来像缺失。
膨胀缩减为 iterations=1——仅用于形态学降噪。间隙覆盖完全由描边处理。
反模式
- 切勿根据视觉判断手绘形状。应使用 CV 提取。
- 切勿在未渲染并比较的情况下声称修复有效。渲染后的比较是唯一的验证方式。
- 切勿使用几何图元(圆形、矩形)近似提取出的轮廓。
- 切勿从源图像提取坐标并插入 SVG,除非已验证对齐情况。
- 切勿全局提升饱和度。应根据测得的 ΔE 针对单种颜色进行调整。
- 切勿大幅合并接近背景色的颜色。仅合并与背景色 RGB 距离小于 10 且边缘接触严重的颜色。
- 除非收到请求,否则不要使用贝塞尔平滑。简单的 L 形多边形能生成更小的 SVG。
- 不要使用大于 3×3 的膨胀核。在 3×3 核上使用
iterations=1——stroke=fill会在矢量空间处理间隙覆盖,因此膨胀只需封闭噪声孔洞。
已知限制
- 细线条(仅填充):用于防止照片出现木刻伪影的深色形状筛选,可能会过滤图形艺术中有意绘制的细线。“graphic” 模式会放宽筛选,但非常细的交叉排线仍可能退化。对于线稿输入,请使用
pipeline="compositional"——它会将细小特征提取为 SVG 描边。 - 曲线(compositional):霍夫变换只能检测直线线段。弧线、螺旋线、手绘曲线等曲线笔画会落入填充阶段,并渲染为填充多边形。后续工作:在骨架分支上使用
cv2.fitEllipse或样条拟合。 - 环形/弧形结构:大型深色圆环(如 Kandinsky 的外圆)会分散到多个 K-means 簇中。每个簇的轮廓彼此独立,因此圆环无法形成一个平滑的整体形状。增加深色簇合并步骤会有所帮助。
- 渐变过渡:无论 K 为何,平滑渐变都会产生阶梯状色带。增大 K 可以减少这种现象,但无法彻底消除。
- 平行线组(compositional):如果相邻线条之间的垂直距离低于合并阈值(6px),密集排线或网格线可能会被错误合并。目前的合并步骤无法将平行但偏移的线条识别为独立笔画。
依赖项
pip install opencv-python-headless scikit-image scipy scikit-learn --break-system-packages
apt-get install -y librsvg2-bin # for rsvg-convert
编译加速:如果存在 gcc,首次使用时会自动编译 nn_assign.c(标签分配速度提升 27 倍)。如果不可用,则回退到 numpy。
跨技能依赖(由 pipeline.py 自动解析):
- flowing — DAG 工作流运行器