視系統(tǒng)IPM變換:從內(nèi)外參推導(dǎo)到Python實(shí)現(xiàn)與調(diào)試)
做AVM環(huán)視監(jiān)控系統(tǒng)這些年繞不開的一個(gè)基礎(chǔ)問題就是IPM變換。很多人一開始以為IPM不過是用OpenCV算一個(gè)單應(yīng)矩陣H把前視圖像做一個(gè)透視變換就完事。但在實(shí)際項(xiàng)目中尤其是要生成四個(gè)魚眼相機(jī)拼接的360環(huán)視圖時(shí)你會發(fā)現(xiàn)內(nèi)外參、坐標(biāo)系、畸變模型、映射方向、融合權(quán)重任何一個(gè)環(huán)節(jié)沒理清最后出來的圖都是歪的、虛的甚至接縫處直接斷裂。這篇文章我打算從相機(jī)內(nèi)外參出發(fā)把IPM變換方程一步步推清楚再給出可復(fù)用的Python/OpenCV代碼實(shí)現(xiàn)最后把我在實(shí)車調(diào)試中踩過的坑一起講透。內(nèi)容面向做自動泊車、360影像、攝像頭標(biāo)定和視覺感知的工程師也適合剛接觸鳥瞰圖的同學(xué)。1. 項(xiàng)目概述與方案選型1.1 什么是IPM變換AVM為什么繞不開它IPM全稱Inverse Perspective Mapping逆透視映射。簡單說就是把相機(jī)拍的透視圖通過幾何變換變成從車輛正上方往下看的“鳥瞰圖”。透視效果的本質(zhì)是近大遠(yuǎn)小地面上的兩條平行線在圖像里會收斂到一個(gè)消失點(diǎn)。IPM就是要消除這種透視效應(yīng)把每個(gè)像素重新投到地面坐標(biāo)上讓平行線重新變成平行線。AVMAround View Monitor系統(tǒng)通常由四顆魚眼相機(jī)組成分別裝在車頭、車尾、左右后視鏡下方。四路圖像覆蓋車身周圍一圈但每路相機(jī)原本都是向斜下方看的透視圖直接放在一起根本拼不成連貫的畫面。必須先把每一路都投影到統(tǒng)一的地面平面上變成俯視視角然后再把四張“局部鳥瞰圖”拼成一張完整環(huán)視圖。這個(gè)“投到地面平面”的過程就是IPM。它和普通的透視變換不太一樣透視變換可以是任意平面到任意平面而IPM特指“相機(jī)圖像平面”到“地平面”的映射。因?yàn)榈孛嫱ǔJ瞧矫嫠钥梢杂脝螒?yīng)矩陣描述這也是為什么實(shí)現(xiàn)IPM時(shí)很多人第一反應(yīng)是找四個(gè)對應(yīng)點(diǎn)算單應(yīng)。1.2 方案選型直接單應(yīng) vs 內(nèi)外參推導(dǎo)實(shí)現(xiàn)IPM常見有三條路我分別說說適用場景和坑。第一種是“標(biāo)定板上取點(diǎn)法”。在地上鋪一張棋盤格標(biāo)出格子的四個(gè)角點(diǎn)在像素坐標(biāo)系和地面坐標(biāo)系中的對應(yīng)坐標(biāo)然后調(diào)用cv2.getPerspectiveTransform或者cv2.findHomography算出3x3單應(yīng)矩陣H。這個(gè)方法在固定相機(jī)、固定安裝角度的靜態(tài)場景里完全夠用幾分鐘就能出圖。但缺點(diǎn)是只要相機(jī)支架稍微松動、或者換了安裝位置原來的H就廢了必須重新取點(diǎn)。第二種是“內(nèi)外參推導(dǎo)法”。先通過標(biāo)定拿到相機(jī)內(nèi)參K、畸變系數(shù)以及相機(jī)相對于車體坐標(biāo)系的外參R和t然后用針孔模型公式推導(dǎo)出圖像平面到地面的單應(yīng)矩陣H。這樣做的好處是物理意義清晰安裝參數(shù)變化時(shí)可以直接更新外參不用重新取點(diǎn)。AVM量產(chǎn)項(xiàng)目中每個(gè)相機(jī)的外參通常由標(biāo)定設(shè)備自動輸出所以內(nèi)外參推導(dǎo)法更適合工程落地。第三種是“基于DLT或非線性優(yōu)化的萬能法”。比如直接用地面上的多個(gè)特征點(diǎn)用直接線性變換求H甚至用光束平差同時(shí)優(yōu)化內(nèi)外參。這種方法適合沒有現(xiàn)成標(biāo)定結(jié)果的科研場景但對于AVM這種已經(jīng)知道大致外參的情況有點(diǎn)殺雞用牛刀而且結(jié)果不保證物理合理。我在這個(gè)項(xiàng)目里選擇的是第二種。四路魚眼相機(jī)標(biāo)定完之后內(nèi)參和外參都存在一個(gè)標(biāo)定文件里我只需要寫一段通用代碼輸入標(biāo)定參數(shù)就能生成任意視角下的IPM變換。這個(gè)方案最大的收益在于“可復(fù)現(xiàn)”同一套代碼換一輛車、換一個(gè)安裝位置只改參數(shù)不用改邏輯。2. 從內(nèi)外參推導(dǎo)IPM變換方程2.1 先定好坐標(biāo)系這一步亂后面全亂推導(dǎo)IPM之前必須把坐標(biāo)系定義清楚否則矩陣乘來乘去全是錯(cuò)的。以車輛后軸中心為原點(diǎn)建立車體坐標(biāo)系(X_w)車頭方向?yàn)檎?Y_w)車輛左側(cè)為正(Z_w)垂直地面向上為正地面平面就是 (Z_w 0)。相機(jī)坐標(biāo)系以相機(jī)光心為原點(diǎn)(Z_c)沿著光軸向前(X_c)向右(Y_c)向下。像素坐標(biāo)系以圖像左上角為原點(diǎn)(u)向右(v)向下。相機(jī)內(nèi)參矩陣用 (K) 表示[ K \begin{bmatrix} f_x 0 c_x \ 0 f_y c_y \ 0 0 1 \end{bmatrix} ]其中 (f_x, f_y) 是歸一化焦距單位是像素(c_x, c_y) 是光心像素坐標(biāo)。外參表示車體坐標(biāo)系到相機(jī)坐標(biāo)系的變換(\mathbf{p}_c R \mathbf{p}_w t)。即一個(gè)車體坐標(biāo)下的三維點(diǎn)先旋轉(zhuǎn)再平移得到它在相機(jī)坐標(biāo)系下的坐標(biāo)。這里的 (R) 是3x3旋轉(zhuǎn)矩陣(t) 是平移向量。注意有些資料里外參反過來定義為相機(jī)到世界使用前一定要確認(rèn)方向不然后面IPM出來的圖會旋轉(zhuǎn)錯(cuò)亂。2.2 由針孔模型到地面單應(yīng)矩陣推導(dǎo)針孔相機(jī)模型把三維點(diǎn)投影到像素坐標(biāo)[ s \cdot \begin{bmatrix} u \ v \ 1 \end{bmatrix} K \cdot [R \mid t] \cdot \begin{bmatrix} X_w \ Y_w \ Z_w \ 1 \end{bmatrix} ]其中 (s) 是縮放因子等于點(diǎn)在相機(jī)坐標(biāo)系下的深度 (Z_c)。因?yàn)樗行枰成涞牡孛纥c(diǎn)都在 (Z_w 0) 平面上所以可以把第三列 (R) 中的第3列 (r_3) 乘0消掉只保留前兩列和 (t)[ s \cdot \begin{bmatrix} u \ v \ 1 \end{bmatrix} K \cdot \begin{bmatrix} r_1 r_2 t \end{bmatrix} \cdot \begin{bmatrix} X_w \ Y_w \ 1 \end{bmatrix} ]定義單應(yīng)矩陣[ H K \cdot \begin{bmatrix} r_1 r_2 t \end{bmatrix} ]那么地面點(diǎn) ((X_w, Y_w)) 到像素點(diǎn) ((u, v)) 的投影就是[ s \cdot \begin{bmatrix} u \ v \ 1 \end{bmatrix} H \cdot \begin{bmatrix} X_w \ Y_w \ 1 \end{bmatrix} ]這里的 (H) 是3x3矩陣。注意它只適用于 (Z_w0) 的平面這也是“平面假設(shè)”的由來。如果想從像素坐標(biāo)反算地面坐標(biāo)就用 (H^{-1})[ \lambda \cdot \begin{bmatrix} X_w \ Y_w \ 1 \end{bmatrix} H^{-1} \cdot \begin{bmatrix} u \ v \ 1 \end{bmatrix} ]同樣需要除以齊次坐標(biāo)最后一個(gè)分量 (\lambda)才能得到真正的 (X_w, Y_w)。掌握了這個(gè)推導(dǎo)過程你就明白了兩件事第一單應(yīng)矩陣 (H) 不是隨便猜的它是內(nèi)參和外參組合出來的第二IPM本質(zhì)上是在做“圖像平面”到“地面平面”的透視逆變換只要外參正確映射關(guān)系就是確定的。2.3 用歐拉角構(gòu)造旋轉(zhuǎn)矩陣換算安裝姿態(tài)實(shí)際標(biāo)定得到的外參有很多種表達(dá)方式。一種直接給旋轉(zhuǎn)矩陣 (R)另一種給歐拉角或者四元數(shù)。在AVM項(xiàng)目里我們最關(guān)心的是相機(jī)相對于車輛坐標(biāo)系的橫滾roll、俯仰pitch和偏航y(tǒng)aw。假設(shè)旋轉(zhuǎn)順序?yàn)?(Z)-(Y)-(X)即先繞 (Z) 軸偏航再繞 (Y) 軸俯仰最后繞 (X) 軸橫滾那么合成旋轉(zhuǎn)矩陣是[ R R_z(\text{yaw}) \cdot R_y(\text{pitch}) \cdot R_x(\text{roll}) ]其中三個(gè)基本旋轉(zhuǎn)矩陣為[ R_x(\theta) \begin{bmatrix} 1 0 0 \ 0 \cos\theta -\sin\theta \ 0 \sin\theta \cos\theta \end{bmatrix} ][ R_y(\theta) \begin{bmatrix} \cos\theta 0 \sin\theta \ 0 1 0 \ -\sin\theta 0 \cos\theta \end{bmatrix} ][ R_z(\theta) \begin{bmatrix} \cos\theta -\sin\theta 0 \ \sin\theta \cos\theta 0 \ 0 0 1 \end{bmatrix} ]實(shí)際安裝時(shí)俯仰角 (pitch) 是最敏感的。一般相機(jī)都斜向下看(pitch) 在十幾到三十幾度之間。偏航角如果裝歪了一點(diǎn)IPM輸出的鳥瞰圖會呈平行四邊形而不是矩形這個(gè)現(xiàn)象在拼接AVM時(shí)非常明顯。橫滾角通常非常小但如果有會導(dǎo)致鳥瞰圖傾斜。平移向量 (t) 就是相機(jī)光心在車體坐標(biāo)系下的位置。對AVM來說最有用的是 (t_z)也就是相機(jī)離地高度。它放在 (H) 的第三列對地面尺度有直接影響。高度錯(cuò)了IPM輸出圖會整體縮放不準(zhǔn)車輛模型和真實(shí)車身大小對不上。2.4 畸變校正與魚眼相機(jī)的前置處理AVM用的都是魚眼鏡頭畸變非常嚴(yán)重不能直接套用針孔模型。在推導(dǎo)上述單應(yīng)矩陣時(shí)默認(rèn)輸入圖像已經(jīng)是校正到針孔模型下的圖像。所以完整流程是用魚眼相機(jī)標(biāo)定得到內(nèi)參、畸變系數(shù) (k_1,k_2,k_3,k_4)OpenCV fisheye模型或針孔畸變系數(shù) (k_1,k_2,p_1,p_2)對原始魚眼圖做畸變校正undistort得到滿足針孔模型的“透視圖像”對透視圖像做IPM。OpenCV提供了兩種思路。對普通廣角鏡頭直接用cv2.undistort對魚眼鏡頭用cv2.fisheye.undistortImage。實(shí)際操作中我更建議一次性把畸變校正和IPM合并成一張remap映射表不要運(yùn)行時(shí)重復(fù)計(jì)算畸變這樣能省不少CPU。合并的原理很簡單IPM的目標(biāo)位置是輸出鳥瞰圖上的每個(gè)像素通過 (H) 投影到“校正后的圖像”上的一個(gè)像素位置然后這個(gè)位置還要繼續(xù)通過畸變模型反算到原始魚眼圖上的像素位置。把兩步查表合成一張map運(yùn)行時(shí)一次remap搞定。3. 代碼實(shí)現(xiàn)從單路IPM到AVM拼接3.1 準(zhǔn)備標(biāo)定參數(shù)與IPM輸出范圍開始寫代碼前需要定義幾個(gè)關(guān)鍵參數(shù)。相機(jī)內(nèi)參K3x3浮點(diǎn)數(shù)組魚眼畸變系數(shù)D這里用cv2.fisheye模型的4個(gè)系數(shù)外參旋轉(zhuǎn)矩陣R_cam_to_veh或者車體系到相機(jī)系的旋轉(zhuǎn)需要換算成我們需要的 (R)外參平移向量t車體系下相機(jī)光心位置IPM圖像寬度和高度以及每個(gè)像素對應(yīng)的地面尺寸比如每像素5毫米鳥瞰圖在原車體坐標(biāo)下的起點(diǎn)比如 (X) 從車尾方向開始。我習(xí)慣把一個(gè)相機(jī)封裝成一個(gè)類初始化時(shí)就把映射表算好。下面給出一個(gè)典型的結(jié)構(gòu)import cv2 import numpy as np class IPM: def __init__(self, K, D, R_cam2veh, t_veh, map_width600, map_height800, resolution0.01, origin_x-1.0, origin_y-1.0): K: 魚眼相機(jī)內(nèi)參 (3,3) D: 魚眼畸變系數(shù) (4,) R_cam2veh: 相機(jī)-車體 旋轉(zhuǎn)矩陣 t_veh: 相機(jī)在車體坐標(biāo)下的位置 map_width, map_height: 輸出鳥瞰圖寬高 resolution: 每個(gè)像素對應(yīng)的地面米數(shù) origin_x, origin_y: 輸出圖左上角對應(yīng)的車體坐標(biāo) self.K K self.D D self.map_shape (map_height, map_width) self.resolution resolution self.origin_x origin_x self.origin_y origin_y # 需要推導(dǎo)從“車體地面”到“相機(jī)圖像”的單應(yīng)矩陣H # 注意R_cam2veh是相機(jī)系到車體系而我們公式需要的是車體系到相機(jī)系 R_veh2cam R_cam2veh.T t_cam -R_veh2cam t_veh r1 R_veh2cam[:, 0] r2 R_veh2cam[:, 1] self.H self.K np.column_stack([r1, r2, t_cam]).astype(np.float64) # 之后根據(jù)H和畸變系數(shù)生成remap映射表這里有一個(gè)容易繞暈的點(diǎn)標(biāo)定文件里常見的rvec、tvec是棋盤格坐標(biāo)系到相機(jī)坐標(biāo)系的變換未必直接是我們需要的車體系到相機(jī)系。一定要明確旋轉(zhuǎn)矩陣的方向。我習(xí)慣統(tǒng)一成“車體系到相機(jī)系”公式里的R_veh2cam和t_cam必須滿足[ p_c R_{veh2cam} p_v t_{cam} ]如果標(biāo)定給出的是R_cam2veh那么取轉(zhuǎn)置即可。3.2 用查表法生成IPM映射生成映射表的思路是遍歷輸出鳥瞰圖上的每個(gè)像素計(jì)算該像素對應(yīng)的車體地面坐標(biāo)再用單應(yīng)矩陣 (H) 投影到校正后的針孔圖像上最后用畸變模型反查到原始魚眼圖像素位置。我把兩步合在一起寫進(jìn)build_map方法。def build_map(self): h, w self.map_shape map_x np.zeros((h, w), dtypenp.float32) map_y np.zeros((h, w), dtypenp.float32) # 生成地面網(wǎng)格坐標(biāo)注意Y方向通常取向下為正還是向上為正 # 如果希望鳥瞰圖上方向?yàn)檐囶^那么車體X增加的方向?qū)?yīng)圖像從上到下還是從下到上 u_coords, v_coords np.meshgrid(np.arange(w), np.arange(h)) # 根據(jù)輸出像素位置計(jì)算車體坐標(biāo)(X_w, Y_w) X_w self.origin_x u_coords * self.resolution Y_w self.origin_y v_coords * self.resolution # 齊次坐標(biāo) ones np.ones_like(X_w) ground_pts np.stack([X_w.ravel(), Y_w.ravel(), ones.ravel()], axis1).T # shape (3, N) # 投影到校正后的針孔圖像 img_pts self.H ground_pts # 3xN img_pts img_pts / img_pts[2, :] # 歸一化 u_img img_pts[0, :].reshape(h, w) v_img img_pts[1, :].reshape(h, w) # 反畸變已知針孔模型下的(u_img, v_img)在魚眼圖像中的位置 # 這里用OpenCV的fisheye.projectPoints更直接或者用迭代校正。 # 簡化處理如果鏡頭畸變不大可以省略這一步但魚眼必須先做。 # 實(shí)際操作中我會使用cv2.fisheye.initUndistortRectifyMap生成的 # “畸變校正圖-原圖”映射和“鳥瞰圖-校正圖”映射做復(fù)合。更簡潔的做法是先調(diào)用一次cv2.fisheye.initUndistortRectifyMap得到從校正圖到原始魚眼圖的映射map1_u、map1_v再把IPM的H變換結(jié)果作為校正圖坐標(biāo)去查這個(gè)map。這樣能復(fù)用OpenCV的優(yōu)化實(shí)現(xiàn)而且代碼更清晰。def build_map(self, D, R, K): # Step 1: 生成校正圖到原魚眼圖的remap map1_u, map1_v cv2.fisheye.initUndistortRectifyMap( K, D, np.eye(3), K, (self.corrected_w, self.corrected_h), cv2.CV_32FC1) # Step 2: 對校正圖上的網(wǎng)格坐標(biāo)做IPM變換得到校正圖坐標(biāo) # 生成目標(biāo)鳥瞰圖網(wǎng)格對應(yīng)地面坐標(biāo)然后 H - 校正圖坐標(biāo) # ... 省略中間代碼最終得到 bird_view_coord_u, bird_view_coord_v # Step 3: 復(fù)合查表最終map_x cv2.remap采樣自map1_umap_y同理 map_x cv2.remap(map1_u, bird_view_coord_u, bird_view_coord_v, cv2.INTER_LINEAR) map_y cv2.remap(map1_v, bird_view_coord_u, bird_view_coord_v, cv2.INTER_LINEAR) return map_x, map_y這個(gè)方法的好處是明顯映射表提前算好運(yùn)行時(shí)對每幀圖片只調(diào)用cv2.remap性能很高。AVM需要同時(shí)處理四路視頻流每路都可以預(yù)先算好映射表放在內(nèi)存里圖像數(shù)據(jù)來了直接查表出圖。3.3 四路拼接融合策略四路相機(jī)的IPM圖像生成后需要在同一張鳥瞰圖上拼接。我的做法是為每個(gè)相機(jī)定義一個(gè)有效區(qū)域掩膜通常是車身周圍的梯形區(qū)域四個(gè)區(qū)域之間有重疊重疊部分不能直接硬切否則接縫處會有明顯跳變采用距離加權(quán)融合計(jì)算輸出像素到當(dāng)前相機(jī)有效區(qū)域中心的距離距離越遠(yuǎn)權(quán)重越低歸一化后作為融合權(quán)重。下面是一個(gè)簡化的前后兩路重疊融合示例# front_ipm, rear_ipm 已經(jīng)生成 # 計(jì)算融合權(quán)重w_range是重疊寬度 alpha np.clip((x_start overlap_w - x) / overlap_w, 0, 1) merged front_ipm * alpha rear_ipm * (1 - alpha)當(dāng)然工程上不會只處理前后兩路而是四路兩兩重疊。更穩(wěn)妥的方案是“金字塔融合”或者“拉普拉斯融合”但那樣計(jì)算量偏大。AVM系統(tǒng)對實(shí)時(shí)性要求高我用線性加權(quán)已經(jīng)能解決大部分問題關(guān)鍵是要保證各路的曝光和增益一致。如果前后相機(jī)的自動曝光差異太大再好的融合算法也救不回來。3.4 性能優(yōu)化與內(nèi)存管理經(jīng)驗(yàn)在嵌入式設(shè)備上跑AVM性能是第一道坎。我總結(jié)幾個(gè)行之有效的優(yōu)化點(diǎn)映射表提前算絕對不要在渲染循環(huán)里算投影矩陣或畸變只做一次cv2.remap。多線程并行四路相機(jī)采集和IPM可以拆到四個(gè)線程每個(gè)線程獨(dú)立處理一路圖像最后在主線程拼接。Python里可以用concurrent.futures.ThreadPoolExecutor注意OpenCV的remap線程安全但要注意GIL重活最好讓C干。單精度存儲map_x、map_y用float32不要用float64否則內(nèi)存翻倍而且OpenCV部分接口不支持double。裁剪輸出范圍鳥瞰圖不需要全部顯示比如只保留以車輛為中心的 7m x 9m 區(qū)域超出范圍的空間根本用不上反而增加計(jì)算量。分辨率權(quán)衡每像素5毫米和每像素10毫米視覺差距不大但計(jì)算量差4倍。我一般先用10毫米跑通流程再按需提高。4. 常見問題與排查技巧實(shí)錄4.1 外參不準(zhǔn)引起的錯(cuò)位怎么查AVM拼接最常見的現(xiàn)象是相鄰相機(jī)的鳥瞰圖對不齊比如車道線在前后兩路重疊區(qū)錯(cuò)開一個(gè)像素到幾十像素。首當(dāng)其沖的原因是外參不準(zhǔn)尤其是偏航角和高度。排查方法是在車身周圍鋪幾根長直膠帶或者畫直線從車頭到車尾、從左到右形成明顯的直線標(biāo)線。然后分別查看四路IPM圖看同一條線在相鄰視角里是否對齊。如果發(fā)現(xiàn)某一路里的直線歪了一般就是該相機(jī)的外參偏航角有誤差。如果整體縮放不對那就是高度不準(zhǔn)。另一種情況是左右兩側(cè)和前后相機(jī)的重疊區(qū)域都已經(jīng)對齊但車輛四周一圈的車位線整體偏移。這往往是車體坐標(biāo)系定義不一致導(dǎo)致的比如左右相機(jī)安裝時(shí)X軸方向標(biāo)錯(cuò)了需要重新核對標(biāo)定流程。4.2 單應(yīng)矩陣退化與數(shù)值陷阱在某些安裝角度下H矩陣可能接近奇異。什么意思就是相機(jī)光軸幾乎平行于地面或者俯仰角接近90度導(dǎo)致地面點(diǎn)在圖像中投影后區(qū)分度很低IPM變換會把很遠(yuǎn)的地面點(diǎn)映射到很近的像素輸出圖嚴(yán)重拉伸。經(jīng)驗(yàn)上相機(jī)相對地面的俯仰角小于10度時(shí)IPM結(jié)果會很糟糕因?yàn)橥敢曅Ч鯖]有足夠的“頂視信息”。這時(shí)候應(yīng)該調(diào)整相機(jī)安裝角度或者考慮用多個(gè)視角融合。代碼上我習(xí)慣在初始化時(shí)檢查det np.linalg.det(self.H) cond np.linalg.cond(self.H) assert abs(det) 1e-8, H matrix is singular assert cond 1e8, fH matrix is ill-conditioned: {cond:.2e}另外注意浮點(diǎn)精度。如果直接手動拼H建議用雙精度計(jì)算最后存map_x/map_y再轉(zhuǎn)float32避免累計(jì)誤差。4.3 拼接縫發(fā)虛、亮度不均的調(diào)整即便外參正確拼接縫處仍可能出現(xiàn)“發(fā)虛”或者“一條線”的現(xiàn)象。原因多數(shù)是曝光不一致。AVM四路相機(jī)朝向不同車頭的天空和車尾的地面亮度差異很大自動曝光各自為政重疊區(qū)的灰度就會突變。我采用的臨時(shí)方案是在標(biāo)定階段固定四路相機(jī)的曝光時(shí)間和增益不啟用自動曝光如果必須自動曝光就需要做圖像亮度均衡比如計(jì)算重疊區(qū)域的灰度直方圖做線性映射。更進(jìn)一步的做法是在融合階段使用漸變權(quán)重。但底層原因不解決權(quán)重只能讓邊界“模糊化”不能完全消除。如果接縫處出現(xiàn)“重影”而不是亮度突變重點(diǎn)檢查外參微調(diào)量。有些標(biāo)定工具輸出的是“相機(jī)到標(biāo)定板”的外參傳到IPM代碼里方向反了H就錯(cuò)了表現(xiàn)就是部分區(qū)域完全錯(cuò)位。我建議在代碼里寫一個(gè)自檢函數(shù)輸入一副帶直線的地面圖像輸出IPM圖后計(jì)算邊緣角點(diǎn)的位置已知量自動判斷外參是否可信。4.4 常見問題速查表現(xiàn)象可能原因解決方法鳥瞰圖整體旋轉(zhuǎn)了一個(gè)角度外參偏航角符號或旋轉(zhuǎn)順序不對檢查旋轉(zhuǎn)矩陣定義統(tǒng)一坐標(biāo)方向地面物體尺寸不對相機(jī)高度或IPM分辨率設(shè)置錯(cuò)誤核對t向量中的高度確認(rèn)每像素實(shí)際米數(shù)拼接處車道線斷開相鄰相機(jī)外參相對誤差大重新標(biāo)定或加一個(gè)在線優(yōu)化的小偏轉(zhuǎn)角圖像邊緣嚴(yán)重拉伸相機(jī)俯仰角過小或輸出范圍過大縮小IPM輸出范圍調(diào)整相機(jī)安裝角度魚眼圖邊緣畸變校正后視角缺失魚眼內(nèi)參不準(zhǔn)鏡頭視場角超出模型范圍重新標(biāo)定魚眼內(nèi)參或只用FOV內(nèi)的區(qū)域映射表耗時(shí)太長循環(huán)遍歷每個(gè)像素使用numpy批量計(jì)算或者預(yù)生成一次5. 實(shí)操中的一點(diǎn)體會代碼跑通只是第一步真正把AVM做到好用拼的是細(xì)節(jié)。我在實(shí)車調(diào)試中體會最深的是永遠(yuǎn)不要相信外參是一次標(biāo)定永遠(yuǎn)不變的。車輛懸掛高度、輪胎胎壓、負(fù)載都會改變相機(jī)相對地面的位置導(dǎo)致原本調(diào)好的拼接圖過幾天又偏了。所以量產(chǎn)系統(tǒng)里通常會加一個(gè)“在線自標(biāo)定”環(huán)節(jié)比如用車道線或者停車位的特征來實(shí)時(shí)修正外參。這篇文章沒有展開那一部分但如果你打算做產(chǎn)品級AVM可以往這個(gè)方向繼續(xù)深入。最后再分享一個(gè)小技巧調(diào)試IPM時(shí)把單應(yīng)矩陣H打印出來看一眼如果發(fā)現(xiàn)第三列平移部分和第一列、第二列量級差距懸殊或者H的行列式特別大多半是內(nèi)外參的單位或者坐標(biāo)系方向沒統(tǒng)一。把這些基礎(chǔ)問題排掉IPM本身其實(shí)是一個(gè)非常穩(wěn)定的幾何工具。希望這篇推導(dǎo)和代碼實(shí)現(xiàn)能幫你少走一段彎路。