OpenGlass 视觉模型批量评测:基于 moondream / llava 的多模态图片描述生成与输出对比

原创2026-10-02 15:21:081,336 阅读
文章标签:智能硬件AI 应用嵌入式物联网

OpenGlass 视觉模型批量评测:基于 moondream / llava 的多模态图片描述生成与输出对比

OpenGlass 是一个把任意普通眼镜改造成 AI 智能眼镜的开源项目,其视觉能力的关键一环,是用本地多模态模型对眼镜拍摄的画面实时生成文字描述。本文以仓库中 prompts/series_1/img_45.md 这一实测样例为骨架,完整还原"一张图片被 4 个不同视觉模型描述"的生成流程,并结合 prompts/generate.ts、sources/agent/imageDescription.ts、sources/modules/ollama.ts 等源码,讲清批处理脚本如何工作、每个模型输出差异在哪、以及如何在你本地复现这套评测。读完本文,你将掌握 OpenGlass 的图片描述生成管线、Ollama 推理的调用细节,并能自己跑通多模型对比实验。

一、样例文档是什么:一份四模型输出对比报告

prompts/series_1/img_45.md 是 OpenGlass 批量视觉评测脚本对第 45 张测试图(img_45.jpeg,600×800 室内场景)生成的描述记录。文档本身不含任何人工撰写内容,而是由 4 段被 ####标题#### 分隔的输出拼接而成,每段对应一个模型的独立输出:

文档小节标题 使用的视觉模型 输出要点
####Description#### 默认模型 moondream:1.8b-v2-fp16 拱门下的多人交谈场景,画面左中部有一台疑似笔记本的设备
####Description (llava-llama3)#### llava-llama3 室内白墙灰顶、大拱门、自然光,两人站在拱门旁,一人坐在带显示器的桌前
####Description (llava:34b-v1.6)#### llava:34b-v1.6 工业感室内空间,高天花板、管道与天窗,一人在信息自助机前,另有人在长椅上
####Description (moondream:1.8b-v2-fp16)#### moondream:1.8b-v2-fp16(第二次运行) 白色墙面与拱廊的开放式建筑内,有人在打电话,多人分散站立

这份文档的价值在于:它本身就是一份可复现、可对比的视觉模型评测数据。同一张图,不同模型在"是否识别出人物动作""是否注意到桌面设备""如何理解建筑空间"上给出了不同的答案,直接反映了小体积视觉模型(moondream 约 1.8B 参数)与大参数视觉模型(llava:34b-v1.6)之间的描述粒度差异。原图 img_45.jpeg 展示了画面中真实存在的拱券、采光窗、多人站立及左侧带标识的白色设备,可作为人工核对模型描述准确性的基准。

OpenGlass 视觉评测输入样张 img_45:室内拱门大厅场景

二、这些描述是怎么批量生成的:generate.ts 全流程拆解

prompts/series_1 目录下的 57 组 img_N.jpeg / img_N.md 文件并非手工整理,而是由 prompts/generate.ts 一次性批量生成的。理解这个脚本,就理解了整个评测数据集的来源。

2.1 自动发现图片,建立任务队列

脚本启动后遍历 __dirname(即 prompts 目录)下的所有子目录,把每个以 .jpeg 结尾的图片文件收集起来,并将输出路径映射为同名的 .md 文件:

let allFiles = fs.readdirSync(__dirname);
for (let f of allFiles) {
    if (fs.statSync(path.join(__dirname, f)).isDirectory()) {
        let files = fs.readdirSync(path.join(__dirname, f));
        for (let s of files) {
            if (s.endsWith('.jpeg')) {
                let image = fs.readFileSync(path.join(__dirname, f, s));
                imageTests.push({
                    path: path.join(__dirname, f, s).replace('.jpeg', '.md'),
                    image, outputs: ''
                });
            }
        }
    }
}

要点:任务队列 imageTests 中每个元素同时持有图片 Buffer和目标 .md 输出路径;outputs 字段用于在内存中累积所有模型的输出文本,最后一次性写盘。

2.2 多模型顺序跑批,输出拼接到同一文档

核心的 runTest(title, test) 函数负责按序对每张图调用一次指定的模型,并把结果以 ####标题#### 前缀追加到 outputs:

async function runTest(title: string, test: (img: Uint8Array) => Promise<string>) {
    let bar = new progress.SingleBar({}, progress.Presets.shades_classic);
    bar.start(imageTests.length, 0);
    for (let i = 0; i < imageTests.length; i++) {
        let o = await test(imageTests[i].image);
        imageTests[i].outputs += '####' + title + '####\n';
        imageTests[i].outputs += o + '\n';
        bar.increment();
    }
    bar.stop();
}

可以看到,img_45.md 中那 4 个以 #### 开头的小节标题,正是这里拼接进去的。脚本随后依次注册了 4 轮评测:

await runTest('Description', (img) => imageDescription(img));                              // 默认 moondream:1.8b-v2-fp16
await runTest('Description (llava-llama3)', (img) => imageDescription(img, 'llava-llama3'));
await runTest('Description (llava:34b-v1.6)', (img) => imageDescription(img, 'llava:34b-v1.6'));
await runTest('Description (moondream:1.8b-v2-fp16)', (img) => imageDescription(img, 'moondream:1.8b-v2-fp16'));

这正是 img_45.md 中 4 个小节标题与顺序的直接来源。此外脚本中还保留了一段被注释掉的模糊检测评测(####Blurry####,调用 imageBlurry),说明该批数据集的原始设计包含"描述 + 质量检测"两类任务,模糊检测在正式生成时被禁用。

2.3 批量完成后的落盘

全部模型跑完后,脚本遍历队列将累积的 outputs 写入对应 .md 文件:

for (let i of imageTests) {
    fs.writeFileSync(i.path, i.outputs);
}

因此 img_45.md 是纯脚本产物,没有任何人工编辑痕迹,这也保证了评测数据的客观性与可复现性:只要环境与模型一致,重跑脚本即可得到完全相同的输出。

三、单张图片如何被描述:imageDescription 与 Ollama 调用链

批量脚本最终会调用 sources/agent/imageDescription.ts 中定义的 imageDescription。该函数是"图片 → 文字描述"的入口,签名与默认模型如下:

export async function imageDescription(src: Uint8Array, model: KnownModel = 'moondream:1.8b-v2-fp16'): Promise<string> {
    return ollamaInference({
        model: model,
        messages: [{
            role: 'system',
            content: 'You are a very advanced model and your task is to describe the image as precisely as possible. Transcribe any text you see.'
        }, {
            role: 'user',
            content: 'Describe the scene',
            images: [src],
        }]
    });
}

三个值得注意的设计点:

  1. 系统提示词明确要求"尽可能精确描述并转录图中文字",因此评测输出会偏向细节列举而非散文式描述——img_45.md 中 llava-llama3 提到"computer monitor and keyboard(显示器与键盘)"、moondream 提到"talking on his cell phone(正在打电话)"这类细节,正是受该提示约束的结果。
  2. 图片以 images: [src] 数组形式随用户消息一起发送,即走的是 Ollama 的多模态 Chat API,而非纯文本补全接口。
  3. model 参数类型 KnownModel 在 sources/modules/ollama.ts 中定义为联合类型,当前仓库支持 7 个模型:llama3、llama3-gradient、llama3:8b-instruct-fp16、llava-llama3、llava:34b-v1.6、moondream:1.8b-v2-fp16、moondream:1.8b-v2-moondream2-text-model-f16。其中 llava-llama3 与 llava:34b-v1.6 是描述评测的常用模型,moondream 系列两个变体分别用于描述与模糊检测。

3.1 ollamaInference 的底层实现

实际发请求的逻辑在 ollamaInference(sources/modules/ollama.ts):

export async function ollamaInference(args: {
    model: KnownModel,
    messages: { role: 'system' | 'user' | 'assistant', content: string, images?: Uint8Array[] }[],
}) {
    const response = await backoff<any>(async () => {
        let converted: { role: string, content: string, images?: string[] }[] = [];
        for (let message of args.messages) {
            converted.push({
                role: message.role,
                content: trimIdent(message.content),
                images: message.images ? message.images.map((image) => toBase64(image)) : undefined,
            });
        }
        let resp = await axios.post(keys.ollama, {
            stream: false,
            model: args.model,
            messages: converted,
        });
        return resp.data;
    });
    return trimIdent(((response.message?.content ?? '') as string));
}

其关键机制包括:

  • 图片二进制转 Base64:message.images 中的 Uint8Array 通过 sources/utils/base64.ts 的 toBase64 逐张转为字符串后随请求发送,这是 Ollama 多模态请求的标准做法。
  • 文本清理:trimIdent(sources/utils/trimIdent.ts)会去掉提示词中的缩进空白,避免多余空格进入模型输入;返回的模型输出同样经过 trimIdent 裁剪,保证写入 .md 的描述干净规整。
  • 请求地址来自 keys.ts:axios.post(keys.ollama, ...) 中的 URL 由 sources/keys.ts 提供,即环境变量 EXPO_PUBLIC_OLLAMA_API_URL。按 README.md 的说明,Ollama 需自托管,URL 形如 http://localhost:11434/api/chat,且要在终端先执行 ollama pull moondream:1.8b-v2-fp16 拉取模型。
  • 重试与流式关闭:整个请求被 backoff(sources/utils/time.ts)包裹,失败时按退避策略重试;请求体显式设置 stream: false,等待完整 JSON 响应,再从中读取 response.message?.content 作为最终文本。

四、输出差异背后的原因:四个模型各自的定位

对照 img_45.md 的四段输出,可以归纳出 OpenGlass 项目中这几个视觉模型的真实差异(这些差异与各模型参数规模、训练数据的定位一致,也解释了为何脚本要同时评测多个模型):

  • moondream:1.8b-v2-fp16:轻量、可运行在低端硬件上的默认描述模型。输出偏"事实清单"风格——先给人数,再给位置与可疑物体("five individuals""an object that resembles a laptop"),语言直白、确定性表达多("This could indicate"),适合眼镜端低延迟推理场景。
  • llava-llama3:基于 Llama 3 的 LLaVA 变体,描述更注重场景关系与氛围("casual yet focused atmosphere"),能捕捉"坐在桌前、面前有显示器与键盘"这样的空间布局。
  • llava:34b-v1.6:参数规模最大的模型,输出最具"推理感",把空间解读为"waiting area or a transit hub(候车区或换乘枢纽)",并推断人物"seeking instructions or about to interact(在寻求指引或即将操作)"。它给出了最丰富的语义解读,但部分推断(如自助机)属于模型假设而非文档确认的事实。
  • moondream:1.8b-v2-fp16(文档第四段):与第一段同一模型,但输出侧重人物动作细节("talking on his cell phone"),这提醒我们:同一模型在不同运行下输出会有波动,做评测时应多次采样或结合人工核对,这正是 img_45.md 中同模型出现两段的原因之一。

4.1 若需要"看图问答"而非"描述":llamaFind / openAIFind

imageDescription 只负责生成描述。当需要基于描述回答具体问题时,仓库在 sources/agent/imageDescription.ts 中还提供了 llamaFind(走 Groq 的 llama3-70b-8192,见 sources/modules/groq-llama3.ts)与 openAIFind(走 OpenAI gpt-4o,见 sources/modules/openai.ts)。两者的系统提示词要求模型"只依据图片描述作答、不泛化、不提及图片本身、简洁具体"——即先由视觉模型把图像转成文本,再由大语言模型做事实问答的两段式管线。这与"图片描述 + 文本推理"的架构定位一致,可作为理解整套视觉链路的延伸阅读。

五、如何在你本地复现这套评测

以下步骤基于仓库当前状态与 README.md 的说明,可完整重现 img_45.md 的生成过程:

  1. 准备本地 Ollama 环境:自托管 Ollama REST API(http://localhost:11434/api/chat),并按评测所需拉取模型:
    ollama pull moondream:1.8b-v2-fp16
    ollama pull llava-llama3
    ollama pull llava:34b-v1.6
    
  2. 配置 API 地址:在 sources/keys.ts 中通过环境变量注入,例如:
    export EXPO_PUBLIC_OLLAMA_API_URL=http://localhost:11434/api/chat
    
  3. 安装依赖:在仓库根目录执行 npm install(或 yarn install)。
  4. 准备评测图片:将待测图片放入 prompts 下的任一子目录(如 prompts/series_1/),命名以 .jpeg 结尾,脚本会自动发现。
  5. 运行生成脚本:
    npx tsx prompts/generate.ts
    
    脚本会依次执行 4 轮评测,每轮用进度条显示进度,完成后为每张图片生成同名 .md 文件,格式与 img_45.md 完全一致。

注意:脚本依赖本地已拉取全部模型,若某个模型缺失,对应轮次会失败;llava:34b-v1.6 体积较大,首次加载耗时明显,属正常现象。

六、总结

prompts/series_1/img_45.md 表面上是 57 份描述文件中的普通一份,实则是 OpenGlass 视觉评测管线的完整缩影:generate.ts 负责批量发现图片并驱动多轮模型评测,imageDescription 统一封装"精确描述 + 转录文字"的提示词,ollamaInference 完成 Base64 编码、退避重试与结果清洗,最终把 moondream 与 llava 系列模型的差异化输出沉淀为可对比、可复现的 Markdown 数据。对希望复现或扩展该评测、或者想为智能眼镜挑选更合适视觉模型的开发者而言,这份样例文档连同其背后的源码,就是一份可以直接上手的最小工作集。

(本文基于仓库 prompts/series_1/img_45.md 及其生成脚本 prompts/generate.ts、推理封装 sources/agent/imageDescription.ts 与 sources/modules/ollama.ts 撰写;文中所有模型行为描述均来自该样例文档输出,未对模型性能作任何外部宣称。)

登录后查看全文
OpenGlass