定:工業(yè)相機(jī)內(nèi)參求解與去畸變實(shí)戰(zhàn))
簡(jiǎn)介這是一套基于OpenCV實(shí)現(xiàn)張正友相機(jī)標(biāo)定的完整工程代碼主要面向計(jì)算機(jī)視覺初學(xué)者、機(jī)器人導(dǎo)航與三維重建領(lǐng)域的開發(fā)者解決相機(jī)內(nèi)參與外參求解、畸變矯正等基礎(chǔ)但關(guān)鍵的問題。工程基于VS2015編寫包含詳細(xì)注釋與一鍵運(yùn)行腳本覆蓋從棋盤格圖像讀取、角點(diǎn)提取到標(biāo)定結(jié)果評(píng)價(jià)與圖像矯正的完整流程同時(shí)附帶可打印棋盤格PDF、10張手機(jī)拍攝標(biāo)定圖和20張攝像頭拍攝標(biāo)定圖無需額外采集即可體驗(yàn)整個(gè)標(biāo)定過程。資源包共107個(gè)文件以jpg測(cè)試圖像、cpp源碼、pdf棋盤格、exe可執(zhí)行文件為主體整體大小95.67MB目錄組織清晰便于按模塊研讀。標(biāo)定輸出包括相機(jī)內(nèi)參矩陣、旋轉(zhuǎn)向量與平移向量并自動(dòng)計(jì)算重投影誤差以評(píng)價(jià)精度利用標(biāo)定結(jié)果可直接對(duì)原始棋盤圖進(jìn)行去畸變矯正實(shí)測(cè)所得標(biāo)定參數(shù)與Matlab標(biāo)定工具箱結(jié)果非常接近適合作為理論學(xué)習(xí)的配套實(shí)踐或項(xiàng)目預(yù)研的參考。該資源已有6817人學(xué)習(xí)下載兼顧教學(xué)演示與實(shí)際應(yīng)用是快速掌握張正友標(biāo)定法的優(yōu)質(zhì)材料。1. 張正友標(biāo)定為什么 OpenCV 實(shí)現(xiàn)仍是當(dāng)下最實(shí)用的內(nèi)參求解路線手頭一個(gè)工業(yè)相機(jī)裝上鏡頭后畫面邊緣的直線全是彎的用相機(jī)做尺寸測(cè)量、做機(jī)器人抓取定位第一步就得把內(nèi)參和畸變系數(shù)算準(zhǔn)。張正友標(biāo)定法是目前最省器材的解法——不需要昂貴的三維標(biāo)定塊只要一張 A4 打印的棋盤格從不同角度拍十幾張圖交給 OpenCV 就能解出相機(jī)內(nèi)參、畸變系數(shù)還能順帶拿到每張圖拍攝時(shí)相機(jī)相對(duì)棋盤格的位置姿態(tài)。這套方案的實(shí)際效果直接決定后續(xù)去畸變、三維重建和位姿估計(jì)的精度適合做機(jī)器視覺、SLAM 初始化、增強(qiáng)現(xiàn)實(shí)注冊(cè)的開發(fā)者。本文就從原理、資源包結(jié)構(gòu)、完整實(shí)現(xiàn)到踩坑記錄把這條路線完整拆開。2. 標(biāo)定原理與資源包結(jié)構(gòu)從棋盤格幾何約束到內(nèi)參矩陣的五個(gè)關(guān)鍵量2.1 張氏標(biāo)定的數(shù)學(xué)模型一張棋盤格為什么能解出五個(gè)內(nèi)參傳統(tǒng)標(biāo)定需要一個(gè)精密加工的三維標(biāo)定塊讓相機(jī)拍攝已知空間坐標(biāo)的角點(diǎn)再用透視投影關(guān)系直接建立方程。張氏標(biāo)定的巧妙之處在于把三維靶標(biāo)降到了二維——棋盤格平面上的所有角點(diǎn)都滿足 Z 0這樣每個(gè)角點(diǎn)的三維坐標(biāo)只剩 X 和 Y 兩個(gè)自由度。三維世界坐標(biāo)到像素坐標(biāo)的投影矩陣此時(shí)退化為一個(gè) 3×3 的單應(yīng)性矩陣 H物理世界的平面點(diǎn)和像素點(diǎn)之間就構(gòu)成了一個(gè)直接的射影映射。具體來說相機(jī)的針孔模型寫成 s·m K·[R|t]·M其中 M 是世界坐標(biāo)、m 是像素坐標(biāo)、K 是內(nèi)參矩陣。當(dāng) M 所在的平面是 Z 0 時(shí)旋轉(zhuǎn)矩陣 R 的第三列被消掉剩下的關(guān)系變成 s·m H·MH λ·K·[r1 r2 t]。這個(gè) H 就是每張棋盤格圖片都能算出來的單應(yīng)性矩陣。既然 H 已知而 K 內(nèi)又藏著 fx、fy、cx、cy、s 這五個(gè)量的信息張正友證明了從單應(yīng)性矩陣的約束條件中可以把內(nèi)參逐步分離出來。每一張棋盤格照片提供的兩個(gè)基本約束來自旋轉(zhuǎn)矩陣的列向量 r1 和 r2 的單位正交性。因?yàn)?r1 和 r2 是真實(shí)旋轉(zhuǎn)矩陣的前兩列它們滿足 r1^T·r2 0 且 |r1| |r2|。把 H 分解后代入這兩個(gè)條件每個(gè)條件就變成了一個(gè)關(guān)于內(nèi)參矩陣 K 的二次方程。每一個(gè)視角提供兩個(gè)方程五個(gè)內(nèi)參理論上至少需要三張不同角度的圖像才能線性解出。實(shí)踐中我到手最少拍 12 張常規(guī)項(xiàng)目拍到 18 到 20 張?jiān)蚝芎?jiǎn)單角點(diǎn)檢測(cè)有像素級(jí)噪聲方程數(shù)量多了才能讓最小二乘解穩(wěn)定下來?;兡P偷募尤胱寙栴}從線性求解變成了非線性優(yōu)化。張氏標(biāo)定在得到線性初值后會(huì)用 Levenberg-Marquardt 算法迭代最小化所有角點(diǎn)的重投影誤差優(yōu)化變量包括內(nèi)參、畸變系數(shù) k1、k2、p1、p2 以及每張圖的外參。也正因?yàn)榇嬖谶@套優(yōu)化機(jī)制它不需要像 DLT 那樣對(duì)每張圖片單獨(dú)求投影矩陣而是把相機(jī)內(nèi)參作為全局共享量做聯(lián)合估計(jì)。這也是為什么同一組圖片張氏標(biāo)定在 OpenCV 里的求解結(jié)果比早期直接求單應(yīng)性矩陣的粗糙做法穩(wěn)定得多。2.2 資源包拿到手棋盤格PDF、測(cè)試圖片與標(biāo)定腳本三件套怎么協(xié)同典型的張正友標(biāo)定資源包通常包含三類內(nèi)容可打印的棋盤格 PDF、不同角度拍攝的測(cè)試圖片集、以及一套基于 OpenCV 的標(biāo)定腳本。三者的關(guān)系是一個(gè)完整的流水線——PDF 負(fù)責(zé)生成物理靶標(biāo)測(cè)試圖片是輸入數(shù)據(jù)腳本完成角點(diǎn)提取、標(biāo)定和參數(shù)輸出。拿到資源包的第一步不是立刻跑代碼而是先做文件體檢我一般會(huì)按下面這個(gè)流程檢查資源項(xiàng)關(guān)鍵檢查點(diǎn)用途棋盤格 PDF格子行列數(shù)、單格邊長(zhǎng)標(biāo)注、是否含多個(gè)尺寸版本決定 patternSize 參數(shù)與實(shí)際物理尺寸測(cè)試圖片集數(shù)量是否 ≥ 10 張、分辨率是否一致、棋盤格是否完整入鏡決定標(biāo)定方程約束是否充分標(biāo)定腳本是否單文件、依賴 OpenCV 版本、是否含保存參數(shù)的邏輯決定后續(xù)驗(yàn)證和復(fù)用方式棋盤格 PDF 的坑在于打印縮放。大多數(shù) PDF 是按 100% 比例設(shè)計(jì)的但打印機(jī)的“適應(yīng)頁(yè)面”選項(xiàng)會(huì)擅自縮放實(shí)際打印出來的格子邊長(zhǎng)和 PDF 標(biāo)注值相差幾個(gè)百分點(diǎn)。拿到打印好的棋盤格我必做的一件事是用卡尺量三個(gè)不同位置的格子邊長(zhǎng)取平均值填入腳本的 square_size 變量。這個(gè)值影響的是外參平移向量 t 的單位內(nèi)參 fx、fy 是像素單位下的焦距不受影響但如果后面要用標(biāo)定結(jié)果做三維測(cè)量比如算棋盤格到相機(jī)的距離square_size 錯(cuò)了距離就全錯(cuò)了。測(cè)試圖片集的檢查重點(diǎn)是角度覆蓋。合格的測(cè)試集應(yīng)該包含正拍、左右側(cè)傾、上下俯仰、近拍和遠(yuǎn)拍幾種情況而且棋盤格不能總停留在畫面中央。如果圖片都是同一個(gè)視角拍的單應(yīng)性約束退化內(nèi)參解不唯一重投影誤差再低也是假象。另外圖片分辨率必須一致我遇到過資源包里混入一張尺寸不同的圖導(dǎo)致 imagePoints 和 objectPoints 的維度對(duì)不上腳本在第一輪就報(bào)錯(cuò)。腳本部分的代碼組織方式各有不同。有的資源包把角點(diǎn)提取和標(biāo)定放在一個(gè)腳本里跑完有的則分成 extract_corners.py 和 calibrate.py 兩個(gè)階段。分步的好處是第一步提取角點(diǎn)后可以人工檢查是否有誤檢再進(jìn)標(biāo)定單文件的省事但排錯(cuò)時(shí)容易一鍋端。我習(xí)慣拿到腳本后先跑一遍原始數(shù)據(jù)和默認(rèn)參數(shù)確認(rèn)能輸出結(jié)果再根據(jù)自己相機(jī)的分辨率、棋盤格尺寸去調(diào)整參數(shù)而不是直接改代碼。3. OpenCV 標(biāo)定全流程角點(diǎn)提取、亞像素細(xì)化與 calibrateCamera 參數(shù)設(shè)置3.1 第零步準(zhǔn)備棋盤格的行列數(shù)、物理尺寸與拍攝數(shù)量實(shí)現(xiàn)之前最容易被忽略的三個(gè)參數(shù)是內(nèi)角點(diǎn)數(shù)、格子實(shí)際邊長(zhǎng)和圖片數(shù)量。棋盤格 PDF 上標(biāo)注的是格子數(shù)比如 10 列 × 7 行而 OpenCV 的 findChessboardCorners 要求傳入的是內(nèi)角點(diǎn)數(shù)——也就是格子之間的交叉點(diǎn)數(shù)量等于 9 × 6。這一點(diǎn)我踩過至少三次坑每次都是拿著 10×7 的格子數(shù)直接填 patternSize結(jié)果要么檢測(cè)失敗要么畫出來的角點(diǎn)連邊錯(cuò)亂。棋盤格物理尺寸的選擇要兼顧提取穩(wěn)定性和畸變覆蓋。單格邊長(zhǎng)在 20mm 到 30mm 之間比較合適太小了遠(yuǎn)拍時(shí)角點(diǎn)在圖像里只有幾個(gè)像素亞像素細(xì)化也救不回來太大了近拍時(shí)棋盤格超出畫面沒法覆蓋畫面邊緣。打印出來的棋盤格要貼在平整的硬板上平面度直接影響標(biāo)定精度。拍攝數(shù)量的經(jīng)驗(yàn)值通常取 15 到 20 張。我的拍攝分配策略是先正拍一張然后左右各轉(zhuǎn) 15 度、30 度分別拍再上下傾斜各拍幾張最后讓棋盤格分別出現(xiàn)在畫面左上、右上、左下、右下四個(gè)角落各拍一張。遠(yuǎn)近變化也很重要——保證棋盤格占畫面寬度的 1/3 到 2/3 之間浮動(dòng)。3.2 角點(diǎn)提取與亞像素細(xì)化findChessboardCorners 到 cornerSubPix 的用法拿到測(cè)試圖片后先從灰度圖走角點(diǎn)提取。OpenCV 的 findChessboardCorners 對(duì)光照魯棒性還行但反光、陰影和模糊都會(huì)導(dǎo)致檢測(cè)失敗所以提取前可以用自適應(yīng)直方圖均衡化增強(qiáng)一下對(duì)比度。下面的代碼是提取和細(xì)化角點(diǎn)的核心部分import cv2 import numpy as np # 棋盤格內(nèi)角點(diǎn)數(shù)10列x7行格子 - (9, 6) pattern_size (9, 6) # 亞像素細(xì)化的迭代終止條件迭代30次或精度達(dá)到0.001像素 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corner_list [] for img_path in image_paths: img cv2.imread(img_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 尋找棋盤格內(nèi)角點(diǎn) ret, corners cv2.findChessboardCorners(gray, pattern_size, None) if not ret: print(f角點(diǎn)提取失敗: {img_path}) continue # 亞像素級(jí)細(xì)化窗口11x11避免靠近邊緣時(shí)越界 corners_refined cv2.cornerSubPix( gray, corners, (11, 11), (-1, -1), criteria ) corner_list.append(corners_refined) # 可視化確認(rèn)把角點(diǎn)畫回原圖人工檢查排列順序 img_show img.copy() cv2.drawChessboardCorners(img_show, pattern_size, corners_refined, ret) cv2.imshow(check, img_show) cv2.waitKey(100)findChessboardCorners 返回的 ret 是布爾值OpenCV 的策略是要么找到全部?jī)?nèi)角點(diǎn)要么一個(gè)都不找所以不用擔(dān)心中途部分成功導(dǎo)致點(diǎn)數(shù)不一致。cornerSubPix 的原理是基于梯度正交性迭代優(yōu)化角點(diǎn)位置讓角點(diǎn)在兩個(gè)正交梯度方向上的坐標(biāo)誤差同時(shí)最小。winSize 是搜索窗口半徑取 11 對(duì) 1280×720 分辨率的圖夠用如果圖片分辨率更高窗口可以適當(dāng)加大到 15。zerzone 設(shè)為 (-1, -1) 表示窗口中心不設(shè)禁區(qū)。drawChessboardCorners 畫出來的是按行列順序排列的彩色連線這一步務(wù)必人眼確認(rèn)。角點(diǎn)的提取順序是先行后列、每行從左到右如果棋盤格在畫面中發(fā)生了旋轉(zhuǎn)OpenCV 也能按內(nèi)部邏輯確定起始角點(diǎn)但前提是棋盤格傾斜角度不要超過 45 度否則容易出現(xiàn) 180 度翻轉(zhuǎn)的歧義。3.3 calibrateCamera 參數(shù)解說從五個(gè)內(nèi)參到畸變系數(shù)與重投影誤差角點(diǎn)收集完成后進(jìn)入標(biāo)定階段。objectPoints 是棋盤格在物理平面上的三維坐標(biāo)Z 全部為 0X、Y 按格子間距生成imagePoints 是每張圖的亞像素角點(diǎn)兩者數(shù)量必須一致。下面這段代碼給出了完整的標(biāo)定調(diào)用方式square_size 25.0 # 單格邊長(zhǎng)單位mm用卡尺實(shí)測(cè)后填入 objp np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) # 生成物理坐標(biāo)每列x間隔25mm每行y間隔25mm objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) * square_size object_points [] # 每張圖一份相同的物理坐標(biāo) image_points [] for corners_refined in corner_list: object_points.append(objp) image_points.append(corners_refined.reshape(-1, 2)) # 標(biāo)定主函數(shù)rms是重投影誤差 rms, camera_matrix, dist_coeffs, rvecs, tvecs cv2.calibrateCamera( object_points, image_points, gray.shape[::-1], # 傳入(width, height) None, None ) print(重投影誤差:, rms) print(內(nèi)參矩陣:\n, camera_matrix) print(畸變系數(shù):, dist_coeffs.ravel())calibrateCamera 默認(rèn)求解的畸變系數(shù)是 k1、k2、p1、p2 四個(gè)也就是只含兩種徑向畸變和兩種切向畸變。如果鏡頭畸變比較大比如廉價(jià)的廣角 USB 攝像頭需要把 flags 參數(shù)加上 cv2.CALIB_RATIONAL_MODEL這樣會(huì)多解一個(gè) k3。加了 k3 之后dist_coeffs 變成 5 個(gè)元素但求解的自由度變大對(duì)圖片數(shù)量的要求也更高少于 15 張圖時(shí) k3 容易不收斂。內(nèi)參矩陣 camera_matrix 里的 fx 和 fy 是等效焦距單位是像素cx、cy 是主點(diǎn)坐標(biāo)。對(duì)多數(shù)工業(yè)相機(jī)fx 和 fy 理論值應(yīng)該很接近如果標(biāo)定結(jié)果兩者差了超過 5%優(yōu)先檢查圖片是否被縮放、裁剪或者輸入給 calibrateCamera 的 imageSize 是否和實(shí)際圖片尺寸一致。rvecs 和 tvecs 分別保存每張圖的外參也就是棋盤格坐標(biāo)系相對(duì)相機(jī)坐標(biāo)系的旋轉(zhuǎn)向量和平移向量長(zhǎng)度必須等于成功提取角點(diǎn)的圖片數(shù)。旋轉(zhuǎn)向量是三維的后續(xù)可以用 Rodrigues 轉(zhuǎn)成 3×3 旋轉(zhuǎn)矩陣。rms 返回的是所有角點(diǎn)重投影誤差的均方根單位是像素。根據(jù)我的經(jīng)驗(yàn)rms 小于 0.1 屬于非常理想0.1 到 0.3 在工程上可用超過 0.5 多半是角點(diǎn)檢測(cè)、棋盤格平面度或參數(shù)設(shè)置有明顯問題。需要強(qiáng)調(diào)的是rms 只是全局平均指標(biāo)單張圖可能誤差很大但被平均掩蓋下一章專門講怎么逐張驗(yàn)證。4. 標(biāo)定質(zhì)量驗(yàn)證與去畸變輸出重投影誤差之外還要看實(shí)測(cè)線條4.1 重投影誤差怎么看RMS 數(shù)值不是唯一裁判多數(shù)人標(biāo)定完只看一個(gè) rms這就是把標(biāo)定當(dāng)成黑匣子的典型心態(tài)。rms 是所有圖片角點(diǎn)誤差的均方根單張圖的局部畸變誤差會(huì)被稀釋一個(gè) 0.08 的 rms 背后可能藏著某張圖的極大誤差。我的做法是標(biāo)定完成后把每張圖的平均重投影誤差逐張打印出來定位離群圖片再?zèng)Q定是剔除還是重拍。下面這段代碼利用標(biāo)定輸出的 rvecs、tvecs 做逐圖重投影def per_image_error(object_points, image_points, rvecs, tvecs, camera_matrix, dist_coeffs): for i in range(len(object_points)): # 把物理坐標(biāo)點(diǎn)投影到像素平面 proj, _ cv2.projectPoints( object_points[i], rvecs[i], tvecs[i], camera_matrix, dist_coeffs ) error np.sqrt(np.sum((proj - image_points[i]) ** 2, axis1)) mean_err error.mean() print(f第{i:02d}張圖 平均誤差: {mean_err:.3f} pix, f最大誤差: {error.max():.3f} pix)逐張輸出后凡是平均誤差明顯大于整體水平的圖片先檢查是不是棋盤格邊緣反光、紙張不平整或者畫面模糊導(dǎo)致的角點(diǎn)質(zhì)量下降。剔除個(gè)別爛圖再重新標(biāo)定比保留所有圖片靠最小二乘硬扛要有效得多。rms 的評(píng)價(jià)標(biāo)準(zhǔn)可以按這個(gè)表做一個(gè)快速判斷rms 范圍工程判斷建議動(dòng)作 0.1優(yōu)秀直接進(jìn)入去畸變與驗(yàn)證0.1 ~ 0.3可用結(jié)合逐張誤差分析輕微優(yōu)化0.3 ~ 0.5存疑檢查棋盤格平面度與角點(diǎn)檢測(cè) 0.5失敗重新拍攝校正參數(shù)設(shè)置還有一類情況是 rms 很低但實(shí)際去畸變效果差棋盤格圖像全部集中在畫面中央?yún)^(qū)域邊緣畸變沒有被觀測(cè)數(shù)據(jù)約束畸變參數(shù)就成了無約束自由量?jī)?yōu)化器只能給出數(shù)值上湊合的估計(jì)。所以第四步還有一個(gè)硬性檢查——把棋盤格單獨(dú)放在畫面四個(gè)角落拍四張圖如果這批圖的重投影誤差普遍偏高說明畸變模型和真實(shí)鏡頭不匹配或者原本的畸變系數(shù)初始值選得不對(duì)。4.2 基于標(biāo)定結(jié)果做去畸變initUndistortRectifyMap 與 remap 的參數(shù)陷阱拿到內(nèi)參和畸變系數(shù)后最直接的驗(yàn)證手段是拿一張棋盤格圖片做去畸變觀察邊緣直線是否恢復(fù)平直。OpenCV 提供兩種方式一種是一步到位的 cv2.undistort另一種是先計(jì)算映射表再用 remap 重采樣。映射表方式更適合視頻流和多張圖重復(fù)使用因?yàn)橛成浔碇恍栌?jì)算一次后面每幀只是查表重映射省掉重復(fù)的坐標(biāo)變換計(jì)算。h, w image.shape[:2] # alpha1 保留所有像素視野邊緣可能出現(xiàn)黑邊 new_camera_matrix, roi cv2.getOptimalNewCameraMatrix( camera_matrix, dist_coeffs, (w, h), alpha1, newImgSize(w, h) ) # 計(jì)算像素坐標(biāo)映射表map1為x坐標(biāo)映射map2為y坐標(biāo)映射 map1, map2 cv2.initUndistortRectifyMap( camera_matrix, dist_coeffs, None, new_camera_matrix, (w, h), cv2.CV_32FC1 ) # 查表重映射完成去畸變 undistorted_img cv2.remap(image, map1, map2, cv2.INTER_LINEAR)getOptimalNewCameraMatrix 里的 alpha 參數(shù)是這里最容易翻車的點(diǎn)。alpha0 表示去畸變后裁剪掉所有無效區(qū)域輸出圖像只保留無畸變且有內(nèi)容的部分畫面會(huì)輕微縮小alpha1 表示保留原始圖像的全部視野范圍去畸變后邊緣會(huì)露出黑色空白區(qū)域。對(duì)于視覺測(cè)量場(chǎng)景我一般選 alpha1寧可有黑邊也不裁剪視野因?yàn)椴眉魰?huì)改變圖像分辨率與內(nèi)參的對(duì)應(yīng)關(guān)系后續(xù)如果要用去畸變圖繼續(xù)做特征匹配必須重新確認(rèn)內(nèi)參是否仍然適用。這段代碼里還有一個(gè)隱藏坑initUndistortRectifyMap 的最后一個(gè)參數(shù)類型。很多人習(xí)慣用 cv2.CV_16SC2 覺得省內(nèi)存但對(duì)于亞像素精度要求高的場(chǎng)景CV_32FC1 是更穩(wěn)妥的選擇避免 map 坐標(biāo)的量化誤差。remap 的插值方式選 INTER_LINEAR 即可選更高階的 INTER_CUBIC 在邊緣處容易產(chǎn)生過沖偽影。去畸變之后可以用直線邊緣的圖片做個(gè)簡(jiǎn)單檢查把原圖和去畸變圖并排顯示觀察畫面邊緣的直線是否變直。如果去畸變后邊緣反而出現(xiàn)波浪形失真通常是畸變系數(shù)的符號(hào)或量級(jí)出了問題需要回到標(biāo)定環(huán)節(jié)重新檢查 flags 和圖片質(zhì)量。5. 張氏標(biāo)定避坑實(shí)錄四類翻車現(xiàn)場(chǎng)與對(duì)應(yīng)解法5.1 內(nèi)角點(diǎn)數(shù)寫錯(cuò)導(dǎo)致全部角點(diǎn)提取失敗現(xiàn)象運(yùn)行 findChessboardCorners 返回的 ret 全是 False不管換哪張測(cè)試圖片都提取不到角點(diǎn)或者某些圖雖然返回 True但 drawChessboardCorners 畫出來的連線順序錯(cuò)亂。原因patternSize 誤填成了棋盤格的行列數(shù)而不是內(nèi)角點(diǎn)數(shù)。比如 10×7 的棋盤格內(nèi)角點(diǎn)數(shù)量是 (9, 6)寫成 (10, 7) 會(huì)讓檢測(cè)器在整個(gè)棋盤格范圍內(nèi)找不存在的 70 個(gè)角點(diǎn)算法內(nèi)部在多個(gè)尺度上都會(huì)失配。解決先用一張圖打印出 pattern_size 的每個(gè)候選值跑一遍 findChessboardCorners從 (cols - 1, rows - 1) 開始試確定正確內(nèi)角點(diǎn)數(shù)后再在批量處理時(shí)用 assert 保護(hù)一旦有圖檢測(cè)失敗就直接打印文件名終止避免后面 imagePoints 數(shù)量不一致引起更隱蔽的錯(cuò)位。5.2 所有棋盤格都在畫面中心標(biāo)定誤差虛低、去畸變翻車現(xiàn)象rms 輸出 0.05 左右看起來非常理想但用標(biāo)定結(jié)果去矯正一張畫面邊緣有很多直線的實(shí)拍圖邊緣依舊是彎的而且畫面四角的畸變完全沒有被修正。原因所有棋盤格圖像都集中在畫面中央角點(diǎn)數(shù)據(jù)只覆蓋了圖像中心區(qū)域。畸變系數(shù)本質(zhì)上是描述像素坐標(biāo)偏離理想針孔模型的程度中心區(qū)域的徑向畸變很小數(shù)據(jù)里幾乎沒有邊緣畸變的信息優(yōu)化器只能把邊緣畸變參數(shù)推向一個(gè)任意值。解決重新拍攝把棋盤格分別放到畫面左上、右上、左下、右下四個(gè)角落每放一個(gè)位置拍兩三張不同角度的圖。同時(shí)檢查每張圖里棋盤格是否至少有 70% 面積位于畫面邊緣帶再重新標(biāo)定并對(duì)比 rms 和去畸變效果。5.3 打印紙沒有貼平板兩次標(biāo)定結(jié)果差了幾個(gè)百分點(diǎn)現(xiàn)象同一臺(tái)相機(jī)用同一張棋盤格上午標(biāo)定的 fx 是 1423下午重新標(biāo)定變成 1385差距超過 2.5%而且每次 rms 都在 0.2 以內(nèi)。原因棋盤格打印紙直接平鋪在桌面上紙面有輕微波浪形翹曲。張氏標(biāo)定假設(shè)靶標(biāo)是一個(gè)嚴(yán)格平面紙面翹曲等于給每個(gè)角點(diǎn)疊加了一個(gè)隨機(jī)的幾毫米級(jí) Z 方向偏移這些偏移被畸變和外參吸收導(dǎo)致內(nèi)參不穩(wěn)定。解決按照工業(yè)上常見的做法棋盤格打印紙要平貼在剛性的亞克力板或鋁板上四角用膠帶或磁吸固定。貼完板子后從側(cè)面觀察板面是否有彎曲或者把棋盤格貼在平整桌面上用重物壓住四個(gè)角再做標(biāo)定驗(yàn)證。5.4 角點(diǎn)檢測(cè)成功但標(biāo)定時(shí)報(bào) objectPoints 與 imagePoints 數(shù)量不匹配現(xiàn)象程序在 build objectPoints 階段不報(bào)錯(cuò)進(jìn)入 calibrateCamera 后拋異常提示 cannot reshape array of size 或者 objectPoints 的維度不一致。原因某些圖片在 findChessboardCorners 時(shí)返回了真但后續(xù)處理沒有統(tǒng)一 reshape比如有的 corners 是 (54, 1, 2) 有的被處理成 (54, 2)。另一個(gè)常見原因是不同批次的圖片分辨率不一致導(dǎo)致 imagePoints 的數(shù)量雖然都是 54但坐標(biāo)尺度不同。解決在收集角點(diǎn)的循環(huán)里強(qiáng)制每個(gè) corners 都執(zhí)行 corners_refined.reshape(-1, 2)并斷言 shape[0] 等于 pattern_size[0] * pattern_size[1]同時(shí)在讀取圖片后統(tǒng)一檢查寬度高度不一致的先 resize 或剔除。我在做多相機(jī)標(biāo)定的時(shí)候還遇到過把兩臺(tái)相機(jī)圖片混到同一個(gè)角點(diǎn)列表里的情況這類問題很難從報(bào)錯(cuò)直接看出來所以每次批量操作前都要用一條 assert 保證所有圖片的 shape 一致。6. 進(jìn)階驗(yàn)證用 drawChessboardCorners 配合外參繪制三維坐標(biāo)軸讓標(biāo)定結(jié)果眼見為實(shí)標(biāo)定完成后內(nèi)參和畸變系數(shù)已經(jīng)落盤但很多人不知道還能從 rvecs、tvecs 里榨出更多驗(yàn)證信息。一個(gè)很實(shí)用的進(jìn)階操作是選一張棋盤格圖片用 solvePnP 重新求一次該圖的外參然后調(diào)用 cv2.drawFrameAxes 把棋盤格坐標(biāo)系的三維坐標(biāo)軸疊加到原圖上。如果標(biāo)定結(jié)果正確坐標(biāo)軸的 X、Y 軸會(huì)精確貼合棋盤格平面Z 軸垂直于板面指向相機(jī)方向。# 選第 5 張圖做外參驗(yàn)證 idx 5 ret, rvec, tvec cv2.solvePnP( object_points[idx], image_points[idx], camera_matrix, dist_coeffs ) # 坐標(biāo)軸長(zhǎng)度設(shè)為1.5倍格子邊長(zhǎng) axis_length 1.5 * square_size img_axis cv2.drawFrameAxes( img_orig.copy(), camera_matrix, dist_coeffs, rvec, tvec, axis_length )這張疊加了坐標(biāo)軸的圖有三個(gè)用途。第一判斷外參是否存在 180 度翻轉(zhuǎn)棋盤格坐標(biāo)系 Z 軸應(yīng)該指向相機(jī)如果畫出來的 Z 軸指向遠(yuǎn)離相機(jī)的方向說明棋盤格角點(diǎn)順序在檢測(cè)時(shí)發(fā)生了整體旋轉(zhuǎn)需要重新檢查 pattern_size 的傳入順序或者圖像輸入方向。第二直觀判斷棋盤格平面與相機(jī)是否對(duì)稱坐標(biāo)軸在畫面的位置會(huì)隨 tvec 變化如果 tvec 對(duì)應(yīng)的棋盤格位置與實(shí)際圖像中棋盤格的位置明顯錯(cuò)位說明外參求解存在粗差。第三這套坐標(biāo)軸可視化可以直接遷移到后續(xù)的 AR 注冊(cè)或機(jī)械臂手眼標(biāo)定里——用同一個(gè)相機(jī)模型疊加虛擬物體時(shí)如果虛擬物體不能穩(wěn)定貼合棋盤格平面基本可以肯定是內(nèi)參標(biāo)定有殘留誤差。從那以后我每次標(biāo)定完都會(huì)強(qiáng)制走一遍“rms 檢查 → 逐張誤差分析 → 去畸變直線驗(yàn)證 → drawFrameAxes 可視化”這套流程四步全過才把相機(jī)參數(shù)寫進(jìn)配置文件。任何一步出問題我都優(yōu)先懷疑棋盤格平面度和圖片覆蓋范圍而不是直接調(diào)優(yōu)化參數(shù)。這套流程看著多實(shí)際跑一遍不到五分鐘卻能省下后面整周的在三維測(cè)量精度上排查的時(shí)間。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取