節(jié)點(diǎn)設(shè)計(jì):從HSV目標(biāo)提取到穩(wěn)定脫靶量輸出)
簡(jiǎn)介這份資源是2023年全國(guó)大學(xué)生電子設(shè)計(jì)競(jìng)賽E題視覺(jué)部分的完整代碼包面向備戰(zhàn)電賽視覺(jué)賽題的高校參賽隊(duì)與指導(dǎo)教師也適合想入門(mén)嵌入式視覺(jué)的開(kāi)發(fā)者參考。方案基于Sipeed Maix BitK210開(kāi)發(fā)板實(shí)現(xiàn)圖像采集、預(yù)處理、特征提取與目標(biāo)檢測(cè)跟蹤等視覺(jué)功能并配套主從云臺(tái)控制代碼主控采用STC32G12K128形成視覺(jué)與運(yùn)動(dòng)控制協(xié)同的完整框架。壓縮包共352個(gè)文件、約19.47MB以C與H源碼、Keil工程文件uvproj、uvopt、編譯中間文件obj、lst、crf、map及hex燒錄文件為主另有少量Python腳本、lib庫(kù)、asm匯編、PDF與md說(shuō)明文檔可直接在Keil環(huán)境下打開(kāi)編譯。目前已有619人學(xué)習(xí)下載。讀者可從中獲得賽題視覺(jué)部分的工程組織方式、K210與STC32G的通信與控制分工、云臺(tái)步進(jìn)電機(jī)與MPU6050姿態(tài)模塊的驅(qū)動(dòng)代碼以及可復(fù)用的圖像處理函數(shù)便于快速搭建穩(wěn)定的視覺(jué)系統(tǒng)并在其基礎(chǔ)上做二次開(kāi)發(fā)與賽前調(diào)試。1. 需求先立住視覺(jué)部分要交付的是穩(wěn)定脫靶量不是一張識(shí)別截圖拿到某屆電子設(shè)計(jì)競(jìng)賽 E 題視覺(jué)部分代碼這個(gè)題目很多人第一反應(yīng)是先把攝像頭點(diǎn)亮、把目標(biāo)框出來(lái)截一張識(shí)別成功的圖就算交差。真正上云臺(tái)一跑問(wèn)題立刻暴露光斑在畫(huà)面里跳、云臺(tái)來(lái)回?cái)[、串口數(shù)據(jù)斷斷續(xù)續(xù)。E 題視覺(jué)部分要交付的不是一張識(shí)別截圖而是一串穩(wěn)定、有坐標(biāo)系、有時(shí)序約束的脫靶量。適合誰(shuí)正在做電子設(shè)計(jì)競(jìng)賽視覺(jué)題、課程設(shè)計(jì)里要做目標(biāo)跟蹤、畢設(shè)需要搭一套攝像頭到云臺(tái)執(zhí)行鏈路的人。關(guān)鍵詞里寫(xiě)的是軟件/插件實(shí)際這套代碼更像獨(dú)立視覺(jué)節(jié)點(diǎn)它不替主控做控制決策只負(fù)責(zé)把目標(biāo)位置、置信狀態(tài)和幀序號(hào)講清楚。先把需求立住后面的方案才不會(huì)變成調(diào)參玄學(xué)。2. 總體方案設(shè)計(jì)采集、識(shí)別、解算、通信四層鏈路怎么拆視覺(jué)部分最容易寫(xiě)成一坨取圖、閾值、找輪廓、算中心、發(fā)串口全塞在一個(gè)while True里。調(diào)參時(shí)看著能跑換一個(gè)光照或者換一個(gè)云臺(tái)就翻車(chē)。我一般把電賽視覺(jué)題拆成四層每層只回答一個(gè)問(wèn)題采集層回答“畫(huà)面是否穩(wěn)定且低延遲”識(shí)別層回答“目標(biāo)在哪里、可不可信”解算層回答“像素偏差對(duì)應(yīng)云臺(tái)要轉(zhuǎn)多少”通信層回答“主控什么時(shí)候拿到、拿到的數(shù)據(jù)是否完整”。四層拆開(kāi)之后參數(shù)有歸屬問(wèn)題有邊界答辯時(shí)也能把鏈路講清楚。2.1 視覺(jué)鏈路拆分四個(gè)層各自只做一件事采集層不參與識(shí)別只負(fù)責(zé)相機(jī)索引、分辨率、幀率、曝光和增益。競(jìng)賽現(xiàn)場(chǎng)常見(jiàn)的問(wèn)題是自動(dòng)曝光把光斑壓暗或者自動(dòng)白平衡讓顏色漂移所以相機(jī)參數(shù)通常要手動(dòng)鎖定。分辨率不是越高越好640×480 在 60 幀下往往比 1280×720 在 30 幀下更適合跟蹤因?yàn)榭刂骗h(huán)需要的是低延遲而不是一張好看的圖。采集層還要記錄幀時(shí)間戳后面做丟幀判斷和延遲統(tǒng)計(jì)都靠它。識(shí)別層只輸出候選目標(biāo)列表不直接決定最終目標(biāo)。候選列表里包含中心點(diǎn)、面積、外接圓半徑、圓形度、置信分?jǐn)?shù)。最終目標(biāo)選擇放在解算層因?yàn)檫x擇邏輯會(huì)用到上一幀位置和跟蹤狀態(tài)。這樣拆的好處是識(shí)別層可以單獨(dú)拿靜態(tài)圖片回放測(cè)試不用接云臺(tái)解算層可以拿錄好的像素坐標(biāo)離線跑不用反復(fù)上電。解算層負(fù)責(zé)坐標(biāo)映射、死區(qū)、限幅和丟幀保持。它不直接驅(qū)動(dòng)舵機(jī)只輸出角度增量或者歸一化偏差。通信層負(fù)責(zé)幀頭、長(zhǎng)度、校驗(yàn)、序號(hào)和發(fā)送頻率。視覺(jué)節(jié)點(diǎn)只發(fā)“偏差狀態(tài)”不背控制鍋主控愿意用 PID 還是比例控制是主控的事。職責(zé)一混出現(xiàn)振蕩時(shí)就會(huì)互相甩鍋視覺(jué)說(shuō)主控參數(shù)太猛主控說(shuō)視覺(jué)數(shù)據(jù)太跳。把接口定死排查才有黑匣子。目錄上我習(xí)慣分成camera/、detect/、solve/、comm/、config/五塊配置文件單獨(dú)放現(xiàn)場(chǎng)改閾值不用翻代碼。下面這份參數(shù)表是視覺(jué)節(jié)點(diǎn)的起點(diǎn)不同場(chǎng)地要改的是 HSV 和曝光不是整個(gè)結(jié)構(gòu)。camera: index: 0 width: 640 height: 480 fps: 60 exposure: -6 # 手動(dòng)曝光現(xiàn)場(chǎng)按光斑亮度微調(diào) gain: 8 # 增益過(guò)高會(huì)放大噪聲 target: hsv_lower: [0, 120, 80] hsv_upper: [10, 255, 255] min_area: 80 max_area: 8000 min_circularity: 0.55 blur_ksize: 5 track: kp_x: 0.08 kp_y: 0.08 deadband_px: 3 lost_hold_frames: 5 serial: port: /dev/ttyUSB0 baud: 115200 frame_head: 0xAA55這段配置把相機(jī)、目標(biāo)、跟蹤、串口分開(kāi)。exposure和gain需要現(xiàn)場(chǎng)鎖定自動(dòng)模式在光斑移動(dòng)時(shí)會(huì)反復(fù)調(diào)整導(dǎo)致閾值跟著漂。min_area和max_area不是拍腦袋寫(xiě)的先用靜態(tài)靶在目標(biāo)距離上量出光斑像素面積再取 0.5 倍到 3 倍作為范圍。deadband_px用來(lái)抑制中心點(diǎn)抖動(dòng)太小會(huì)讓云臺(tái)追著噪聲走太大又會(huì)出現(xiàn)明顯滯后。lost_hold_frames是丟幀保持幀數(shù)目標(biāo)短暫被遮擋時(shí)不要立刻發(fā)丟失否則主控會(huì)急停再啟動(dòng)機(jī)械結(jié)構(gòu)容易抖。2.2 坐標(biāo)系對(duì)齊圖像中心、光軸中心和云臺(tái)零位不能混視覺(jué)題翻車(chē)最多的地方不是識(shí)別而是坐標(biāo)系。圖像中心是(width/2, height/2)光軸中心是相機(jī)鏡頭實(shí)際瞄準(zhǔn)的點(diǎn)云臺(tái)零位是舵機(jī)或電機(jī)安裝后的機(jī)械零位。這三個(gè)點(diǎn)不重合時(shí)畫(huà)面里目標(biāo)在中心云臺(tái)卻偏一邊。我的做法是先做靜態(tài)對(duì)中把靶點(diǎn)固定在遠(yuǎn)處手動(dòng)把云臺(tái)零位對(duì)準(zhǔn)再讀圖像中心偏差把這個(gè)偏差記成offset_x、offset_y后面所有脫靶量都先減掉它。像素到角度的映射有兩種常用做法。小孔模型適合快速起步已知水平視場(chǎng)角HFOV焦距像素近似為f (width/2) / tan(HFOV/2)像素偏差dx對(duì)應(yīng)角度theta atan(dx / f)。但鏡頭畸變和安裝誤差會(huì)讓邊緣不準(zhǔn)所以正式驗(yàn)收前通常要做一張標(biāo)定表在畫(huà)面橫向和縱向各取 5 個(gè)點(diǎn)記錄像素偏差和云臺(tái)實(shí)際角度用分段線性或者二次擬合修正。標(biāo)定表不用很復(fù)雜但一定要有否則中心準(zhǔn)、邊緣飄追蹤快速目標(biāo)時(shí)就會(huì)畫(huà)圈。提示先把云臺(tái)零位、相機(jī)光軸、圖像中心三件事在同一張靜態(tài)圖上對(duì)齊再談跟蹤算法。順序反了后面調(diào)參全是玄學(xué)。2.3 通信幀設(shè)計(jì)視覺(jué)節(jié)點(diǎn)輸出偏差、狀態(tài)和序號(hào)視覺(jué)節(jié)點(diǎn)和主控之間最常見(jiàn)的問(wèn)題是“數(shù)據(jù)看起來(lái)發(fā)了主控解析出來(lái)是亂碼”。原因通常不是串口壞了而是幀邊界不清楚只發(fā)浮點(diǎn)數(shù)沒(méi)有幀頭沒(méi)有長(zhǎng)度沒(méi)有校驗(yàn)主控一上電從中間開(kāi)始讀就把半個(gè)浮點(diǎn)當(dāng)成幀頭。我一般用固定幀結(jié)構(gòu)幀頭兩字節(jié)、長(zhǎng)度一字節(jié)、兩個(gè)浮點(diǎn)偏差、一個(gè)狀態(tài)字節(jié)、一個(gè)序號(hào)字節(jié)、一個(gè)校驗(yàn)字節(jié)。浮點(diǎn)用小端打包主控和視覺(jué)端約定死。import struct def pack_frame(dx, dy, state, seq): # 幀頭固定 0xAA55長(zhǎng)度表示 payload 字節(jié)數(shù) head b\xAA\x55 # ffBB 表示小端兩個(gè) float、兩個(gè) unsigned char payload struct.pack(ffBB, dx, dy, state, seq) length len(payload) checksum sum(payload) 0xFF return head bytes([length]) payload bytes([checksum])這段代碼只做打包不負(fù)責(zé)發(fā)送。dx、dy是解算后的像素偏差或歸一化偏差單位要在協(xié)議里寫(xiě)清楚state用位標(biāo)志表示“目標(biāo)有效、目標(biāo)丟失、幀超時(shí)、飽和”seq每幀加一主控可以用它判斷有沒(méi)有丟幀。checksum用簡(jiǎn)單累加和就夠競(jìng)賽現(xiàn)場(chǎng)干擾沒(méi)有大到必須上 CRC但幀頭和長(zhǎng)度必須有。發(fā)送頻率通常跟圖像幀率一致50 到 60 幀足夠主控控制環(huán)再快也不能拿舊幀硬補(bǔ)。3. 目標(biāo)提取實(shí)現(xiàn)HSV 閾值、輪廓篩選與中心點(diǎn)穩(wěn)定的三組參數(shù)目標(biāo)提取是視覺(jué)部分最直觀的一層也是最容易被“能識(shí)別”騙過(guò)去的一層。靜態(tài)圖上閾值一拉光斑清清楚楚現(xiàn)場(chǎng)燈光一打白墻反光、地面高光、隊(duì)友衣服上的顏色全跑進(jìn)來(lái)。HSV 比 RGB 穩(wěn)是因?yàn)樯{(diào) H 和亮度 V 分開(kāi)光照變化主要影響 V顏色類(lèi)別主要看 H 和 S。但 HSV 不是萬(wàn)能藥閾值范圍給太寬會(huì)把背景吃進(jìn)來(lái)給太窄目標(biāo)稍微暗一點(diǎn)就丟。我的習(xí)慣是先用固定曝光拍幾張現(xiàn)場(chǎng)圖再用滑動(dòng)條調(diào) H、S、V 上下限最后把范圍收窄到剛好包住目標(biāo)而不是包住所有可能。3.1 顏色空間選擇HSV 閾值要按現(xiàn)場(chǎng)光而不是屏幕光調(diào)調(diào)閾值時(shí)不要只看攝像頭預(yù)覽窗口那個(gè)窗口經(jīng)過(guò)顯示器和環(huán)境光和實(shí)際保存下來(lái)的幀不是一回事。我一般先抓一幀原圖存成 PNG用離線腳本讀圖調(diào)參調(diào)完再把參數(shù)寫(xiě)回配置文件。H 通道在 OpenCV 里是 0 到 179紅色通???0 和 179 兩端所以紅色目標(biāo)往往要做兩段掩膜再合并綠色和藍(lán)色一般一段就夠。S 下限用來(lái)壓掉白色和灰色背景V 下限用來(lái)壓掉暗部噪聲。若現(xiàn)場(chǎng)有強(qiáng)反光V 上限也可以適當(dāng)限制避免高光區(qū)域被當(dāng)成目標(biāo)。閾值不是一勞永逸的。比賽現(xiàn)場(chǎng)從上午到下午窗邊自然光會(huì)變LED 補(bǔ)光燈角度也會(huì)變。我的做法是準(zhǔn)備兩套閾值一套“保守閾值”用于正式跟蹤寧可丟幾幀也不要亂框一套“寬松閾值”只用于調(diào)試和找目標(biāo)。運(yùn)行中如果連續(xù)多幀丟失再短暫切到寬松閾值搜目標(biāo)搜到后切回保守閾值。這樣比全程寬松穩(wěn)也比全程保守不容易丟。3.2 輪廓篩選面積、長(zhǎng)寬比、圓形度三板斧二值化之后直接取最大輪廓是另一個(gè)常見(jiàn)翻車(chē)點(diǎn)?,F(xiàn)場(chǎng)最大的白色區(qū)域可能是反光板不是目標(biāo)。輪廓篩選我一般看三個(gè)量面積、外接矩形長(zhǎng)寬比、圓形度。面積用來(lái)排除噪點(diǎn)和過(guò)大背景長(zhǎng)寬比用來(lái)排除長(zhǎng)條形反光圓形度4πA/P2用來(lái)排除不規(guī)則亮斑。光斑通常是近似圓形圓形度在 0.5 以上比較合理。三個(gè)條件不必同時(shí)卡死可以做成評(píng)分面積越接近期望值、圓形度越高、離上一幀位置越近分?jǐn)?shù)越高。import cv2 import numpy as np def extract_target(frame, cfg): hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower np.array(cfg[hsv_lower], dtypenp.uint8) upper np.array(cfg[hsv_upper], dtypenp.uint8) mask cv2.inRange(hsv, lower, upper) # 先開(kāi)后閉去掉孤立噪點(diǎn)再把斷裂的光斑連起來(lái) kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations1) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations2) cnts, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) best None for c in cnts: area cv2.contourArea(c) if area cfg[min_area] or area cfg[max_area]: continue perimeter cv2.arcLength(c, True) if perimeter 1e-6: continue circularity 4 * np.pi * area / (perimeter * perimeter) if circularity cfg[min_circularity]: continue (cx, cy), radius cv2.minEnclosingCircle(c) if radius 3: continue score area * circularity if best is None or score best[score]: best { x: float(cx), y: float(cy), r: float(radius), area: float(area), score: float(score), } return best, mask這段函數(shù)只返回一個(gè)最佳候選和掩膜方便調(diào)試時(shí)把掩膜顯示出來(lái)看。min_area和max_area決定哪些輪廓進(jìn)入評(píng)分min_circularity卡掉不規(guī)則亮斑score area * circularity是簡(jiǎn)單評(píng)分實(shí)際項(xiàng)目里還可以加上“離上一幀距離”的權(quán)重避免兩個(gè)相似光斑之間來(lái)回跳。注意minEnclosingCircle給的是外接圓中心不是質(zhì)心光斑被部分遮擋時(shí)外接圓中心更穩(wěn)但形狀被拉長(zhǎng)時(shí)會(huì)偏所以后面還要濾波。3.3 中心點(diǎn)解算與濾波滑動(dòng)平均不能替代丟幀保護(hù)中心點(diǎn)解算常用質(zhì)心、外接圓中心、最小二乘擬合圓心。質(zhì)心對(duì)形狀敏感外接圓對(duì)遮擋敏感擬合圓心計(jì)算量大一些。競(jìng)賽場(chǎng)景里目標(biāo)通常是圓形光斑我一般用外接圓中心加滑動(dòng)平均起步夠用且計(jì)算量小。濾波窗口不要太大5 幀左右窗口越大越平滑但相位滯后越明顯云臺(tái)追快速目標(biāo)時(shí)會(huì)畫(huà)弧。更好的做法是自適應(yīng)濾波目標(biāo)靜止時(shí)窗口大一點(diǎn)目標(biāo)快速移動(dòng)時(shí)窗口小一點(diǎn)用中心點(diǎn)位移速度判斷。丟幀保護(hù)比濾波更重要。目標(biāo)短暫被遮擋時(shí)如果立刻發(fā)“丟失”主控會(huì)急停再出現(xiàn)時(shí)又猛追機(jī)械結(jié)構(gòu)容易抖。我一般設(shè)lost_hold_frames 5丟失后的前 5 幀繼續(xù)發(fā)上一幀偏差但狀態(tài)位標(biāo)記“預(yù)測(cè)/保持”主控可以選擇降速跟蹤。超過(guò) 5 幀再發(fā)丟失主控進(jìn)入搜索模式。這個(gè)數(shù)字不是固定的云臺(tái)慣量大就設(shè)大一點(diǎn)慣量小就設(shè)小一點(diǎn)。濾波和保持都要在解算層做不要塞進(jìn)識(shí)別函數(shù)否則離線回放時(shí)很難復(fù)現(xiàn)。4. 坐標(biāo)映射與云臺(tái)接口像素偏差到角度指令的閉環(huán)設(shè)計(jì)識(shí)別到目標(biāo)只是半成品真正決定追蹤效果的是映射和控制接口。像素偏差 100 在畫(huà)面中心可能對(duì)應(yīng) 5 度在邊緣可能對(duì)應(yīng) 7 度鏡頭畸變?cè)酱蟛町愒矫黠@。云臺(tái)控制環(huán)的輸入如果是“像素偏差”主控就要自己猜比例如果視覺(jué)節(jié)點(diǎn)直接發(fā)“角度增量”主控的 PID 更容易調(diào)。我一般讓視覺(jué)節(jié)點(diǎn)輸出歸一化偏差或者角度增量單位在協(xié)議里寫(xiě)死主控只做閉環(huán)。這樣視覺(jué)部分可以單獨(dú)用錄制的像素序列回放不用每次都接云臺(tái)。4.1 相機(jī)標(biāo)定與像素-角度映射小孔模型起步分段標(biāo)定收尾小孔模型適合快速搭鏈路。已知相機(jī)水平視場(chǎng)角焦距像素f (width / 2) / tan(HFOV / 2)像素偏差dx對(duì)應(yīng)的角度近似atan(dx / f)。這個(gè)公式在畫(huà)面中心附近準(zhǔn)邊緣會(huì)受畸變影響。如果鏡頭是廣角邊緣誤差可能大到讓云臺(tái)追偏。我的做法是先用小孔模型跑通再在畫(huà)面橫向取 -240、-120、0、120、240 五個(gè)像素點(diǎn)記錄云臺(tái)實(shí)際角度做分段線性修正。縱向同理。標(biāo)定表不用很密五點(diǎn)足夠覆蓋競(jìng)賽場(chǎng)地。標(biāo)定時(shí)要注意云臺(tái)角度讀數(shù)和圖像采集的時(shí)間同步。如果云臺(tái)還在動(dòng)就讀數(shù)標(biāo)定表會(huì)帶滯后。正確做法是云臺(tái)轉(zhuǎn)到位置后停 200 毫秒等畫(huà)面穩(wěn)定再采圖記錄當(dāng)前角度和光斑中心。標(biāo)定完成后把offset_x、offset_y和分段斜率寫(xiě)進(jìn)配置運(yùn)行時(shí)先減偏移再查表。若現(xiàn)場(chǎng)換了鏡頭或者相機(jī)安裝角度標(biāo)定表必須重做不能沿用舊參數(shù)。4.2 串口協(xié)議實(shí)現(xiàn)幀頭、長(zhǎng)度、校驗(yàn)、浮點(diǎn)打包串口協(xié)議在第 2 章已經(jīng)給了打包函數(shù)這里補(bǔ)接收端和狀態(tài)位。主控解析時(shí)先找0xAA55再讀長(zhǎng)度再按長(zhǎng)度收 payload最后校驗(yàn)。校驗(yàn)不過(guò)直接丟幀不要嘗試修復(fù)。浮點(diǎn)數(shù)用struct.unpack(ffBB, payload)解析字節(jié)序必須和發(fā)送端一致。狀態(tài)位建議定義清楚bit0 目標(biāo)有效bit1 目標(biāo)丟失bit2 幀超時(shí)bit3 輸出飽和。主控看到 bit3 時(shí)應(yīng)該限幅而不是繼續(xù)加大控制量。import struct def pack_frame(dx, dy, state, seq): # dx/dy 單位為角度增量保留浮點(diǎn)state 為位標(biāo)志 head b\xAA\x55 payload struct.pack(ffBB, dx, dy, state, seq) checksum sum(payload) 0xFF return head bytes([len(payload)]) payload bytes([checksum]) def unpack_frame(buf): # buf 至少包含幀頭長(zhǎng)度payload校驗(yàn) if len(buf) 4 or buf[0] ! 0xAA or buf[1] ! 0x55: return None length buf[2] if len(buf) 3 length 1: return None payload buf[3:3 length] checksum buf[3 length] if (sum(payload) 0xFF) ! checksum: return None if length ! 10: return None dx, dy, state, seq struct.unpack(ffBB, payload) return {dx: dx, dy: dy, state: state, seq: seq}發(fā)送端每幀調(diào)用pack_frame接收端把串口緩沖喂給unpack_frame。length ! 10是防御性判斷防止協(xié)議版本不一致時(shí)解析錯(cuò)位。實(shí)際項(xiàng)目里還可以加時(shí)間戳但競(jìng)賽現(xiàn)場(chǎng)主控和視覺(jué)端通常共用一個(gè)電源時(shí)間戳同步要求不高序號(hào)足夠判斷丟幀。若串口出現(xiàn)連續(xù)校驗(yàn)失敗先查波特率和地線再查發(fā)送頻率是否超過(guò)主控解析能力。4.3 跟蹤策略比例控制、死區(qū)和丟幀保護(hù)視覺(jué)節(jié)點(diǎn)不直接做 PID但可以給主控一個(gè)建議控制量。比例控制u kp * error簡(jiǎn)單有效kp太大云臺(tái)會(huì)來(lái)回?cái)[太小追不上快速目標(biāo)。死區(qū)deadband_px用來(lái)忽略中心附近的小抖動(dòng)通常 2 到 5 像素。輸出限幅必須有否則目標(biāo)突然出現(xiàn)在畫(huà)面邊緣時(shí)云臺(tái)會(huì)猛沖機(jī)械結(jié)構(gòu)容易撞限位。丟幀保護(hù)前面說(shuō)過(guò)保持幀數(shù)內(nèi)繼續(xù)發(fā)上一幀偏差并標(biāo)記預(yù)測(cè)狀態(tài)超過(guò)后發(fā)丟失。調(diào)參順序我一般這樣走先把云臺(tái)斷開(kāi)只看視覺(jué)輸出的偏差曲線確認(rèn)目標(biāo)靜止時(shí)偏差不跳再接云臺(tái)給很小的kp讓云臺(tái)慢慢追確認(rèn)方向?qū)υ僦鸩郊哟髃p直到出現(xiàn)輕微過(guò)沖然后回調(diào) 20%。不要一上來(lái)就上大kp云臺(tái)撞限位一次齒輪間隙可能就變了后面調(diào)參全是血淚經(jīng)驗(yàn)。死區(qū)也不要設(shè)太大否則目標(biāo)在中心附近移動(dòng)時(shí)云臺(tái)不動(dòng)看起來(lái)像“識(shí)別丟了”其實(shí)是控制沒(méi)響應(yīng)。5. 避坑與排查視覺(jué)部分最容易翻車(chē)的五個(gè)現(xiàn)場(chǎng)問(wèn)題視覺(jué)代碼在實(shí)驗(yàn)室跑通到現(xiàn)場(chǎng)翻車(chē)通常不是算法不夠先進(jìn)而是幾個(gè)工程細(xì)節(jié)沒(méi)兜住。下面五條是電賽視覺(jué)題里最常見(jiàn)的現(xiàn)場(chǎng)問(wèn)題每條按“現(xiàn)象、原因、解決”寫(xiě)照著排查能省很多時(shí)間。5.1 光照一變閾值全廢現(xiàn)象是上午能跟下午丟目標(biāo)現(xiàn)象同一套 HSV 閾值上午在窗邊能穩(wěn)定跟蹤下午太陽(yáng)角度變了目標(biāo)要么丟要么背景反光被當(dāng)成目標(biāo)。原因自動(dòng)曝光和自動(dòng)白平衡沒(méi)有鎖定環(huán)境光變化讓 H、S、V 整體漂移閾值范圍又給得太寬背景一亮就進(jìn)入掩膜。解決相機(jī)曝光、增益、白平衡全部手動(dòng)鎖定現(xiàn)場(chǎng)先用靜態(tài)靶抓圖按實(shí)際幀調(diào)閾值準(zhǔn)備保守和寬松兩套閾值丟失時(shí)短暫切寬松搜索搜到后切回保守跟蹤。閾值范圍收窄到剛好包住目標(biāo)不要追求“所有光照都能識(shí)別”。5.2 反光與同色背景導(dǎo)致輪廓亂跳現(xiàn)象是目標(biāo)框在兩個(gè)亮斑之間跳現(xiàn)象畫(huà)面里目標(biāo)明明只有一個(gè)識(shí)別框卻在兩個(gè)位置之間來(lái)回跳云臺(tái)跟著左右擺。原因現(xiàn)場(chǎng)有同色反光或者高光區(qū)域面積和圓形度都接近目標(biāo)評(píng)分函數(shù)只按面積選最大誰(shuí)大選誰(shuí)。解決在評(píng)分里加入“離上一幀中心距離”的權(quán)重近距離候選優(yōu)先用跟蹤門(mén)限限制候選范圍超過(guò)上一幀一定像素半徑的候選直接排除實(shí)在分不開(kāi)時(shí)用形狀和亮度分布做二次篩選。不要只靠面積面積最大的不一定是目標(biāo)。5.3 坐標(biāo)沒(méi)對(duì)齊導(dǎo)致越追越偏現(xiàn)象是目標(biāo)在畫(huà)面中心云臺(tái)卻往一邊跑現(xiàn)象靜態(tài)看目標(biāo)已經(jīng)在畫(huà)面中心云臺(tái)零位也調(diào)過(guò)但一追動(dòng)態(tài)目標(biāo)就慢慢偏出畫(huà)面。原因圖像中心、光軸中心、云臺(tái)零位沒(méi)有完全對(duì)齊或者像素角度映射只用了中心點(diǎn)比例邊緣誤差被放大。解決先做靜態(tài)對(duì)中記錄offset_x、offset_y再用五點(diǎn)標(biāo)定做分段修正運(yùn)行時(shí)先減偏移再映射。若換了相機(jī)安裝角度標(biāo)定表必須重做。這個(gè)坑很隱蔽因?yàn)殪o態(tài)圖看起來(lái)一切正常。5.4 分辨率太高幀率掉現(xiàn)象是識(shí)別準(zhǔn)但云臺(tái)反應(yīng)慢半拍現(xiàn)象高分辨率下識(shí)別很準(zhǔn)但云臺(tái)總是慢半拍快速移動(dòng)目標(biāo)追不上。原因圖像分辨率和幀率、處理耗時(shí)是矛盾的大圖找輪廓耗時(shí)增加控制環(huán)拿到的是舊幀。解決跟蹤階段降到 640×480 或 320×240先保證 50 到 60 幀識(shí)別只在 ROI 里做ROI 根據(jù)上一幀中心外擴(kuò)關(guān)閉不必要的顯示窗口和調(diào)試輸出。幀率比單幀精度更重要控制環(huán)吃的是延遲。5.5 串口丟包與浮點(diǎn)解析錯(cuò)現(xiàn)象是數(shù)據(jù)偶爾飛點(diǎn)云臺(tái)猛抖一下現(xiàn)象大部分時(shí)間正常偶爾云臺(tái)猛抖日志里出現(xiàn)一個(gè)非常大的偏差值。原因串口幀沒(méi)有幀頭或者校驗(yàn)接收端從中間開(kāi)始讀浮點(diǎn)字節(jié)序不一致發(fā)送頻率超過(guò)主控解析能力。解決固定幀頭、長(zhǎng)度、校驗(yàn)和序號(hào)接收端先找?guī)^再收長(zhǎng)度校驗(yàn)失敗直接丟幀浮點(diǎn)統(tǒng)一小端發(fā)送頻率和主控解析頻率匹配。出現(xiàn)飛點(diǎn)時(shí)先看seq是否連續(xù)再看校驗(yàn)失敗計(jì)數(shù)不要先懷疑算法。6. 驗(yàn)收與答辯用五組可復(fù)現(xiàn)測(cè)試講清視覺(jué)部分的邊界視覺(jué)部分做完驗(yàn)收不要只說(shuō)“能跟蹤”。我一般準(zhǔn)備五組可復(fù)現(xiàn)測(cè)試每組都有操作和通過(guò)標(biāo)準(zhǔn)答辯時(shí)直接拿數(shù)據(jù)說(shuō)話(huà)。靜態(tài)靶看中心誤差和抖動(dòng)階躍看響應(yīng)和過(guò)沖勻速看跟蹤滯后遮擋看丟幀保持和恢復(fù)光照變化看閾值魯棒性。測(cè)試前把相機(jī)曝光鎖定把云臺(tái)零位對(duì)齊把串口日志打開(kāi)記錄幀序號(hào)和偏差序列。沒(méi)有日志的驗(yàn)收評(píng)委一問(wèn)細(xì)節(jié)就只能憑感覺(jué)答。測(cè)試項(xiàng)操作通過(guò)標(biāo)準(zhǔn)靜態(tài)靶目標(biāo)固定在畫(huà)面中心附近 30 秒中心誤差小于 5 像素偏差峰峰值小于 3 像素階躍目標(biāo)從中心快速移到畫(huà)面一側(cè)云臺(tái) 1 秒內(nèi)跟上過(guò)沖小于 10%無(wú)持續(xù)振蕩勻速目標(biāo)沿畫(huà)面橫向勻速移動(dòng)跟蹤滯后穩(wěn)定不出現(xiàn)周期性畫(huà)圈遮擋手動(dòng)遮擋目標(biāo) 0.5 秒保持幀內(nèi)不報(bào)丟失恢復(fù)后 0.5 秒內(nèi)重新鎖定光照改變補(bǔ)光燈角度和亮度保守閾值下不誤框背景丟失后能切寬松閾值找回答辯講法我建議按“約束、鏈路、數(shù)據(jù)、邊界”四段走。先講現(xiàn)場(chǎng)約束光照不可控、云臺(tái)有慣量、串口有延遲再講鏈路采集、識(shí)別、解算、通信各做什么然后拿表格里的數(shù)據(jù)講靜態(tài)誤差、階躍響應(yīng)和丟幀恢復(fù)最后主動(dòng)講失敗邊界比如強(qiáng)反光下會(huì)誤判、快速目標(biāo)在畫(huà)面邊緣會(huì)有滯后。主動(dòng)講邊界比被評(píng)委問(wèn)出來(lái)更加分。代碼不用逐行念挑extract_target的篩選邏輯和pack_frame的協(xié)議設(shè)計(jì)講清楚足夠說(shuō)明你懂工程。從那以后我每次交付視覺(jué)代碼都強(qiáng)制先跑一遍靜態(tài)靶和階躍測(cè)試再談追蹤效果。沒(méi)有這兩組數(shù)據(jù)調(diào)參就是碰運(yùn)氣。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取