









图片超分这类工具,模型早就够用了,卡住普通人的是环境:Python、PyTorch、CUDA 版本、一长串命令行参数。 Final2x 做的事情是把这堆东西塞进一个六百多像素宽的小窗口——拖图进去、选模型、点开始。 让我想写一篇的不是它的界面,是它划进程边界的方式:界面这边一行推理代码都没有,真正干活的是另一个进程、另一门语言,两边靠三样东西通信。
跨平台的图片超分辨率工具,Windows、macOS、Linux 都有发行物,BSD 3-Clause。当前是 v4.0.0,推理后端换成了 cccv,开始支持挂自定义权重。
它不训练模型也不实现算法,做的是把一堆现成的超分模型包装成能双击的东西。内置的权重列表覆盖 Real-ESRGAN、Real-CUGAN、DAT、HAT、SwinIR、SCUNet、EDSR、SRCNN 八个系列七十多项,里头还有 APISR、AnimeJaNai、Ani4K 这些动漫向的微调版本,通用照片和二次元插画各有各的选择。
仓库结构是这篇想讲的重点。GUI 这边是 TypeScript 加 Vue,从主进程到界面组件,没有任何推理代码。所有算法在一个叫 Final2x-core 的独立项目里,Python 写的,能单独 pip 装、单独当命令行工具用。桌面应用只是它的一个前端。
Windows 和 macOS 下安装包就行。macOS 没走签名公证,第一次要手动放行:
xattr -cr /Applications/Final2x.appLinux 得自己先备好环境,因为安装包里不带推理内核:
pip install Final2x-core
Final2x-core -h
apt install -y libomp5 xdg-utils打开是个固定大小的窗口,把图拖进去、选模型和倍率、点开始。输出目录不填的话,它会从第一张输入图的所在目录推一个。
一次超分的进程往返:配置 base64 后当命令行参数下去,stdout 文本流上来,界面靠三个哨兵正则解析进度
(交互版,可缩放、可播放调用轨迹。)
一次超分的完整往返是这样:界面把设置攒成一个配置对象,通过 IPC 交给主进程;主进程把它序列化、base64、拼成命令行参数,起一个子进程;子进程一边跑一边往 stdout 打日志,主进程原样转发回界面;跑完给一个退出码。
两个进程之间的契约就这三样:一个 base64 过的 JSON、一条 stdout 文本流、一个退出码。没有 socket,没有本地 HTTP 服务,没有 gRPC,也没有反向通道——子进程起来之后,界面唯一还能对它做的事就是把它杀掉。
base64 那一步值得单说。配置里有引号,路径里有空格,中文路径也常见,而 Windows 的 cmd 和 POSIX shell 的转义规则完全不是一回事。编码之后参数里只剩 A-Za-z0-9+/=,引号地狱一次性消失。这招土,但比自己写一套跨平台的 shell 转义靠谱得多。
杀进程那头也有个坑。子进程是带 shell 起的,直接对拿到的 pid 发信号只会杀掉 shell,Python 那层变成孤儿继续占着显存。所以它按 pid 树整棵杀,并且在应用退出前先拦下 quit 事件,等杀干净了再放行。
进度条怎么来的?正则匹配 stdout。
const skipImageRegex = /______Skip_Image______:(.+)/
const processingRegex = /Processing------\[ ([\d.]+)% /
const srSuccessRegex = /______SR_COMPLETED______/界面每收到一段输出就拿这三条过一遍:匹配到百分比就更新进度条,匹配到跳过就弹一条提示,匹配到那个下划线包起来的完成标记就把成功标志置上。子进程退出时如果这个标志还是假,直接弹失败对话框——退出码为零不算成功,见过完成哨兵才算。
脆弱是明摆着的,内核那边改一句日志格式,进度条就静默失效。但它换来一件事:内核的输出对人也是可读的。同一份日志既喂给界面解析,也原样显示在日志抽屉里,不需要维护「给人看的」和「给程序看的」两套。人和程序读同一份输出,这个取舍我觉得是划算的。
推理内核从哪来:构建期拉一份打进安装包,运行期先探测系统里有没有,两条路汇到同一句 spawn
(交互版)
「到底用哪个内核」这件事有三段逻辑。
先探测:直接跑一次 Final2x-core -h,退出码为零就说明系统里有 pip 装的版本,那就直接用命令名调用。探不到才回退到安装包里带的那份,位置是可执行文件目录再上跳一级——打包配置里那份内核是以额外资源的身份放在 asar 归档旁边的,不进归档。
包里那份是构建期下载来的:CI 打包前先从内核项目的 Release 拉一个压缩包解开,再交给打包器塞进去。Linux 这一步直接跳过,因为 PyTorch 加上权重的体积不适合放进安装包,所以 Linux 的发行物文件名里干脆写着 pip。
还有个细节:界面里那个七十多项的模型下拉框不是手抄的,文件头上写着由 Final2x-core 自动生成、不要手改。跨语言的枚举同步交给代码生成,比两边各维护一份靠谱。而且这个下拉框是可输入的,v4 挂自定义权重就是从这儿输名字进去。
模型系列 —— Real-ESRGAN、Real-CUGAN、DAT、HAT、SwinIR、SCUNet、EDSR、SRCNN,含 APISR、AnimeJaNai、Ani4K 等动漫向微调权重 目标倍率 —— 可以不按模型固有倍率走,填一个目标缩放值,由内核自己组合放大与重采样 设备选择 —— Auto、CUDA、MPS、CPU 四挡,默认 Auto 分块推理 —— 显存不够时切块跑,默认开着 批量与格式 —— 一次拖多张,输出可选 PNG、JPG、WebP、TIFF 权重代理 —— 权重要从 GitHub 拉,可以填一个加速前缀 自定义权重 —— v4 起模型框可以直接输入名字
设置是持久化的,关掉再开还在。
pip 装,还得先有 Python 3.9+ 和 PyTorch 2.0+。这不算隐藏条款,发行物名字里就写着 pip。disableHardwareAcceleration,注释写的理由是 Windows 兼容性。xattr -cr 通常就够了。把 GUI 和推理拆成两个进程、两门语言,是这类工具的常见形态,但拆到什么程度差别很大。多数做法是嵌一个本地服务:起个 HTTP 或者 gRPC,双向通道,结构化消息,进度、日志、取消各走各的路。Final2x 选了最土的那一档,单向命令行加 stdout 文本。
代价上面写了。收益有两条,我认为都成立。
一是内核有了 GUI 之外的生命。它本来就是个 pip 包,装完直接是命令行工具,能进脚本、进批处理、进别人的流水线。如果协议是私有的 socket,内核就退化成桌面应用的附属品了。
二是两边可以完全独立发版。界面不需要知道内核的内部结构,内核也不需要为界面留接口。运行期那套「先看系统里有没有」的逻辑,正是因为协议足够薄才敢写——用户自己装的版本可能比包里那份新,但只要命令行参数没变就能直接顶上。
判断一个本地 AI 工具的外壳做得薄不薄,我现在会看一条:把 GUI 删掉,内核还能不能独立活着。能活的,外壳大概率是对的。
这类工具最容易走的一条路是:为了界面写起来顺手,把推理逻辑也拉进同一个进程,最后得到一个既不好用也不好改的整体。Final2x 反过来,把外壳压到只剩窗口、参数拼装和一个进度条正则,别的全推给一个能独立存在的命令行程序。
代码在 EutropicAI/Final2x,BSD 3-Clause,内核在 Final2x-core。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。