← 返回
AI技术

离线 Arduino UNO Q 赛博终端:识别鸟鸣并绘制复古野外笔记

✍️ zhirenhun 📅 2026/10/1 👁 239 阅读 ⏱ 33 分钟
离线 Arduino UNO Q 赛博终端:识别鸟鸣并绘制复古野外笔记

在这篇文章中,我将介绍 B.L.O.O.M.(连接本地观察,公开映射),一个基于 Arduino UNO Q 的离线网络甲板,帮助自然学家记录野外笔记,使用 Edge Impulse 识别鸟鸣,并使用 Cloudinary's image generation API 生成复古野外速写。它将所有数据存储在本地,只有在您选择时才会同步到社区站点。

您将学到

网络甲板又回来了。80年代科幻梦想中的便携、可黑客的电脑设备又被重新拾起,特别是那些希望技术能离线工作、尊重隐私且看起来很酷的互联网自然爱好者角落。我为这个项目构建的设备放置在一个10美元的复古化妆火车盒中,并生成 Grinnell 风格野外笔记,当您轻手轻脚地穿过郁金香时。

本文介绍了 硬件到云端的管道,特别是对Cloudinary介绍的部分进行了最详细的阐述。完整的构建(布线、外壳、每一步)位于 Hackster。

架构一览

┌──────────── In the field (offline) ────────────┐        ┌────────────── Back online ──────────────┐
│                                                │        │                                          │
│  Mic ─► Edge Impulse birdsong model            │        │  Netlify Function                        │
│  Camera ─► photo                               │  POST  │   ├─► Cloudinary image_to_image          │
│  Keyboard/mouse ─► sketch + notes    ─► SQLite ├───────►│   │     (Nano Banana → field sketch)      │
│  Gemma LLM ─► AI note                          │        │   └─► Octokit: open PR on GitHub         │
│                                                │        │          │                               │
│  Arduino UNO Q (App Lab, Chromium kiosk)       │        │   merge ─► Astro rebuild ─► live site    │
└────────────────────────────────────────────────┘        └──────────────────────────────────────────┘

除非你点击“同步到云端”,否则不会有任何数据离开设备。这是一个硬性要求,因为这个项目同样隶属于我创立的非营利组织 Her AI Studio,我们在那里经常讨论数据治理、安全和隐私,主要在设备上运行本地 AI。

以下是完整的示意图:

云端的核心是向 Cloudinary 发送一次 POST 请求:

{
  "prompt": "Create a field sketch illustration in the style of the reference image [1]...",
  "reference_images": [{ "source_type": "url", "url": "<your reference sketch>" }],
  "model": { "family": "nano-banana", "tier": "premium" },
  "target": { "target_type": "managed_asset", "public_id": "field-notes/<slug>-ai" }
}

完整函数如下。

硬件:Arduino UNO Q 零件清单

  • Arduino UNO Q (4 GB)
  • 5 英寸 HDMI 屏幕 + 6 英寸 HDMI 线缆
  • USB 5合1 集线器
  • USB 摄像头
  • USB 麦克风
  • 移动电源 + USB-A 转 Micro-USB 数据线
  • 带内置触控板的蓝牙键盘
  • 复古火车箱(或您最喜欢的外壳!这是有趣的部分)

The Arduino UNO Q 负责运行一切。4 GB 版本可以完全离线运行小型语言模型和音频分类器,这使得设备在森林小径上无信号时仍然有用……并且可以离线探索硬件上的本地 AI 模型!

由于这是一副网络甲板,它有外设,所有外设都连接到 USB 集线器:屏幕、麦克风、摄像头和电源进入集线器,而集线器的 USB-C 插头连接到 UNO Q。在 Debian 中通过蓝牙配对键盘和鼠标,您就能恢复这些 USB 端口。

At Her AI Studio,学生将获得此 AI 工具包的使用权。

在 UNO Q 上使用 App Lab 运行 Gemma 和鸟鸣检测

该应用程序由 Arduino App Lab 中的三个“砖块”构建,这些砖块是为 Arduino Uno Q 预先构建好的模块化代码。

  • 用于摄像头和草图界面的 Web UI
  • 运行 Gemma 的 LLM 砖块,已下载到开发板
  • 用于鸟鸣的音频分类砖块

Web UI 砖块可帮助您使用少量 JS 和 HTML 构建整洁的界面,该界面通过 Arduino App Lab 在您的本地开发板上运行。这就是您编写野外笔记的方式。LLM 砖块提供一个小空间,让您可以与 LLM 聊天,并了解您使用内置摄像头拍摄的动植物照片的更多信息。

副任务:在 Debian 上使用 kiosk 砖块修复 Chromium 弹出窗口
在 Debian 上,默认的 Web UI 砖块会在其他窗口后面打开 Chromium 弹出窗口。在桌面上运行正常,但在设备上不行。我通过一个小的自定义砖块修复了这个问题,该砖块会等待本地服务器启动,然后以 kiosk 模式启动 Chromium:

def build_kiosk_command(url: str):
    if system == "Linux":
        return ["chromium-browser", "--kiosk", "--noerrdialogs",
                "--disable-infobars", f"--app={url}"]

def launch_kiosk(url: str, ready_timeout: float = 15.0):
    if not wait_for_server(url, timeout=ready_timeout):
        logger.error(f"Server never became reachable at {url}")
        return None
    cmd = build_kiosk_command(url)
    logger.info(f"Launching: {' '.join(cmd)}")
    return subprocess.Popen(cmd)

界面以全屏方式呈现,没有桌面装饰,因此屏幕就是野外笔记本,如下所示:

聆听鸟鸣

我想为我的网络甲板添加一些“耳朵”,于是转向了一个通过 App Lab 向 Arduino 添加本地自定义训练模型效果很好的平台:Edge Impulse。

我从 Edge Impulse 社区画廊中的鸟鸣分类器开始。该模型存在类别不平衡且样本太少,于是我:

  • 复制项目并删除不可用的片段
  • 从 xeno-canto(一个鸟鸣数据库)中分别获取至少 20 条蓝松鸦、哀鸽和北红雀的录音
  • 将所有文件转换为 WAV 格式,添加标签并上传
  • 重新平衡训练/测试集的划分并重新训练
  • 将新模型放入 App Lab 项目中

当您的 USB 麦克风捕捉到鸟鸣时,点击按钮,模型会对音频进行分类,并将结果保存到野外笔记中。如果您想添加当地物种,该模型是 公开且可克隆 的。

同步:从 SQLite 到拉取请求

每条观察都存储在本地 SQLite 数据库中,但您可能想将野外笔记展示到我们的公开仓库。当您选择共享时,设备会将条目发送到 Netlify 函数。照片和草图经过 Base64 编码,因此整个条目以单一 JSON 有效载荷的形式传输:

def sync_entry(entry_id):
    with _db_lock:
        conn = sqlite3.connect(DB_PATH)
        row = conn.execute(
            "SELECT id, timestamp, photo, sketch, ai_note, locality, weather, habitat "
            "FROM entries WHERE id = ?",
            (entry_id,)
        ).fetchone()
        conn.close()

    eid, timestamp, photo, sketch, ai_note, locality, weather, habitat = row
    payload = {
        "id": eid,
        "catalog_no": catalog_number(eid),
        "timestamp": timestamp,
        "note": ai_note or "",
        "locality": locality or "",
        "weather": weather or "",
        "habitat": habitat or "",
        "photo": base64.b64encode(photo).decode("utf-8") if photo else None,
        "sketch": base64.b64encode(sketch).decode("utf-8") if sketch else None,
    }

    response = requests.post(CLOUD_SYNC_URL, json=payload, timeout=CLOUD_SYNC_TIMEOUT)

The function does not write to a database. It opens a pull request against the field-notes repo with Octokit:

const { data: pr } = await octokit.pulls.create({
  owner: OWNER,
  repo: REPO,
  title: `New field note: ${catalog_no}`,
  head: branchName,
  base: BASE_BRANCH,
  body: [
    "Submitted automatically from a Bloom device.",
    "",
    `**Locality:** ${locality || "—"}`,
    `**Weather:** ${weather || "—"}`,
    `**Habitat:** ${habitat || "—"}`,
    aiSketchPath ? "\n_AI-generated sketch included._" : null,
  ].filter((line) => line !== null).join("\n"),
  labels: ["device-submission"],
});

这个流程让社区站点更容易获得人工审核:维护者审查 PR,合并它,Netlify 重新构建 Astro 站点。您可以在 community-field-notes.netlify.app 查看结果。


使用 Cloudinary 的 image_to_image API 生成 AI 场景草图
格里넬传统的自然学家将手写笔记与图画结合。我希望每条同步的笔记都配有一幅这种复古风格的插图,即使撰写笔记的人不会画画。

在 PR 被打开之前,Netlify 函数会使用 image_to_image 端点和 Nano Banana 模型将笔记文本发送到 Cloudinary 的图像生成 API。我还会传递一张参考图像,即已经存储在我的 Cloudinary 账户中的旧式野外速写,以确保每次输出都具有相同的外观:

async function generateAiSketch(noteText, slug) {
  const prompt = [
    "Create a field sketch illustration in the style of the reference image [1].",
    "The sketch should depict:",
    noteText || "A natural world observation",
    "Use a vintage field journal aesthetic with earthy tones, hand-drawn quality, and scientific illustration style.",
  ].join(" ");

  const auth = Buffer.from(
    `${CLOUDINARY_API_KEY}:${CLOUDINARY_API_SECRET}`
  ).toString("base64");

  const response = await fetch(
    `https://api.cloudinary.com/v2/generate/${CLOUDINARY_CLOUD_NAME}/image_to_image`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Basic ${auth}`,
      },
      body: JSON.stringify({
        prompt,
        reference_images: [
          { source_type: "url", url: REFERENCE_IMAGE_URL },
        ],
        model: { family: "nano-banana", tier: "premium" },
        target: {
          target_type: "managed_asset",
          public_id: `field-notes/${slug}-ai`,
        },
      }),
    }
  );

  // ...handle response, return the asset path for the PR
}

我喜欢这个设置的几点:

  • 使用参考图像可以保持风格一致,您可以选择模型,或者将其保持为 'auto' —— 这样模型会根据您的使用情况自动选择。
  • 提示词描述要绘制的内容,参考图像决定其外观。更换参考图像后,每个后续笔记都会获得新的视觉身份。 target_type: "managed_asset" 将输出直接保存到我的 Cloudinary 媒体库中,公共 ID 可预测(field-notes/-ai)。我不需要下载并重新上传任何内容,图像已准备好交付给我的社区野外笔记应用。
  • 之后它就是一个普通的 Cloudinary 资源,站点可以使用常规转换进行交付:f_auto,q_auto,用于卡片和缩略图的调整大小等等。
  • 这只需要一次 fetch 调用。无需 SDK 和额外基础设施,非常适合无服务器函数。

设置技巧

  • 将 CLOUDINARY_CLOUD_NAME、CLOUDINARY_API_KEY 和 CLOUDINARY_API_SECRET 保存在 Netlify 环境变量中。切勿将它们放在设备上。
  • 首先将您的参考草图上传到 Cloudinary,并使用其交付 URL 作为 REFERENCE_IMAGE_URL。
  • 图像生成 API 提供免费额度,足以支持类似此社区项目的使用。如果您尚未有账户,可免费注册。

外壳:无需 3D 打印机

我没有打印外壳,而是重复使用了一个(更适合赛博朋克装置!):一个复古的 Lancôme 化妆火车箱,在 eBay 上大约售价 10 美元。以下是我将所有东西放入箱中的做法:

  • 在侧面为相机镜头开一个小孔
  • 使用回收海绵和布料制作搁板
  • 用扎带固定 USB 集线器
  • 为屏幕制作纸板背板,预留线路空间
  • 为 UNO Q 预留专用位置,考虑散热(海绵和布料会锁住热量,因此需注意温度或使用如藤条这样的透气编织——我甚至用过寿司卷帘来制作这种搁板)

这个高箱还能放下放大镜、压花、小型水彩套装或叶子指南。

这张图片显示一位女性坐在草地上,手持一个电子设备并戴上一只手套

作者享受秋日的野外笔记时光


亲近自然:在野外使用 B.L.O.O.M.

打开电源,走到户外,记录你发现的一切:鸟鸣、照片、草图、对LLM的问题、所在位置、天气和栖息地。当你重新连上Wi‑Fi时,点击同步将笔记发送到GitHub,获取由Cloudinary生成的草图,并将其加入共享的自然历史记录。

这张图片显示了一只橘猫站在一只开启的旅行箱旁边,旅行箱里装着一台小型电脑和一个平板电脑

发现新生物

资源

📖 Hackster 上的完整构建:B.L.O.O.M.: 野外笔记社区网络设备

💻 设备代码:

B.L.O.O.M — 离线野外日志

连接本地观察,公开映射

B.L.O.O.M 将 Arduino® UNO Q 变成自供电的离线野外日志网络设备:对准植物、动物或徒步途中值得注意的任何事物,拍照、在其上绘制草图,并使用完全本地的 AI 模型讲述你的观察内容。每条条目——照片、草图和笔记——都会保存到开发板本身,除非你主动选择将其同步到配套网站,否则会一直保留。使用它时无需任何连接。

该项目是为 Her AI Studio 的课程而构建,并提交给包括 使用 Arduino UNO Q 和 App Lab 发明未来 竞赛的黑客马拉松,荣获最佳社会影响奖项——其理念是对抗手机和相机文化的一种对策:一款单一用途的设备,让你停下、对准并等待记录,而不是随手拍照然后快速滑走。

…




🌐 社区网页应用(Astro + Netlify)

野外笔记

一本数字化的野外日志——汇集博物学观察、野外调查与考察记录。

本项目基于 Astro 构建,内容通过 Markdown 内容集合进行管理。

项目结构

/
├── public/
│   └── favicon.svg
├── src/
│   ├── content/
│   │   ├── config.ts
│   │   └── notes/
│   │       ├── observation-of-the-red-shouldered-hawk.md
│   │       ├── botanical-survey-of-coastal-dune-ecology.md
│   │       ├── geological-observations-along-the-appalachian-trail.md
│   │       └── nocturnal-insect-survey-june.md
│   ├── layouts/
│   │   └── Layout.astro
│   ├── pages/
│   │   ├── index.astro
│   │   └── notes/
│   │       └── [...slug].astro
│   └── styles/
│       └── global.css
└── package.json

添加新笔记

新建一个 .md 文件,放到 src/content/notes/ 目录下,并在文件开头添加 frontmatter:

---
title: "Your Note Title"
date: 2026-07-21
location: "Location, State"
excerpt: "A brief summary of the observation."
image: "/images/your-image.jpg"
tags: ["tag1", "tag2"]
---

该笔记将自动出现在……

🐦 Edge Impulse 鸟鸣模型


如果你像这样构建一个热爱自然的网络甲板,为网站贡献一条笔记,或重新训练模型以识别你附近的鸟类,请在评论中分享。我很想看看其他人的野外笔记长什么样,以及你在你所在地区观察到了什么。🌿

Cloudinary ❤️ 开发者
准备好提升您的媒体工作流程了吗?免费使用 Cloudinary,立即构建更佳的视觉体验。
👉 创建您的免费账户

——

广告 广告
🧑‍💻

zhirenhun

一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

arduino iot cloudinary ai