轉載:http://blog.csdn.net/loser__wang/article/details/52836042
代碼參考鄒宇華老師的雙目,Camera calibration With OpenCV,Camera Calibration and 3D Reconstruction部分,按照自己的情況進行了更改。
如果讀者是想快速工程使用,那可以看我的這篇部落格,如果想要系統學習,請先看相關教材,并輔以鄒宇華老師的部落格。
準備環境
因為本文是進行雙目立體視覺實驗,是以你必須有兩個攝像頭,單攝像頭标定的實驗可以看我的前一篇文章。解決方案,并且有特别需要注意的點我都會仔細說明。
雙目攝像頭準備
- 直接購買兩個普通的usb攝像頭,這個方案是我最早采用的,但是遇到不少坑。
-
- 注意不要太廣角,因為廣角的畸變會很大,這在後面比對的時候帶來很大的問題
-
- 安裝的時候注意把兩個安裝的較為平行,雖然可以矯正出來,但是當然還是自身比較平行的好
-
- 同時也要注意兩個攝像頭的軸距,不要太遠或者太近,5-10cm為較為适宜的
-
- 分辨率可以高一點,但實驗上我把他限制在320*240,其實可以标定采用高分辨率,比對采用低分辨率
- 購買淘寶的一種雙目攝像頭
-
- 同樣注意是否廣角,這點我覺得有點坑,因為不想涉及到打廣告,淘寶那家內建的很好,但是,最廣角的貌似有點問題,我換成不太廣的了
标定闆準備
- 淘寶購買标定闆,一個字貴,土豪可以忽略。
-
自己列印,那麼可以按照我的前一篇文章裡的方法準備,注意記好到底是幾乘幾的。
這個幾乘以幾是按照黑白格子的交叉的數量算的,例如,标定闆就是9*6的,記好。
雙目标定環節
最下面給了所有實驗的源代碼,代碼有點亂,還是分開來說。雙目标定環節就是需要得到兩個錄影機各自的内參,以及他們倆的外參數。
雙目錄影機讀取
直接上雙目讀取的代碼,我把兩個攝像頭的分辨率都改了一下。這個得看具體攝像頭的支援程度,有的無法改,你改了也沒效果。
#include "opencv2/opencv.hpp"
#include "opencv2/opencv.hpp"
#include "opencv2/highgui.hpp"
using namespace std;
using namespace cv;
int main(){
VideoCapture camera_l();
VideoCapture camera_r();
if (!camera_l.isOpened()) { cout << "No left camera!" << endl; return -; }
if (!camera_r.isOpened()) { cout << "No right camera!" << endl; return -; }
camera_l.set(CAP_PROP_FRAME_WIDTH, );
camera_l.set(CAP_PROP_FRAME_HEIGHT, );
camera_r.set(CAP_PROP_FRAME_WIDTH, );
camera_r.set(CAP_PROP_FRAME_HEIGHT, );
cv::Mat frame_l, frame_r;
while () {
camera_l >> frame_l;
camera_r >> frame_r;
imshow("Left Camera", frame_l);
imshow("Right Camera", frame_r);
char key = waitKey();
if (key == || key == 'q' || key == 'Q') //Allow ESC to quit
break;
}
return ;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
本來雙目錄影機讀取起來并沒有什麼可說的,但是我在後期做别的實驗的時候發現,錄影機有時候讀取的有問題,無法完成初始化,如果确定兩個攝像頭分辨讀取沒有問題,隻是無法一起讀取,有如下兩種解決方案。
* 鄒老師的方案
* 在兩個錄影機分别都可以運作之後,我們改變初始化部分的代碼,把兩個if判斷改成如下兩行。
while (!camera_l.isOpened()) { camera_l.open(); };
while (!camera_r.isOpened()) { camera_r.open(); };
- 1
- 2
- 1
- 2
實驗效果如下:

注意到,右攝像頭的圖像相對于左攝像頭的圖像有點“左移”。這點自己分析一下原因。很重要,如果不是這項,你下面的工作會白做。因為比對的算法就是遵循這種“左移”的。
标定環節
基本上的流程就是讀取左右攝像頭,分别檢測棋盤的角點,當同時都檢測到完整的角點之後,進行精細化處理,得到更精确地角點并存儲。攢夠一定的數量之後(20-30)之後進行參數計算。并将參數進行存儲。還是直接上代碼,并說明一些實作的細節部分。
請慢慢閱讀。
一上來的這個ChessboardStable 是用來檢測棋盤格是否穩定的,因為在我的試驗中,雙目攝像頭是用手拿着的,或多或少會有一些抖動,這樣如果隻是檢測是否存在角點,可能會通過不是很清晰穩定的圖像進行分析,這樣會帶來比較大的誤差,如果通過一個隊列判斷是否穩定,則可以避免這種誤差。我是簡單粗暴的使用vector代替隊列的。
後面的部分需要注意的就是boardSize,squareSize 需要設定為你的标定闆對應的尺寸,我拿A4紙簡單的列印的一份,每個格子的大小經過測量時26mm,你可以根據你自己的标定闆進行相應的設定。
#include <string>
#include <stdio.h>
#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace std;
using namespace cv;
vector<vector<Point2f> >corners_l_array, corners_r_array;
int array_index = ;
bool ChessboardStable(vector<Point2f>corners_l, vector<Point2f>corners_r){
if (corners_l_array.size() < ){
corners_l_array.push_back(corners_l);
corners_r_array.push_back(corners_r);
return false;
}
else{
corners_l_array[array_index % ] = corners_l;
corners_r_array[array_index % ] = corners_r;
array_index++;
double error = ;
for (int i = ; i < corners_l_array.size(); i++){
for (int j = ; j < corners_l_array[i].size(); j++){
error += abs(corners_l[j].x - corners_l_array[i][j].x) + abs(corners_l[j].y - corners_l_array[i][j].y);
error += abs(corners_r[j].x - corners_r_array[i][j].x) + abs(corners_r[j].y - corners_r_array[i][j].y);
}
}
if (error < )
{
corners_l_array.clear();
corners_r_array.clear();
array_index = ;
return true;
}
else
return false;
}
}
int main(){
VideoCapture camera_l();
VideoCapture camera_r();
while (!camera_l.isOpened()) { camera_l.open(); };
while (!camera_r.isOpened()) { camera_r.open(); };
camera_l.set(CAP_PROP_FRAME_WIDTH, );
camera_l.set(CAP_PROP_FRAME_HEIGHT, );
camera_r.set(CAP_PROP_FRAME_WIDTH, );
camera_r.set(CAP_PROP_FRAME_HEIGHT, );
Mat frame_l, frame_r;
Size boardSize(, );
const float squareSize = ; // Set this to your actual square size
vector<vector<Point2f> > imagePoints_l;
vector<vector<Point2f> > imagePoints_r;
int nimages = ;
while (){
camera_l >> frame_l;
camera_r >> frame_r;
bool found_l = false, found_r = false;
vector<Point2f>corners_l, corners_r;
found_l = findChessboardCorners(frame_l, boardSize, corners_l,
CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);
found_r = findChessboardCorners(frame_r, boardSize, corners_r,
CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);
if (found_l && found_r &&ChessboardStable(corners_l, corners_r)) {
Mat viewGray;
cvtColor(frame_l, viewGray, COLOR_BGR2GRAY);
cornerSubPix(viewGray, corners_l, Size(, ),
Size(-, -), TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, , ));
cvtColor(frame_r, viewGray, COLOR_BGR2GRAY);
cornerSubPix(viewGray, corners_r, Size(, ),
Size(-, -), TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, , ));
imagePoints_l.push_back(corners_l);
imagePoints_r.push_back(corners_r);
++nimages;
frame_l += ;
frame_r += ;
drawChessboardCorners(frame_l, boardSize, corners_l, found_l);
drawChessboardCorners(frame_r, boardSize, corners_r, found_r);
putText(frame_l, to_string(nimages), Point(, ), , , Scalar(, , ));
putText(frame_r, to_string(nimages), Point(, ), , , Scalar(, , ));
imshow("Left Camera", frame_l);
imshow("Right Camera", frame_r);
char c = (char)waitKey();
if (c == || c == 'q' || c == 'Q') //Allow ESC to quit
exit(-);
if (nimages >= )
break;
}
else{
drawChessboardCorners(frame_l, boardSize, corners_l, found_l);
drawChessboardCorners(frame_r, boardSize, corners_r, found_r);
putText(frame_l, to_string(nimages), Point(, ), , , Scalar(, , ));
putText(frame_r, to_string(nimages), Point(, ), , , Scalar(, , ));
imshow("Left Camera", frame_l);
imshow("Right Camera", frame_r);
char key = waitKey();
if (key == )
break;
}
}
if (nimages < ){ cout << "Not enough" << endl; return -; }
vector<vector<Point2f> > imagePoints[] = { imagePoints_l, imagePoints_r };
vector<vector<Point3f> > objectPoints;
objectPoints.resize(nimages);
for (int i = ; i < nimages; i++)
{
for (int j = ; j < boardSize.height; j++)
for (int k = ; k < boardSize.width; k++)
objectPoints[i].push_back(Point3f(k*squareSize, j*squareSize, ));
}
cout << "Running stereo calibration ..." << endl;
Size imageSize(, );
Mat cameraMatrix[], distCoeffs[];
cameraMatrix[] = initCameraMatrix2D(objectPoints, imagePoints_l, imageSize, );
cameraMatrix[] = initCameraMatrix2D(objectPoints, imagePoints_r, imageSize, );
Mat R, T, E, F;
double rms = stereoCalibrate(objectPoints, imagePoints_l, imagePoints_r,
cameraMatrix[], distCoeffs[],
cameraMatrix[], distCoeffs[],
imageSize, R, T, E, F,
CALIB_FIX_ASPECT_RATIO +
CALIB_ZERO_TANGENT_DIST +
CALIB_USE_INTRINSIC_GUESS +
CALIB_SAME_FOCAL_LENGTH +
CALIB_RATIONAL_MODEL +
CALIB_FIX_K3 + CALIB_FIX_K4 + CALIB_FIX_K5,
TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, , ));
cout << "done with RMS error=" << rms << endl;
double err = ;
int npoints = ;
vector<Vec3f> lines[];
for (int i = ; i < nimages; i++)
{
int npt = (int)imagePoints_l[i].size();
Mat imgpt[];
imgpt[] = Mat(imagePoints_l[i]);
undistortPoints(imgpt[], imgpt[], cameraMatrix[], distCoeffs[], Mat(), cameraMatrix[]);
computeCorrespondEpilines(imgpt[], + , F, lines[]);
imgpt[] = Mat(imagePoints_r[i]);
undistortPoints(imgpt[], imgpt[], cameraMatrix[], distCoeffs[], Mat(), cameraMatrix[]);
computeCorrespondEpilines(imgpt[], + , F, lines[]);
for (int j = ; j < npt; j++)
{
double errij = fabs(imagePoints[][i][j].x*lines[][j][] +
imagePoints[][i][j].y*lines[][j][] + lines[][j][]) +
fabs(imagePoints[][i][j].x*lines[][j][] +
imagePoints[][i][j].y*lines[][j][] + lines[][j][]);
err += errij;
}
npoints += npt;
}
cout << "average epipolar err = " << err / npoints << endl;
FileStorage fs("intrinsics.yml", FileStorage::WRITE);
if (fs.isOpened())
{
fs << "M1" << cameraMatrix[] << "D1" << distCoeffs[] <<
"M2" << cameraMatrix[] << "D2" << distCoeffs[];
fs.release();
}
else
cout << "Error: can not save the intrinsic parameters\n";
Mat R1, R2, P1, P2, Q;
Rect validRoi[];
stereoRectify(cameraMatrix[], distCoeffs[],
cameraMatrix[], distCoeffs[],
imageSize, R, T, R1, R2, P1, P2, Q,
CALIB_ZERO_DISPARITY, , imageSize, &validRoi[], &validRoi[]);
fs.open("extrinsics.yml", FileStorage::WRITE);
if (fs.isOpened())
{
fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q;
fs.release();
}
else
cout << "Error: can not save the extrinsic parameters\n";
return ;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
注意标定的時候把各個方向,大小都照顧到。
到30之後就進入标定環節了。
立體比對
直接上完整代碼了。注意之前有人問我标定完之後如何去黑邊,可以注意一下裡面的函數
initUndistortRectifyMap(cameraMatrix[0], distCoeffs[0], R1, P1, imageSize, CV_16SC2, rmap[0][0], rmap[0][1]);
- 1
- 1
這個就是用來計算校正後的映射的。後面可以計算出校正後的内接矩形,也就是校正後的無黑邊的圖像部分,會損失掉原圖像的邊緣。
此外,額,這次我自己測試的效果不是很好,之前在實驗室的時候要比寫文檔這次好很多,是以也希望你們可以把自己的結果發出來。看下圖的橫線的話,感覺還是标定的不太好。這次的文檔就當是一個流程介紹吧。
#include <string>
#include <stdio.h>
#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace std;
using namespace cv;
vector<vector<Point2f> >corners_l_array, corners_r_array;
int array_index = ;
bool ChessboardStable(vector<Point2f>corners_l, vector<Point2f>corners_r){
if (corners_l_array.size() < ){
corners_l_array.push_back(corners_l);
corners_r_array.push_back(corners_r);
return false;
}
else{
corners_l_array[array_index % ] = corners_l;
corners_r_array[array_index % ] = corners_r;
array_index++;
double error = ;
for (int i = ; i < corners_l_array.size(); i++){
for (int j = ; j < corners_l_array[i].size(); j++){
error += abs(corners_l[j].x - corners_l_array[i][j].x) + abs(corners_l[j].y - corners_l_array[i][j].y);
error += abs(corners_r[j].x - corners_r_array[i][j].x) + abs(corners_r[j].y - corners_r_array[i][j].y);
}
}
if (error < )
{
corners_l_array.clear();
corners_r_array.clear();
array_index = ;
return true;
}
else
return false;
}
}
int main(){
cv::VideoCapture camera_l();
cv::VideoCapture camera_r();
camera_l.set(CAP_PROP_FRAME_WIDTH, );
camera_l.set(CAP_PROP_FRAME_HEIGHT, );
camera_r.set(CAP_PROP_FRAME_WIDTH, );
camera_r.set(CAP_PROP_FRAME_HEIGHT, );
if (!camera_l.isOpened()){ cout << "No left camera!" << endl; return -; }
if (!camera_r.isOpened()){ cout << "No right camera!" << endl; return -; }
cv::Mat frame_l, frame_r;
Size boardSize(, );
const float squareSize = ; // Set this to your actual square size
vector<Mat> goodFrame_l;
vector<Mat> goodFrame_r;
vector<vector<Point2f> > imagePoints_l;
vector<vector<Point2f> > imagePoints_r;
vector<vector<Point3f> > objectPoints;
int nimages = ;
while (){
camera_l >> frame_l;
camera_r >> frame_r;
bool found_l = false, found_r = false;
vector<Point2f>corners_l, corners_r;
found_l = findChessboardCorners(frame_l, boardSize, corners_l,
CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);
found_r = findChessboardCorners(frame_r, boardSize, corners_r,
CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);
if (found_l && found_r &&ChessboardStable(corners_l, corners_r)){
goodFrame_l.push_back(frame_l);
goodFrame_r.push_back(frame_r);
Mat viewGray;
cvtColor(frame_l, viewGray, COLOR_BGR2GRAY);
cornerSubPix(viewGray, corners_l, Size(, ),
Size(-, -), TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, , ));
cvtColor(frame_r, viewGray, COLOR_BGR2GRAY);
cornerSubPix(viewGray, corners_r, Size(, ),
Size(-, -), TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, , ));
imagePoints_l.push_back(corners_l);
imagePoints_r.push_back(corners_r);
++nimages;
frame_l += ;
frame_r += ;
drawChessboardCorners(frame_l, boardSize, corners_l, found_l);
drawChessboardCorners(frame_r, boardSize, corners_r, found_r);
putText(frame_l, to_string(nimages), Point(, ), , , Scalar(, , ));
putText(frame_r, to_string(nimages), Point(, ), , , Scalar(, , ));
imshow("Left Camera", frame_l);
imshow("Right Camera", frame_r);
char c = (char)waitKey();
if (c == || c == 'q' || c == 'Q') //Allow ESC to quit
exit(-);
if (nimages >= )
break;
}
else{
drawChessboardCorners(frame_l, boardSize, corners_l, found_l);
drawChessboardCorners(frame_r, boardSize, corners_r, found_r);
putText(frame_l, to_string(nimages), Point(, ), , , Scalar(, , ));
putText(frame_r, to_string(nimages), Point(, ), , , Scalar(, , ));
imshow("Left Camera", frame_l);
imshow("Right Camera", frame_r);
char key = waitKey();
if (key == )
break;
}
}
if (nimages < ){ cout << "Not enough" << endl; return -; }
vector<vector<Point2f> > imagePoints[] = { imagePoints_l, imagePoints_r };
objectPoints.resize(nimages);
for (int i = ; i < nimages; i++)
{
for (int j = ; j < boardSize.height; j++)
for (int k = ; k < boardSize.width; k++)
objectPoints[i].push_back(Point3f(k*squareSize, j*squareSize, ));
}
cout << "Running stereo calibration ..." << endl;
Size imageSize(, );
Mat cameraMatrix[], distCoeffs[];
cameraMatrix[] = initCameraMatrix2D(objectPoints, imagePoints_l, imageSize, );
cameraMatrix[] = initCameraMatrix2D(objectPoints, imagePoints_r, imageSize, );
Mat R, T, E, F;
double rms = stereoCalibrate(objectPoints, imagePoints_l, imagePoints_r,
cameraMatrix[], distCoeffs[],
cameraMatrix[], distCoeffs[],
imageSize, R, T, E, F,
CALIB_FIX_ASPECT_RATIO +
CALIB_ZERO_TANGENT_DIST +
CALIB_USE_INTRINSIC_GUESS +
CALIB_SAME_FOCAL_LENGTH +
CALIB_RATIONAL_MODEL +
CALIB_FIX_K3 + CALIB_FIX_K4 + CALIB_FIX_K5,
TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, , ));
cout << "done with RMS error=" << rms << endl;
double err = ;
int npoints = ;
vector<Vec3f> lines[];
for (int i = ; i < nimages; i++)
{
int npt = (int)imagePoints_l[i].size();
Mat imgpt[];
imgpt[] = Mat(imagePoints_l[i]);
undistortPoints(imgpt[], imgpt[], cameraMatrix[], distCoeffs[], Mat(), cameraMatrix[]);
computeCorrespondEpilines(imgpt[], + , F, lines[]);
imgpt[] = Mat(imagePoints_r[i]);
undistortPoints(imgpt[], imgpt[], cameraMatrix[], distCoeffs[], Mat(), cameraMatrix[]);
computeCorrespondEpilines(imgpt[], + , F, lines[]);
for (int j = ; j < npt; j++)
{
double errij = fabs(imagePoints[][i][j].x*lines[][j][] +
imagePoints[][i][j].y*lines[][j][] + lines[][j][]) +
fabs(imagePoints[][i][j].x*lines[][j][] +
imagePoints[][i][j].y*lines[][j][] + lines[][j][]);
err += errij;
}
npoints += npt;
}
cout << "average epipolar err = " << err / npoints << endl;
FileStorage fs("intrinsics.yml", FileStorage::WRITE);
if (fs.isOpened())
{
fs << "M1" << cameraMatrix[] << "D1" << distCoeffs[] <<
"M2" << cameraMatrix[] << "D2" << distCoeffs[];
fs.release();
}
else
cout << "Error: can not save the intrinsic parameters\n";
Mat R1, R2, P1, P2, Q;
Rect validRoi[];
stereoRectify(cameraMatrix[], distCoeffs[],
cameraMatrix[], distCoeffs[],
imageSize, R, T, R1, R2, P1, P2, Q,
CALIB_ZERO_DISPARITY, , imageSize, &validRoi[], &validRoi[]);
fs.open("extrinsics.yml", FileStorage::WRITE);
if (fs.isOpened())
{
fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q;
fs.release();
}
else
cout << "Error: can not save the extrinsic parameters\n";
// OpenCV can handle left-right
// or up-down camera arrangements
bool isVerticalStereo = fabs(P2.at<double>(, )) > fabs(P2.at<double>(, ));
// COMPUTE AND DISPLAY RECTIFICATION
Mat rmap[][];
// IF BY CALIBRATED (BOUGUET'S METHOD)
//Precompute maps for cv::remap()
initUndistortRectifyMap(cameraMatrix[], distCoeffs[], R1, P1, imageSize, CV_16SC2, rmap[][], rmap[][]);
initUndistortRectifyMap(cameraMatrix[], distCoeffs[], R2, P2, imageSize, CV_16SC2, rmap[][], rmap[][]);
Mat canvas;
double sf;
int w, h;
if (!isVerticalStereo)
{
sf = / MAX(imageSize.width, imageSize.height);
w = cvRound(imageSize.width*sf);
h = cvRound(imageSize.height*sf);
canvas.create(h, w * , CV_8UC3);
}
else
{
sf = / MAX(imageSize.width, imageSize.height);
w = cvRound(imageSize.width*sf);
h = cvRound(imageSize.height*sf);
canvas.create(h * , w, CV_8UC3);
}
destroyAllWindows();
Mat imgLeft, imgRight;
int ndisparities = * ; /**< Range of disparity */
int SADWindowSize = ; /**< Size of the block window. Must be odd */
Ptr<StereoBM> sbm = StereoBM::create(ndisparities, SADWindowSize);
sbm->setMinDisparity();
//sbm->setNumDisparities(64);
sbm->setTextureThreshold();
sbm->setDisp12MaxDiff(-);
sbm->setPreFilterCap();
sbm->setUniquenessRatio();
sbm->setSpeckleRange();
sbm->setSpeckleWindowSize();
Ptr<StereoSGBM> sgbm = StereoSGBM::create(, , ,
* * ,
* * ,
, , , , , StereoSGBM::MODE_SGBM);
Mat rimg, cimg;
Mat Mask;
while ()
{
camera_l >> frame_l;
camera_r >> frame_r;
if (frame_l.empty() || frame_r.empty())
continue;
remap(frame_l, rimg, rmap[][], rmap[][], INTER_LINEAR);
rimg.copyTo(cimg);
Mat canvasPart1 = !isVerticalStereo ? canvas(Rect(w * , , w, h)) : canvas(Rect(, h * , w, h));
resize(cimg, canvasPart1, canvasPart1.size(), , , INTER_AREA);
Rect vroi1(cvRound(validRoi[].x*sf), cvRound(validRoi[].y*sf),
cvRound(validRoi[].width*sf), cvRound(validRoi[].height*sf));
remap(frame_r, rimg, rmap[][], rmap[][], INTER_LINEAR);
rimg.copyTo(cimg);
Mat canvasPart2 = !isVerticalStereo ? canvas(Rect(w * , , w, h)) : canvas(Rect(, h * , w, h));
resize(cimg, canvasPart2, canvasPart2.size(), , , INTER_AREA);
Rect vroi2 = Rect(cvRound(validRoi[].x*sf), cvRound(validRoi[].y*sf),
cvRound(validRoi[].width*sf), cvRound(validRoi[].height*sf));
Rect vroi = vroi1&vroi2;
imgLeft = canvasPart1(vroi).clone();
imgRight = canvasPart2(vroi).clone();
rectangle(canvasPart1, vroi1, Scalar(, , ), , );
rectangle(canvasPart2, vroi2, Scalar(, , ), , );
if (!isVerticalStereo)
for (int j = ; j < canvas.rows; j += )
line(canvas, Point(, j), Point(canvas.cols, j), Scalar(, , ), , );
else
for (int j = ; j < canvas.cols; j += )
line(canvas, Point(j, ), Point(j, canvas.rows), Scalar(, , ), , );
cvtColor(imgLeft, imgLeft, CV_BGR2GRAY);
cvtColor(imgRight, imgRight, CV_BGR2GRAY);
//-- And create the image in which we will save our disparities
Mat imgDisparity16S = Mat(imgLeft.rows, imgLeft.cols, CV_16S);
Mat imgDisparity8U = Mat(imgLeft.rows, imgLeft.cols, CV_8UC1);
Mat sgbmDisp16S = Mat(imgLeft.rows, imgLeft.cols, CV_16S);
Mat sgbmDisp8U = Mat(imgLeft.rows, imgLeft.cols, CV_8UC1);
if (imgLeft.empty() || imgRight.empty())
{
std::cout << " --(!) Error reading images " << std::endl; return -;
}
sbm->compute(imgLeft, imgRight, imgDisparity16S);
imgDisparity16S.convertTo(imgDisparity8U, CV_8UC1, / );
cv::compare(imgDisparity16S, , Mask, CMP_GE);
applyColorMap(imgDisparity8U, imgDisparity8U, COLORMAP_HSV);
Mat disparityShow;
imgDisparity8U.copyTo(disparityShow, Mask);
sgbm->compute(imgLeft, imgRight, sgbmDisp16S);
sgbmDisp16S.convertTo(sgbmDisp8U, CV_8UC1, / );
cv::compare(sgbmDisp16S, , Mask, CMP_GE);
applyColorMap(sgbmDisp8U, sgbmDisp8U, COLORMAP_HSV);
Mat sgbmDisparityShow;
sgbmDisp8U.copyTo(sgbmDisparityShow, Mask);
imshow("bmDisparity", disparityShow);
imshow("sgbmDisparity", sgbmDisparityShow);
imshow("rectified", canvas);
char c = (char)waitKey();
if (c == || c == 'q' || c == 'Q')
break;
}
return ;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
此外你們可能還需要的代碼是讀取參數并進行雙目比對的代碼,我也在後面放出來了。
額,因為不可能用一次标一次。
#include <string>
#include <stdio.h>
#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace std;
using namespace cv;
int main(){
cv::VideoCapture camera_l(1);
cv::VideoCapture camera_r(0);
camera_l.set(CAP_PROP_FRAME_WIDTH, 320);
camera_l.set(CAP_PROP_FRAME_HEIGHT, 240);
camera_r.set(CAP_PROP_FRAME_WIDTH, 320);
camera_r.set(CAP_PROP_FRAME_HEIGHT, 240);
if (!camera_l.isOpened()){ cout << "No left camera!" << endl; return -1; }
if (!camera_r.isOpened()){ cout << "No right camera!" << endl; return -1; }
Mat cameraMatrix[2], distCoeffs[2];
FileStorage fs("intrinsics.yml", FileStorage::READ);
if (fs.isOpened())
{
fs["M1"] >> cameraMatrix[0];
fs["D1"] >> distCoeffs[0];
fs["M2"] >> cameraMatrix[1];
fs["D2"] >> distCoeffs[1];
fs.release();
}
else
cout << "Error: can not save the intrinsic parameters\n";
Mat R, T, E, F;
Mat R1, R2, P1, P2, Q;
Rect validRoi[2];
Size imageSize(320, 240);
fs.open("extrinsics.yml", FileStorage::READ);
if (fs.isOpened())
{
fs["R"] >> R;
fs["T"] >> T;
fs["R1"] >> R1;
fs["R2"] >> R2;
fs["P1"] >> P1;
fs["P2"] >> P2;
fs["Q"] >> Q;
fs.release();
}
else
cout << "Error: can not save the extrinsic parameters\n";
stereoRectify(cameraMatrix[0], distCoeffs[0],
cameraMatrix[1], distCoeffs[1],
imageSize, R, T, R1, R2, P1, P2, Q,
CALIB_ZERO_DISPARITY, 1, imageSize, &validRoi[0], &validRoi[1]);
// OpenCV can handle left-right
// or up-down camera arrangements
bool isVerticalStereo = fabs(P2.at<double>(1, 3)) > fabs(P2.at<double>(0, 3));
// COMPUTE AND DISPLAY RECTIFICATION
Mat rmap[2][2];
// IF BY CALIBRATED (BOUGUET'S METHOD)
//Precompute maps for cv::remap()
initUndistortRectifyMap(cameraMatrix[0], distCoeffs[0], R1, P1, imageSize, CV_16SC2, rmap[0][0], rmap[0][1]);
initUndistortRectifyMap(cameraMatrix[1], distCoeffs[1], R2, P2, imageSize, CV_16SC2, rmap[1][0], rmap[1][1]);
Mat canvas;
double sf;
int w, h;
if (!isVerticalStereo)
{
sf = 600. / MAX(imageSize.width, imageSize.height);
w = cvRound(imageSize.width*sf);
h = cvRound(imageSize.height*sf);
canvas.create(h, w * 2, CV_8UC3);
}
else
{
sf = 300. / MAX(imageSize.width, imageSize.height);
w = cvRound(imageSize.width*sf);
h = cvRound(imageSize.height*sf);
canvas.create(h * 2, w, CV_8UC3);
}
cv::Mat frame_l, frame_r;
Mat imgLeft, imgRight;
int ndisparities = 16 * 5; /**< Range of disparity */
int SADWindowSize = 31; /**< Size of the block window. Must be odd */
Ptr<StereoBM> sbm = StereoBM::create(ndisparities, SADWindowSize);
// sbm->setMinDisparity(0);
// sbm->setNumDisparities(64);
// sbm->setTextureThreshold(10);
// sbm->setDisp12MaxDiff(-1);
// sbm->setPreFilterCap(31);
// sbm->setUniquenessRatio(25);
// sbm->setSpeckleRange(32);
// sbm->setSpeckleWindowSize(100);
Ptr<StereoSGBM> sgbm = StereoSGBM::create(0, 64, 7,
10 * 7 * 7,
40 * 7 * 7,
1, 63, 10, 100, 32, StereoSGBM::MODE_SGBM);
Mat rimg, cimg;
Mat Mask;
while (1)
{
camera_l >> frame_l;
camera_r >> frame_r;
if (frame_l.empty() || frame_r.empty())
continue;
remap(frame_l, rimg, rmap[0][0], rmap[0][1], INTER_LINEAR);
rimg.copyTo(cimg);
Mat canvasPart1 = !isVerticalStereo ? canvas(Rect(w * 0, 0, w, h)) : canvas(Rect(0, h * 0, w, h));
resize(cimg, canvasPart1, canvasPart1.size(), 0, 0, INTER_AREA);
Rect vroi1(cvRound(validRoi[0].x*sf), cvRound(validRoi[0].y*sf),
cvRound(validRoi[0].width*sf), cvRound(validRoi[0].height*sf));
remap(frame_r, rimg, rmap[1][0], rmap[1][1], INTER_LINEAR);
rimg.copyTo(cimg);
Mat canvasPart2 = !isVerticalStereo ? canvas(Rect(w * 1, 0, w, h)) : canvas(Rect(0, h * 1, w, h));
resize(cimg, canvasPart2, canvasPart2.size(), 0, 0, INTER_AREA);
Rect vroi2 = Rect(cvRound(validRoi[1].x*sf), cvRound(validRoi[1].y*sf),
cvRound(validRoi[1].width*sf), cvRound(validRoi[1].height*sf));
Rect vroi = vroi1&vroi2;
imgLeft = canvasPart1(vroi).clone();
imgRight = canvasPart2(vroi).clone();
rectangle(canvasPart1, vroi1, Scalar(0, 0, 255), 3, 8);
rectangle(canvasPart2, vroi2, Scalar(0, 0, 255), 3, 8);
if (!isVerticalStereo)
for (int j = 0; j < canvas.rows; j += 32)
line(canvas, Point(0, j), Point(canvas.cols, j), Scalar(0, 255, 0), 1, 8);
else
for (int j = 0; j < canvas.cols; j += 32)
line(canvas, Point(j, 0), Point(j, canvas.rows), Scalar(0, 255, 0), 1, 8);
cvtColor(imgLeft, imgLeft, CV_BGR2GRAY);
cvtColor(imgRight, imgRight, CV_BGR2GRAY);
//-- And create the image in which we will save our disparities
Mat imgDisparity16S = Mat(imgLeft.rows, imgLeft.cols, CV_16S);
Mat imgDisparity8U = Mat(imgLeft.rows, imgLeft.cols, CV_8UC1);
Mat sgbmDisp16S = Mat(imgLeft.rows, imgLeft.cols, CV_16S);
Mat sgbmDisp8U = Mat(imgLeft.rows, imgLeft.cols, CV_8UC1);
if (imgLeft.empty() || imgRight.empty())
{
std::cout << " --(!) Error reading images " << std::endl; return -1;
}
sbm->compute(imgLeft, imgRight, imgDisparity16S);
imgDisparity16S.convertTo(imgDisparity8U, CV_8UC1, 255.0 / 1000.0);
cv::compare(imgDisparity16S, 0, Mask, CMP_GE);
applyColorMap(imgDisparity8U, imgDisparity8U, COLORMAP_HSV);
Mat disparityShow;
imgDisparity8U.copyTo(disparityShow, Mask);
sgbm->compute(imgLeft, imgRight, sgbmDisp16S);
sgbmDisp16S.convertTo(sgbmDisp8U, CV_8UC1, 255.0 / 1000.0);
cv::compare(sgbmDisp16S, 0, Mask, CMP_GE);
applyColorMap(sgbmDisp8U, sgbmDisp8U, COLORMAP_HSV);
Mat sgbmDisparityShow;
sgbmDisp8U.copyTo(sgbmDisparityShow, Mask);
imshow("bmDisparity", disparityShow);
imshow("sgbmDisparity", sgbmDisparityShow);
imshow("rectified", canvas);
char c = (char)waitKey(1);
if (c == 27 || c == 'q' || c == 'Q')
break;
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
謝謝你能有耐心看完這篇文檔