feat: 新增像素化节点(面向像素游戏资产,含 AI 伪像素图网格还原)

产出真实小分辨率、颜色数受控、边缘硬朗的 sprite,而非马赛克滤镜。
纯 numpy/PIL 自研实现,无额外依赖、无需模型权重。

三种模式:按目标宽度 / 按像素块大小 / 自动检测网格。第三种专治
AI 生成的伪像素图(看着像素风,实际网格歪斜、边缘抗锯齿、上千颜色)。

选型说明:Pixel Snapper(MIT)解决的是「伪像素图 → 完美像素图」,
而「普通图 → 像素画」是另一个问题,核心在降采样与调色板量化,
故两条路都做进同一节点。

网格检测处理了两类经典误判:
- 谐波(八度)错误:取得最高分后回查真约数(octave killer)
- 内容周期冒充像素周期:把最佳/最差相位的分差并入评分(anti-phase)
评分用单元内方差而非相邻差分——差分对模糊极敏感,实测会把 4 像素
网格判成 24~28;组内方差则天然压制过大的 s。

实测:干净放大图 k=2~16 共 27/27 全对零八度错误;非方形网格与相位
偏移全对;普通插画正确判为未检出;端到端把 32×32 放大 10 倍加模糊
噪点的图还原回 32×32,与真值 MAE 0.0049;整数倍放大为纯复制无插值。

调色板在 CIELAB 空间聚类(RGB 距离与人眼感受相差很远,会丢暗部层次),
降采样默认主导色(均值会造出新颜色并糊边),内置 PICO-8/Game Boy 等
可确认取值的复古调色板。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
rui40000
2026-07-29 18:23:43 +08:00
co-authored by Claude Opus 4.8
parent d47fd27062
commit b517c5714b
8 changed files with 1074 additions and 0 deletions
+10
View File
@@ -69,6 +69,14 @@ except Exception as _e:
print(f"[Ruinode] FeyNobg 抠图 节点未加载:{_e}")
FEYNOBG_NODE_CLASS_MAPPINGS = {}
FEYNOBG_NODE_DISPLAY_NAME_MAPPINGS = {}
# 新增:像素化节点(面向像素游戏资产:网格检测/主导色降采样/调色板量化/抖动)
try:
from .pixelate_node import NODE_CLASS_MAPPINGS as PIXELATE_NODE_CLASS_MAPPINGS
from .pixelate_node import NODE_DISPLAY_NAME_MAPPINGS as PIXELATE_NODE_DISPLAY_NAME_MAPPINGS
except Exception as _e:
print(f"[Ruinode] 像素化 节点未加载:{_e}")
PIXELATE_NODE_CLASS_MAPPINGS = {}
PIXELATE_NODE_DISPLAY_NAME_MAPPINGS = {}
# 新增:Lucida 全自动抠图节点(BiRefNet_HR 微调,擅长文字/Logo/插画/玻璃)
try:
from .lucida_node import NODE_CLASS_MAPPINGS as LUCIDA_NODE_CLASS_MAPPINGS
@@ -111,6 +119,7 @@ NODE_CLASS_MAPPINGS.update(TEXTBOX_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(WATERMARK_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(FEYNOBG_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(LUCIDA_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(PIXELATE_NODE_CLASS_MAPPINGS)
# 合并节点显示名称映射
NODE_DISPLAY_NAME_MAPPINGS = {}
@@ -137,5 +146,6 @@ NODE_DISPLAY_NAME_MAPPINGS.update(TEXTBOX_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(WATERMARK_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(FEYNOBG_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(LUCIDA_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(PIXELATE_NODE_DISPLAY_NAME_MAPPINGS)
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']