airi-factorio 纯视觉方案实战:从 Docker 化游戏客户端到浏览器端 YOLO 实时目标检测
导读
AIRI 是一款以"让 AI 拥有具身数字生命"为目标的开源项目,而 airi-factorio 是其让 AI 学会玩《异星工厂》(Factorio) 的探索分支。本文基于 AIRI 维护者 [@LemonNekoGH] 的月度开发日志(DevLog @ 2025.08.26),完整复现一条"纯视觉(pure vision)"闭环流水线:把 Factorio 客户端装进 Docker 并通过 VNC 暴露到浏览器、用 Lua 脚本自动采集游戏画面并标注数据、基于 YOLO11n 训练目标检测模型、再借助 onnxruntime-web 与 WebGPU 在浏览器里完成近实时推理。读完本文,你将掌握一套可复制的"游戏视觉感知栈",并为后续"让 AI 看懂画面再决策操控"打下基础。
背景:在上一期 DevLog @ 2025.07.18 中,作者基于 Factorio Learning Environment 论文梳理了
airi-factorio的改进方向;本期则聚焦其中"纯视觉"路线的具体落地成果。
一、整体思路:为什么先做"纯视觉"
在作者此前的方案里,AI 是通过 Mod + RCON 直接读写游戏状态的(用 TypeScript 写 Mod、经 RCON 执行 /c 命令、让 LLM 生成 Lua 代码操控游戏)。这套方案虽然能拿到精确的游戏数据,但调试繁琐、命令长度受限、健壮性差。
纯视觉方向则完全不同:让 AI 像人一样直接"看屏幕"。触发点是 2025 年 6 月社区发布的近实时 VLM Playground 演示,作者决定先实现"简单的实时图像识别",再交给 AI 决策,最后以某种方式输出动作到游戏。这条路线分为三个可独立验证的环节:
- 构建一个可控、开箱即用的游戏运行环境(Docker + 虚拟显示 + VNC);
- 训练一个能识别游戏内物体的目标检测模型(YOLO);
- 在浏览器端完成近实时的推理与结果后处理(NMS)。
最终效果是一个网页版的 Playground:通过 VNC 在浏览器里玩 Factorio,右侧几乎实时地显示物体检测结果(演示视频见 airi-factorio-yolo-v0-playground-vnc.mp4)。
二、把 Factorio 客户端装进 Docker
要让 AI"看见"游戏画面,前提是游戏运行在一个不受窗口大小、位置干扰的受控环境中,且能开箱即用。Factorio 官方虽然提供 Docker 镜像,但那是纯服务器版;要看到画面并操控游戏,需要的是客户端。社区没有现成的客户端镜像(且 Factorio 许可证不允许以这种方式分发客户端),因此只能自行打包,并且打包后的镜像同样不能公开分发,只能共享 Dockerfile。
把客户端装进 Docker 一共三步:
| 步骤 | 目标 | 对应组件 |
|---|---|---|
| 1 | 拿到 Factorio 客户端本体 | factorio-dl 下载脚本 |
| 2 | 准备虚拟显示,让图形程序有地方画画面 | xvfb + 最小 X 环境 |
| 3 | 提供 VNC 服务,把虚拟显示内容传出并接收输入 | x11vnc |
至于音频——目前 AI 还听不见,暂时忽略。
2.1 下载 Factorio 客户端
官方下载需要人工登录,不利于自动化流程。作者使用了一个非常复杂的 shell 脚本 factorio-dl:给定用户名、密码和目标版本后,它会根据系统架构自动下载对应客户端。这一步为后续在 CI / 容器构建脚本中无人工下载客户端提供了前提。
2.2 准备虚拟显示
图形应用必须有显示服务才能运行,但并不需要完整的桌面环境或窗口管理器——一个最小化的 X 环境加一个显示服务器就足够了。安装命令:
sudo apt install -y xvfb x11-apps mesa-utils
组件说明:
xvfb:虚拟帧缓冲(Virtual Framebuffer)和 X 服务器,让图形程序在"看不见的屏幕"上渲染;x11-apps:一些 X 相关工具,安装它会把 X 环境一并装好;mesa-utils:Mesa 相关工具;Mesa 是 OpenGL 的软件实现,提供工具用于测试和调试 OpenGL 应用(Factorio 的渲染依赖 OpenGL,因此这一步必不可少)。
2.3 准备 VNC 服务
VNC(Virtual Network Computing)是一种远程桌面协议,可以像坐在电脑前一样远程控制另一台机器:
sudo apt install -y x11vnc
到这里,我们已经可以在 Docker 里跑起 Factorio 客户端并通过 VNC 控制它了。但作者的最终目标是在浏览器里游玩并实时推理,而浏览器只支持 HTTP 协议,还需要两件东西:
websockify:把 VNC 协议转换为 HTTP(WebSocket)协议;novnc:一个网页端的 VNC 客户端界面,方便调试时直接显示 VNC 画面。
sudo apt install -y websockify novnc
至此 Docker 镜像准备完毕。这四类系统包(xvfb / x11-apps / mesa-utils / x11vnc / websockify / novnc)共同构成了容器内的显示与远程访问链路。
三、训练目标检测模型
为了快速验证思路,作者直接用 YOLO11n 的预训练权重作为底座来训练自己的检测模型,而不是从零训练。
3.1 用 Lua 脚本自动采集数据集
数据采集完全在游戏内自动化完成,利用 Factorio 的 Lua API:
- 用
surface.create_entity在场景随机位置放置机器,同时记录其选择框(selection box)的尺寸与位置; - 用
game.take_screenshot在不同缩放级别、不同光照条件(白天)下截取画面; - 依据选择框生成标注数据,再用
helpers.write_file写入文件。
实现上的关键点:数据采集脚本用 TypeScript 编写,通过 typescript-to-lua 编译为 Lua,再经 RCON 传给 Factorio 执行——这正是上一期 DevLog 沉淀下来的工具链(TypeScript 写 Mod + RCON 通信)。为了调试采集脚本,作者还开发了一个 VSCode 插件,提供 CodeLens 操作,一键编译并执行脚本。
数据集规模(v0):采集了 3 种组装机(assemblers)和传送带(conveyors),每种机器 20 张图,每张 1280×1280 分辨率、不带 UI。
3.2 组织数据集并上传 Ultralytics Hub
采集到图像和标注后,需要按 YOLO 官方数据集格式 组织目录,之后上传到 Ultralytics Hub 查看效果:
图为数据上传 Ultralytics Hub 后的训练预览界面,可直观看到标注框与训练趋势。
3.3 训练与导出
训练代码直接沿用了 Ultralytics 官方 Get Started 示例,几行即可完成训练与导出:
from ultralytics import YOLO
model = YOLO("yolo11n.pt")
model.train(data="./dataset/detect.yaml", epochs=100, imgsz=640, device="mps")
model.export(format="onnx")
关键训练参数与结果(来自日志原文,可在相应环境复现验证):
| 项目 | 取值 |
|---|---|
| 输入分辨率 | 640×640 |
| 训练设备 | mps(macOS 下 Metal 性能加速) |
| 轮数 | 100 epochs,每 epoch 5 个 batch |
| 最优权重 | 约在第 70 epoch 达到最佳 |
| 导出格式 | ONNX |
| 训练耗时 | 约 8 分钟 |
| 模型大小 | 约 10MB |
四、浏览器端实时推理
数据链路在浏览器端打通,作者选用了两个库:
@novnc/novnc:在浏览器显示 VNC 画面,同时提取 canvas 数据喂给模型;onnxruntime-web:在浏览器执行推理,支持 WebGPU 以利用 GPU 性能。
4.1 性能优化的三次跳跃
初始推理非常慢,约 400ms,而且会冻结 UI、导致 VNC 卡得没法用。作者靠两条经验把延迟一步步压了下来:
第一步:分离推理与显示。 快速学习 WebWorker 用法后,把推理从渲染线程挪到 Worker 里,解决了 UI 冻结问题——但此时速度依然慢,因为根本没有真正启用 WebGPU。
第二步:显式声明执行后端。 只写 executionProviders: ['webgpu'] 并不够,必须同时声明 WASM 作为兜底,WebGPU 不可用时自动切换:
ort.InferenceSession.create(model, { executionProviders: ['webgpu', 'wasm'] })
启用 WebGPU 后,推理速度提升到约 80ms。
第三步:用乘法替换除法。 在对像素颜色值做归一化时,原始代码反复执行除以 255。改为先计算 1/255 一次,再直接乘上这个值,避免每次除法。看似微不足道的改动,让推理速度进一步提升到约 20ms,体验已经相当流畅。
这一小段经历也点出了一个容易被忽视的常识:除法通常比乘法慢。在 8400 个候选框 × 每个框若干像素的归一化循环里,替换除法的收益被显著放大。
4.2 处理模型输出:IOU 与 NMS
模型输出的是一个 84000 个元素的数组,其 dims 为 [1, 10, 8400]:84000 按每 10 个一组划分,每组包含边界框中心 x、中心 y、宽、高,以及 6 个类别的置信度,共 8400 组候选结果。
直接输出没法用,需要两步后处理:
- 置信度过滤:过滤掉置信度低于 0.6 的框;
- NMS 去重:用 IOU(Intersection over Union,交并比)作为依据,去除互相重叠的框。
IOU 的直观定义:把两个框的面积相加,减去重叠区域得到"实际占用的并集面积",再用重叠面积除以并集面积:
IOU = 交集面积 / 并集面积 = 交集面积 / (框1面积 + 框2面积 - 交集面积)
作者的 NMS 实现非常简洁:按置信度降序排序,从最高分开始遍历,凡是与当前最高分框 IOU 大于 0.7 的都视为同一物体并剔除:
function nms(boxes: Box[], iouThreshold: number): Box[] {
// 1. Filter by confidence and sort in descending order
const candidates = boxes
.filter(box => box.confidence > 0.6)
.sort((a, b) => b.confidence - a.confidence)
const result: Box[] = []
while (candidates.length > 0) {
// 2. Pick the box with the highest confidence
const bestCandidate = candidates.shift()!
result.push(bestCandidate)
// 3. Compare with remaining boxes and remove ones with high IOU
for (let i = candidates.length - 1; i >= 0; i--) {
// The iou() function needs to be implemented separately, as described in the article.
if (iou(bestCandidate, candidates[i]) > iouThreshold) {
candidates.splice(i, 1)
}
}
}
return result
}
仓库内还有一个配套的 IOU/NMS 可视化组件:基于 Vue 3 + @vueuse/core 的 useDraggable 与 useElementBounding 实现,页面中有两个可拖拽的检测框(box_1 / box_2),组件实时计算交集矩形(取 Math.max 的左/上边界与 Math.min 的右/下边界)、并集面积与 IOU 值并展示计算过程,让 IOU/NMS 的几何含义一目了然,是理解上文算法的最佳配套教具。
4.3 实践中发现的问题
通过这次完整实践,作者总结出纯视觉方案当前的三处短板:
- 无法识别非方形图像:一旦输入不是正方形,模型所有结果的置信度都会变得极低,甚至为 0;
- 类别混淆:模型能区分一级和二级组装机,但会把箱子这类方形物体也误识别为组装机;
- UI 叠加干扰:实际游玩时,机器贴图上常叠加状态指示(电力、当前配方、已安装模块等),会干扰模型识别。
这些问题也为后续改进指明了方向——提升模型性能、让 AI 真正上手控制游戏,是下一阶段的核心任务(作者在 DevLog @ 2026.02.16 中记录了将该纯视觉 Playground 复用到 Dome Keeper 等更多游戏的计划)。
五、总结:一条可复用的"游戏视觉感知"流水线
回顾这条纯视觉路线,它实际上沉淀了一套与具体游戏解耦的通用流程:
游戏客户端 Docker 化(xvfb + x11vnc + websockify + novnc)
→ Lua 脚本自动采集画面 + 标注(RCON 驱动)
→ YOLO 训练与 ONNX 导出(Ultralytics)
→ 浏览器端 onnxruntime-web + WebGPU 推理
→ 置信度过滤 + NMS 后处理 → 输出检测结果
几个可以直接迁移到其他项目的关键经验:
- 图形应用跑在容器里并不需要完整桌面环境,
xvfb加最小 X 环境即可; - 浏览器只能走 HTTP,VNC 到浏览器的链路需要
websockify+novnc桥接; - onnxruntime-web 必须显式声明
['webgpu', 'wasm']双后端,否则 WebGPU 不生效; - 像素归一化时用
1/255乘法替代除法,在 8400 级循环上是实打实的性能优化; - 检测结果后处理 = 置信度阈值(0.6)+ 按置信度排序的贪心 NMS(IOU 阈值 0.7),实现不过数十行代码。
作者也坦诚列出了局限:非方形输入失效、类别混淆、UI 叠加干扰。如果你对"AI 玩游戏"的纯视觉路线感兴趣,这套方案是一个完整、可运行、且被真实项目验证过的起点。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0632
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
