畸變矯正是上一篇博文的遺留問題,當畸變系數和內外參數矩陣標定完成后,就應該進行畸變的矯正,以達到消除畸變的目的,此其一。
在該系列第一部分的博文中介紹的立體成像原理中提到,要通過兩幅圖像估計物點的深度信息,就必須在兩幅圖像中准確的匹配到同一物點,這樣才能根據該物點在兩幅圖像中的位置關系,計算物體深度。為了降低匹配的計算量,兩個攝像頭的成像平面應處於同一平面。但是,單單依靠嚴格的擺放攝像頭來達到這個目的顯然有些困難。立體校正就是利用幾何圖形變換(Geometric Image Transformation)關系,使得原先不滿足上述位置關系的兩幅圖像滿足該條件,此其二。
數學原理
- 畸變矯正(compensate lens distortion)
畸變矯正的方法就是用上一篇博文給出的公式對像素位置進行重新映射。這里重新寫出重新映射的公式。
先矯正徑向畸變,


再矯正切向畸變,


- 立體矯正(stereo rectify)
立體矯正能夠有效降低立體匹配的計算量,立體矯正的具體作用見下圖,
立體矯正前,
立體矯正后,

立體矯正的算法原理沒有詳細了解,此處從略。
OpenCV相關函數說明
- 畸變矯正函數 undistort()
undistort() 是獨立的一個畸變矯正函數,一次性可以完成映射矩陣的求解和重新映射。下面我們還會看到把這兩步分開來做的函數。
調用方法,
- src-輸入未經過矯正的圖像
- dst-經過矯正后輸出的圖像
- cameraMatrix-標定而得到的攝像機矩陣
- distCoeffs-標定得到的攝像機畸變矩陣
- newCameraMatrix-輸入矯正后的攝像機矩陣(可以省略)
- 立體標定函數 stereoCalibrate()
stereoCalibrate() 是用來標定一個立體攝像頭的,也就是同時標定兩個攝像頭。標定的結果除了能夠求出兩個攝像頭的內外參數矩陣,跟能夠得出兩個攝像頭的位置關系R,T。
調用方法,
double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1,
InputArrayOfArrays imagePoints2, InputOutputArray cameraMatrix1,InputOutputArray distCoeffs1,
InputOutputArray cameraMatrix2, InputOutputArray distCoeffs2, Size imageSize, OutputArray R, OutputArray T, OutputArray E, OutputArray F, TermCriteria criteria= TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 1e-6), int flags=CALIB_FIX_INTRINSIC )
- objectPoints- vector<point3f> 型的數據結構,存儲標定角點在世界坐標系中的位置
- imagePoints1- vector<vector<point2f>> 型的數據結構,存儲標定角點在第一個攝像機下的投影后的亞像素坐標
- imagePoints2- vector<vector<point2f>> 型的數據結構,存儲標定角點在第二個攝像機下的投影后的亞像素坐標
- cameraMatrix1-輸入/輸出型的第一個攝像機的相機矩陣。如果
CV_CALIB_USE_INTRINSIC_GUESS , CV_CALIB_FIX_ASPECT_RATIO ,CV_CALIB_FIX_INTRINSIC , or CV_CALIB_FIX_FOCAL_LENGTH其中的一個或多個標志被設置,該攝像機矩陣的一些或全部參數需要被初始化 - distCoeffs1-第一個攝像機的輸入/輸出型畸變向量。根據矯正模型的不同,輸出向量長度由標志決定
- cameraMatrix2-輸入/輸出型的第二個攝像機的相機矩陣。參數意義同第一個相機矩陣相似
- distCoeffs2-第一個攝像機的輸入/輸出型畸變向量。根據矯正模型的不同,輸出向量長度由標志決定
- imageSize-圖像的大小
- R-輸出型,第一和第二個攝像機之間的旋轉矩陣
- T-輸出型,第一和第二個攝像機之間的平移矩陣
- E-輸出型,基本矩陣
- F-輸出型,基礎矩陣
- term_crit-迭代優化的終止條件
-
flag-
- CV_CALIB_FIX_INTRINSIC 如果該標志被設置,那么就會固定輸入的cameraMatrix和distCoeffs不變,只求解$$$R,T,E,F$$$.
- CV_CALIB_USE_INTRINSIC_GUESS 根據用戶提供的cameraMatrix和distCoeffs為初始值開始迭代
- CV_CALIB_FIX_PRINCIPAL_POINT 迭代過程中不會改變主點的位置
- CV_CALIB_FIX_FOCAL_LENGTH 迭代過程中不會改變焦距
- CV_CALIB_SAME_FOCAL_LENGTH 強制保持兩個攝像機的焦距相同
- CV_CALIB_ZERO_TANGENT_DIST 切向畸變保持為零
- CV_CALIB_FIX_K1,...,CV_CALIB_FIX_K6 迭代過程中不改變相應的值。如果設置了 CV_CALIB_USE_INTRINSIC_GUESS 將會使用用戶提供的初始值,否則設置為零
- CV_CALIB_RATIONAL_MODEL 畸變模型的選擇,如果設置了該參數,將會使用更精確的畸變模型,distCoeffs的長度就會變成8
- 立體校正函數 stereoRectify()
stereoRectify() 的作用是為每個攝像頭計算立體校正的映射矩陣。所以其運行結果並不是直接將圖片進行立體矯正,而是得出進行立體矯正所需要的映射矩陣。
調用方法,
void stereoRectify(InputArray cameraMatrix1, InputArray distCoeffs1, InputArray cameraMatrix2,InputArray distCoeffs2, Size imageSize, InputArray R, InputArray T,OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags=CALIB_ZERO_DISPARITY, double alpha=-1,
Size newImageSize=Size(), Rect* validPixROI1=0, Rect* validPixROI2=0 )
- cameraMatrix1-第一個攝像機的攝像機矩陣
- distCoeffs1-第一個攝像機的畸變向量
- cameraMatrix2-第二個攝像機的攝像機矩陣
- distCoeffs1-第二個攝像機的畸變向量
- imageSize-圖像大小
- R- stereoCalibrate() 求得的R矩陣
- T- stereoCalibrate() 求得的T矩陣
- R1-輸出矩陣,第一個攝像機的校正變換矩陣(旋轉變換)
- R2-輸出矩陣,第二個攝像機的校正變換矩陣(旋轉矩陣)
- P1-輸出矩陣,第一個攝像機在新坐標系下的投影矩陣
- P2-輸出矩陣,第二個攝像機在想坐標系下的投影矩陣
- Q-4*4的深度差異映射矩陣
- flags-可選的標志有兩種零或者 CV_CALIB_ZERO_DISPARITY ,如果設置 CV_CALIB_ZERO_DISPARITY 的話,該函數會讓兩幅校正后的圖像的主點有相同的像素坐標。否則該函數會水平或垂直的移動圖像,以使得其有用的范圍最大
- alpha-拉伸參數。如果設置為負或忽略,將不進行拉伸。如果設置為0,那么校正后圖像只有有效的部分會被顯示(沒有黑色的部分),如果設置為1,那么就會顯示整個圖像。設置為0~1之間的某個值,其效果也居於兩者之間。
- newImageSize-校正后的圖像分辨率,默認為原分辨率大小。
- validPixROI1-可選的輸出參數,
Rect型數據。其內部的所有像素都有效 - validPixROI2-可選的輸出參數,
Rect型數據。其內部的所有像素都有效
- 映射變換計算函數 initUndistortRectifyMap()
該函數功能是計算畸變矯正和立體校正的映射變換。
調用方法,
void initUndistortRectifyMap(InputArray cameraMatrix, InputArray distCoeffs, InputArray R,InputArray newCameraMatrix, Size size, int m1type, OutputArray map1, OutputArray map2)
- cameraMatrix-攝像機參數矩陣
- distCoeffs-畸變參數矩陣
- R- stereoCalibrate() 求得的R矩陣
- newCameraMatrix-矯正后的攝像機矩陣(可省略)
- Size-沒有矯正圖像的分辨率
- m1type-第一個輸出映射的數據類型,可以為 CV_32FC1 或 CV_16SC2
- map1-輸出的第一個映射變換
- map2-輸出的第二個映射變換
- 幾何變換函數 remap()
調用方法,
void remap(InputArray src, OutputArray dst, InputArray map1, InputArray map2, int interpolation,int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
- src-原圖像
- dst-幾何變換后的圖像
- map1-第一個映射,無論是點(x,y)或者單純x的值都需要是
CV_16SC2,CV_32FC1, 或CV_32FC2類型 - map2-第二個映射,y需要是
CV_16UC1,CV_32FC1類型。或者當map1是點(x,y)時,map2為空。 - interpolation-插值方法,但是不支持最近鄰插值
- 剩下兩個我也沒看懂,但是一般示例程序中不會設置
基於OpenCV的仿真
- 仿真程序
1 int main() 2 { 3 //initialize some parameters 4 bool okcalib = false; 5 Mat intrMatFirst, intrMatSec, distCoeffsFirst, distCoffesSec; 6 Mat R, T, E, F, RFirst, RSec, PFirst, PSec, Q; 7 vector<vector<Point2f>> imagePointsFirst, imagePointsSec; 8 vector<vector<Point3f>> ObjectPoints(1); 9 Rect validRoi[2]; 10 Size imageSize; 11 int cameraIdFirst = 0, cameraIdSec = 1; 12 double rms = 0; 13 14 //get pictures and calibrate 15 vector<string> imageList; 16 string filename = "stereo_calib.xml"; 17 bool okread = readStringList(filename, imageList); 18 if (!okread || imageList.empty()) 19 { 20 cout << "can not open " << filename << " or the string list is empty" << endl; 21 return false; 22 } 23 if (imageList.size() % 2 != 0) 24 { 25 cout << "Error: the image list contains odd (non-even) number of elements\n"; 26 return false; 27 } 28 29 //calibrate 30 cout << "calibrate left camera..." << endl; 31 okcalib = calibrate(intrMatFirst, distCoeffsFirst, imagePointsFirst, ObjectPoints, 32 imageSize, cameraIdFirst, imageList); 33 34 if (!okcalib) 35 { 36 cout << "fail to calibrate left camera" << endl; 37 return -1; 38 } 39 else 40 { 41 cout << "calibrate the right camera..." << endl; 42 } 43 44 okcalib = calibrate(intrMatSec, distCoffesSec, imagePointsSec, ObjectPoints, 45 imageSize, cameraIdSec, imageList); 46 47 if (!okcalib) 48 { 49 cout << "fail to calibrate the right camera" << endl; 50 return -1; 51 } 52 destroyAllWindows(); 53 54 //estimate position and orientation 55 cout << "estimate position and orientation of the second camera" << endl 56 << "relative to the first camera..." << endl; 57 rms = stereoCalibrate(ObjectPoints, imagePointsFirst, imagePointsSec, 58 intrMatFirst, distCoeffsFirst, intrMatSec, distCoffesSec, 59 imageSize, R, T, E, F, CV_CALIB_FIX_INTRINSIC, 60 TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 30, 1e-6)); 61 cout << "done with RMS error=" << rms << endl; 62 63 //stereo rectify 64 cout << "stereo rectify..." << endl; 65 stereoRectify(intrMatFirst, distCoeffsFirst, intrMatSec, distCoffesSec, imageSize, R, T, RFirst, 66 RSec, PFirst, PSec, Q, 0, 1, imageSize, &validRoi[0], &validRoi[1]); 67 68 //read pictures for 3d-reconstruction 69 namedWindow("canvas", 1); 70 cout << "read the picture for 3d-reconstruction..."; 71 Mat canvas(imageSize.height, imageSize.width * 2, CV_8UC3), viewLeft, viewRight; 72 Mat canLeft = canvas(Rect(0, 0, imageSize.width, imageSize.height)); 73 Mat canRight = canvas(Rect(imageSize.width, 0, imageSize.width, imageSize.height)); 74 viewLeft = imread(imageList[cameraIdFirst], 1); 75 viewRight = imread(imageList[cameraIdSec], 1); 76 viewLeft.copyTo(canLeft); 77 viewRight.copyTo(canRight); 78 cout << "done" << endl; 79 imshow("canvas", canvas); 80 waitKey(50); 81 82 //stereoRectify 83 Mat rmapFirst[2], rmapSec[2], rviewFirst, rviewSec; 84 initUndistortRectifyMap(intrMatFirst, distCoeffsFirst, RFirst, PFirst, 85 imageSize, CV_16SC2, rmapFirst[0], rmapFirst[1]); 86 initUndistortRectifyMap(intrMatSec, distCoffesSec, RSec, PSec, 87 imageSize, CV_16SC2, rmapSec[0], rmapSec[1]); 88 remap(viewLeft, rviewFirst, rmapFirst[0], rmapFirst[1], INTER_LINEAR); 89 remap(viewRight, rviewSec, rmapSec[0], rmapSec[1], INTER_LINEAR); 90 rviewFirst.copyTo(canLeft); 91 rviewSec.copyTo(canRight); 92 93 rectangle(canLeft, validRoi[0], Scalar(255, 0, 0), 3, 8); 94 rectangle(canRight, validRoi[1], Scalar(255, 0, 0), 3, 8); 95 for (int j = 0; j <= canvas.rows; j += 16) 96 line(canvas, Point(0, j), Point(canvas.cols, j), Scalar(0, 255, 0), 1, 8); 97 cout << "stereo rectify done" << endl; 98 imshow("canvas", canvas); 99 waitKey(50);
子函數calibrate()和calcChessboardCorners()分別是用來表達相機和計算objectPoints的。函數體如下,
1 bool calibrate(Mat& intrMat, Mat& distCoeffs, vector<vector<Point2f>>& imagePoints, 2 vector<vector<Point3f>>& ObjectPoints, Size& imageSize,const int cameraId , 3 vector<string> imageList) 4 { 5 int w = 6; 6 int h = 9; 7 double rms = 0; 8 9 Size boardSize; 10 boardSize.width = w; 11 boardSize.height = h; 12 vector<Point2f> pointBuf; 13 float squareSize = 1.f; 14 vector<Mat> rvecs, tvecs; 15 bool ok = false; 16 17 int nImages = (int)imageList.size() / 2; 18 namedWindow("View", 1); 19 for (int i = 0; i<nImages ; i++) 20 { 21 Mat view, viewGray; 22 view = imread(imageList[i*2+cameraId], 1); 23 imageSize = view.size(); 24 cvtColor(view, viewGray, COLOR_BGR2GRAY); 25 26 bool found = findChessboardCorners(view, boardSize, pointBuf, 27 CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE); 28 29 if (found) 30 { 31 cornerSubPix(viewGray, pointBuf, Size(11, 11), 32 Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1)); 33 drawChessboardCorners(view, boardSize, Mat(pointBuf), found); 34 bitwise_not(view, view); 35 imagePoints.push_back(pointBuf); 36 cout << '.'; 37 } 38 imshow("View", view); 39 waitKey(50); 40 } 41 //calculate chessboardCorners 42 calcChessboardCorners(boardSize, squareSize, ObjectPoints[0]); 43 ObjectPoints.resize(imagePoints.size(), ObjectPoints[0]); 44 45 rms = calibrateCamera(ObjectPoints, imagePoints, imageSize, intrMat, distCoeffs, 46 rvecs, tvecs); 47 ok = checkRange(intrMat) && checkRange(distCoeffs); 48 49 if (ok) 50 { 51 cout << "done with RMS error=" << rms << endl; 52 return true; 53 } 54 else 55 return false; 56 }
1 static void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners) 2 { 3 corners.resize(0); 4 for (int i = 0; i < boardSize.height; i++) //height和width位置不能顛倒 5 for (int j = 0; j < boardSize.width; j++) 6 { 7 corners.push_back(Point3f(j*squareSize, i*squareSize, 0)); 8 } 9 }
- 仿真結果

