指南)
1. 項目背景與核心價值最近在整理過去的項目資料翻到了2021年參加全國大學(xué)生智能汽車競賽我們通常簡稱“國賽”時為F題“送藥小車”編寫的OpenMV巡線代碼。當(dāng)時為了趕進(jìn)度代碼寫得比較“飛線”注釋也是寥寥無幾。現(xiàn)在回過頭看雖然比賽已經(jīng)過去幾年但這段代碼里涉及的圖像處理思路、PID控制邏輯以及嵌入式編程的坑對于剛接觸OpenMV做視覺巡線的新手來說依然有很強(qiáng)的參考價值。特別是看到網(wǎng)上很多同學(xué)在搜索“OpenMV巡線代碼注釋”、“國賽送藥小車”這類關(guān)鍵詞說明這個需求一直很旺盛但能找到的、帶詳細(xì)解說的完整代碼并不多。所以我決定把這段代碼重新拿出來逐行加上詳細(xì)的注釋并結(jié)合比賽中的實際調(diào)試經(jīng)驗把背后的原理、為什么這么寫、以及調(diào)試時踩過的坑都講清楚。這不僅僅是一份代碼注釋更像是一份濃縮了實戰(zhàn)經(jīng)驗的視覺巡線開發(fā)筆記。無論你是正在備戰(zhàn)智能車競賽、電賽還是單純想用OpenMV做個巡線小車相信都能從中獲得直接的幫助。代碼本身是基于OpenMV IDE使用MicroPython開發(fā)的核心任務(wù)是通過攝像頭識別賽道上的黑色引導(dǎo)線計算出小車偏離中心的誤差然后輸出控制量給底層電機(jī)實現(xiàn)自動循跡。2. 代碼整體結(jié)構(gòu)與設(shè)計思路拆解2.1 核心需求與方案選型2021年國賽F題“送藥小車”是一個典型的室內(nèi)場景任務(wù)小車需要在模擬醫(yī)院走廊的賽道上行駛完成送藥任務(wù)。賽道通常由白色底板和黑色引導(dǎo)線組成有時會有十字路口、環(huán)島等元素。我們的核心需求是穩(wěn)定、快速地識別并跟蹤這條黑線。為什么選擇OpenMV而不是其他傳感器當(dāng)時主要基于幾點考量信息豐富度相比單個紅外對管或灰度傳感器陣列攝像頭能獲取一行甚至一個區(qū)域的完整圖像信息不僅能判斷是否壓線還能提前預(yù)知彎道曲率實現(xiàn)更前瞻、更平滑的控制。靈活性通過修改圖像處理算法可以相對容易地適應(yīng)不同光照、賽道材質(zhì)的變化甚至后期擴(kuò)展識別路口、數(shù)字等任務(wù)。開發(fā)效率OpenMV集成了MicroPython和豐富的機(jī)器視覺庫對于學(xué)生團(tuán)隊來說從獲取圖像到輸出控制量的開發(fā)周期比從零搭建一個基于單片機(jī)的圖像處理系統(tǒng)要短得多。方案上我們采用了最經(jīng)典也最可靠的“單行掃描重心法求偏差”策略。即不對整幅圖像進(jìn)行處理而是只提取圖像中某一行通常是中間偏下的行的像素數(shù)據(jù)在這一行里尋找黑線所在的區(qū)域并計算該區(qū)域的中心位置。將這個中心位置與圖像的中心線位置進(jìn)行比較其差值就是橫向偏差error。這個偏差值隨后被送入PID控制器計算出電機(jī)調(diào)速的PWM占空比。2.2 代碼文件與模塊規(guī)劃我們的主要代碼通常寫在一個main.py文件中這是OpenMV上電后自動執(zhí)行的文件。代碼結(jié)構(gòu)大致分為以下幾個部分初始化模塊導(dǎo)入必要的庫sensor,image,time,pyb等初始化攝像頭傳感器參數(shù)分辨率、像素格式、白平衡等。圖像處理函數(shù)核心函數(shù)負(fù)責(zé)獲取一幀圖像提取特定行進(jìn)行二值化尋找色塊黑線并計算偏差。PID控制模塊包含PID參數(shù)和計算函數(shù)將圖像處理得到的偏差轉(zhuǎn)化為控制輸出。電機(jī)控制模塊根據(jù)PID輸出設(shè)置左右電機(jī)的PWM信號實現(xiàn)差速轉(zhuǎn)向。主循環(huán)不斷循環(huán)執(zhí)行“圖像采集-處理-計算偏差-PID運算-電機(jī)控制”這個流程并可以加入一些調(diào)試信息輸出如通過串口發(fā)送偏差值、圖像等方便上位機(jī)調(diào)參。這種模塊化的思想雖然簡單但在緊張的比賽調(diào)試中非常有效可以快速定位問題是出在圖像識別不準(zhǔn)還是PID參數(shù)不對或是電機(jī)驅(qū)動有問題。3. 核心代碼逐行注釋與原理詳解下面我將以代碼塊的形式展示關(guān)鍵代碼并附上詳細(xì)的注釋。請注意為了適應(yīng)文章格式代碼可能不是百分百原樣但核心邏輯和注釋完全一致。3.1 傳感器與攝像頭初始化import sensor, image, time, pyb # 初始化攝像頭傳感器 sensor.reset() # 重置并初始化攝像頭傳感器硬件 sensor.set_pixformat(sensor.GRAYSCALE) # 設(shè)置像素格式為灰度圖。巡線通常不需要彩色信息灰度圖處理速度更快內(nèi)存占用更小。 sensor.set_framesize(sensor.QQVGA) # 設(shè)置圖像分辨率為QQVGA (160x120)。分辨率越低處理速度越快但細(xì)節(jié)越少。QQVGA是巡線常用的平衡點。 sensor.skip_frames(time 2000) # 等待攝像頭設(shè)置生效。剛啟動時自動增益、白平衡等需要時間穩(wěn)定跳過開始的2000幀讓畫面穩(wěn)定。 sensor.set_auto_gain(False) # 關(guān)閉自動增益。自動增益會隨環(huán)境光變化調(diào)整整體亮度可能導(dǎo)致二值化閾值失效必須關(guān)閉 sensor.set_auto_whitebal(False) # 關(guān)閉自動白平衡。理由同上保持圖像處理條件穩(wěn)定。 # 注意有時也需要關(guān)閉自動曝光sensor.set_auto_exposure但具體取決于環(huán)境光變化是否劇烈。 clock time.clock() # 創(chuàng)建一個時鐘對象用于計算幀率FPS評估算法效率。關(guān)鍵點解析與避坑sensor.GRAYSCALE巡線任務(wù)中顏色信息往往是干擾。黑色引導(dǎo)線在灰度圖下就是深色區(qū)域直接用灰度值閾值分割最簡單可靠。除非賽道是彩色線否則不要用RGB。sensor.QQVGA (160x120)這是一個經(jīng)驗值。更高的分辨率如QVGA(320x240)會顯著增加處理時間可能無法達(dá)到高幀率如30fps以上的要求導(dǎo)致控制延遲大。160x120足夠在大多數(shù)場景下清晰分辨黑線。set_auto_gain(False)和set_auto_whitebal(False)這是巡線穩(wěn)定的生命線如果開啟攝像頭會根據(jù)環(huán)境自動調(diào)整畫面亮度/色溫。早上和下午的光照不同可能導(dǎo)致同一閾值上午能識別黑線下午就識別不了了。固定這些參數(shù)意味著我們假設(shè)環(huán)境光相對穩(wěn)定或者通過補(bǔ)光如在小車上加LED燈條來創(chuàng)造穩(wěn)定光照條件。3.2 圖像處理與偏差計算函數(shù)這是整個代碼的核心我們將其封裝為一個函數(shù)方便調(diào)用和調(diào)試。def find_line_error(img): 在圖像中尋找黑線并計算橫向偏差。 參數(shù): img: 由 sensor.snapshot() 獲取的圖像對象。 返回: error: 計算出的橫向偏差值單位像素。正數(shù)通常表示線在中心右側(cè)負(fù)數(shù)表示在左側(cè)。 found: 布爾值是否成功找到黑線。 # 1. 定義感興趣區(qū)域(ROI)和掃描行 # 我們不需要處理整個圖像只關(guān)心地面附近的區(qū)域。ROI可以排除天花板等干擾。 # roi格式: (x, y, width, height) roi (0, 80, 160, 40) # 從y80像素開始高度40像素的區(qū)域。這個值需要根據(jù)攝像頭安裝高度和角度調(diào)整。 scan_line_y roi[1] roi[3] // 2 # 取ROI中間的那一行進(jìn)行掃描。這里是 y 80 20 100。 # 2. 提取掃描行的像素數(shù)據(jù) # 獲取圖像第scan_line_y行的所有像素值灰度值0-255。 line img.get_pixel(scan_line_y, roi[0], roi[2]) # line現(xiàn)在是一個包含160個整數(shù)的列表因為寬度是160每個整數(shù)代表一個像素的灰度值。 # 3. 二值化與尋找黑線區(qū)域 threshold 50 # 二值化閾值。灰度值低于此值的被認(rèn)為是黑線。需要根據(jù)實際賽道顏色和光照手動調(diào)整。 black_start None black_end None black_pixels [] # 記錄被認(rèn)為是黑線的像素的x坐標(biāo) for x in range(roi[0], roi[0] roi[2]): # 遍歷ROI寬度內(nèi)的每一個x坐標(biāo) # 注意img.get_pixel(y, x) 返回的是(x,y)坐標(biāo)的灰度值。我們之前獲取的line是按x順序的。 # 這里我們直接使用line列表索引是 x - roi[0] index x - roi[0] pixel_value line[index] if index len(line) else 255 # 安全訪問 if pixel_value threshold: black_pixels.append(x) # 記錄黑點位置 if black_start is None: black_start x # 記錄黑線區(qū)域的起始x坐標(biāo) black_end x # 不斷更新最終記錄黑線區(qū)域的結(jié)束x坐標(biāo) # 注意這里沒有做區(qū)域合并等復(fù)雜處理假設(shè)黑線是連續(xù)的。 # 4. 計算偏差 error 0 found False img_center_x roi[0] roi[2] // 2 # ROI區(qū)域的水平中心x坐標(biāo)本例中為80。 if black_pixels: # 如果找到了至少一個黑點 found True # 方法1使用黑線區(qū)域中心 if black_start is not None and black_end is not None: line_center_x (black_start black_end) // 2 error line_center_x - img_center_x # 偏差 線中心 - 圖像中心 # 方法2使用所有黑像素的平均位置重心法抗干擾更好 # line_center_x sum(black_pixels) // len(black_pixels) # error line_center_x - img_center_x # 在圖像上畫出掃描線和識別到的黑線區(qū)域用于調(diào)試會消耗時間正式比賽可注釋掉 img.draw_line(roi[0], scan_line_y, roi[0]roi[2], scan_line_y, color127) # 畫掃描線 if black_start and black_end: img.draw_line(black_start, scan_line_y-2, black_end, scan_line_y-2, color0) # 畫識別到的線段 # 如果沒找到黑線可以根據(jù)上一幀的誤差或設(shè)定一個默認(rèn)值這里簡單返回0并標(biāo)記未找到。 # 實際應(yīng)用中可能需要加入“丟線處理”邏輯比如保持上次轉(zhuǎn)角或原地旋轉(zhuǎn)尋線。 return error, found關(guān)鍵點解析與避坑ROI感興趣區(qū)域這是提升處理速度和抗干擾的關(guān)鍵。攝像頭畫面可能包含遠(yuǎn)處的墻壁、天花板燈等。我們只關(guān)心地面附近的一條帶狀區(qū)域。roi的y值和height需要根據(jù)攝像頭俯角物理調(diào)整。一個技巧是將小車放在賽道上在OpenMV IDE中查看實時畫面確定黑線穩(wěn)定出現(xiàn)的y軸范圍。二值化閾值threshold這個值本例中為50不是固定的。它取決于賽道底色白和引導(dǎo)線黑的灰度對比度。需要在比賽現(xiàn)場的光照條件下進(jìn)行校準(zhǔn)??梢栽诖a中加入按鍵調(diào)整閾值的功能或者通過串口發(fā)送指令來微調(diào)。重心法 vs 起止點中心法注釋中提到了兩種計算線中心的方法。起止點中心法計算簡單但如果黑線邊緣有毛刺或噪聲中心點容易跳動。重心法求所有黑點坐標(biāo)的平均值對噪聲的魯棒性更好是更推薦的做法但計算量稍大。丟線處理if black_pixels:這個判斷至關(guān)重要。當(dāng)小車沖出賽道、遇到強(qiáng)光干擾導(dǎo)致全白或者遇到十字路口橫向黑線時black_pixels可能為空。代碼必須妥善處理這種情況否則error會保持為0小車可能直沖出去。高級策略包括使用上一次有效的誤差、讓小車減速并原地旋轉(zhuǎn)直到重新找線、或者進(jìn)入路口處理狀態(tài)機(jī)。3.3 PID控制器實現(xiàn)得到偏差error后我們需要一個控制器來將其轉(zhuǎn)化為平滑的控制指令。PID比例-積分-微分是首選。# PID 參數(shù)定義 Kp 0.5 # 比例系數(shù)。決定了對當(dāng)前誤差的反應(yīng)強(qiáng)度。太大易震蕩太小響應(yīng)慢。 Ki 0.01 # 積分系數(shù)。用于消除靜態(tài)誤差例如小車長期偏向一側(cè)。但容易積分飽和引起超調(diào)。 Kd 0.2 # 微分系數(shù)。預(yù)測誤差變化趨勢抑制振蕩。對噪聲敏感需要濾波。 # PID 變量初始化 error_last 0 # 上一次的誤差 error_integral 0 # 誤差積分項 output 0 # PID輸出值 def pid_compute(error, dt): PID控制計算函數(shù)位置式PID。 參數(shù): error: 當(dāng)前誤差值。 dt: 距離上次計算的時間間隔秒用于積分和微分。 返回: output: 計算出的控制量。 global error_last, error_integral # 比例項 P p_term Kp * error # 積分項 I (加入抗積分飽和) error_integral error * dt # 限制積分項防止積分飽和導(dǎo)致系統(tǒng)失控 integral_limit 100 # 積分限幅根據(jù)實際系統(tǒng)調(diào)整 if error_integral integral_limit: error_integral integral_limit elif error_integral -integral_limit: error_integral -integral_limit i_term Ki * error_integral # 微分項 D (使用不完全微分或?qū)φ`差進(jìn)行濾波會更好) d_term Kd * (error - error_last) / dt if dt 0 else 0 # 注意對誤差直接微分會放大噪聲。實踐中常對誤差進(jìn)行低通濾波或使用“微分先行”等變種。 error_last error # 更新上次誤差 # 總和輸出 output p_term i_term d_term return output關(guān)鍵點解析與避坑參數(shù)整定Kp, Ki, Kd這三個參數(shù)需要現(xiàn)場調(diào)試。經(jīng)典口訣是“先比例后積分再微分”。先將Ki和Kd設(shè)為0調(diào)Kp讓小車能基本循跡但略有振蕩然后加入較小的Ki來消除長期偏向一側(cè)的誤差最后加入Kd來抑制振蕩使運行更平穩(wěn)。積分飽和這是PID實踐中的一個經(jīng)典問題。當(dāng)誤差長時間存在比如卡在角落積分項會不斷累積變得非常大一旦誤差反向需要很長時間才能“消化”掉這個巨大的積分項導(dǎo)致控制反應(yīng)遲鈍甚至反向失控。代碼中加入的integral_limit積分限幅是必須的防護(hù)措施。微分噪聲微分項對誤差的變化率敏感而攝像頭識別帶來的誤差值本身可能有幾個像素的跳動噪聲這會被微分放大導(dǎo)致輸出抖動。解決方案包括對誤差error進(jìn)行一階低通濾波后再微分或者使用“微分先行”PID只對測量值微分而不對設(shè)定值微分。時間間隔dtPID計算需要知道兩次計算的時間差??梢杂胻ime.ticks_ms()獲取毫秒時間戳來計算dt。dt不準(zhǔn)確會影響積分和微分的效果。3.4 電機(jī)控制與主循環(huán)整合最后我們將PID輸出映射到左右電機(jī)的速度差實現(xiàn)轉(zhuǎn)向。# 假設(shè)電機(jī)控制接口函數(shù)具體取決于你的電機(jī)驅(qū)動板可能是PWM、DIR引腳控制 def set_motor_speed(left_speed, right_speed): 設(shè)置左右電機(jī)速度。 參數(shù): left_speed: 左電機(jī)速度范圍例如 -100 到 100正數(shù)前進(jìn)負(fù)數(shù)后退。 right_speed: 右電機(jī)速度。 # 這里需要根據(jù)你的硬件來寫例如使用 pyb.PWM 對象 # left_pwm.duty(left_speed) # 偽代碼 # right_pwm.duty(right_speed) # 偽代碼 pass # 主循環(huán) while(True): clock.tick() # 開始追蹤這一幀的時間 img sensor.snapshot() # 拍攝一張圖片 # 1. 找線計算誤差 error, line_found find_line_error(img) # 2. 如果丟線執(zhí)行丟線處理策略 if not line_found: # 策略示例減速并施加一個固定的旋轉(zhuǎn)速度尋線 set_motor_speed(20, -20) # 原地左轉(zhuǎn)尋線 # 或者保持上一次的output值 # 更復(fù)雜的策略可以記錄丟線前的誤差方向向那個方向旋轉(zhuǎn) continue # 跳過本次PID計算 # 3. 計算時間間隔 dt (秒) dt clock.avg() # clock.avg() 返回平均幀間時間秒。也可以使用 clock.diff() # 4. PID計算 turn_output pid_compute(error, dt) # 5. 將轉(zhuǎn)向輸出轉(zhuǎn)換為電機(jī)速度 base_speed 50 # 基礎(chǔ)前進(jìn)速度 # 差速轉(zhuǎn)向一個輪子加速一個輪子減速 left_motor_speed base_speed - turn_output right_motor_speed base_speed turn_output # 6. 限制電機(jī)速度在合理范圍內(nèi)例如0-100 left_motor_speed max(0, min(100, left_motor_speed)) right_motor_speed max(0, min(100, right_motor_speed)) # 7. 執(zhí)行電機(jī)控制 set_motor_speed(int(left_motor_speed), int(right_motor_speed)) # 8. 調(diào)試用打印幀率和誤差到串口 # print(clock.fps(), error)關(guān)鍵點解析與避坑差速轉(zhuǎn)向模型左速 基礎(chǔ)速度 - 轉(zhuǎn)向輸出右速 基礎(chǔ)速度 轉(zhuǎn)向輸出。這是最常用的模型。當(dāng)error為正線在右邊turn_output為正則左輪減速右輪加速小車向左轉(zhuǎn)以追線。速度限幅必須對計算出的電機(jī)速度進(jìn)行限幅防止超過電機(jī)或驅(qū)動板能承受的PWM范圍否則可能導(dǎo)致硬件損壞或控制異常。dt的獲取clock.avg()返回的是平均幀時間用于PID計算是可行的。更精確的做法是使用time.ticks_diff()計算本次循環(huán)與上次循環(huán)的實際時間差。丟線處理主循環(huán)中加入了簡單的丟線處理if not line_found。這里的示例是讓小車原地左轉(zhuǎn)。在實際比賽中你需要設(shè)計更魯棒的策略比如判斷丟線前最后幾幀誤差的趨勢朝誤差減小的方向旋轉(zhuǎn)或者結(jié)合陀螺儀如果小車有進(jìn)行角度控制尋線。4. 調(diào)試技巧與常見問題排查代碼寫完了調(diào)參和調(diào)試才是真正的戰(zhàn)場。以下是一些血淚換來的經(jīng)驗。4.1 圖像識別調(diào)試使用OpenMV IDE的“幀緩沖區(qū)”和“串行終端”把img.draw_line等繪圖函數(shù)打開在IDE里實時查看攝像頭看到了什么掃描線畫在哪識別出的黑線區(qū)域是否準(zhǔn)確。通過print()語句將threshold、error、black_pixels的長度等關(guān)鍵變量輸出到串行終端??梢詫懸粋€簡單的協(xié)議用鍵盤輸入數(shù)字來動態(tài)調(diào)整閾值threshold這是現(xiàn)場調(diào)參的神器。光照是最大的敵人補(bǔ)光補(bǔ)光補(bǔ)光在小車攝像頭周圍加裝LED燈條自己創(chuàng)造穩(wěn)定的光照環(huán)境。這是提升巡線穩(wěn)定性的最有效手段沒有之一。注意燈光角度避免在賽道上形成反光點??梢試L試用磨砂紙柔化光線。如果賽場光線不可控可以考慮動態(tài)閾值算法比如大津法OTSU但計算量會增大。二值化閾值怎么定把小車放在賽道典型位置直道、彎道、明處、暗處在IDE中查看黑線和白底的灰度值。閾值取一個中間值例如(黑線最大灰度值 白底最小灰度值) / 2并留出足夠的余量。4.2 PID參數(shù)調(diào)試“看波形”調(diào)試法將error偏差和outputPID輸出通過串口發(fā)送到電腦用SerialPlot、CoolTerm等軟件繪制實時曲線。理想狀態(tài)小車在直道上error在0附近小幅波動進(jìn)入彎道error平穩(wěn)地偏向一側(cè)output也隨之平穩(wěn)變化。問題診斷振蕩嚴(yán)重Kp太大或Kd太小。響應(yīng)遲鈍過彎時壓線甚至沖出Kp太小。長期偏向彎道內(nèi)側(cè)有靜態(tài)誤差需要增加Ki但一定要配合積分限幅。電機(jī)“抽搐”可能是Kd太大放大了誤差噪聲。嘗試減小Kd或?qū)φ`差進(jìn)行濾波。“聽聲音”調(diào)試法在直道上電機(jī)應(yīng)該發(fā)出平穩(wěn)的“嗡——”聲。如果發(fā)出“嗡-嗡-嗡-”有節(jié)奏的頓挫聲說明Kp太大導(dǎo)致小車在直道上不斷左右微調(diào)此時應(yīng)減小Kp。4.3 常見問題速查表問題現(xiàn)象可能原因排查與解決思路完全找不到線1. 閾值threshold設(shè)置不當(dāng)。2. 攝像頭焦距不對畫面模糊。3. ROI區(qū)域設(shè)置錯誤黑線不在掃描范圍內(nèi)。4. 環(huán)境光太強(qiáng)/太弱導(dǎo)致黑白對比度低。1. 用串口打印實時灰度值調(diào)整閾值。2. 手動調(diào)整攝像頭焦距確保圖像清晰。3. 在IDE中查看畫面調(diào)整ROI的y坐標(biāo)和高度。4. 加強(qiáng)補(bǔ)光或調(diào)整攝像頭曝光參數(shù)。尋線抖動小車“畫龍”1. 比例系數(shù)Kp過大。2. 微分系數(shù)Kd太小或為0。3. 圖像識別出的error本身就在跳動噪聲大。1. 逐步減小Kp。2. 適當(dāng)增加Kd。3. 檢查圖像處理嘗試對掃描行的灰度值進(jìn)行中值濾波或采用多行掃描取平均誤差。過彎時反應(yīng)慢沖出賽道1. 比例系數(shù)Kp過小。2. 前瞻距離太短掃描行太靠下。3. 基礎(chǔ)速度base_speed過高。1. 逐步增大Kp。2. 將掃描行scan_line_y上移減小y值讓小車“看得更遠(yuǎn)”。3. 彎道前適當(dāng)降低基礎(chǔ)速度。直道上跑偏不居中存在靜態(tài)誤差需要積分項。1. 引入較小的Ki。2.務(wù)必同時設(shè)置integral_limit積分限幅。3. 檢查小車機(jī)械結(jié)構(gòu)是否對稱輪胎摩擦力是否一致。遇到十字路口誤判十字路口會出現(xiàn)橫向黑線單行掃描可能識別為一個很寬的黑塊中心計算錯誤。1. 加入路口識別邏輯當(dāng)識別到的黑線寬度(black_end - black_start)超過某個閾值時判定為十字路口進(jìn)入預(yù)設(shè)的路口處理程序如直行一段或根據(jù)標(biāo)記轉(zhuǎn)彎。2. 使用多行掃描判斷黑線的連續(xù)性。幀率太低控制延遲大1. 圖像分辨率設(shè)置過高。2. 圖像處理算法太復(fù)雜如使用了彩色識別、復(fù)雜的濾波。3. 串口打印了太多調(diào)試信息。1. 使用QQVGA或更低分辨率。2. 優(yōu)化代碼只做必要的計算。關(guān)閉IDE中的圖像傳輸功能以釋放帶寬。3. 正式運行時注釋掉或減少print語句。5. 性能優(yōu)化與進(jìn)階思路當(dāng)基礎(chǔ)巡線穩(wěn)定后可以考慮以下優(yōu)化來提升速度和魯棒性以應(yīng)對更復(fù)雜的賽道和更高的競賽要求。5.1 圖像處理優(yōu)化多行掃描與前瞻控制只掃描一行雖然快但信息有限??梢話呙瓒嘈欣缃幰恍羞h(yuǎn)處一行近處的誤差用于快速反應(yīng)保持穩(wěn)定遠(yuǎn)處的誤差用于提前預(yù)判彎道實現(xiàn)更平滑的轉(zhuǎn)向。兩者的誤差可以加權(quán)融合。error_near, found_near find_line_error(img, scan_line_y100) # 近處 error_far, found_far find_line_error(img, scan_line_y60) # 遠(yuǎn)處 if found_near and found_far: error 0.7 * error_near 0.3 * error_far # 加權(quán)融合側(cè)重近處動態(tài)ROI讓ROI隨著識別到的黑線位置動態(tài)移動。例如上一幀黑線在x50那么下一幀的ROI可以設(shè)置在x30到x70的范圍內(nèi)。這樣可以進(jìn)一步排除畫面邊緣的干擾并減少需要處理的像素數(shù)量提高速度。圖像濾波如果賽道有污漬或光照不均原始灰度圖噪聲會很大??梢栽诙祷皩ine數(shù)組進(jìn)行簡單的軟件濾波比如一個窗口大小為3的中值濾波能有效去除孤立的噪聲點。5.2 控制算法優(yōu)化變參數(shù)PID讓PID參數(shù)根據(jù)誤差大小或小車速度動態(tài)變化。例如當(dāng)誤差很小時使用一組較小的Kp來保證直道平穩(wěn)當(dāng)誤差很大急彎時切換為一組較大的Kp來快速響應(yīng)。也可以根據(jù)車速調(diào)整參數(shù)高速時參數(shù)應(yīng)更柔和。分段PID對直道、彎道、十字路口等不同賽道元素使用完全不同的PID參數(shù)組和控制邏輯。這需要配合一個狀態(tài)機(jī)來識別當(dāng)前所處的賽道階段。結(jié)合其他傳感器純視覺在高速或復(fù)雜環(huán)境下有其局限性??梢匀诤暇幋a器測速進(jìn)行速度閉環(huán)控制融合陀螺儀IMU進(jìn)行角度輔助控制。例如在長直道上可以用陀螺儀來維持航向角減少對視覺的依賴從而允許小車跑得更快。5.3 工程實踐要點電源管理OpenMV、單片機(jī)、舵機(jī)、電機(jī)驅(qū)動共用電池時電機(jī)啟動的瞬間會產(chǎn)生很大的電壓跌落可能導(dǎo)致OpenMV重啟。務(wù)必在電源處加入大電容如4700uF進(jìn)行緩沖并使用穩(wěn)壓模塊為OpenMV單獨供電。機(jī)械結(jié)構(gòu)攝像頭安裝高度和俯角直接影響ROI的設(shè)置。安裝要牢固避免抖動導(dǎo)致圖像模糊。車輪的對稱性和傳動機(jī)構(gòu)的順滑度直接影響控制的對稱性機(jī)械問題是很多“玄學(xué)”bug的根源。代碼版本管理調(diào)試過程中會頻繁修改參數(shù)。一定要做好版本記錄每次只改變一個變量并記錄下效果??梢杂米⑨?、或者簡單的文本文件來記錄每次測試的參數(shù)和表現(xiàn)?;剡^頭來看這份代碼的注釋工作更像是對那段備賽時光的一次復(fù)盤。從最初的攝像頭畫面一片模糊到調(diào)通第一版PID小車能踉蹌走完全程再到最后為了提升0.1秒的成績而反復(fù)打磨參數(shù)和算法每一個環(huán)節(jié)都充滿了挑戰(zhàn)和解決問題的樂趣。視覺巡線作為嵌入式與機(jī)器視覺的經(jīng)典結(jié)合點其核心思想——感知、處理、決策、控制——在更廣闊的機(jī)器人領(lǐng)域也是相通的。希望這份詳細(xì)的注釋和心得能幫你少走一些我們曾經(jīng)走過的彎路更順暢地駛?cè)胫悄苘囬_發(fā)的精彩世界。如果在實現(xiàn)過程中遇到具體問題不妨從圖像是否清晰、誤差計算是否準(zhǔn)確、PID輸出是否合理這三個維度像剝洋蔥一樣一層層去排查大概率能找到問題的根源。