雙攝像頭立體成像(三)-畸變矯正與立體校訂

畸變矯正是上一篇博文的遺留問題,當畸變係數和內外參數矩陣標定完成後,就應該進行畸變的矯正,以達到消除畸變的目的,此其一。html

在該系列第一部分的博文中介紹的立體成像原理中提到,要經過兩幅圖像估計物點的深度信息,就必須在兩幅圖像中準確的匹配到同一物點,這樣才能根據該物點在兩幅圖像中的位置關係,計算物體深度。爲了下降匹配的計算量,兩個攝像頭的成像平面應處於同一平面。可是,單單依靠嚴格的擺放攝像頭來達到這個目的顯然有些困難。立體校訂就是利用幾何圖形變換(Geometric Image Transformation)關係,使得原先不知足上述位置關係的兩幅圖像知足該條件,此其二。c++

數學原理

  • 畸變矯正(compensate lens distortion)

畸變矯正的方法就是用上一篇博文給出的公式對像素位置進行從新映射。這裏從新寫出從新映射的公式。算法

先矯正徑向畸變,canvas

再矯正切向畸變,數據結構

  • 立體矯正(stereo rectify)

立體矯正可以有效下降立體匹配的計算量,立體矯正的具體做用見下圖,ide

立體矯正前,
函數

立體矯正後,優化

立體矯正的算法原理沒有詳細瞭解,此處從略。spa

OpenCV相關函數說明

  • 畸變矯正函數 undistort() 

 undistort() 是獨立的一個畸變矯正函數,一次性能夠完成映射矩陣的求解和從新映射。下面咱們還會看到把這兩步分開來作的函數。3d

調用方法,

 

  1. src-輸入未通過矯正的圖像
  2. dst-通過矯正後輸出的圖像
  3. cameraMatrix-標定而獲得的攝像機矩陣
  4. distCoeffs-標定獲得的攝像機畸變矩陣
  5. 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 )

 

  1. objectPoints- vector<point3f> 型的數據結構,存儲標定角點在世界座標系中的位置
  2. imagePoints1- vector<vector<point2f>> 型的數據結構,存儲標定角點在第一個攝像機下的投影后的亞像素座標
  3. imagePoints2- vector<vector<point2f>> 型的數據結構,存儲標定角點在第二個攝像機下的投影后的亞像素座標
  4. cameraMatrix1-輸入/輸出型的第一個攝像機的相機矩陣。若是CV_CALIB_USE_INTRINSIC_GUESS , CV_CALIB_FIX_ASPECT_RATIO ,CV_CALIB_FIX_INTRINSIC , or CV_CALIB_FIX_FOCAL_LENGTH其中的一個或多個標誌被設置,該攝像機矩陣的一些或所有參數須要被初始化
  5. distCoeffs1-第一個攝像機的輸入/輸出型畸變向量。根據矯正模型的不一樣,輸出向量長度由標誌決定
  6. cameraMatrix2-輸入/輸出型的第二個攝像機的相機矩陣。參數意義同第一個相機矩陣類似
  7. distCoeffs2-第一個攝像機的輸入/輸出型畸變向量。根據矯正模型的不一樣,輸出向量長度由標誌決定
  8. imageSize-圖像的大小
  9. R-輸出型,第一和第二個攝像機之間的旋轉矩陣
  10. T-輸出型,第一和第二個攝像機之間的平移矩陣
  11. E-輸出型,基本矩陣
  12. F-輸出型,基礎矩陣
  13. term_crit-迭代優化的終止條件
  14. 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 )

 

  1. cameraMatrix1-第一個攝像機的攝像機矩陣
  2. distCoeffs1-第一個攝像機的畸變向量
  3. cameraMatrix2-第二個攝像機的攝像機矩陣
  4. distCoeffs1-第二個攝像機的畸變向量
  5. imageSize-圖像大小
  6. R- stereoCalibrate() 求得的R矩陣
  7. T- stereoCalibrate() 求得的T矩陣
  8. R1-輸出矩陣,第一個攝像機的校訂變換矩陣(旋轉變換)
  9. R2-輸出矩陣,第二個攝像機的校訂變換矩陣(旋轉矩陣)
  10. P1-輸出矩陣,第一個攝像機在新座標系下的投影矩陣
  11. P2-輸出矩陣,第二個攝像機在想座標系下的投影矩陣
  12. Q-4*4的深度差別映射矩陣
  13. flags-可選的標誌有兩種零或者 CV_CALIB_ZERO_DISPARITY ,若是設置 CV_CALIB_ZERO_DISPARITY 的話,該函數會讓兩幅校訂後的圖像的主點有相同的像素座標。不然該函數會水平或垂直的移動圖像,以使得其有用的範圍最大
  14. alpha-拉伸參數。若是設置爲負或忽略,將不進行拉伸。若是設置爲0,那麼校訂後圖像只有有效的部分會被顯示(沒有黑色的部分),若是設置爲1,那麼就會顯示整個圖像。設置爲0~1之間的某個值,其效果也居於二者之間。
  15. newImageSize-校訂後的圖像分辨率,默認爲原分辨率大小。
  16. validPixROI1-可選的輸出參數,Rect型數據。其內部的全部像素都有效
  17. validPixROI2-可選的輸出參數,Rect型數據。其內部的全部像素都有效
  • 映射變換計算函數 initUndistortRectifyMap() 

該函數功能是計算畸變矯正和立體校訂的映射變換。

調用方法,

void initUndistortRectifyMap(InputArray cameraMatrix, InputArray 
                 distCoeffs, InputArray R,InputArray newCameraMatrix, Size size, int 
                 m1type, OutputArray map1, OutputArray map2)

 

  1. cameraMatrix-攝像機參數矩陣
  2. distCoeffs-畸變參數矩陣
  3. R- stereoCalibrate() 求得的R矩陣
  4. newCameraMatrix-矯正後的攝像機矩陣(可省略)
  5. Size-沒有矯正圖像的分辨率
  6. m1type-第一個輸出映射的數據類型,能夠爲 CV_32FC1  或  CV_16SC2 
  7. map1-輸出的第一個映射變換
  8. map2-輸出的第二個映射變換
  • 幾何變換函數 remap() 

調用方法,

void remap(InputArray src, OutputArray dst, InputArray map1, InputArray 
      map2, int interpolation,int borderMode=BORDER_CONSTANT, const Scalar& 
      borderValue=Scalar())

 

  1. src-原圖像
  2. dst-幾何變換後的圖像
  3. map1-第一個映射,不管是點(x,y)或者單純x的值都須要是CV_16SC2 ,CV_32FC1 , 或 CV_32FC2類型
  4. map2-第二個映射,y須要是CV_16UC1 , CV_32FC1類型。或者當map1是點(x,y)時,map2爲空。
  5. interpolation-插值方法,可是不支持最近鄰插值
  6. 剩下兩個我也沒看懂,可是通常示例程序中不會設置

基於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);
View Code

 

子函數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 }
View Code

 

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 }

 

  • 仿真結果

相關文章
相關標籤/搜索