戰(zhàn)解析)
簡介本資源是一套面向計(jì)算機(jī)、電子信息工程及數(shù)學(xué)等專業(yè)本科生的圖像畸變校正技術(shù)實(shí)踐代碼適用于課程設(shè)計(jì)、期末大作業(yè)或畢業(yè)設(shè)計(jì)中相機(jī)標(biāo)定與圖像幾何校正模塊的參考實(shí)現(xiàn)。資源聚焦于Matlab平臺(tái)下的畸變建模與逆向映射校正流程涵蓋徑向與切向畸變參數(shù)的模擬與補(bǔ)償幫助學(xué)習(xí)者理解OpenCV標(biāo)定原理在MATLAB中的輕量級(jí)復(fù)現(xiàn)邏輯。壓縮包僅含2個(gè)文件1個(gè)說明文檔txt 1個(gè)核心函數(shù)m文件總大小僅1KB結(jié)構(gòu)精簡便于快速導(dǎo)入調(diào)試與二次開發(fā)。已有476人下載學(xué)習(xí)適合作為入門級(jí)圖像處理項(xiàng)目的技術(shù)錨點(diǎn)——提供可運(yùn)行的完整函數(shù)框架、清晰的調(diào)用說明及關(guān)鍵注釋支持用戶基于實(shí)際相機(jī)參數(shù)修改模型系數(shù)、替換輸入圖像并驗(yàn)證校正效果是理解圖像幾何變換底層邏輯的實(shí)用腳手架。 平時(shí)做圖像處理相關(guān)的工作一提到畸變校正很多人的第一反應(yīng)就是“找標(biāo)定板、跑OpenCV、算內(nèi)參”。這套流程本身不復(fù)雜但真正落地的時(shí)候細(xì)節(jié)特別多。尤其當(dāng)你手里只有一個(gè)Matlab的源碼包沒有配套文檔也沒有一步步的教程時(shí)往往是看著一堆.m文件不知從何下手。這篇博文我就以“基于Matlab實(shí)現(xiàn)圖像畸變校正技術(shù)源碼”這個(gè)項(xiàng)目為例從頭到尾拆一遍畸變是怎么產(chǎn)生的、Matlab標(biāo)定工具箱的底層邏輯是什么、源碼里到底哪些代碼是核心、跑通之后怎么二次開發(fā)成自己的工具。不管你是正在做圖像處理大作業(yè)的學(xué)生還是工作中突然要處理攝像頭畸變問題的工程師這篇文章應(yīng)該能幫你省下不少彎路。先說清楚這個(gè)項(xiàng)目能做什么。它本質(zhì)上解決的是“攝像頭拍出來的圖像是彎的”這個(gè)問題。比如你拍一張棋盤格靠近畫面邊緣的格子明顯向外凸拍一張A4紙紙張的邊在鏡頭里是弧形而不是直線?;冃U前堰@種扭曲消除掉讓圖像中的直線恢復(fù)成直線還原真實(shí)的幾何關(guān)系。這個(gè)技術(shù)在機(jī)器視覺測(cè)量、自動(dòng)駕駛感知、無人機(jī)航測(cè)、醫(yī)學(xué)影像拼接、手機(jī)攝影后期等場(chǎng)景里都是剛需。如果你拿到這個(gè)Matlab源碼包說明你可能已經(jīng)找到了一個(gè)可運(yùn)行的起點(diǎn)接下來要做的就是理解它、跑通它然后把它改造成自己的工具。1. 拿到這個(gè)項(xiàng)目先別急著跑——整體設(shè)計(jì)與需求拆解1.1 這個(gè)項(xiàng)目到底在解決什么問題圖像畸變并不是一個(gè)“看起來不舒服”這么簡單的問題。在機(jī)器視覺里畸變直接影響測(cè)量精度。舉個(gè)例子你用工業(yè)相機(jī)做零件尺寸檢測(cè)鏡頭視野邊緣的畸變可能讓一個(gè)標(biāo)準(zhǔn)10mm的零件在畫面不同位置測(cè)出來是9.8mm到10.2mm不等。如果不做校正后端算法再怎么優(yōu)化閾值測(cè)量結(jié)果都是不可信的。這個(gè)Matlab項(xiàng)目做的事情簡單說就是建立“真實(shí)三維點(diǎn)”到“圖像二維像素”之間的映射然后反過來把畸變圖像恢復(fù)成理想圖像。它通常包含兩條技術(shù)路線一種是基于標(biāo)定板的傳統(tǒng)標(biāo)定法另一種是無需標(biāo)定板的自標(biāo)定法。這個(gè)項(xiàng)目既然打包成源碼大概率走的是第一種路線也就是張正友標(biāo)定法。為什么選這條路因?yàn)樗€(wěn)定、可重復(fù)、精度高而且Matlab的Camera Calibrator工具箱本身就是基于這個(gè)原理實(shí)現(xiàn)的。你不需要自己推導(dǎo)全部數(shù)學(xué)公式但需要理解標(biāo)定板上的角度點(diǎn)如何一步步變成相機(jī)內(nèi)參和畸變系數(shù)。1.2 為什么選擇Matlab而不是OpenCV或Python很多人在論壇上問過這個(gè)問題同樣的功能OpenCV幾十行代碼就能調(diào)用為什么還要用Matlab我的觀點(diǎn)是要看你的應(yīng)用場(chǎng)景和自身基礎(chǔ)。Matlab做畸變校正有三個(gè)不可替代的優(yōu)勢(shì)。第一Computer Vision Toolbox里的標(biāo)定工具是圖形化交互的你能實(shí)時(shí)看到每張標(biāo)定板圖像的重投影誤差、剔除不合格的圖片這個(gè)交互過程對(duì)理解原理非常有幫助。第二Matlab的矩陣運(yùn)算和可視化天然適合圖像處理你可以在命令行里直接查看中間結(jié)果比如每張圖的角點(diǎn)位置、重投影誤差分布調(diào)試效率比C高很多。第三Matlab的文檔寫得非常詳細(xì)每個(gè)函數(shù)都有對(duì)應(yīng)的論文引用和數(shù)學(xué)公式方便你從代碼反推原理。當(dāng)然OpenCV的優(yōu)勢(shì)是部署方便、運(yùn)行時(shí)不需要授權(quán)、可以與C工程集成。但如果你是在做實(shí)驗(yàn)驗(yàn)證、算法研究或者課程大作業(yè)Matlab的效率和可視化能力是碾壓級(jí)的。這個(gè)項(xiàng)目把源碼給你說明作者希望你不僅能跑通還能看懂每一步的中間結(jié)果——這恰恰是Matlab最擅長的事情。1.3 項(xiàng)目的整體流程與模塊劃分拿到源碼包之后先不要急著運(yùn)行。我一般會(huì)先做一件事把.m文件按功能分類。這個(gè)項(xiàng)目里通常會(huì)有這么幾類模塊圖像采集與讀取模塊讀取標(biāo)定板圖片或視頻幀轉(zhuǎn)換成灰度圖角點(diǎn)檢測(cè)模塊用detectCheckerboardPoints之類的函數(shù)找到棋盤格的交叉點(diǎn)參數(shù)求解模塊通過estimateCameraParameters計(jì)算內(nèi)參矩陣、畸變系數(shù)畸變校正模塊用得到的參數(shù)對(duì)目標(biāo)圖像做去畸變處理核心函數(shù)是undistortImage或undistortFisheyeImage效果評(píng)估模塊計(jì)算重投影誤差、繪制校正前后的對(duì)比圖。把這五個(gè)模塊在腦子里過一遍你就知道這個(gè)項(xiàng)目的核心技術(shù)棧是什么了。接下來我逐個(gè)模塊講原理和實(shí)操而不是讓你像無頭蒼蠅一樣去翻代碼。2. 畸變校正的核心原理與關(guān)鍵參數(shù)2.1 相機(jī)成像模型從三維世界到二維像素要理解畸變校正必須先把相機(jī)成像的完整鏈路搞清楚。一個(gè)三維空間點(diǎn)怎么變成圖像上的像素分四步。第一步世界坐標(biāo)系轉(zhuǎn)到相機(jī)坐標(biāo)系。世界坐標(biāo)系是你自己定義的一個(gè)基準(zhǔn)比如標(biāo)定板平面的中心。相機(jī)坐標(biāo)系則是以相機(jī)光心為原點(diǎn)、光軸為Z軸的坐標(biāo)系。這兩者之間是一個(gè)剛體變換有6個(gè)自由度3個(gè)旋轉(zhuǎn)繞X、Y、Z軸的角度和3個(gè)平移。這6個(gè)參數(shù)就是相機(jī)的外參。第二步相機(jī)坐標(biāo)系轉(zhuǎn)到歸一化圖像平面。把三維點(diǎn)除以Z坐標(biāo)得到一個(gè)歸一化坐標(biāo)(x, y)表示這個(gè)點(diǎn)在焦距為1的虛擬成像平面上投影的位置。第三步加上畸變影響。真實(shí)的鏡頭會(huì)因?yàn)橹圃旃に?、光學(xué)設(shè)計(jì)等原因讓光線發(fā)生非理想的折射導(dǎo)致成像點(diǎn)偏離理想位置。這個(gè)偏離量就是畸變。畸變可以是徑向的圖像向外凸或向內(nèi)凹也可以是切向的鏡頭與成像平面不平行導(dǎo)致的。第四步歸一化坐標(biāo)轉(zhuǎn)換到像素坐標(biāo)。乘以內(nèi)參矩陣把物理坐標(biāo)映射到像素坐標(biāo)系。內(nèi)參包括fx、fy焦距與像素尺寸的比值、cx、cy主點(diǎn)偏移還有一個(gè)s傾斜系數(shù)大多數(shù)情況下視為0。整個(gè)過程中畸變校正在第三步前后做手術(shù)。校正畸變就是根據(jù)畸變模型反算回去給定一個(gè)畸變像素點(diǎn)找到它在理想針孔模型下應(yīng)該在的位置。2.2 畸變到底從哪里來徑向畸變與切向畸變畸變主要分兩類。徑向畸變是影響最大的它來自鏡頭的曲率。簡單的說光線穿過鏡頭邊緣時(shí)折射角度與理想情況偏差更大所以越靠近畫面邊緣畸變?cè)矫黠@。徑向畸變用多項(xiàng)式模型描述Matlab里用的公式是x_distorted x * (1 k1 * r^2 k2 * r^4 k3 * r^6)y_distorted y * (1 k1 * r^2 k2 * r^4 k3 * r^6)這里的r是點(diǎn)到主點(diǎn)的距離k1、k2、k3是徑向畸變系數(shù)。k1主導(dǎo)畸變的幅度k2、k3修正邊緣區(qū)域的非線性變化。如果你的鏡頭是普通工業(yè)鏡頭或手機(jī)鏡頭k3通常用不到只有魚眼鏡頭這種超大視場(chǎng)角才會(huì)需要三階以上的多項(xiàng)式。切向畸變則是因?yàn)殓R頭組裝時(shí)透鏡與成像平面不平行導(dǎo)致的。它的模型是x_distorted x (2 * p1 * x * y p2 * (r^2 2 * x^2))y_distorted y (p1 * (r^2 2 * y^2) 2 * p2 * x * y)p1、p2是切向畸變系數(shù)。理論上切向畸變很小但如果鏡頭受過磕碰或者組裝精度差p1、p2的值會(huì)明顯變大。Matlab的標(biāo)定工具箱默認(rèn)采用的就是這個(gè)布朗-康拉迪Brown-Conrady模型也就是5個(gè)畸變系數(shù)[k1, k2, p1, p2, k3]。2.3 關(guān)鍵參數(shù)的含義與作用源碼里你可能會(huì)看到相機(jī)參數(shù)對(duì)象cameraParams它包含幾個(gè)重要字段IntrinsicMatrix內(nèi)參矩陣[fx, 0, 0; s, fy, 0; cx, cy, 1]。fx是X方向焦距像素單位fy是Y方向焦距。如果傳感器像素是正方形fx和fy基本相等。RadialDistortion徑向畸變系數(shù)[k1, k2, k3]。TangentialDistortion切向畸變系數(shù)[p1, p2]。WorldPoints標(biāo)定板坐標(biāo)系下的實(shí)際角點(diǎn)坐標(biāo)每個(gè)格子的大小在這里體現(xiàn)。比如你用的棋盤格邊長為25毫米那么WorldPoints里相鄰兩個(gè)點(diǎn)的坐標(biāo)差就是25。MeanReprojectionError平均重投影誤差單位是像素。這個(gè)數(shù)值越小越好一般小于0.5就算不錯(cuò)。還有兩個(gè)重要概念要區(qū)分EstimateSkew是否估計(jì)傾斜系數(shù)和EstimateTangentialDistortion是否估計(jì)切向畸變。默認(rèn)情況下網(wǎng)格不對(duì)稱時(shí)傾斜系數(shù)可能不為零但大多數(shù)場(chǎng)景建議不估計(jì)傾斜系數(shù)只估計(jì)切向畸變。視角越大估計(jì)的參數(shù)越不穩(wěn)定這是經(jīng)驗(yàn)之談。3. 實(shí)操過程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 環(huán)境準(zhǔn)備與源碼工程結(jié)構(gòu)我這里用的是Matlab R2021a及以上版本因?yàn)樾掳姹緦?duì)estimateCameraParameters的接口做了優(yōu)化函數(shù)簽名更清晰。如果你用的是R2018b之前的版本部分函數(shù)名可能不一樣需要注意兼容性。拿到源碼包后把它解壓到工作目錄然后在Matlab里把當(dāng)前文件夾切換到該項(xiàng)目目錄。我習(xí)慣性地先跑一遍項(xiàng)目自帶的主腳本看看有沒有demo.m或main.m。如果沒有就自己做一次“盲操”。一般而言這個(gè)項(xiàng)目最少需要以下幾個(gè)文件main.m入口腳本負(fù)責(zé)讀圖、調(diào)用標(biāo)定函數(shù)、輸出校正結(jié)果detectCorners.m角點(diǎn)檢測(cè)函數(shù)封裝了detectCheckerboardPointscalibrateCamera.m相機(jī)標(biāo)定函數(shù)封裝了estimateCameraParametersrectifyImage.m畸變校正函數(shù)封裝了undistortImageshowResults.m可視化結(jié)果函數(shù)。如果源碼包里這些文件不全你也完全可以自己補(bǔ)上。重要的是理解標(biāo)定的完整流程而不是死記某一個(gè)文件。3.2 標(biāo)定板圖像的采集與處理標(biāo)定是畸變校正的第一步而標(biāo)定板的采集質(zhì)量直接決定最終效果。這部分我用過很多次踩的坑也不少先分享幾個(gè)關(guān)鍵點(diǎn)。第一標(biāo)定板要用棋盤格或者圓點(diǎn)陣列Matlab默認(rèn)支持棋盤格。格子尺寸要均勻格子數(shù)量建議在9x6到12x9之間太小了角點(diǎn)數(shù)量不足擬合不穩(wěn)定太大了標(biāo)定板占比過大邊緣區(qū)域的采樣不夠。第二采集圖像數(shù)量至少在15到20張之間。不要只拍正對(duì)著相機(jī)的圖那樣畸變信息不足。要改變標(biāo)定板的位姿左右傾斜、上下傾斜、旋轉(zhuǎn)、遠(yuǎn)近移動(dòng)讓標(biāo)定板覆蓋畫面各個(gè)區(qū)域尤其是四角和邊緣。第三每張圖里標(biāo)定板不能太小至少要占整個(gè)畫面的20%以上。否則角點(diǎn)檢測(cè)精度差標(biāo)定出來的畸變系數(shù)也不可靠。第四光照要均勻避免反光。棋盤格如果出現(xiàn)高光角點(diǎn)檢測(cè)會(huì)失敗如果暗部噪點(diǎn)太多角點(diǎn)坐標(biāo)會(huì)偏移。采集完成后把所有圖片放在一個(gè)文件夾里用imageDatastore批量讀取。這一步的代碼大概是imageDir fullfile(pwd, calibration_images); images imageDatastore(imageDir); numImages numel(images.Files);然后你可以用detectCheckerboardPoints檢測(cè)每張圖的角點(diǎn)并可視化一下確認(rèn)檢測(cè)結(jié)果沒有出現(xiàn)“錯(cuò)誤配對(duì)”。[imagePoints, boardSize] detectCheckerboardPoints(images.Files); figure; imshow(images.Files{1}); hold on; plot(imagePoints(:, 1, 1), imagePoints(:, 2, 1), ro);我看到很多人在這一步就直接進(jìn)入標(biāo)定了這是個(gè)壞習(xí)慣。檢測(cè)完之后一定要逐張檢查角點(diǎn)是否貼合棋格交叉點(diǎn)偏離太多的圖像直接剔除寧可少用一張也不要硬撐著用。3.3 相機(jī)標(biāo)定從角點(diǎn)提取到參數(shù)求解標(biāo)定的核心是把“圖像上的角點(diǎn)”和“世界坐標(biāo)系下的角點(diǎn)”一一對(duì)應(yīng)起來然后通過最小化重投影誤差求解內(nèi)外參。這個(gè)優(yōu)化問題的數(shù)學(xué)本質(zhì)是最小二乘問題Matlab內(nèi)部用的是Levenberg-Marquardt算法。生成世界坐標(biāo)點(diǎn)的方式是squareSize 25; % 單位毫米 worldPoints generateCheckerboardPoints(boardSize, squareSize);注意boardSize這里是棋盤格內(nèi)部角點(diǎn)的數(shù)量比如你用的是9x6的棋盤格10x7個(gè)格子boardSize就是[9, 6]。調(diào)用generateCheckerboardPoints會(huì)生成這些內(nèi)部角點(diǎn)的世界坐標(biāo)。假設(shè)每一行imagePoints存儲(chǔ)了一幅圖像所有角點(diǎn)的像素坐標(biāo)那么標(biāo)定調(diào)用就像這樣cameraParams estimateCameraParameters(imagePoints, worldPoints, ... EstimateSkew, false, ... EstimateTangentialDistortion, true, ... NumRadialDistortionCoefficients, 2);這里我把徑向畸變系數(shù)設(shè)為2也就是只估計(jì)k1、k2不估計(jì)k3。原因是普通鏡頭的k3數(shù)值極小參與優(yōu)化反而會(huì)讓數(shù)值穩(wěn)定性變差。只有魚眼鏡頭才需要三階徑向畸變模型。如果你用的是廣角鏡頭可以嘗試把NumRadialDistortionCoefficients設(shè)為3然后比較重投影誤差的變化。標(biāo)定完成后cameraParams對(duì)象里已經(jīng)包含了所有參數(shù)。你可以調(diào)用showExtrinsics看每張圖像上標(biāo)定板相對(duì)于相機(jī)的位置關(guān)系如果某些位姿看起來明顯不合理比如標(biāo)定板跑到相機(jī)后面去了說明那張圖像的角度異常需要剔除重跑。還有一件事必須做檢查MeanReprojectionError。這個(gè)值通常在0.1到0.5像素之間。如果大于1說明標(biāo)定質(zhì)量堪憂優(yōu)先排查圖像清晰度、光照和角點(diǎn)檢測(cè)精度。3.4 畸變校正的實(shí)現(xiàn)與效果評(píng)估標(biāo)定完就是真正的主角——畸變校正。Matlab直接提供了一行命令J undistortImage(I, cameraParams);這個(gè)函數(shù)內(nèi)部做的事情可以拆解成兩步。第一步建立畸變圖像到理想圖像的映射表也就是反畸變映射inverse mapping。第二步對(duì)理想圖像上的每一個(gè)像素在畸變圖像上找到對(duì)應(yīng)的采樣點(diǎn)然后進(jìn)行插值。我建議你在用官方函數(shù)之外自己手動(dòng)實(shí)現(xiàn)一遍校正過程這樣才能真正理解。手動(dòng)實(shí)現(xiàn)的核心步驟如下偽代碼function imgUndistorted myUndistort(img, cameraParams) % 獲取內(nèi)參和畸變系數(shù) K cameraParams.IntrinsicMatrix; k cameraParams.RadialDistortion; p cameraParams.TangentialDistortion; [h, w, ~] size(img); % 生成理想圖像的像素網(wǎng)格 [X, Y] meshgrid(1:w, 1:h); % 像素坐標(biāo)轉(zhuǎn)歸一化坐標(biāo) x_norm (X - K(1,3)) / K(1,1); y_norm (Y - K(2,3)) / K(2,2); % 計(jì)算畸變量 r2 x_norm.^2 y_norm.^2; radial 1 k(1) * r2 k(2) * r2.^2; x_dist x_norm .* radial 2 * p(1) * x_norm .* y_norm p(2) * (r2 2 * x_norm.^2); y_dist y_norm .* radial p(1) * (r2 2 * y_norm.^2) 2 * p(2) * x_norm .* y_norm; % 把畸變歸一化坐標(biāo)轉(zhuǎn)回像素坐標(biāo) u K(1,1) * x_dist K(1,3); v K(2,2) * y_dist K(2,3); % 插值采樣 imgUndistorted zeros(size(img), like, img); for c 1:size(img, 3) imgUndistorted(:, :, c) interp2(double(img(:, :, c)), u, v, linear, 0); end end這個(gè)實(shí)現(xiàn)里最關(guān)鍵的幾個(gè)點(diǎn)是meshgrid生成目標(biāo)圖像坐標(biāo)把目標(biāo)坐標(biāo)反算到源圖像坐標(biāo)再用interp2采樣。如果你的圖像是彩色圖需要對(duì)每個(gè)通道分別插值這也是代碼里循環(huán)通道的原因。interp2的插值方式我建議用linear而不是nearest。nearest速度快但圖像邊緣會(huì)有鋸齒linear就能滿足大多數(shù)場(chǎng)景需求。如果你對(duì)圖像質(zhì)量要求很高可以試試cubic但計(jì)算量會(huì)大一些。效果評(píng)估方面除了肉眼觀察直線是否變直更客觀的方法是使用標(biāo)定得到的重投影誤差以及在畸變較大的圖像區(qū)域放置已知幾何形狀的物體對(duì)比校正前后的尺寸偏差。工業(yè)測(cè)量場(chǎng)景里一般會(huì)用校正后的圖像去測(cè)量標(biāo)準(zhǔn)量塊看偏差是否在允許范圍內(nèi)。4. 源碼二次開發(fā)與工程化落地4.1 把標(biāo)定流程封裝成通用函數(shù)很多教程到校正完了就結(jié)束了但實(shí)際項(xiàng)目里你往往不會(huì)只標(biāo)定一次。攝像頭安裝位置變了、鏡頭焦距調(diào)了甚至溫度變化都會(huì)影響內(nèi)參所以標(biāo)定流程最好是模塊化的、可重復(fù)執(zhí)行的。封裝這一塊我的實(shí)踐經(jīng)驗(yàn)是寫一個(gè)calibrateFromFolder(folderPath, squareSize)函數(shù)輸入是圖片文件夾路徑和棋格邊長輸出是cameraParams對(duì)象。函數(shù)內(nèi)部自動(dòng)遍歷文件夾、檢測(cè)角點(diǎn)、剔除異常、標(biāo)定并打印報(bào)告。這樣不管是實(shí)驗(yàn)室還是產(chǎn)線上只要把新的標(biāo)定圖像丟進(jìn)文件夾運(yùn)行一次就能得到最新的標(biāo)定參數(shù)。如果你有多個(gè)相機(jī)更建議給每個(gè)相機(jī)單獨(dú)建一個(gè)配置結(jié)構(gòu)體保存內(nèi)參和畸變系數(shù)為.mat文件后續(xù)校正時(shí)直接load即可不需要每次都重新標(biāo)定。save(camera_params.mat, cameraParams);4.2 批量處理圖片與視頻流標(biāo)定完后用戶自己會(huì)有很多待校正的圖。一個(gè)非常實(shí)用的擴(kuò)展是批量校正腳本。用imageDatastore讀取一個(gè)文件夾里所有圖片然后循環(huán)調(diào)用校正函數(shù)最后用imwrite保存到新文件夾。注意保持目錄結(jié)構(gòu)一致方便下游處理。視頻流的情況稍微麻煩一點(diǎn)。視頻的每一幀都和時(shí)間相關(guān)你需要考慮要不要用VideoReader逐幀讀取、校正、再寫入VideoWriter。這里有個(gè)性能問題純Matlab逐幀插值很慢1080p視頻一秒鐘可能只有兩三幀的處理速度。這時(shí)候有幾個(gè)優(yōu)化方向使用parfor并行處理多個(gè)幀把校正映射表即u和v矩陣一次性算好對(duì)不同幀只做interp2避免重復(fù)計(jì)算畸變坐標(biāo)用gpuArray把插值放到GPU上。映射表復(fù)用的思路特別有效。因?yàn)閮?nèi)參不變畸變坐標(biāo)關(guān)系是固定的所以完全可以在內(nèi)存里保存一張u和v的查找表后面每幀圖像直接查表采樣。這樣做速度至少能提升3到5倍。4.3 向FPGA或其他語言遷移的路徑在相關(guān)熱搜詞里我看到有人搜“圖像畸變校正fpga”說明很多人關(guān)心的是硬實(shí)時(shí)處理。Matlab驗(yàn)證算法沒問題后如果要部署到FPGA整個(gè)流程大概是這樣。第一步把算法定點(diǎn)化。Matlab里跑的是浮點(diǎn)FPGA做浮點(diǎn)成本很高需要把歸一化坐標(biāo)、畸變系數(shù)、插值權(quán)重全部轉(zhuǎn)換成固定點(diǎn)數(shù)。你可以用fi對(duì)象在Matlab里做量化仿真對(duì)比浮點(diǎn)和定點(diǎn)的輸出差異確定合適的定點(diǎn)位寬。第二步把插值算法換成雙線性插值硬件實(shí)現(xiàn)簡單通常只需要4個(gè)乘法器和幾個(gè)加法器。在Matlab里interp2的線性插值和FPGA上的雙線性插值誤差很小可以接受。第三步計(jì)算流水線化?;冃U贔PGA上通常按行處理只緩存必要的行數(shù)據(jù)而不是整幅圖像。每來一個(gè)像素時(shí)鐘根據(jù)查找表得到目標(biāo)坐標(biāo)再從緩存窗口中取4個(gè)像素做雙線性插值。如果你是用Vivado HLS甚至可以把Matlab函數(shù)轉(zhuǎn)成C再綜合成RTL但這里有兩個(gè)坑一是動(dòng)態(tài)內(nèi)存分配在HLS里很難綜合二是interp2這種函數(shù)需要自己重寫??傮w而言Matlab在算法驗(yàn)證階段的優(yōu)勢(shì)明顯但離真正的硬件部署還有一段距離。5. 常見問題與排查技巧實(shí)錄5.1 標(biāo)定精度不夠高重投影誤差偏大重投影誤差在0.5像素以下通常沒問題但如果到了幾個(gè)像素排查方向有幾個(gè)。先檢查標(biāo)定板圖像數(shù)量和質(zhì)量。我見過有人只用6張圖就跑標(biāo)定誤差當(dāng)然大。辦法很直接刪除那些標(biāo)定板細(xì)節(jié)不清晰的圖片補(bǔ)充多角度、不同距離的圖片重新跑一輪。另外一個(gè)容易被忽略的因素是標(biāo)定板不平整。如果打印的棋盤格貼在彎曲的紙板上角點(diǎn)坐標(biāo)本身就帶了系統(tǒng)誤差怎么優(yōu)化都救不回來。建議用玻璃基板或鋁合金基板。其次確認(rèn)你的solver選項(xiàng)是否合理。estimateCameraParameters內(nèi)部會(huì)做非線性優(yōu)化。如果圖像數(shù)量足夠但誤差仍然大試著提高徑向畸變系數(shù)個(gè)數(shù)或者把WorldUnits設(shè)置正確毫米還是米。還有一點(diǎn)確保boardSize傳入的是角點(diǎn)數(shù)量而不是格子數(shù)量。很多人在這犯錯(cuò)導(dǎo)致世界坐標(biāo)整體偏移標(biāo)定出的焦距錯(cuò)誤。5.2 校正后圖像邊緣出現(xiàn)奇怪的翹曲這種現(xiàn)象通常發(fā)生在廣角鏡頭的標(biāo)定中尤其是靠近圖像四角的位置。原因有兩個(gè)一是鏡頭的徑向畸變模型階數(shù)不足四角的實(shí)際畸變比三階多項(xiàng)式描述的更復(fù)雜二是標(biāo)定板圖像中邊緣區(qū)域采樣不足模型對(duì)邊緣區(qū)域的擬合能力弱。解決辦法是增加不同傾斜角度的標(biāo)定板圖像讓四角區(qū)域有更多角點(diǎn)參與優(yōu)化。還有一個(gè)非常實(shí)用的技巧先不估計(jì)切向畸變只估計(jì)徑向畸變矯正后再查剩余誤差。如果邊緣還是翹曲再打開切向畸變估計(jì)。順序反過來往往會(huì)讓切向參數(shù)吸收徑向誤差導(dǎo)致參數(shù)失真。5.3 角點(diǎn)檢測(cè)失敗或識(shí)別錯(cuò)誤detectCheckerboardPoints有一個(gè)特性棋盤格必須有足夠的對(duì)比度且必須是完整的內(nèi)部角點(diǎn)。如果你的標(biāo)定板不完整地出現(xiàn)在畫面邊緣檢測(cè)就會(huì)失敗。辦法是裁剪掉那些標(biāo)定板太靠近圖像邊界的圖片或者干脆用單獨(dú)一個(gè)文件夾存放可用圖片用imageDatastore循環(huán)檢測(cè)每張圖時(shí)加入異常處理。如果檢測(cè)出來角點(diǎn)數(shù)目不對(duì)大概率是boardSize傳錯(cuò)了。比如棋盤格是內(nèi)角點(diǎn)9x6但你傳成了10x7函數(shù)會(huì)去找10x7的角點(diǎn)陣列匹配不上就會(huì)報(bào)錯(cuò)。還有一個(gè)很隱蔽的情況當(dāng)棋盤格旋轉(zhuǎn)45度時(shí)Matlab會(huì)把它識(shí)別成另一個(gè)方向的棋盤如果兩張圖互補(bǔ)旋轉(zhuǎn)容易出問題。目前新版本Matlab用了更魯棒的檢測(cè)方法老版本需要確保每張圖棋盤格朝向一致。5.4 校正后效果反而變差這通常不是代碼的問題而是參數(shù)選擇的問題。當(dāng)你把NumRadialDistortionCoefficients設(shè)為3時(shí)k3參與優(yōu)化會(huì)導(dǎo)致近中心的像素被過度修正。比如一張照片中心區(qū)域的直線本來只有很小的彎曲但k3引入后中心區(qū)域被反向拉彎整張圖看起來更奇怪。解決方法是默認(rèn)使用2個(gè)徑向畸變系數(shù)除非你明確知道鏡頭是魚眼。如果標(biāo)定后校正效果依然不行另一個(gè)可能是你用了無畸變鏡頭或已做過后置軟件校正的圖片。這種情況下強(qiáng)行套徑向畸變模型會(huì)破壞原圖這時(shí)候需要先判斷圖像是否已經(jīng)有校正標(biāo)記比如EXIF里有無畸變校正字段。6. 最后的幾點(diǎn)實(shí)戰(zhàn)心得做畸變校正這幾年我最大的體會(huì)是這個(gè)技術(shù)看起來是幾個(gè)公式的事實(shí)際做起來全是細(xì)節(jié)。標(biāo)定板的平整度、圖片的張數(shù)、畸變模型的階數(shù)、插值方式、查找表優(yōu)化每一個(gè)點(diǎn)都能讓結(jié)果差出一大截。這個(gè)Matlab源碼項(xiàng)目的價(jià)值在于給你一個(gè)完整的起點(diǎn)而不是終點(diǎn)。跑通它只代表理解了流程真正的功力在于怎么把它調(diào)整到適用于你自己的鏡頭和場(chǎng)景。如果你是在做圖像處理大作業(yè)我建議你花時(shí)間把myUndistort自己實(shí)現(xiàn)一遍不要只調(diào)庫。手動(dòng)實(shí)現(xiàn)會(huì)讓你對(duì)歸一化坐標(biāo)、畸變系數(shù)、插值這三個(gè)環(huán)節(jié)有非常直觀的認(rèn)識(shí)這在面試和答辯時(shí)是加分項(xiàng)。最后分享一個(gè)小技巧標(biāo)定完成后一定要把標(biāo)定參數(shù)保存下來并且在同一場(chǎng)景下驗(yàn)證一次畸變校正效果。比如放一把直尺在畫面邊緣位置看看校正后直尺是否真的是直線。這種物理驗(yàn)證雖然簡單但比任何指標(biāo)都更能說明問題。后續(xù)如果你換了鏡頭或者調(diào)整了焦距記得重新標(biāo)定參數(shù)不可沿用這是最容易忽略的一點(diǎn)。本文還有配套的精品資源點(diǎn)擊獲取