:OpenMV+STM32云臺視覺伺服全程實錄)
簡介面向2023年全國大學生電子設(shè)計競賽E題備賽者這份壓縮包圍繞“基于STM32F1的自動追光云臺”提供了完整工程與源碼參考。包內(nèi)包含STM32F10x系列外設(shè)驅(qū)動如ADC、I2C、USART、定時器的C語言源文件及頭文件附帶Keil工程配置、編譯中間文件、鏈接腳本與燒錄文件可直接用于評估或二次開發(fā)。資源共199個文件以.c/.h源碼、編譯生成的.o/.d/.crf文件及Keil工程文件為主整體體積僅6.74MB結(jié)構(gòu)清晰便于檢索。已有7735人學習下載說明內(nèi)容具備較高參考價值。對于正在備賽或希望理解自動追光云臺系統(tǒng)實現(xiàn)的讀者該包能提供從電機控制、傳感器數(shù)據(jù)采集到主控邏輯的工程化范例幫助快速上手實踐。 2023年的全國大學生電子設(shè)計競賽e題“運動目標控制與自動追蹤系統(tǒng)”在當年屬于視覺和運動控制結(jié)合的重頭戲。題目本身不復雜搭建兩套二自由度云臺一套讓紅色激光按指定軌跡移動作為運動目標另一套讓綠色激光自動跟蹤紅點。但實際做下來大家發(fā)現(xiàn)“追得上”和“追得穩(wěn)”完全是兩個世界。很多隊伍基礎(chǔ)題能跑通發(fā)揮部分一加速就露餡綠激光不是原地畫圈就是追到紙外。我陪隊伍從校賽到國賽一路調(diào)下來把視覺識別、坐標標定、PID控制這些核心環(huán)節(jié)都摸了一遍這篇文章就把可復現(xiàn)的方案、關(guān)鍵參數(shù)、現(xiàn)場踩坑記錄全部放出來給后面參賽、做自動追蹤類課設(shè)或者單純想玩云臺視覺的同學當參考底稿。1. 題目解讀與整體方案選擇1.1 基礎(chǔ)題和發(fā)揮題到底考什么先把題目要求拆清楚。2023年電賽e題的基礎(chǔ)部分要求在兩維云臺上架設(shè)紅、綠兩個激光筆紅色激光模擬運動目標能在規(guī)定時間內(nèi)沿指定軌跡比如白紙邊界或給定圖形移動綠色激光作為追蹤器需要在紅點運動過程中保持跟蹤誤差控制在一定范圍內(nèi)。發(fā)揮部分在此基礎(chǔ)上加大難度要么要求目標運動速度更高要么要求軌跡更復雜比如斜線、折線甚至任意手繪軌跡有些賽區(qū)還加了“識別到追蹤點后自動回到畫面中心”的視覺伺服任務(wù)。這個題目表面上是“做兩個云臺”核心考察點其實是三塊第一是視覺識別要穩(wěn)定提取激光點位置這是整個系統(tǒng)的眼睛第二是坐標映射把攝像頭看到的像素坐標換算成云臺的控制角度第三是閉環(huán)控制讓綠色云臺實時跟隨紅點又快又穩(wěn)。機械結(jié)構(gòu)反而是最簡單的一環(huán)但恰恰是很多隊伍在機械上翻車云臺松動、激光不垂直紙面后邊怎么調(diào)算法都白搭。從拿分策略來講基礎(chǔ)題只要保證“能走軌跡、能跟上”就能拿大部分分數(shù)發(fā)揮部分拼的是動態(tài)響應(yīng)和穩(wěn)定性。所以方案設(shè)計時不要在奇怪的地方炫技走成熟路線才是王道。1.2 整體系統(tǒng)怎么搭一個視覺伺服閉環(huán)我最后定下來的整體方案是這樣的兩套獨立的二自由度云臺并排放置在紙面兩側(cè)云臺分別由偏航舵機和俯仰舵機驅(qū)動云臺上固定紅、綠激光筆。紙面上方固定一個攝像頭垂直俯瞰整個A4紙區(qū)域。主控分兩級視覺前端攝像頭加圖像處理負責識別紅色激光點的像素坐標通過串口發(fā)給主控主控根據(jù)預(yù)先標定好的映射關(guān)系把紅點的像素坐標換算成綠色云臺應(yīng)該轉(zhuǎn)到的角度再用PID算法驅(qū)動綠色云臺的舵機讓綠色激光始終壓住紅色激光。這個架構(gòu)本質(zhì)上是一個典型的視覺反饋閉環(huán)攝像頭是傳感器主控是控制器云臺舵機是執(zhí)行器紙面上的光斑位置是被控量。紅色部分是一個開環(huán)軌跡發(fā)生器程序控制紅色云臺按規(guī)劃好的軌跡移動綠色部分是一個視覺閉環(huán)跟蹤器。兩個系統(tǒng)之間唯一的聯(lián)系就是畫面中的紅點位置。這樣設(shè)計有三點好處。一是模塊化視覺、控制、機械分開調(diào)試哪塊出問題就單獨排查二是視覺前端獨立可以實現(xiàn)20ms左右一幀的識別速度不給控制回路拖后腿三是對硬件要求不高不需要高性能嵌入式平臺用常見的STM32加攝像頭模塊就能跑起來。1.3 方案選型為什么是“OpenMV STM32 雙舵機云臺”最常見的三條實現(xiàn)路線分別是用OpenMV這類帶圖像處理的單片機做視覺識別再配STM32做主控用樹莓派這類Linux板卡做完整圖像處理加控制用K210等AI芯片跑目標檢測。我實測下來最適合電賽場景的是第一種。樹莓派方案開發(fā)效率高Python代碼寫起來很順手但現(xiàn)場測試時經(jīng)常會遇到兩個問題一是啟動慢開機電賽測試那幾分鐘尤其煎熬二是供電穩(wěn)定性差配USB攝像頭時畫面會周期性掉幀直接導致追蹤滯后。K210跑目標檢測沒問題但激光點這種小目標用傳統(tǒng)顏色閾值法就夠了殺雞用牛刀而且部署SDK對新手不友好。OpenMV的方案成熟社區(qū)資料多顏色識別、坐標輸出這些都是現(xiàn)成的而且OpenMV帶LED補光和可調(diào)曝光能在現(xiàn)場光線變化時做一定補償。STM32則負責讀坐標、跑PID、輸出PWM驅(qū)動舵機實時性好現(xiàn)場調(diào)參也方便。OpenMV和STM32之間的通信用串口波特率115200一幀發(fā)9個字節(jié)包括幀頭、x坐標、y坐標、校驗位實測下來比較穩(wěn)定沒出現(xiàn)過粘包問題。坐標頻率盡量保持在50Hz以上否則PID響應(yīng)會明顯遲鈍。2. 核心硬件選型與機械結(jié)構(gòu)設(shè)計2.1 主控、攝像頭和舵機怎么選這里直接給一個我實測過、可以照抄的硬件清單表格模塊型號備注視覺模塊OpenMV Cam H7 Plus內(nèi)存大高分辨率不卡頓主控STM32F103C8T6跑PID完全夠用舵機DS3218數(shù)字舵機扭矩大、響應(yīng)快、線性度好攝像頭OpenMV內(nèi)置/外接OV5640外接可調(diào)焦距更靈活供電5V/5A開關(guān)電源舵機單獨一路避免干擾舵機這塊多說兩句。我用了兩套DS3218數(shù)字舵機每個云臺各兩個。DS3218扭矩大、響應(yīng)快關(guān)鍵是價格比較友好。MG996R模擬舵機也可以用但有一個坑模擬舵機的線性度差在中位附近有死區(qū)追蹤時容易在小范圍內(nèi)來回抖動。后文會講怎么通過軟件死區(qū)解決但硬件上還是建議數(shù)字舵機。2.2 云臺結(jié)構(gòu)和激光安裝要點云臺結(jié)構(gòu)可以用成品兩自由度云臺支架或者3D打印一個。需要注意兩點一是云臺重心盡量低舵機臂越短越好否則高速擺動時整個云臺會晃直接影響激光指向二是兩個云臺之間的距離和角度要固定最好做一塊底板把兩個云臺底座鎖死。我用的是亞克力底板加螺絲固定整個測試過程中幾乎沒位移。激光筆的安裝往往被忽略但這里有個特別關(guān)鍵的細節(jié)激光筆必須與云臺轉(zhuǎn)軸保持垂直也就是讓激光出射方向盡量垂直于云臺的俯仰軸。如果激光筆斜著固定云臺轉(zhuǎn)動角度和光斑移動距離之間就不是線性關(guān)系標定會非常痛苦。固定方式我推薦用熱熔膠加一個小的3D打印固定座不要直接用膠帶纏膠帶受熱會松比賽現(xiàn)場最怕激光歪。2.3 供電系統(tǒng)設(shè)計別讓重啟毀掉測試這個板塊不太起眼但出問題特別多。兩個DS3218舵機加一個OpenMV如果全部從一個5V電源取電啟動瞬間電流會飆到2A以上劣質(zhì)穩(wěn)壓芯片會被拉垮OpenMV會頻繁重啟。我最后用了兩路獨立供電OpenMV用一個5V/1A的隔離模塊舵機用一個5V/5A的開關(guān)電源并且舵機電源地線要單獨拉回不要跟信號線共用地線回路。信號線上還有一個容易踩的坑OpenMV的串口TXD、RXD和STM32連接時電平都是3.3V但如果STM32板子帶5V電平的串口芯片比如CH340一定要先確認電平匹配。我曾經(jīng)因為直接連到CH340的5V引腳把OpenMV的串口燒了后來加了一個電平轉(zhuǎn)換模塊才解決。接線時記得所有模塊共地邏輯電平要一致。3. 圖像識別與坐標系標定3.1 激光點識別顏色閾值怎么調(diào)才穩(wěn)定激光點在攝像頭畫面里通常是一個很小的亮點可能有幾像素到幾十像素。識別思路是先用顏色閾值把紅色區(qū)域分離出來再找最大連通域的重心作為激光點坐標。OpenMV里最常用的是LAB顏色空間因為LAB的亮度分量L跟色度分量A、B分開在不同光照下比RGB穩(wěn)定。這里貼一段我在OpenMV上跑的核心識別代碼可以直接作為模板import sensor, image, time from machine import UART sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QQVGA) # 160x120幀率優(yōu)先 sensor.skip_frames(time2000) sensor.set_auto_gain(False) # 關(guān)閉自動增益防止顏色漂移 sensor.set_auto_whitebal(False) # 關(guān)閉自動白平衡 uart UART(3, 115200, timeout_char1000) threshold_red (60, 100, 45, 80, 40, 80) # LAB閾值按現(xiàn)場微調(diào) while True: img sensor.snapshot() blobs img.find_blobs([threshold_red], pixels_threshold5, area_threshold5) if blobs: b max(blobs, keylambda x: x.pixels()) img.draw_cross(b.cx(), b.cy(), color(0, 255, 0)) data bytearray([0xAA, (b.cx() 8) 0xFF, b.cx() 0xFF, (b.cy() 8) 0xFF, b.cy() 0xFF, 0x55]) uart.write(data) else: uart.write(bytearray([0xAA, 0, 0, 0, 0, 0x55]))圖像分辨率我設(shè)置成QQVGA也就是160x120。當初用QVGA320x240試過識別精度更高但幀率會掉到30fps左右對PID控制來說不夠用。QQVGA下幀率能穩(wěn)定在50fps以上識別延遲小很多配合后續(xù)的PID效果反而更好。像素分辨率低的誤差可以通過標定環(huán)節(jié)彌補但幀率低造成的滯后很難通過算法救回來。閾值調(diào)節(jié)這塊我推薦的做法是現(xiàn)場對著目標環(huán)境反復測把激光關(guān)閉和開啟各截一張圖用OpenMV IDE的“閾值編輯器”工具直接看LAB直方圖然后微調(diào)。另外一定要關(guān)掉攝像頭的自動增益和自動白平衡否則測光環(huán)境變化時畫面會突然整體變色顏色閾值直接失效。3.2 像素坐標到云臺角度的映射這是整個e題里最核心也最容易翻車的地方。攝像頭給出的紅點坐標是像素值而綠色云臺需要知道自己應(yīng)該轉(zhuǎn)到什么角度才能讓綠色激光打到紅點位置。直觀思路是線性映射比如圖像寬度是160像素對應(yīng)云臺左右各30度那差一像素就是0.375度。但實際做出來誤差很大因為攝像頭安裝在紙面上方有透視效應(yīng)圖像邊緣一個像素對應(yīng)的實際距離比中心大再加上鏡頭畸變邊界處偏差可能達到一兩厘米。我的解決方法是標定查表加雙線性插值。具體操作分三步第一步固定綠色云臺和攝像頭讓綠色激光正對紙面某個已知位置記錄此時云臺的兩個舵機角度和圖像中綠色光斑的像素坐標。第二步在紙面上用網(wǎng)格線劃分比如每隔2cm打一個點描出一張5x8的點陣。對每個點手動控制綠色云臺讓綠光打到該點記錄對應(yīng)的兩個舵機角度和該點在圖像中的像素坐標。這樣得到一組標定數(shù)據(jù)像素坐標和舵機角度的一一對應(yīng)。第三步把這些數(shù)據(jù)存入STM32的查找表。運行時紅點像素坐標進入系統(tǒng)先在查找表里找到最近的幾個標定點做雙線性插值算出綠色云臺的目標角度。用公式表示就是假設(shè)標定數(shù)據(jù)是(u_i, v_i)對應(yīng)角度(pan_i, tilt_i)對任意紅點像素(up, vp)找到它周圍的四個標定點分別在u方向和v方向做線性插值。實際效果很好整個A4紙范圍內(nèi)追蹤誤差能控制在1cm以內(nèi)。標定數(shù)據(jù)一定要覆蓋紙面的四個角和中間區(qū)域邊緣采樣點不夠會導致追蹤到邊界時誤差明顯變大。我也試過用多項式擬合做映射效果也不錯但有一個前提攝像頭的安裝角度不能變。一旦測試時撞了一下攝像頭整個擬合函數(shù)就廢了必須重新標定。查表法雖然標定量大一點但抗干擾性好攝像頭位置略有偏移時只要重新采幾個點就能修正。3.3 攝像頭安裝高度和視角怎么選攝像頭的安裝對后續(xù)標定影響很大。我建議攝像頭垂直正對紙面中心高度根據(jù)紙面大小調(diào)整讓紙面四角都在畫面內(nèi)最好留出一圈10%的余量。如果攝像頭裝歪了有比較大的透視角度雖然標定仍然能補償一部分但激光點識別時紙面邊緣的激光很容易被紙張厚度或者陰影遮擋。一個小技巧在紙面四角貼上黑色標記程序識別時先檢測這四個角用來做透視矯正和動態(tài)補償。如果測試過程中攝像頭輕微移動通過四個角的位置變化就能估計出變換矩陣并修正坐標。這個功能在正式比賽那種高壓環(huán)境下很有用因為現(xiàn)場難免會碰到攝像頭。4. PID控制與調(diào)參實戰(zhàn)4.1 控制回路怎么設(shè)計綠色云臺的追蹤本質(zhì)是一個位置伺服系統(tǒng)。給定目標角度由紅點像素坐標映射得到需要讓云臺舵機快速到達并穩(wěn)定在那個角度。由于舵機內(nèi)部本身有位置閉環(huán)給舵機的PWM信號其實就對應(yīng)目標角度整個外環(huán)就簡化為視覺檢測紅點像素坐標查表得到目標角度PID計算輸出PWM增量驅(qū)動云臺。我使用位置式PID控制周期設(shè)在20ms左右。這里貼一段STM32上的核心實現(xiàn)typedef struct { float target_angle; float current_angle; float error; float integral; float prev_error; float kp, ki, kd; float out; } PID_t; void PID_Calc(PID_t *pid, float target, float current) { pid-target_angle target; pid-current_angle current; pid-error target - current; pid-integral pid-error; // 積分限幅防止長時間跑偏產(chǎn)生大積分 if (pid-integral 100) pid-integral 100; if (pid-integral -100) pid-integral -100; float derivative pid-error - pid-prev_error; pid-out pid-kp * pid-error pid-ki * pid-integral pid-kd * derivative; pid-prev_error pid-error; }輸出最終被轉(zhuǎn)換成PWM脈寬發(fā)給舵機。注意位置式PID的輸出如果直接作為絕對值給舵機積分項可能會導致上電瞬間舵機猛轉(zhuǎn)實際應(yīng)用時建議對輸出做限幅轉(zhuǎn)換到PWM時再做平滑處理。4.2 PID參數(shù)整定流程整定PID參數(shù)是有套路的。先只用P從小到大慢慢加觀察綠激光對紅點的跟隨情況。如果紅點在某一位置輕微移動綠點能跟上但不穩(wěn)定地振蕩說明P偏高如果反應(yīng)遲鈍、追不上說明P偏低。P調(diào)好后加少量I來消除靜態(tài)誤差最后加一點D來抑制超調(diào)。我實測這組參數(shù)可以作為起點kp2.0ki0.02kd0.5。每個隊伍機械結(jié)構(gòu)不同參數(shù)差別會很大千萬別照抄一定要在自己的裝置上重新整定。整定過程中可以讓紅點以固定速度走直線或者畫圓觀察綠點的最大偏差和相位滯后這是最直觀的評價方式。有個細節(jié)很重要D項在系統(tǒng)有噪聲時容易放大抖動。激光點識別本身會有1-2像素的噪聲表現(xiàn)在角度上就是一個小量D項對這個噪聲特別敏感。我的做法是把誤差做一階低通濾波之后再算微分能顯著減小抖動而且不影響對真實運動的響應(yīng)。4.3 追蹤速度和精度怎么平衡發(fā)揮部分要求目標運動更快這時候單純加大P值會導致系統(tǒng)振蕩必須在控制策略上想辦法。我有兩個經(jīng)驗。第一對視覺延遲做簡單補償。攝像頭識別和串口通信會產(chǎn)生約20ms到40ms的延遲這個延遲在高速運動下會引起明顯的相位滯后??梢栽赑ID之前加一個預(yù)測器把最近幾幀紅點目標角度存下來做線性預(yù)測估算出當前時刻紅點大概的角度再把預(yù)測角度作為PID目標。公式大概是預(yù)測角度 當前角度 (當前角度 - 上一幀角度) / 幀間隔 * 延遲時間。實測對追蹤效果提升明顯代碼量也很小。第二對舵機輸出做速度限制。舵機雖然允許大幅度PWM跳變但機械機構(gòu)不可能瞬間到達目標如果輸出跳變太猛反而會造成“過沖-回擺-再過沖”的死循環(huán)。我在PID輸出后加了一級限速讓目標角度每秒變化不超過某個上限。這個上限要根據(jù)云臺實際響應(yīng)能力來定太高了沒用太低了跟不上我最后設(shè)在每秒60度左右。4.4 紅色云臺的軌跡控制紅色云臺這邊的控制相對簡單不需要視覺反饋只要按規(guī)劃好的軌跡給出目標角度再用同樣的PID讓它平滑運動就行。軌跡可以用參數(shù)方程生成比如畫矩形時分成四段直線每段按時間均勻插入角度點畫圓時按角度步進生成一系列點。這里同樣建議做插值不要讓兩個目標角度直接跳變否則紅激光會飛出紙面。這部分的調(diào)參經(jīng)驗和前面類似唯一要注意的是紅色云臺的運動速度要保持均勻不要忽快忽慢。因為綠色云臺是在跟蹤紅點的如果紅點突然加速綠點很容易產(chǎn)生較大超調(diào)。5. 常見問題與排查實錄5.1 綠激光原地抖個不停這是現(xiàn)場出現(xiàn)最多的故障。檢查順序是先看顏色識別是否穩(wěn)定用OpenMV IDE觀察紅點坐標如果坐標在固定位置來回跳說明是識別噪聲問題可以通過增大pixels_threshold、對坐標做滑動平均濾波解決。然后再看PID把D項暫時調(diào)成0如果抖動消失說明D項放大了噪聲需要加低通濾波或者減小D。還有一個容易被忽略的點舵機自身的死區(qū)。模擬舵機在目標角度附近存在一個不響應(yīng)區(qū)域PID輸出在一個小范圍內(nèi)來回變化時舵機就會在死區(qū)邊界反復顫動發(fā)出“嗞嗞”聲。解決辦法是設(shè)置軟件死區(qū)當誤差絕對值小于某個閾值比如0.5度時PID輸出保持上一拍不變。5.2 紅點不動時綠點緩慢漂移出現(xiàn)緩慢漂移大概率是積分項在作怪。舵機本身沒有外部持續(xù)干擾位置式PID的積分項主要用來消除靜差但如果不做限幅長時間運行后積分會積累到很大導致輸出慢慢偏移。我處理的方法是給積分加限幅并且當誤差小于一定范圍時停止積分累加。這個小改動之后靜態(tài)穩(wěn)定性立即提升。5.3 追蹤到紙面邊緣時誤差突然變大這個問題幾乎每位同學都碰到過。原因有三層鏡頭畸變、透視效應(yīng)、激光傾斜。鏡頭畸變會讓圖像邊緣的像素誤差變大這可以通過標定數(shù)據(jù)覆蓋邊緣來補償透視效應(yīng)上文已經(jīng)說過通過雙線性插值能兜住大部分激光傾斜是指云臺轉(zhuǎn)到極限角度時激光不是垂直入射紙面光斑被拉長變形顏色識別找到的重心會偏離真實落點。第三點沒法完全消除只能在機械上盡量讓云臺靠近紙面中心減小極端角度。5.4 現(xiàn)場燈光一暗紅點就找不到了電賽測試現(xiàn)場燈光情況很復雜有時候評委把環(huán)境光調(diào)暗激光點反而更清楚但有時候紅色激光被環(huán)境光淹沒OpenMV識別失敗。我的經(jīng)驗是激光器選650nm波長、功率稍微大一點的5mW的激光筆在現(xiàn)場環(huán)境下表現(xiàn)一般。同時OpenMV的曝光時間要手動固定不要用自動曝光。如果還是不行可以在攝像頭前面加一個紅色濾光片能極大提高信噪比。濾光片用那種幾十塊錢的紅色帶通片就行實測效果非常明顯。5.5 賽前檢查清單整理一份每次測試前必過的清單螺絲所有云臺和激光筆固定座螺絲是否松動。通信串口是否正常開機后OpenMV是否持續(xù)發(fā)包。閾值紅色閾值在比賽房間的燈光下是否依然穩(wěn)定。標定攝像頭位置是否有變化如果動過必須重新標定。電源電池或者電源適配器是否充滿、供電是否穩(wěn)定。備份備用舵機、備用激光筆、備用編程器是否帶齊。最后一次測試時我們隊伍在競賽倒數(shù)第二天發(fā)現(xiàn)綠色云臺的底座螺絲松了快一圈這就是抖動很難消除的根源。所以我現(xiàn)在對機械的重視程度遠高于算法再好的控制遇到手能晃動的云臺都是白搭。如果你準備做類似的自動追蹤項目建議從始至終把“穩(wěn)定”兩個字放在第一位軟件上多做限幅和保護硬件上把所有活動部件擰死。賽前別光顧著調(diào)程序把一個螺絲一個螺絲都過一遍比多跑十次測試都管用。本文還有配套的精品資源點擊獲取