算法漸進(jìn)式集成實(shí)戰(zhàn):從環(huán)境搭建到性能調(diào)優(yōu))
最近手頭有個(gè)項(xiàng)目要在RK3588上跑視覺(jué)定位算法整個(gè)過(guò)程下來(lái)感觸挺多。這塊板子性能確實(shí)強(qiáng)但想把算法從PC端穩(wěn)穩(wěn)當(dāng)當(dāng)?shù)匾浦策^(guò)來(lái)并最終在產(chǎn)線(xiàn)上長(zhǎng)期運(yùn)行中間隔著不少坑。這篇文章就把我這次RK3588視覺(jué)算法漸進(jìn)式集成的完整思路和實(shí)戰(zhàn)過(guò)程記錄下來(lái)從開(kāi)發(fā)環(huán)境搭建、圖像采集、算法移植到系統(tǒng)聯(lián)調(diào)再到最后遇到的各種奇怪問(wèn)題一次性說(shuō)清楚。1. 為什么選擇RK3588以及漸進(jìn)式集成到底解決什么問(wèn)題先聊聊選型。當(dāng)時(shí)項(xiàng)目需求是視覺(jué)引導(dǎo)定位要識(shí)別目標(biāo)物體并輸出坐標(biāo)給機(jī)械臂同時(shí)還要在本地跑yolov8做缺陷檢測(cè)。市面上能選的方案不少比如Jetson Orin系列、瑞芯微的RK3588還有傳統(tǒng)的x86工控機(jī)加顯卡。最終選擇RK3588核心原因是它在算力、功耗、成本和供貨穩(wěn)定性之間找到了一個(gè)比較舒服的平衡點(diǎn)。6TOPS的NPU算力跑輕量級(jí)yolov8s或yolov5s是夠用的8核A76A55的CPU也能跑一些預(yù)處理和邏輯控制任務(wù)空閑時(shí)整板功耗能做到10W左右比x86加獨(dú)顯動(dòng)不動(dòng)上百瓦的方案省太多。更重要的是RK3588的AI開(kāi)發(fā)工具鏈RKNN-Toolkit2經(jīng)過(guò)幾個(gè)版本的迭代現(xiàn)在已經(jīng)比較成熟雖然跟NVIDIA的TensorRT生態(tài)比還有差距但應(yīng)對(duì)常規(guī)的模型轉(zhuǎn)換和部署需求文檔和社區(qū)案例已經(jīng)足夠了。然后是漸進(jìn)式集成這套思路。我記得以前做算法部署習(xí)慣是先在PC上把模型訓(xùn)好然后一股腦兒把Python環(huán)境和PyTorch代碼都搬到板子上試圖一晚上搞定。結(jié)果往往是環(huán)境沖突、依賴(lài)缺失、算子不支持各種問(wèn)題一起爆發(fā)最后連問(wèn)題出在哪一層都分不清楚。這次換個(gè)打法把視覺(jué)算法集成拆成三個(gè)階段每個(gè)階段有獨(dú)立的驗(yàn)證目標(biāo)和完成標(biāo)準(zhǔn)確定沒(méi)問(wèn)題了再進(jìn)入下一階段。第一階段搞定圖像采集鏈路。確保開(kāi)發(fā)板能從MIPI攝像頭或USB攝像頭拿到穩(wěn)定的圖像幀同時(shí)驗(yàn)證RTSP視頻流接入的連通性如果這一步圖像就是花屏、掉幀或者顏色不對(duì)后面算法跑得再好也白搭。第二階段完成算法模型轉(zhuǎn)換與NPU推理驗(yàn)證。用RKNN-Toolkit2把PyTorch或ONNX模型轉(zhuǎn)成RKNN格式在板子上跑通推理確認(rèn)精度損失在可接受范圍內(nèi)并且單幀推理延遲滿(mǎn)足需求。第三階段系統(tǒng)集成與業(yè)務(wù)邏輯聯(lián)調(diào)。把圖像采集、算法推理、結(jié)果輸出、IO控制等模塊通過(guò)ROS2或自定義的通信框架整合起來(lái)形成一個(gè)完整的閉環(huán)應(yīng)用。實(shí)際操作下來(lái)這套流程最大的好處是每一層的問(wèn)題都能在它出現(xiàn)的那個(gè)階段被及時(shí)暴露和修復(fù)不會(huì)帶著隱患進(jìn)入下一層。比如有的模型結(jié)構(gòu)在PC上推理沒(méi)問(wèn)題但轉(zhuǎn)成RKNN后在量化過(guò)程中精度損失明顯或者某些算子無(wú)法映射到NPU這類(lèi)問(wèn)題在第二階段如果沒(méi)有及時(shí)發(fā)現(xiàn)等到集成了全套業(yè)務(wù)邏輯后再去排查定位成本會(huì)高得多。2. 環(huán)境準(zhǔn)備從拿到開(kāi)發(fā)板到點(diǎn)亮屏幕最容易被絆倒的幾個(gè)細(xì)節(jié)先介紹一下我用的硬件環(huán)境RK3588開(kāi)發(fā)板這里不特指某家市面上主流品牌的板子在核心配置上大同小異搭載Debian 11系統(tǒng)配了一塊MIPI CSI接口的攝像頭模組另外還有一個(gè)USB接口的工業(yè)相機(jī)做備選測(cè)試。主機(jī)環(huán)境則是Ubuntu 20.04用于模型訓(xùn)練和RKNN-Toolkit2模型轉(zhuǎn)換。第一次接觸到開(kāi)發(fā)板的人最容易在系統(tǒng)燒錄和信息獲取這兩個(gè)環(huán)節(jié)卡住。2.1 系統(tǒng)燒錄與啟動(dòng)方式RK3588的燒錄方式主要有兩種分別是使用RKDevTool工具的Loader模式燒錄以及直接寫(xiě)入SD卡啟動(dòng)。開(kāi)發(fā)板出廠(chǎng)時(shí)一般已經(jīng)預(yù)裝了SDK和系統(tǒng)鏡像但是考慮到后續(xù)調(diào)試的靈活性我建議還是手動(dòng)燒錄一遍自己需要的系統(tǒng)版本。燒錄時(shí)需要注意一下開(kāi)發(fā)板的模式切換常見(jiàn)的方式是按住板子上的Recovery/MaskROM按鍵然后用USB Type-C數(shù)據(jù)線(xiàn)連接電腦最后給板上電。進(jìn)入Loader模式后主機(jī)的設(shè)備管理器會(huì)出現(xiàn)一個(gè)Rockusb設(shè)備這時(shí)候用RKDevTool選擇對(duì)應(yīng)的分區(qū)表通常是parameter.txt和鏡像文件點(diǎn)擊執(zhí)行即可燒錄。我用的Debian 11系統(tǒng)鏡像是從開(kāi)發(fā)板廠(chǎng)商官網(wǎng)下載的執(zhí)行燒錄后接上HDMI線(xiàn)就能看到系統(tǒng)桌面。這里有一個(gè)小經(jīng)驗(yàn)如果顯示器沒(méi)有畫(huà)面先檢查燒錄時(shí)是否勾選了對(duì)應(yīng)的分區(qū)選項(xiàng)比如boot、rootfs、misc這些基礎(chǔ)分區(qū)缺一個(gè)都不能正常啟動(dòng)。2.2 網(wǎng)絡(luò)配置與遠(yuǎn)程開(kāi)發(fā)拿到板子后我做的第一件事就是配置網(wǎng)絡(luò)因?yàn)楹罄m(xù)所有開(kāi)發(fā)工作都會(huì)通過(guò)SSH遠(yuǎn)程進(jìn)行。RK3588自帶以太網(wǎng)口和WiFi模塊連上網(wǎng)線(xiàn)后在系統(tǒng)設(shè)置里查看IP地址主機(jī)通過(guò)ssh rk3588IP地址連接。但是這里有一個(gè)很常見(jiàn)的坑就是網(wǎng)絡(luò)連接受限。有些開(kāi)發(fā)板在連接路由器時(shí)系統(tǒng)時(shí)間不同步會(huì)導(dǎo)致HTTPS連接異常進(jìn)而影響apt update和pip install。解決辦法很簡(jiǎn)單用date -s手動(dòng)設(shè)置系統(tǒng)時(shí)間或者安裝chrony來(lái)自動(dòng)同步NTP時(shí)間。我在剛開(kāi)機(jī)時(shí)就遇到過(guò)幾次網(wǎng)絡(luò)下載特別慢的情況最終都確認(rèn)是時(shí)間不同步導(dǎo)致的證書(shū)校驗(yàn)問(wèn)題。2.3 風(fēng)扇與散熱監(jiān)控RK3588性能全開(kāi)的時(shí)候發(fā)熱量很大尤其是跑NPU密集計(jì)算時(shí)核心溫度輕松飆到80度以上。所以主動(dòng)散熱是必須的開(kāi)發(fā)板通常會(huì)帶一個(gè)PWM接口的風(fēng)扇。系統(tǒng)啟動(dòng)后風(fēng)扇默認(rèn)由硬件自動(dòng)控制但如果你想讀取當(dāng)前風(fēng)扇轉(zhuǎn)速或者手動(dòng)調(diào)整轉(zhuǎn)速策略就需要用到pwm-fan驅(qū)動(dòng)和傳感器節(jié)點(diǎn)。在Debian 11系統(tǒng)下可以通過(guò)/sys/class/thermal/cooling_device0查看當(dāng)前風(fēng)扇控制狀態(tài)。比如執(zhí)行cat /sys/class/thermal/cooling_device0/cur_state返回的數(shù)字表示當(dāng)前風(fēng)扇檔位范圍通常是0到255之間的離散檔位不同系統(tǒng)設(shè)置不同。如果是0說(shuō)明風(fēng)扇沒(méi)轉(zhuǎn)此時(shí)如果CPU溫度已經(jīng)很高就需要檢查風(fēng)扇供電線(xiàn)和PWM信號(hào)線(xiàn)有沒(méi)有接對(duì)。讀取風(fēng)扇轉(zhuǎn)速的話(huà)RK3588平臺(tái)上需要確認(rèn)風(fēng)扇是否有FG轉(zhuǎn)速反饋線(xiàn)有的話(huà)對(duì)應(yīng)到板子上的某個(gè)GPIO通過(guò)設(shè)備的計(jì)數(shù)器節(jié)點(diǎn)讀取cat /sys/devices/platform/pwm-fan/hwmon/hwmon*/fan1_input如果沒(méi)有這個(gè)節(jié)點(diǎn)說(shuō)明你的風(fēng)扇沒(méi)有反饋信號(hào)線(xiàn)轉(zhuǎn)速只能通過(guò)PWM占空比推算無(wú)法精確讀取。2.4 串口調(diào)試與報(bào)錯(cuò)排查很多奇怪的問(wèn)題在SSH連不上時(shí)只能用串口查看。找一根USB轉(zhuǎn)TTL線(xiàn)連接開(kāi)發(fā)板上的調(diào)試串口引腳波特率通常是1500000這也是RK3588平臺(tái)默認(rèn)的調(diào)試串口波特率比常見(jiàn)的115200高很多。連接后如果輸出亂碼先排查一下波特率是否設(shè)置正確以及是否連接了正確的TX/RX引腳。在調(diào)試過(guò)程中串口最有用的場(chǎng)景是查看內(nèi)核啟動(dòng)日志。比如系統(tǒng)無(wú)法識(shí)別MIPI攝像頭這類(lèi)硬件問(wèn)題用dmesg | grep mipi看驅(qū)動(dòng)初始化日志比用什么工具都直接。3. 第一階段圖像采集鏈路花了一下午排查cant find suitable delayline圖像采集是整個(gè)視覺(jué)算法集成的地基。當(dāng)時(shí)我的目標(biāo)是讓RK3588通過(guò)MIPI CSI接口正常采集到攝像頭畫(huà)面為后面的視覺(jué)定位算法提供穩(wěn)定的視頻源。MIPI攝像頭不是USB攝像頭插上去系統(tǒng)就能用。它需要底層驅(qū)動(dòng)配合在設(shè)備樹(shù)Device Tree里配置對(duì)應(yīng)的sensor型號(hào)、I2C地址、數(shù)據(jù)通道lane數(shù)量等信息。我的開(kāi)發(fā)板配套的是一顆索尼IMX系列傳感器官方內(nèi)核里帶了驅(qū)動(dòng)所以理論上只要在設(shè)備樹(shù)里使能對(duì)應(yīng)的節(jié)點(diǎn)即可。3.1 設(shè)備樹(shù)配置與內(nèi)核編譯設(shè)備樹(shù)文件在SDK內(nèi)核目錄下的arch/arm64/boot/dts/rockchip/中。你需要找到對(duì)應(yīng)你板型配置的dts文件比如rk3588-evb1.dts。在這個(gè)文件里通常攝像頭部分有一個(gè)camera相關(guān)的節(jié)點(diǎn)csi2_dphy0 { status okay; ports { #address-cells 1; #size-cells 0; port0 { reg 0; mipi_in_ucam0: endpoint { remote-endpoint ucam0_out; >make ARCHarm64 rockchip_linux_defconfig make ARCHarm64 dtbs然后把生成的rk3588-evb1.dtb拷貝到開(kāi)發(fā)板的boot分區(qū)重啟生效。3.2 串口日志深度分析從報(bào)錯(cuò)到修復(fù)設(shè)備樹(shù)使能后我通過(guò)dmesg檢查攝像頭驅(qū)動(dòng)是否正常加載[ 3.542103] rockchip-csi2-dphy0: no remote-endpoint [ 3.542215] rockchip-mipi-csi2: failed to find suitable delayline這個(gè)報(bào)錯(cuò)搞了我好幾個(gè)小時(shí)。字面上看是MIPI CSI2控制器沒(méi)有找到合適的delayline這是RK3588平臺(tái)MIPI調(diào)試時(shí)比較常見(jiàn)的問(wèn)題。其實(shí)這里的delayline是指MIPI DPHY用來(lái)對(duì)齊時(shí)鐘和數(shù)據(jù)信號(hào)、做信號(hào)延遲調(diào)整的參數(shù)如果驅(qū)動(dòng)在初始化時(shí)沒(méi)有正確配置對(duì)應(yīng)的時(shí)序關(guān)系就會(huì)出現(xiàn)failed to find suitable delayline。問(wèn)題根源通常是設(shè)備樹(shù)中時(shí)序參數(shù)不完整或者驅(qū)動(dòng)版本與sensor驅(qū)動(dòng)不匹配。排查鏈路是這樣的先看內(nèi)核源碼中drivers/media/i2c/目錄下是否包含你的sensor驅(qū)動(dòng)如果沒(méi)有需要確認(rèn)內(nèi)核配置里把對(duì)應(yīng)的驅(qū)動(dòng)編譯進(jìn)內(nèi)核或者模塊。然后檢查設(shè)備樹(shù)里sensor端點(diǎn)endpoint的link-frequencies屬性這是給MIPI時(shí)鐘提供參考值的。我檢查了一下發(fā)現(xiàn)這個(gè)值沒(méi)有設(shè)置導(dǎo)致驅(qū)動(dòng)不知道如何計(jì)算delayline的配置。添加如下屬性后重新編譯port1 { reg 1; ucam0_out: endpoint { remote-endpoint mipi_in_ucam0; link-frequencies /bits/ 64 297000000; }; };重新燒錄dtb后dmesg不再報(bào)錯(cuò)/dev/video0節(jié)點(diǎn)正常生成用GStreamer或者v4l2-ctl可以抓取到畫(huà)面。這個(gè)案例想說(shuō)明的是RK3588的MIPI調(diào)試問(wèn)題設(shè)備樹(shù)配置上的微小遺漏會(huì)導(dǎo)致內(nèi)核驅(qū)動(dòng)層面的報(bào)錯(cuò)而這種報(bào)錯(cuò)信息通常不會(huì)直接告訴你少了哪個(gè)參數(shù)。排查這類(lèi)問(wèn)題需要從數(shù)據(jù)流方向一步步反推從CSIS接收端到PHY層再到sensor發(fā)送端逐個(gè)確認(rèn)配置是否完整。3.3 視頻格式與YUV數(shù)據(jù)攝像頭采集到的原始數(shù)據(jù)一般是RAW Bayer格式或者YUV格式。RK3588的ISP單元可以將RAW數(shù)據(jù)轉(zhuǎn)換成YUV輸出。我選的是YUV422格式可以直接通過(guò)V4L2接口讀取。用如下命令驗(yàn)證采集的圖像格式v4l2-ctl --list-formats-ext -d /dev/video0輸出中能看到支持的分辨率和像素格式。常見(jiàn)的有YUYV屬于YUV422也有NV12屬于YUV420的變體。這里提一句如果你的后續(xù)算法是基于YOLO系列或者大多數(shù)深度學(xué)習(xí)框架輸入圖像通常要求RGB或者BGR格式在采集端直接讀NV12數(shù)據(jù)然后用OpenCV的cvtColor轉(zhuǎn)成BGR會(huì)比直接要求V4L2輸出RGB更快因?yàn)轵?qū)動(dòng)層面做RGB轉(zhuǎn)換比較占用CPU資源且效率不高。圖像采集的最終驗(yàn)證標(biāo)準(zhǔn)連續(xù)采集1000幀無(wú)花屏、無(wú)丟幀色彩正常幀率達(dá)到預(yù)期。我當(dāng)時(shí)的測(cè)試代碼如下import cv2 cap cv2.VideoCapture(0, cv2.CAP_V4L2) cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(M, J, P, G)) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080) count 0 while count 1000: ret, frame cap.read() if not ret: continue count 1 print(fCaptured {count} frames)需要注意的是OpenCV調(diào)用V4L2時(shí)FOURCC設(shè)置對(duì)采集速度影響很大。MJPEG格式可以減少USB帶寬占用但MIPI攝像頭一般直接出YUV。如果采集頻率上不去先檢查攝像頭模組本身是否支持高幀率再看分辨率是否過(guò)高導(dǎo)致帶寬瓶頸。4. 第二階段yolov8模型轉(zhuǎn)換、NPU部署與精度實(shí)測(cè)圖像采集搞定后進(jìn)入核心環(huán)節(jié)把在PC上訓(xùn)練的yolov8模型部署到RK3588上通過(guò)NPU進(jìn)行推理加速。這一階段也是很多人最頭疼的部分因?yàn)樯婕澳P娃D(zhuǎn)換和量化。4.1 模型選擇與預(yù)處理邏輯首先明確一下場(chǎng)景。視覺(jué)引導(dǎo)定位需要的目標(biāo)檢測(cè)模型是處理工業(yè)場(chǎng)景用的目標(biāo)是識(shí)別出產(chǎn)線(xiàn)上特定工件并框出其位置??紤]到NPU算力和實(shí)時(shí)性要求我選擇yolov8s作為基礎(chǔ)模型輸入分辨率640x640。訓(xùn)練完成后保存模型為ONNX格式。這里必須強(qiáng)調(diào)一點(diǎn)轉(zhuǎn)換ONNX之前一定要把模型的預(yù)處理邏輯固定下來(lái)具體包括歸一化方式一般是除以255、通道順序RGB還是BGR、圖像縮放方式letterbox的參數(shù)即保持寬高比并填充灰邊。因?yàn)檫@些參數(shù)在PC上用PyTorch推理時(shí)可能很隨意但導(dǎo)出ONNX后這些預(yù)處理步驟如果混入模型中或者與后處理邏輯不一致會(huì)導(dǎo)致板子上推理結(jié)果與PC上完全對(duì)不上。我的做法是輸入給onnx的tensor張量已經(jīng)是經(jīng)過(guò)letterbox并歸一化后的float數(shù)據(jù)不做混入模型內(nèi)部的預(yù)處理這樣靈活性更高也方便后面直接調(diào)用NPU接口。4.2 RKNN-Toolkit2的轉(zhuǎn)換環(huán)境配置模型轉(zhuǎn)換需要在x86主機(jī)上運(yùn)行RKNN-Toolkit2。這里強(qiáng)烈建議用Docker環(huán)境因?yàn)镽KNN-Toolkit2的依賴(lài)庫(kù)比較多直接裝在Ubuntu原生環(huán)境容易產(chǎn)生沖突。啟動(dòng)一個(gè)容器掛載模型目錄docker run -v $(pwd):/workspace -it rknn-toolkit2:1.6.0 bash在容器中執(zhí)行轉(zhuǎn)換腳本from rknn.api import RKNN rknn RKNN() rknn.config(target_platformrk3588, mean_values[[0, 0, 0]], std_values[[255, 255, 255]], quantized_dtypew8a8) rknn.load_onnx(model./best.onnx) rknn.build(do_quantizationTrue, dataset./dataset.txt) rknn.export_rknn(./best.rknn)這里有幾個(gè)參數(shù)值得展開(kāi)說(shuō)明一下。quantized_dtypew8a8表示權(quán)重和激活值都量化到INT8。RK3588的NPU對(duì)INT8計(jì)算效率最高FP16雖然不需要量化、精度更高但實(shí)際吞吐量比INT8低很多。如果對(duì)精度要求實(shí)在苛刻可以用混合量化只量化部分層不過(guò)這種情況比較少見(jiàn)。dataset.txt是量化校準(zhǔn)數(shù)據(jù)集每行是一個(gè)圖片路徑。這個(gè)文件非常重要它的內(nèi)容直接影響量化精度。我當(dāng)時(shí)花了比較多時(shí)間測(cè)試結(jié)論是選取100到200張覆蓋場(chǎng)景足夠多樣化的圖片比用幾百?gòu)堉貜?fù)場(chǎng)景的圖片效果更好。對(duì)于視覺(jué)定位這種工業(yè)場(chǎng)景建議專(zhuān)門(mén)拍一些不同光照、不同角度、不同遮擋程度下的工件樣本作為校準(zhǔn)集。轉(zhuǎn)換過(guò)程如果報(bào)算子不支持的錯(cuò)誤需要回到模型訓(xùn)練階段修改網(wǎng)絡(luò)結(jié)構(gòu)或者使用ONNX Simplifier簡(jiǎn)化模型消除Reduce、Reshape等冗余節(jié)點(diǎn)。4.3 開(kāi)發(fā)板上的推理實(shí)現(xiàn)轉(zhuǎn)換完成后將best.rknn文件拷貝到開(kāi)發(fā)板在開(kāi)發(fā)板上用RKNN-Toolkit-Lite2的Python接口運(yùn)行推理。這里用一個(gè)官方提供的yolov8后處理邏輯封裝好了的推理腳本import cv2 import numpy as np from rknnlite.api import RKNNLite rknn_lite RKNNLite() rknn_lite.load_rknn(./best.rknn) rknn_lite.init_runtime() img cv2.imread(./test.jpg) img_resized letterbox(img, (640, 640)) img_input img_resized[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 img_input np.expand_dims(img_input, axis0) outputs rknn_lite.inference(inputs[img_input]) boxes, classes, scores postprocess(outputs)這里letterbox函數(shù)和postprocess函數(shù)需要和你在PC端訓(xùn)練時(shí)使用的版本保持一致。尤其是postprocessyolov8的輸出是一個(gè)大的維度張量例如[1, 84, 8400]需要解析成邊界框坐標(biāo)、類(lèi)別置信度等信息。解析邏輯中的步長(zhǎng)、錨框設(shè)定都與模型訓(xùn)練時(shí)的配置有關(guān)如果你從UItralytics的ultralytics庫(kù)訓(xùn)練而來(lái)對(duì)應(yīng)的后處理代碼可以復(fù)用官方倉(cāng)庫(kù)里的實(shí)現(xiàn)直接照搬即可。4.4 精度與性能實(shí)測(cè)結(jié)果轉(zhuǎn)成RKNN模型后我用實(shí)拍測(cè)試集對(duì)比了一下FP16在PC上的推理結(jié)果和RK3588 INT8量化的推理結(jié)果。在隨機(jī)抽取的200張圖片中mAP50的下降在1到2個(gè)百分點(diǎn)之間定位框的偏差大概在2到3個(gè)像素左右。對(duì)于視覺(jué)引導(dǎo)定位這個(gè)需求來(lái)說(shuō)這個(gè)精度損失完全在可接受范圍內(nèi)。性能數(shù)據(jù)如下模型輸入分辨率平臺(tái)單幀推理耗時(shí)yolov8s640x640PC (RTX3060, FP16)8毫秒yolov8s640x640RK3588 NPU (INT8)32毫秒yolov8s640x640RK3588 CPU (FP32)480毫秒可以看到Int8量化在RK3588 NPU上比CPU推理快了15倍左右雖然和獨(dú)立顯卡相比有差距但在邊緣計(jì)算場(chǎng)景中已經(jīng)算非常能打了。如果對(duì)實(shí)時(shí)性要求更高可以將模型換成yolov8n推理耗時(shí)能進(jìn)一步降到15到20毫秒。需要補(bǔ)充的是yolov8s部署到RK3588后實(shí)際多線(xiàn)程并發(fā)調(diào)用時(shí)需要額外注意RKNNLite實(shí)例是線(xiàn)程不安全的不同線(xiàn)程要分別創(chuàng)建不同的RKNNLite實(shí)例每個(gè)實(shí)例占用一份NPU上下文。同時(shí)跑4路視頻的話(huà)常見(jiàn)做法是4個(gè)線(xiàn)程各持有一個(gè)實(shí)例NPU核心會(huì)進(jìn)行時(shí)間片調(diào)度。5. 第三階段系統(tǒng)集成從單一算法到可用的視覺(jué)系統(tǒng)模型跑通只是萬(wàn)里長(zhǎng)征第一步。要把視覺(jué)算法真正用起來(lái)必須把它和數(shù)據(jù)流、控制邏輯結(jié)合起來(lái)形成一套完整的系統(tǒng)。這一步我最大的體會(huì)是算法部署的工程量只有30%剩下的是工程化、容錯(cuò)與性能優(yōu)化。5.1 架構(gòu)設(shè)計(jì)采集、推理、控制分離整個(gè)視覺(jué)系統(tǒng)的軟件架構(gòu)如下圖像采集模塊獨(dú)立線(xiàn)程負(fù)責(zé)從V4L2設(shè)備讀取圖像幀放進(jìn)共享內(nèi)存隊(duì)列。算法推理模塊獨(dú)立線(xiàn)程從隊(duì)列取幀做預(yù)處理送入NPU推理推理結(jié)果封裝成結(jié)構(gòu)體包括目標(biāo)類(lèi)別、置信度、坐標(biāo)中心點(diǎn)、寬高寫(xiě)入結(jié)果隊(duì)列。業(yè)務(wù)邏輯模塊主線(xiàn)程從結(jié)果隊(duì)列取結(jié)果做坐標(biāo)變換、通信、決策。三個(gè)模塊通過(guò)隊(duì)列解耦。這樣的好處是即使某一幀推理超時(shí)采集線(xiàn)程不會(huì)阻塞業(yè)務(wù)邏輯也不會(huì)卡死系統(tǒng)整體魯棒性大大增強(qiáng)。5.2 通信與控制UART串口與Modbus視覺(jué)引導(dǎo)定位的結(jié)果最終要發(fā)給機(jī)械臂控制器。這里有幾種選擇如果是標(biāo)準(zhǔn)工業(yè)設(shè)備走M(jìn)odbus RTU或者TCP是常見(jiàn)的如果是配合機(jī)器人開(kāi)放接口走TCP/IP或者ROS標(biāo)準(zhǔn)消息更合適。用串口UART、Modbus實(shí)現(xiàn)數(shù)據(jù)通信。RK3588的UART節(jié)點(diǎn)在設(shè)備樹(shù)里配置后/dev/ttyS0創(chuàng)建設(shè)備節(jié)點(diǎn)。這里有個(gè)大坑如果出現(xiàn)了cant find suitable delayline這類(lèi)串口相關(guān)報(bào)錯(cuò)先確認(rèn)使用的uart在dts中的pinctrl引腳mux是否配置沖突因?yàn)橛行┮_默認(rèn)被分配給其他功能不顯式配置是不會(huì)有信號(hào)輸出的。串口發(fā)送采用簡(jiǎn)單的幀協(xié)議幀頭數(shù)據(jù)長(zhǎng)度目標(biāo)Xint16目標(biāo)Yint16目標(biāo)角度int16校驗(yàn)和0xAA 0x550x060x00000x00000x00000xXX主控端解析后即可控制執(zhí)行機(jī)構(gòu)動(dòng)作。5.3 ROS2環(huán)境適配如果你的視覺(jué)系統(tǒng)要集成到機(jī)器人環(huán)境里讓算法作為ROS2的功能包運(yùn)行是比較常見(jiàn)的選擇。RK3588跑Ubuntu或Debian系統(tǒng)時(shí)安裝ROS2 Humble需要選擇對(duì)應(yīng)的發(fā)行版。Debian 11對(duì)應(yīng)的ROS2版本是Humble Hawksbill可以從官方倉(cāng)庫(kù)安裝。安裝完成后寫(xiě)一個(gè)簡(jiǎn)單的ROS2節(jié)點(diǎn)訂閱圖像話(huà)題發(fā)布檢測(cè)結(jié)果話(huà)題import rclpy from rclpy.node import Node from sensor_msgs.msg import Image from vision_interfaces.msg import DetectionResult class VisionNode(Node): def __init__(self): super().__init__(vision_node) self.sub self.create_subscription(Image, /camera/image_raw, self.image_callback, 10) self.pub self.create_publisher(DetectionResult, /vision/detection, 10) def image_callback(self, msg): # 轉(zhuǎn)換圖像數(shù)據(jù)并調(diào)用算法推理 result detect(msg) self.pub.publish(result)ROS2的引入帶來(lái)了一個(gè)好處圖像和檢測(cè)結(jié)果都可以通過(guò)ros2 topic echo實(shí)時(shí)查看和調(diào)試但同時(shí)也帶來(lái)了一些性能損耗主要是消息序列化和反序列化的開(kāi)銷(xiāo)。對(duì)于追求極致幀率的場(chǎng)景用共享內(nèi)存或直接通過(guò)ZeroMQ傳遞壓縮后的檢測(cè)結(jié)果會(huì)比走ROS2消息更輕量。5.4 多路視頻流與RTSP接入有些場(chǎng)景下需要用網(wǎng)口拉取IP攝像機(jī)的RTSP流。RK3588處理這類(lèi)任務(wù)的能力還是比較充裕的。用FFmpeg或GStreamer拉流解碼將解碼后的幀送入算法模塊。一個(gè)簡(jiǎn)單的GStreamer拉流管道gst-launch-1.0 rtspsrc locationrtsp://192.168.1.64:554/stream1 latency100 ! rtph264depay ! h264parse ! mpph264dec ! videoconvert ! video/x-raw,formatBGR ! appsink注意這里用的是mpph264dec這是瑞芯微提供的基于VPU硬件解碼的插件解碼1080p視頻幾乎不占CPU資源。如果用的是軟件解碼插件avdec_h264一路視頻就能吃掉兩三個(gè)核心多路視頻直接卡死。RTSP接入的一個(gè)常見(jiàn)痛點(diǎn)是網(wǎng)絡(luò)延遲和抖動(dòng)導(dǎo)致圖像畫(huà)面卡頓或花屏。解決方式是在rtspsrc上設(shè)置latency參數(shù)毫秒同時(shí)適當(dāng)調(diào)整drop-on-latencytrue這樣在解碼延遲超限時(shí)會(huì)自動(dòng)丟幀保證后續(xù)圖像處理的實(shí)時(shí)性。6. 性能調(diào)優(yōu)與功耗控制讓系統(tǒng)可靠運(yùn)行系統(tǒng)的長(zhǎng)期穩(wěn)定運(yùn)行是視覺(jué)算法落地中最容易被輕視的部分。算法跑通和算法能7x24小時(shí)穩(wěn)定跑中間還是有很大距離的。這里我從三個(gè)方面做了優(yōu)化。6.1 CPU與NPU資源分配RK3588有4個(gè)A76大核和4個(gè)A55小核。對(duì)于視覺(jué)應(yīng)用建議做CPU綁核圖像采集線(xiàn)程綁在大核上保證圖像數(shù)據(jù)能及時(shí)取走防止因?yàn)檎{(diào)度延遲導(dǎo)致緩沖區(qū)覆蓋后處理邏輯包括解碼、NMS等也綁在大核上業(yè)務(wù)邏輯、通信等任務(wù)放在小核上執(zhí)行讓出大核資源給計(jì)算密集型的任務(wù)。用taskset命令可以實(shí)現(xiàn)綁核taskset -c 4-5 ./vision_app6.2 NPU多模型并發(fā)與吞吐如果你需要同時(shí)跑兩個(gè)模型比如一個(gè)做目標(biāo)檢測(cè)一個(gè)做圖像分割要注意NPU內(nèi)存占用。RK3588的NPU共用的是一塊固定的SRAM模型同時(shí)加載可能導(dǎo)致內(nèi)存不足。建議在rknn_lite.init_runtime()時(shí)設(shè)置rknn_batch_size1同時(shí)讓多個(gè)模型分時(shí)復(fù)用NPU不要同時(shí)創(chuàng)建過(guò)多的RKNNLite實(shí)例。另外RKNN-Toolkit2的runtime支持NPU、GPU、CPU三層異構(gòu)可以通過(guò)設(shè)置device_type讓特定算子回退到GPU或者CPU執(zhí)行。但除非必要不建議這么做因?yàn)樗阕踊赝送殡S多次內(nèi)存拷貝實(shí)際速度可能更慢。6.3 熱管理風(fēng)扇控制與降頻策略NPU滿(mǎn)負(fù)荷運(yùn)行時(shí)芯片溫度會(huì)迅速攀升。溫度超過(guò)85度時(shí)RK3588會(huì)主動(dòng)降頻導(dǎo)致推理速度突然下降。我當(dāng)時(shí)的處理方案是自定義風(fēng)扇控制邏輯根據(jù)溫度多級(jí)調(diào)整PWM占空比。在python里讀寫(xiě)PWM占空比的方式def set_fan_duty(duty): with open(/sys/class/hwmon/hwmon0/pwm1, w) as f: f.write(str(duty))溫度低于60度時(shí)占空比設(shè)為80正常散熱60到70度之間逐步加檔超過(guò)75度占空比拉滿(mǎn)。這樣既能保證性能釋放又不會(huì)讓風(fēng)扇全程高速運(yùn)轉(zhuǎn)產(chǎn)生噪音。6.4 任務(wù)看門(mén)狗與異?;謴?fù)長(zhǎng)期運(yùn)行的系統(tǒng)最怕算法進(jìn)程因?yàn)槟硯瑪?shù)據(jù)異常崩潰然后一直死在那邊不重啟。添加一個(gè)簡(jiǎn)單的看門(mén)狗機(jī)制算法進(jìn)程周期性通過(guò)UDP包上報(bào)心跳主控端如果在10秒內(nèi)沒(méi)收到心跳就執(zhí)行systemctl restart vision_app。這種方式雖然談不上多智能但至少能保證系統(tǒng)具備從異常中自我恢復(fù)的能力。7. 高階話(huà)題FPGA與協(xié)處理器、多核調(diào)優(yōu)的設(shè)想RK3588雖然強(qiáng)大但有些特定的視覺(jué)任務(wù)還是需要額外算力的。比如高速運(yùn)動(dòng)的視覺(jué)定位需要極高的幀率再比如高分辨率圖像的缺陷檢測(cè)在NPU上做實(shí)時(shí)處理比較吃力。這時(shí)候可以考慮搭配協(xié)處理器來(lái)協(xié)作完成任務(wù)。RK3588擁有PCIe接口可以外接FPGA板卡或者ASIC加速卡。FPGA的強(qiáng)項(xiàng)是低延遲并行處理和時(shí)序確定性適合做高速圖像的預(yù)處理、觸發(fā)式抓拍、和特定算法的定制加速。比如一些視覺(jué)定位算法中Gaussian濾波和Sobel邊緣檢測(cè)這部分邏輯用FPGA做硬件流水線(xiàn)處理可以做到微秒級(jí)延遲完全不占CPU和NPU資源。而NPU負(fù)責(zé)后續(xù)基于深度學(xué)習(xí)的目標(biāo)識(shí)別與分類(lèi)實(shí)現(xiàn)FPGANPU的異構(gòu)協(xié)同。這種組合的集成復(fù)雜度相對(duì)較高但我認(rèn)為這是RK3588作為邊緣計(jì)算主控比較有潛力的擴(kuò)展方向。前期先用單板跑通整個(gè)算法鏈路后續(xù)如果項(xiàng)目確實(shí)需要高幀率或者多相機(jī)并行處理再引入FPGA做卸載整體升級(jí)路徑是比較平滑的。另外關(guān)于多核調(diào)優(yōu)一個(gè)可行的優(yōu)化是使用OpenMP或C多線(xiàn)程重寫(xiě)算法后處理部分。Python的GIL限制了CPU密集型的后處理無(wú)法真正多核并行導(dǎo)致單幀延遲中有相當(dāng)一部分是消耗在NMS等后處理環(huán)節(jié)的。把后處理改為C實(shí)現(xiàn)通過(guò)pybind11封裝成Python模塊可以有效降低后處理耗時(shí)親測(cè)在邊界框數(shù)量較多的時(shí)候效率提升能達(dá)到3倍以上。8. 常用開(kāi)發(fā)工具與踩坑記錄最后整理一下項(xiàng)目開(kāi)發(fā)過(guò)程中的一些工具和問(wèn)題記錄這些內(nèi)容對(duì)后續(xù)做RK3588視覺(jué)項(xiàng)目的開(kāi)發(fā)者應(yīng)該有一定的參考價(jià)值。工具/命令用途說(shuō)明RKDevTool燒錄鏡像工具配合開(kāi)發(fā)板Loader模式使用rknn-toolkit2模型轉(zhuǎn)換工具在x86主機(jī)運(yùn)行將ONNX/PyTorch轉(zhuǎn)RKNNrknn-toolkit-lite2板端推理工具RK3588上運(yùn)行RKNN模型的Python接口dmesg內(nèi)核日志查看調(diào)試驅(qū)動(dòng)加載、硬件識(shí)別問(wèn)題v4l2-ctlV4L2設(shè)備控制查看視頻格式、抓取單幀gst-launch-1.0GStreamer管道測(cè)試快速驗(yàn)證視頻采集與RTSP拉流tasksetCPU綁核優(yōu)化多線(xiàn)程任務(wù)在不同核心上的分布perf性能分析定位代碼熱點(diǎn)優(yōu)化瓶頸踩坑記錄重點(diǎn)提幾個(gè)都是當(dāng)時(shí)真正困擾過(guò)我、最后反復(fù)比對(duì)才找到原因的問(wèn)題。第一個(gè)是ES8388。有些RK3588開(kāi)發(fā)板板載了ES8388這顆音頻編解碼芯片如果你的系統(tǒng)在啟動(dòng)時(shí)出現(xiàn)ES8388相關(guān)報(bào)錯(cuò)原因是設(shè)備樹(shù)里音頻節(jié)點(diǎn)和I2C地址沖突或者驅(qū)動(dòng)沒(méi)編譯進(jìn)內(nèi)核。本來(lái)不影響視覺(jué)任務(wù)但每次dmesg里都報(bào)這個(gè)錯(cuò)會(huì)導(dǎo)致其他驅(qū)動(dòng)日志被刷掉排查起來(lái)不方便所以盡量修復(fù)。修法很簡(jiǎn)單在設(shè)備樹(shù)里確認(rèn)i2c2節(jié)點(diǎn)下ES8388的reg值然后檢查內(nèi)核配置中的CONFIG_SND_SOC_ES8388是否開(kāi)啟。第二個(gè)是MIPI顯示問(wèn)題。有段時(shí)間HDMI顯示突然不工作排查到最后發(fā)現(xiàn)是設(shè)備樹(shù)里hdmi0節(jié)點(diǎn)被誤改成了status disabled。這類(lèi)問(wèn)題經(jīng)常發(fā)生在從原廠(chǎng)SDK拷貝dts文件做修改時(shí)某個(gè)細(xì)節(jié)遺漏就會(huì)導(dǎo)致整體顯示鏈路失效。第三個(gè)是開(kāi)機(jī)啟動(dòng)時(shí)USB設(shè)備枚舉失敗。后來(lái)發(fā)現(xiàn)是供電不足RK3588在同時(shí)驅(qū)動(dòng)NPU、多路攝像頭和機(jī)械臂控制器時(shí)如果電源適配器輸出能力不夠需要12V/5A以上USB設(shè)備的供電就會(huì)不穩(wěn)定。排查思路很簡(jiǎn)單接上外部供電后問(wèn)題就消失了。如果你也在RK3588上做視覺(jué)開(kāi)發(fā)還有一個(gè)建議是盡量養(yǎng)成梳理啟動(dòng)日志的習(xí)慣。用journalctl --since 10 minutes ago或者dmesg -w實(shí)時(shí)監(jiān)控內(nèi)核輸出很多硬件層面的問(wèn)題在日志里其實(shí)有非常明確的提示只是容易被忽略。