背景:最近的項目中用到的圖像去畸變的知識,剛開始是直接調用opencv中提供的函數cv::initUndistortRectifyMap()和cv::remap()函數,實現圖像的全局去畸變,但是由于圖像的分辨率很高,再加上,實際過程中我們只用到了很小一塊的圖像,所以為了降低電腦的負擔,則想選用局部圖像去畸變的方法來代替全局圖像去畸變。
于是我想到了《SLAM十四講》書中在相機模型中有相關的去畸變的代碼,我就直接使用測試了,效果還不錯。
void LocalZoneUndistortion(cv::Mat& localImage, cv::Mat& undistortLocalImage) {undistortLocalImage = cv::Mat(localImage.rows, localImage.cols, CV_8UC1);for (int row = 0; row < localImage.rows; ++row) {for (int col = 0; col < localImage.cols; ++col) {double x = (col - cx_) / fx_;double y = (row - cy_) / fy_;double r = sqrt(x * x + y * y);double xDistorted = x * (1 + k1_ * r * r + k2_ * r * r * r * r) + 2 * p1_* x * y + p2_ * (r * r + 2 * x * x);double yDistorted = y * (1 + k1_ * r * r + k2_ * r * r * r * r) + p1_ * (r * r + 2 * y * y) + 2 * p2_* x * y;double uDistorted = fx_ * xDistorted + cx_;double vDistorted = fy_ * yDistorted + cy_;if (uDistorted >= 0 && vDistorted >= 0 && uDistorted < localImage.cols && vDistorted < localImage.rows) {undistortLocalImage.at<uchar>(row, col) = localImage.at<uchar>((int)vDistorted,(int)uDistorted);}else {undistortLocalImage.at<uchar>(row, col) = 0;} }}}
當然在使用的時候要把相機內參和畸變系數傳進去呦,我這里是把它們定義為成員變量,就沒有傳進去。其實我之前一直對這個去畸變的過程很困惑的,這明明是一個添加畸變的過程,為啥可以達到去畸變的效果呢?時隔多日之后再次使用這個函數,其實我還是沒怎么關心她背后的邏輯,直到我同時又需要完成對點添加畸變的任務,這個時候我才重新審視這兩個過程背后的邏輯。
添加畸變的函數如下
void Tracking::DistortPoints(cv::Point2f& undistPoint, cv::Point2f& distPoint) {double x = (undistPoint.x - cx_) / fx_;double y = (undistPoint.y - cy_) / fy_;double r2 = x * x + y * y;// Radial distorsiondouble xDistort = x * (1 + k1_ * r2 + k2_ * r2 * r2 + k3_ * r2 * r2 * r2);double yDistort = y * (1 + k1_ * r2 + k2_ * r2 * r2 + k3_ * r2 * r2 * r2);// Tangential distorsionxDistort = xDistort + (2 * p1_ * x * y + p2_ * (r2 + 2 * x * x));yDistort = yDistort + (p1_ * (r2 + 2 * y * y) + 2 * p2_ * x * y);// Back to absolute coordinates.xDistort = xDistort * fx_ + cx_;yDistort = yDistort * fy_ + cy_;distPoint = cv::Point2f((float)xDistort, (float)yDistort);};
我們平時遇到的都是將帶有畸變的點轉換為不帶畸變的點,很少會遇到在不帶畸變的點上添加畸變,因為我要在一個帶有畸變的圖像上標注一個不帶畸變的點,那標注出的位置和我們真正的目標之間就有一定的偏差了,這個時候只有在這些不帶畸變的點上添加上畸變,這樣才能適應帶有畸變的圖像。
我們可以發現,上面兩個過程,去畸變和添加畸變剛開始的部分都是在添加畸變的過程,
首先是將像素位置點,從像平面內轉換到歸一化平面內,
double x = (col - cx_) / fx_;
double y = (row - cy_) / fy_;
double x = (undistPoint.x - cx_) / fx_;
double y = (undistPoint.y - cy_) / fy_;
然后再分別添加,徑向畸變和切向畸變,
// Radial distorsion
double xDistort = x * (1 + k1_ * r2 + k2_ * r2 * r2 + k3_ * r2 * r2 * r2);
double yDistort = y * (1 + k1_ * r2 + k2_ * r2 * r2 + k3_ * r2 * r2 * r2);// Tangential distorsion
xDistort = xDistort + (2 * p1_ * x * y + p2_ * (r2 + 2 * x * x));
yDistort = yDistort + (p1_ * (r2 + 2 * y * y) + 2 * p2_ * x * y);
然后在把點轉換到像平面內。
// Back to absolute coordinates.
xDistort = xDistort * fx_ + cx_;
yDistort = yDistort * fy_ + cy_;
以上這些步驟兩個過程是一樣的,最后一步是不一樣的。
在去畸變過程中,我們相當于給不帶畸變的坐標PointA添加畸變,得到帶有畸變的點PointB,而這正是我們直接獲取得到的帶有畸變的圖像I上的點,這個時候我們可以將帶有畸變的圖像上的PointB位置處的灰度值映射賦值給PointA的位置處,這樣操作圖像I上所有有效點,就完成了對帶有畸變的圖像I的去畸變操作。
if (uDistorted >= 0 && vDistorted >= 0 && uDistorted < localImage.cols && vDistorted < localImage.rows) {undistortLocalImage.at<uchar>(row, col) = localImage.at<uchar>((int)vDistorted,(int)uDistorted);
} else {undistortLocalImage.at<uchar>(row, col) = 0;
}
而添加畸變的過程就比較好理解了,就是給不帶有畸變的點添加上徑向畸變和切向畸變,直接得到帶畸變的點。