相機標定之畸變矯正與反畸變計算
相機標定問題已經是比較成熟的問題,OpenCV中提供了比較全面的標定、矯正等函數接口。但是如果我想通過一張矯正好的圖像,想獲得原始的畸變圖,卻沒有比較好的方法,這裏討論了點的畸變和反畸變問題。
1.問題提出:給定一些已經經過矯正的二維點集,如何獲得矯正前帶畸變的二維點集?
2.理論基礎:理論基礎無它,就是相機的小孔成像模型和畸變參數模型,
這裏需要註意,k1,k2等為徑向畸變參數,p1,p2為切向畸變參數,s1,s2為薄棱鏡畸變參數(常忽略),x‘為理想的無畸變的圖像像素坐標或者點(Points)坐標,x‘‘是帶透鏡畸變參數的圖像像素或點坐標,上述公式詳盡表達了它們之間的關系。通俗點講,x‘,y‘是指通過針孔模型求出來的理想點的位置,x‘‘,y‘‘是其真實位置,所以要通過後面的畸變模型進行近似,獲得x‘‘,y‘‘的值,但是在實際應用時,x‘‘,y‘‘通常是已知的,即我們通過相機獲得了帶畸變的圖像,但是我們需要反算回去得到x‘,y‘,OpenCV提供了這樣的過程函數接口,可以直接使用。
而這裏想做的是通過矯正圖得到畸變圖點,即通過x‘,y’得到x‘‘,y‘‘,所以只要通過上述公式進行求解就行了。
3.求解過程:
直接給出相關程序:
void myDistortPoints(const std::vector<cv::Point2d> & src, std::vector<cv::Point2d> & dst, const cv::Mat & cameraMatrix, const cv::Mat & distortionCoeff) { dst.clear(); double fx = cameraMatrix.at<double>(0, 0); double fy = cameraMatrix.at<double>(1, 1); double ux = cameraMatrix.at<double>(0, 2); double uy = cameraMatrix.at<double>(1, 2); double k1 = distortionCoeff.at<double>(0, 0); double k2 = distortionCoeff.at<double>(0, 1); double p1 = distortionCoeff.at<double>(0, 2); double p2 = distortionCoeff.at<double>(0, 3); double k3 = distortionCoeff.at<double>(0, 4); double k4;// double k5;// double k6;// for (unsigned int i = 0; i < src.size(); i++) { const cv::Point2d & p = src[i];
//獲取的點通常是圖像的像素點,所以需要先通過小孔相機模型轉換到歸一化坐標系下; double xCorrected = (p.x - ux) / fx; double yCorrected = (p.y - uy) / fy; double xDistortion, yDistortion;
//我們已知的是經過畸變矯正或理想點的坐標; double r2 = xCorrected*xCorrected + yCorrected*yCorrected; double deltaRa = 1. + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2; double deltaRb = 1 / (1. + k4 * r2 + k5 * r2 * r2 + k6 * r2 * r2 * r2); double deltaTx = 2. * p1 * xCorrected * yCorrected + p2 * (r2 + 2. * xCorrected * xCorrected); double deltaTy = p1 * (r2 + 2. * yCorrected * yCorrected) + 2. * p2 * xCorrected * yCorrected;
//下面為畸變模型; xDistortion = xCorrected * deltaRa * deltaRb + deltaTx; yDistortion = yCorrected * deltaRa * deltaRb + deltaTy;
//最後再次通過相機模型將歸一化的坐標轉換到像素坐標系下; xDistortion = xDistortion * fx + ux; yDistortion = yDistortion * fy + uy; dst.push_back(cv::Point2d(xDistortion, yDistortion)); } }
4.存在的問題:經過上面的過程,我們就獲得了在畸變原圖中點的坐標,但是有一點值得註意的是,我們將獲得的畸變圖中的點經過OpenCV提供的UndistortPoints()函數進行矯正的時候,不能得到原始的點,即這裏存在誤差。這種誤差是由於自己寫的畸變模型和OpenCV提供的畸變函數模型不同導致的,有興趣的可以查看OpenCV源碼,你會發現後面關於 r 的平方和 r 的四次方,六次方在OpenCV中都是平方項,所以計算結果存在誤差,這裏有兩個解決辦法,第一是修改上述代碼,統一改成平方項;第二就是自己實現UndistortPoints函數:
void myUndistortPoints(const std::vector<cv::Point2d> & src, std::vector<cv::Point2d> & dst, const cv::Mat & cameraMatrix, const cv::Mat & distortionCoeff) { dst.clear(); double fx = cameraMatrix.at<double>(0, 0); double fy = cameraMatrix.at<double>(1, 1); double ux = cameraMatrix.at<double>(0, 2); double uy = cameraMatrix.at<double>(1, 2); double k1 = distortionCoeff.at<double>(0, 0); double k2 = distortionCoeff.at<double>(0, 1); double p1 = distortionCoeff.at<double>(0, 2); double p2 = distortionCoeff.at<double>(0, 3); double k3 = distortionCoeff.at<double>(0, 4); double k4; double k5; double k6; for (unsigned int i = 0; i < src.size(); i++) { const cv::Point2d & p = src[i];
//首先進行坐標轉換; double xDistortion = (p.x - ux) / fx; double yDistortion = (p.y - uy) / fy; double xCorrected, yCorrected; double x0 = xDistortion; double y0 = yDistortion;
//這裏使用叠代的方式進行求解,因為根據2中的公式直接求解是困難的,所以通過設定初值進行叠代,這也是OpenCV的求解策略; for (int j = 0; j < 10; j++) { double r2 = xDistortion*xDistortion + yDistortion*yDistortion; double distRadialA = 1 / (1. + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2); double distRadialB = 1. + k4 * r2 + k5 * r2 * r2 + k6 * r2 * r2 * r2; double deltaX = 2. * p1 * xDistortion * yDistortion + p2 * (r2 + 2. * xDistortion * xDistortion); double deltaY = p1 * (r2 + 2. * yDistortion * yDistortion) + 2. * p2 * xDistortion * yDistortion; xCorrected = (x0 - deltaX)* distRadialA * distRadialB; yCorrected = (y0 - deltaY)* distRadialA * distRadialB; xDistortion = xCorrected; yDistortion = yCorrected; }
//進行坐標變換; xCorrected = xCorrected * fx + ux; yCorrected = yCorrected * fy + uy; dst.push_back(cv::Point2d(xCorrected, yCorrected)); } }
上面的叠代求解過程是值得學習的地方,因為所求的x本身就被包含在r中,所以直接求解變得幾乎不可能,而上面的叠代方法提供了一種解決方法,當叠代次數足夠多時,求解接近精確值。
5.結果展示:下面給出通過設定好相機的畸變參數和內參矩陣,給定一幅帶有點線的原圖,通過3生成畸變圖,然後再通過4反算原圖的過程:
註:上面的線是在一個全黑的Mat矩陣中畫點生成的。
相機標定之畸變矯正與反畸變計算