把NPU检测直接嵌进采集终端|RK3576 AI采集终端全链路拆解教程-上

很多项目里"相机"和"AI 识别"是两套程序:相机管拍,识别另起进程跑。

本文基于触觉智能RK3576开发板Purple Pi OH2开发板制作的采集终端(RkDataTerminal)Demo里做的另一种结合方式------把NPU检测直接嵌进采集链路 :相机取帧→模型出框→框烧进照片→连同当时的通道数据一起归档上报;值守时更是1fps持续检测,人/动物经过就自动拍下带框的留证照片 。

采集终端演示视频:
https://www.bilibili.com/video/BV1Y2pr6aE2X/
深圳触觉智能
,赞107

本文给出链路、关键代码、实测数据,以及"AI 如何部署上板"的完整步骤

目标:不是"拍照 + 识别",而是"拍下即判"

传统做法:相机拍一张图存盘,再让另一个程序读图去推理------两者时间对不上、数据也对不上。

我们要的是一次采集完成三件事 :

  1. 拍摄:拿到现场画面(拍照 4K / 值守低帧率取帧);
  2. 判断:同一时刻送 NPU 推理,得到目标类别与位置;
  3. 留寸:把检测框和当时的采集数据烧进同一张图并入库,附带结构化 JSON。

一句话:证据自证 ------图上既有现场,也有判定,还有当时的数据。

总体设计:三条链路,一张图看懂

三条链路的分工 :

链路 取帧方式 推理频率 用途
拍照留证 Camera2 抓 4K JPEG 每次拍照 1 次 手动/定时/被触发时的证据
值守检测 预览挂 YUV ImageReader ~1fps 持续 决定"什么时候该拍"
远程查看 复用值守帧压 JPEG ~1--2fps 浏览器看现场

相机侧:两条取帧路径

3.1 拍照路径:会话重建(预览 → 拍照 → 回预览)

Camera2 下"预览 / 拍照 / 录像"通常不能同会话共存(尤其这套 legacy 包装的 HAL),所以拍照采用按需重建会话 :

复制代码
预览会话(preview + 值守YUV)
   └─ takePhoto → 换成 still 会话(preview + JPEG ImageReader)
        └─ 出图后 restorePreview() → 回到预览会话

代价是每次重建约1.2s(HAL实测),所以拍照不能太频繁 ------这也是后面"值守 15s冷却"的由来之一。

3.2 值守路径:给预览挂一路 YUV,且必须"随到随清"

值守要在不打断预览的前提下持续拿帧。做法是给预览会话再挂一个720p的YUV ImageReader ,作为重复请求的第二个target。

这里有个极易踩的坑 :如果回调里觉得"没到检测节奏"就直接return、不取帧 ,两个缓冲很快占满,相机侧没有空 buffer 就会停摆------实测帧率掉到~6 秒一帧。

正确写法是每回调必排空,节奏另判 :

复制代码
// CameraController.onWatchFrame()
image = reader.acquireLatestImage();      // 先取走最新帧 → 缓冲永远有空位
if (now - mLastWatchMs < MIN_INTERVAL) {  // 到了节奏才往下送
    return;                               // 已经 drain,直接丢不影响相机
}
mLastWatchMs = now;
mWatchListener.onWatchFrame(w, h, nv21);  // 交给检测线程

一句话记住:ImageReader 必须"随到随清",节流逻辑放后面。

另外两条相机侧硬约束:

  • 预览分辨率必须 ≤1080P:试过 4K 预览,会把 legacy HAL 直接拖崩(device error 3);
  • 拍照/录像期间值守暂停:会话被占用,恢复预览后值守自动续上。

AI 侧:从一帧图像到一组框

4.1 预处理:letterbox 到 640×640(且必须记住缩放与偏移)

模型输入固定640×640,而相机画面是16:9(如1280×720)。直接拉伸会变形,所以用letterbox :等比缩放+居中补边(补灰色114)。

关键是把缩放比和补边量记下来 ,后面才能把模型输出的框坐标反算回原图 :

复制代码
float scale = Math.min(640f / w, 640f / h);   // 等比
int nw = round(w * scale), nh = round(h * scale);
int padX = (640 - nw) / 2, padY = (640 - nh) / 2;   // 补边量
// ...缩放绘制到 640x640,再把像素读成 byte[] RGB 喂给 native

4.2 模型与 runtime 随身带,不依赖固件

不假设固件预装了 NPU 运行时,把两样东西直接打进 APK:

  • librknnrt.so(RKNN runtime,引用树内 hardware/rockchip/rknpu2 模块);

  • yolov5s-640-640.rknn(COCO80 模型,8MB)放在 assets/,首次运行时解包到私有目录再 rknn_init。

Android.bp 里真正关键的两行:

复制代码
cc_library_shared {
    name: "librkdt_yolo",
    stl: "c++_static",     // 必须静态 libc++:App 命名空间没有动态 libc++.so
}
android_app {
    name: "RkDataTerminal",
    jni_libs: ["librkdt_yolo", "librknnrt"],   // 两个 .so 一起内嵌进 APK
}

4.3 推理与后处理:三头 int8 + NMS

native 侧(jni/Detector.cpp + 移植自官方 demo 的 postprocess.cc)职责很单一:

复制代码
rknn_inputs_set(uint8 RGB, NHWC)  →  rknn_run  →  三头 int8 输出        →  反量化(zp/scale) + 置信度过滤 + NMS  →  出框 class/score/box

出框坐标先在 640 坐标系 里,再用 4.1 记下的 scale/padX/padY 反算回原图:

复制代码
原图坐标 = (框坐标 − pad) / scale

这就是"结合"的第一处细节:相机给的尺度和模型要的尺度不一样,中间的 letterbox + 逆映射是必须补齐的一环。

4.4 两条推理入口:位图 与 NV21

  • 拍照链路:detect(Bitmap) ------ 从4K JPEG 采样解码 (decodeSampled(path, 1920))再 letterbox;

  • 值守链路:detectNv21(w,h,byte\[\]) ------ YUV 帧直接 转RGB并letterbox,免一次JPEG解码 。

两条入口共用一套"letterbox→native→逆映射"逻辑,仅喂进去像素来源不同。

复制代码
takePhoto → JPEG(4K)
   ├─ 送 NPU 检测 → dets[]
   ├─ 画水印:站点/SN/时间/8 路通道值 + dets 的框与类别
   └─ 入库:记录 JSON 里追加 detections[],图为带框的 _wm.jpg

结合点:什么时候让 AI 干活

5.1 拍照即判:一次拍照,两种产物

复制代码

检测在原始大图上算,水印在JPEG上 ,两者坐标系一致⇒图上的框就是真实位置。

5.2 值守触发:AI 决定"什么时候按快门"

值守每秒判一帧,命中就自动走上面的"拍照即判":

复制代码
// handleWatchDetections()
if (best != null && best.score >= TRIGGER_SCORE          // 0.55
        && now - mLastAlertMs > ALERT_COOLDOWN_MS) {     // 15s 冷却
    mLastAlertMs = now;
    mAutoNote = label(best.cls) + " " + best.score;      // 写进水印/摘要
    triggerCapture();                                    // 自动留证
}
参数 取值 说明
触发类别 person + 10 类动物 COCO80 里 person 与 bird/cat/dog/horse/sheep/cow/elephant/bear/zebra/giraffe
置信度阈值 0.55 太低误报、太高漏检,实测取中
冷却时间 15s 目标站着不动时不会连拍
检测频率 ~1fps NPU 单帧 0.1--0.2s,够用省电

触发后的记录摘要会带上 值守 person 0.88 前缀------翻记录时一眼就知道"为什么拍了这张"。

相关推荐
深蓝AI6 小时前
26.1% 的 Agent 技能带漏洞:skills 霸榜 GitHub 当天,NVIDIA 和 OpenAI 同时掏出安检仪
人工智能·程序员
锋行天下11 小时前
langgraph 后端发自定义数据,前端接收方法概括
人工智能·python
泯泷13 小时前
单个 AI 再强,为什么也干不过一支 AI 团队?
人工智能·llm·agent
浪遏13 小时前
认识 wm:从多平台发布到可扩展 CLI 的架构设计
人工智能
深蓝AI13 小时前
英伟达换帅当天,我先跑了一遍 GPU 环境体检:版本矩阵比人事更值得盯
人工智能·gpu
罗西的思考15 小时前
【轻量化复刻大模型 / KIMI K3】AttnRes 模块交互式演示
人工智能·算法·机器学习
stormzhangV15 小时前
涨薪 50% 怎么做到的?
人工智能·面试·aigc
老金带你玩AI16 小时前
如何在5分钟之内创建Grok Bot的超级团队
人工智能
程序员cxuan16 小时前
离谱,AI 大佬顺手搓了个 YouTube ,而我顺手给他搓了个中文插件
人工智能·后端·程序员
zixu_star16 小时前
上下文工程:Prompt、记忆和上下文如何管理?
人工智能·prompt