Projects · · 10 分钟阅读

WingLens:从相机快门到观鸟记录,一张照片的完整旅程

用 Sony A7C2 拍鸟后,照片如何自动传输到 iPhone、在 Mac 上完成 AI 识别,最终变成一条可追溯的观鸟记录。兼谈 1000+ 鸟种、35000+ 张训练数据背后的实践与教训。

AI 视觉 摄影

去年秋天,我在北京的沙河水库拍到了一只不认识的鸟。

回家后我把照片导入电脑、裁切、上传到识鸟小程序,等了十几秒——结果是"白骨顶"。但翻看了几张示例图后,我又不确定了:它的额头没有白骨顶那么白,体型也偏瘦。可能是黑水鸡的亚成体,也可能是某种我完全没听过的秧鸡科鸟类。

那一刻我突然意识到:拍鸟和识别鸟之间的那段路,太远了。

相机里有几百张照片,每一张要经历:挑图 → 裁切 → 上传 → 等待 → 对比 → 记录。一天拍下来,整理比拍摄更累。

于是 WingLens 这个项目开始了。它的目标不是做一个"拍照识物"的 App——那种东西已经有很多了。我想做的是:让相机拍下鸟的那一刻,就已经自动开始记录时间、地点、鸟种和置信度,最后只需要你点一下"确认"。


一张照片的完整旅程

整个过程可以用一条线串起来:

相机拍照iPhone 取图 Mac mini M4YOLO + BioCLIP确认记录数据回流 Wi-Fi / USB局域网Top-5 结果 标注数据

不复杂,但每个环节都要跑通,才能让"拍完即记录"成为现实。


第一环:相机和 iPhone 之间

Sony A7C II 是一台很好的拍鸟相机——对焦快、长焦够用、画质细腻。但相机不是手机,它不会自己把照片传出去。

相机和 iPhone 之间有三条路:

相机Wi-FiPTP/IP 协议USB有线直连SD 卡手动导入iPhone 无线取图无线取图有线取图有线取图手动导入手动导入

Wi-Fi PTP/IP 是索尼相机自带的协议。相机创建热点,iPhone 连接后可以通过 PTP 协议获取照片列表、下载原图、甚至遥控拍摄。Sony 官方提供了 CameraRemoteCommand SDK 参考实现(Linux 版),我基于它的 PTP 3 协议文档,用 Swift 实现了一个跨平台版本——SonyCameraCross,可以在 macOS/iPadOS/iOS 上编译运行。

USB PTP 连线更稳定,速度更快。macOS 上可以通过 libusb 走 Bulk PTP 通道;iOS 端目前只支持 Wi-Fi。

SD 卡是最传统的方案,但在户外拍鸟时不方便——你需要拔卡、插读卡器、等导入,鸟可能就飞走了。

第一版优先跑通 Wi-Fi 路径。拍照后 iPhone 自动从相机拉取最新照片,用户不需要任何操作。


第二环:AI 识别——找鸟,然后认鸟

图片到了 iPhone,下一步是识别。这是一个典型的"检测 + 分类"两阶段问题。

为什么不能直接把整张图丢给分类模型?因为照片里可能什么都没有——大片的天空、水面、树枝;也可能有多只鸟,分布在画面不同位置,主体只占几十个像素。

所以需要先"找到鸟",再"认鸟"。

原图 YOLO11s 检测找鸟框 · ≥0.60裁剪 BioCLIP2 提特征768 维向量 线性分类头768 → 1009 种Top-5

为什么用 YOLO11s?

YOLO(You Only Look Once)是一个经典的目标检测模型家族。YOLO11 是目前最新的版本,s 代表 small。它可以在一张图上同时找到多个目标的位置,并给出每个目标的置信度。

我选择 YOLO11s 而不是更小的 YOLO11n,是因为鸟在画面中可能很小——有时候只有几十个像素——需要更高的检测精度。代价是模型大了几 MB,推理慢了十几毫秒,在 Mac 上几乎感觉不到。

为什么用 BioCLIP 而不是自己训分类模型?

这是整个项目中最重要的一个决策。

BioCLIP 是由 Imageomics 研究所训练的"生物视觉基础模型"。它在 450,000 个物种、80 万张图片上训练过,涵盖了从昆虫到哺乳动物的大量生物类别。关键是:它见过很多鸟。

如果你自己从零训练一个鸟类分类器,需要海量标注数据——每种鸟至少几百张高质量图片,还要覆盖不同角度、光线、季节、姿态。对于 1000+ 种鸟来说,这个数据量是不可承受的。

BioCLIP 的思路是"站在巨人的肩膀上":

  1. BioCLIP 主干固定不训练:它负责把裁剪后的鸟图转换成一个 768 维的数学向量(embedding)。这个向量编码了鸟的形态、颜色、纹理等视觉信息。
  2. 只训练一个线性分类头:这是一个很小的矩阵(768 × 1009),把 BioCLIP 的特征向量映射到具体的鸟种编号。训练它只需要几百 MB 显存、几十分钟。

这就像 BioCLIP 已经学会了"看鸟"这件事,你只需要告诉它"你看到的这只具体叫什么名字"。

实际验证下来,BioCLIP 2(ViT-L/14,768 维)是效果和成本的最佳平衡点:

  • 原版 BioCLIP(ViT-B/16,512 维):最轻量,但细粒度识别不够准
  • BioCLIP 2(ViT-L/14,768 维):✅ 当前主力
  • BioCLIP 2.5 Huge(ViT-H/14,1024 维):理论效果更好,但模型更大、推理更慢

第三环:服务端——Mac mini 里的"鸟识别工厂"

iPhone 不适合直接跑 BioCLIP。一是模型太大(1.7GB),二是推理需要 PyTorch + Apple MPS 加速。所以我把识别服务部署在一台 Mac mini M4 上,放在家里的局域网里。

但其实这个架构是"云端"的——只不过云长这样:

iPhone FastAPI鉴权 · 分发Redis YOLO Worker检测 + 裁剪 BioCLIP Worker特征 + 分类 PostgreSQL + MinIO元数据 · 文件 上传投递检测分类 裁剪图结果查询

整套系统命名为 OrniVision-Server。技术栈是:

组件 技术 说明
API FastAPI + Uvicorn 接收图片、返回结果、Web 测试台
队列 Redis 异步任务分发,解耦 API 和 Worker
检测 YOLO11s 找鸟框、裁剪鸟图
分类 BioCLIP 2 + 线性头 768 维特征 → 1009 种鸟
元数据 PostgreSQL 任务、结果、用户、图片索引
文件 MinIO 原图、裁剪图、识别结果 JSON
监控 Grafana + Loki 日志、吞吐、延迟

为什么用 Redis 队列?因为一次识别可能耗时几秒(BioCLIP 推理 + 前后处理),不能让 HTTP 请求一直等着。iPhone 上传照片后立即拿到一个 task_id,然后轮询结果。这种异步模式也方便未来扩展到多台 Worker 并行处理。

基础设施用 OrbStack 在本机跑——PostgreSQL、Redis、MinIO 都是容器化的,数据挂载到外接 SSD。

从外部访问怎么办?用 frp 做了内网穿透:Mac mini 通过 frpc 连接到一台有公网 IP 的轻量服务器,iPhone 在外网也能调 API。


数据:最难的不是模型,是"找到对的图"

很多人以为 AI 项目的瓶颈是模型,其实往往是数据。

WingLens 目前覆盖 1,009 种中国鸟类,共 35,275 张训练图片。平均每种鸟约 33 张,中位数 28 张。

这些数据从哪来?

  1. birdreport.cn:中国观鸟记录中心的公开数据,包含大量鸟种、地点、时间的标注图片。我写了脚本按鸟种目录批量下载整理。
  2. Android ADB 半自动采集:用小红书搜索鸟名,通过 ADB 控制 Android 手机截图选图,再拉回电脑按目录保存。听起来很土,但效率比手动高不少。
  3. 标注纠正:在 OrniVision 标注工作台上,用户可以手动确认或纠正模型的 Top-5 结果,错误标注的图片会被记录下来,后续改进。

数据质量比数量更重要。训练脚本中有几个关键机制:

  • YOLO 裁剪后再训练:不是用整张原图,而是先用 YOLO 找出鸟的准确位置,裁出鸟图,再去训练分类头。这样模型不会被背景干扰。
  • 数据审阅服务:训练完一版后,启动一个本地 Web 服务(端口 8765),逐鸟种看训练样本——有些图可能根本不是鸟,或者鸟种标错了。标记后重新裁剪、重新训练。
  • label_map:目录名如 白骨顶_4230,下划线后的数字是鸟种唯一编号。训练和推理都基于这个编号,保证数据可追溯。

当前数据的短板也很明显:

  • 很多鸟种只有 10-20 张图,对模型来说太少
  • 部分图片质量不高——模糊、遮挡、角度刁钻
  • 稀有鸟种的数据尤其稀少,模型容易把它们认成常见鸟种

iPhone 端:WingLens App

iPhone 端目前是 SwiftUI 开发的横屏应用,核心交互是:

左侧主图区域:显示最新照片 + 检测框 + 鸟名标签。每只鸟的框用不同颜色标记。

右侧结果面板:按鸟展示 Top-5 候选,每只鸟一张卡片,颜色与主图检测框对应。

App 主界面:左侧实时画面,右侧识别结果

第一版的设计原则是"少操作":拍完照后自动识别,用户只需要扫一眼 Top-5,点确认。不需要手动裁切、不需要输入鸟名、不需要打开其他 App。


OrniVision:标注工作台

除了给 iPhone 用的 OrniVision-Server,还有一个独立的标注 Web 应用——OrniVision

它的定位是"数据标注工作台":

  • 上传图片 → 自动识别所有鸟框 → 每个框展示 Top-5
  • 用户可以从 Top-5 中选择正确的鸟种,或手动输入
  • 确认后图片按鸟种目录保存(白骨顶_4230/),自动成为训练数据
  • 需要登录(支持多账号和 API Key)
  • 可以切换 BioCLIP 原版 / BioCLIP 2 / BioCLIP 2.5 三种分类头

这是一个关键的"数据闭环"工具:用户在使用过程中纠正的错误,会变成下一版模型的训练素材。就像数字员工的日会——每天复盘哪些认错了,下次做得更好。


踩过的坑

1. 模型加载和首次推理太慢

BioCLIP 2 的权重大约 1.7GB,首次加载需要几秒。解决方案是在 Worker 启动时预加载模型到内存,保持常驻,避免每次请求都重新加载。MPS(Apple Silicon 的 GPU 加速)比纯 CPU 快 3-5 倍。

2. 相机连接不稳定

索尼相机的 Wi-Fi 热点偶尔会断开,PTP 协议有些边缘情况会超时。处理方式是加重试机制、连接状态指示器、以及降级到 SD 卡的手动流程。

3. BioCLIP 2.5 太大,不适合本地

BioCLIP 2.5 Huge 效果确实更好一点,但权重大约 3.9GB,推理时间是 BioCLIP 2 的 2 倍。对于实时场景(用户等几秒看结果),BioCLIP 2 是更好的折中。

4. 鸟种之间的混淆

有些鸟种长得非常像。比如"白骨顶"和"黑水鸡"在光线不好时很难区分;"红嘴鸥"和"黑嘴鸥"的冬羽也容易混淆。这类情况下模型给出的置信度通常较低(40-60%),需要用户人工判断。这也是为什么 Top-5 比 Top-1 更实用。

5. 吸管喝水式的边缘情况

类比我的另一个项目"智能水杯秤":如果用户用吸管喝水,杯子始终不离开底座,传感器无法判断。WingLens 也有类似的边缘情况——如果鸟只露出半个头、或者在画面边缘、或者被树叶遮挡超过 70%,YOLO 可能根本检测不到。目前的对策是:提高检测阈值(≥0.60),宁可漏检也不要误检太多。


当前进展

模块 状态 说明
相机连接 ✅ Wi-Fi PTP/IP 已跑通 SonyCameraCross Swift 实现
YOLO 检测 ✅ YOLO11s 在 Mac mini M4 上跑 MPS 加速
BioCLIP 分类 ✅ BioCLIP 2 768 维特征 + 线性头,1009 种鸟
训练数据 ✅ 35K 张 1009 种,平均 33 张/种
标注工作台 ✅ OrniVision Web 支持 3 种 backbone 切换
服务端 API ✅ OrniVision-Server Redis 队列 + 异步 Worker
iPhone App 🚧 开发中 SwiftUI 横屏,已跑通主流程
公网访问 ✅ frp 穿透 Mac mini → 轻量服务器
鉴权与付费 🔜 P4 license_token + StoreKit 2
鸟声识别 🔜 远期 麦克风 + 声纹模型

写在最后

从沙河水库那只认不出来的鸟开始,到 1000+ 种鸟的训练数据、一台 Mac mini 跑着识别服务、一个横屏 App 正在变完整——这条路比我想象的长得多。

但每一次模型认出一只我没见过的鸟,每一次标注数据又多了一个物种,我都会觉得这件事是对的。

WingLens 不是要替代你的观鸟图鉴,也不是要让你放下相机去用手机拍。它只是想在你按下快门之后,把后面的麻烦事都省掉:

让它自己去找鸟、认鸟、记下来。你只需要继续看下一只。


参考资源

  • 项目仓库(AI_Birds):包含训练脚本、数据流水线、OrniVision 标注工具
  • OrniVision-Server:Mac mini M4 上的异步识别服务(FastAPI + Redis + BioCLIP2)
  • SonyCameraCross:基于 PTP 3 协议的 Swift 相机控制库
  • WingLens iOS App:SwiftUI 横屏识别工作台
  • 训练方案:YOLO 裁剪 + BioCLIP 特征 + 线性分类头(详见 training/bioclip_head/方案介绍.md