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

本文详解基于RK3576芯片的AI采集终端设计,将NPU检测深度嵌入相机采集链路,实现拍照即判与值守触发双模式。涵盖YUV/NV21双路径取帧、letterbox预处理与坐标逆映射、int8模型推理与NMS后处理、RKNN runtime动态加载等关键技术,确保图像采集、AI推理、结构化标注在毫秒级同步完成,达成‘图即证据’的端侧智能采集目标。

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

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


采集终端演示视频:

深圳触觉智能
,赞107

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

图片

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

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

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

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

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

图片

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

mermaid-diagram

三条链路的分工:

链路

取帧方式

推理频率

用途

拍照留证

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] 前缀——翻记录时一眼就知道"为什么拍了这张"。

                  评论
                  成就一亿技术人!
                  拼手气红包6.0元
                  还能输入1000个字符
                   
                   条评论被折叠 查看
                  添加红包

                  请填写红包祝福语或标题

                  个

                  红包个数最小为10个

                  元

                  红包金额最低5元

                  当前余额3.43元 前往充值 >
                  需支付:10.00元
                  成就一亿技术人!
                  领取后你会自动成为博主和红包主的粉丝 规则
                  hope_wisdom
                  发出的红包

                  打赏作者

                  Industio_触觉智能

                  你的鼓励将是我创作的最大动力

                  ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
                  扫码支付:¥1
                  获取中
                  扫码支付

                  您的余额不足,请更换扫码支付或充值

                  打赏作者

                  实付元
                  使用余额支付
                  点击重新获取
                  扫码支付
                  钱包余额 0

                  抵扣说明:

                  1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
                  2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

                  余额充值