前言
知道影像畸變矯對映的原理之後,那麼如何得到相機的內參是矯正的第一步,內參決定了內參矩陣(中心點、焦距等),用內參矩陣才能計算出投影矩陣,從而將原本畸變的影像矯正為平面投影影像。
本篇描述了相機成形的原理,並繪製出識別的角點。
得到矩陣計算原理:
得到計算過程:
相機的畸變是指相機鏡頭對物體所成的像相對於物體本身而言的失真程度,它是光學透鏡的固有特性。畸變產生的原因主要是透鏡的邊緣部分和中心部分的放大倍率不一樣。
畸變分為以下幾類:
- 徑向畸變
- 切向畸變
- 薄稜鏡畸變
通常情況下,徑向畸變的影響要遠遠大於其他畸變。畸變是不可消除的,但在實際的應用中,可以透過一些軟體來進行畸變的補償,如OpenCV、MATLAB等。
主要由透鏡不同部位放大倍率不同造成,它又分為枕形畸變和桶形畸變兩種。枕形畸變,也稱為鞍形形變,視野中邊緣區域的放大率遠大於光軸中心區域的放大率,常用在遠攝鏡頭中。桶形畸變則與枕形畸變相反,視野中光軸中心區域的放大率遠大於邊緣區域的放大率,常出現在廣角鏡頭和魚眼鏡頭中。
主要由透鏡安裝與成像平面不平行造成,類似於透視原理,如近大遠小、圓變橢圓等。
由透鏡設計缺陷和加工安裝誤差造成,又稱為線性畸變。其影響較小,一般忽略不計。
採集一張棋盤圖片,要確認他是可以被識別的。
讀取影像,這裡由於圖片較大,我們重設大小為原來寬高的1/2:
// 使用圖片
std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/chessboard.png";
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/24.jpg";
cv::Mat srcMat = cv::imread(srcFilePath);
int chessboardColCornerCount = 6;
int chessboardRowCornerCount = 9;
// 步驟一:讀取檔案
// cv::imshow("1", srcMat);
// cv::waitKey(0);
// 步驟二:縮放,太大了縮放下(可省略)
cv::resize(srcMat, srcMat, cv::Size(srcMat.cols / 2, srcMat.rows / 2));
cv::Mat srcMat2 = srcMat.clone();
cv::Mat srcMat3 = srcMat.clone();
// cv::imshow("2", srcMat);
// cv::waitKey(0);
先灰度化,然後輸入預製的縱向橫向角數量,使用棋盤角點函式提取角點
// 步驟三:灰度化
cv::Mat grayMat;
cv::cvtColor(srcMat, grayMat, cv::COLOR_BGR2GRAY);
cv::imshow("3", grayMat);
// cv::waitKey(0);
// 步驟四:檢測角點
std::vector<cv::Point2f> vectorPoint2fCorners;
bool patternWasFound = false;
patternWasFound = cv::findChessboardCorners(grayMat,
cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
vectorPoint2fCorners,
cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_NORMALIZE_IMAGE);
/*
enum { CALIB_CB_ADAPTIVE_THRESH = 1, // 使用自適應閾值將影像轉化成二值影像
CALIB_CB_NORMALIZE_IMAGE = 2, // 歸一化影像灰度係數(用直方圖均衡化或者自適應閾值)
CALIB_CB_FILTER_QUADS = 4, // 在輪廓提取階段,使用附加條件排除錯誤的假設
CALIB_CB_FAST_CHECK = 8 // 快速檢測
};
*/
cvui::printf(srcMat, 0, 0, 1.0, 0xFF0000, "found = %s", patternWasFound ? "true" : "false");
cvui::printf(srcMat, 0, 24, 1.0, 0xFF0000, "count = %d", vectorPoint2fCorners.size());
qDebug() << __FILE__ << __LINE__ << vectorPoint2fCorners.size();
// 步驟五:繪製棋盤點
cv::drawChessboardCorners(srcMat2,
cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
vectorPoint2fCorners,
patternWasFound);
// 步驟六:進一步提取亞畫素角點
cv::TermCriteria criteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, // 型別
30, // 引數二: 最大次數
0.001); // 引數三:迭代終止閾值
/*
#define CV_TERMCRIT_ITER 1 // 終止條件為: 達到最大迭代次數終止
#define CV_TERMCRIT_NUMBER CV_TERMCRIT_ITER //
#define CV_TERMCRIT_EPS 2 // 終止條件為: 迭代到閾值終止
*/
qDebug() << __FILE__ << __LINE__ << vectorPoint2fCorners.size();
cv::cornerSubPix(grayMat,
vectorPoint2fCorners,
cv::Size(11, 11),
cv::Size(-1, -1),
criteria);
OpenCV 中用於檢測影像中棋盤角點的函式。
bool cv::findChessboardCorners(InputArray image,
Size patternSize,
OutputArray corners,
int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE)
引數解釋:
- image:輸入的影像,通常是一個灰度影像,因為角點檢測在灰度空間中進行更為準確。
- patternSize:棋盤的內角點數量,例如一個 8x6 的棋盤會有 48 個內角點,所以 patternSize 會是 Size(8, 6)。
- corners:檢測到的角點輸出陣列。
- flags:不同的標誌,用於指定角點檢測的不同方法。可以是以下的一個或多個標誌的組合:
CALIB_CB_ADAPTIVE_THRESH:使用自適應閾值將影像轉換為二值影像,而不是使用固定的全域性閾值。
CALIB_CB_NORMALIZE_IMAGE:在尋找角點之前,先對影像進行歸一化,以提高魯棒性。
CALIB_CB_FAST_CHECK:僅檢查角點候選者中的少量點,用於快速檢測,但可能不如標準方法準確。
函式返回值是一個布林值,如果找到足夠的角點以形成一個棋盤模式,則返回 true;否則返回 false。
findChessboardCorners 函式通常用於相機標定,透過檢測棋盤角點來確定影像與真實世界之間的對應關係。一旦角點被檢測到,就可以使用這些點來估計相機的內參(如焦距、主點)和外參(如旋轉和平移矩陣)。
OpenCV中的一個函式,用於在檢測到的棋盤角點周圍繪製方框。這對於相機標定、影像對齊等應用非常有用。
void cv::drawChessboardCorners(InputOutputArray image,
Size patternSize,
InputArray corners,
bool patternWasFound)
引數解釋:
- image:輸入的影像,通常是一個彩色影像,函式會在這個影像上繪製角點。
- patternSize:棋盤的內角點數量,例如一個 8x6 的棋盤會有 48 個內角點,所以 patternSize 會是 Size(8, 6)。
- corners:檢測到的角點,通常是透過 findChessboardCorners 函式得到的。
- patternWasFound:一個布林值,表示是否找到了足夠的角點來形成一個棋盤模式。如果為 true,則函式會在角點周圍繪製彩色的方框;如果為 false,則只會繪製白色的方框。
這個函式通常與 findChessboardCorners 結合使用,以檢測影像中的棋盤角點,並在檢測到的角點周圍繪製方框。這對於視覺校準和相機標定等任務非常有用。
TermCriteria是OpenCV中用於指定迭代演算法終止條件的模板類。它取代了之前的CvTermCriteria,並且在許多OpenCV演算法中作為迭代求解的結構被使用。
struct TermCriteria {
enum { COUNT=1, MAX_ITER=COUNT, EPS=2 };
TermCriteria();
TermCriteria(int type, int maxCount, double epsilon);
TermCriteria(const CvTermCriteria& criteria);
};
構造時需要三個引數:
- 型別(type):它決定了迭代終止的條件。型別可以是CV_TERMCRIT_ITER、CV_TERMCRIT_EPS或CV_TERMCRIT_ITER+CV_TERMCRIT_EPS。在C++中,這些宏對應的版本分別為TermCriteria::COUNT、TermCriteria::EPS。
CV_TERMCRIT_ITER或TermCriteria::COUNT:表示迭代終止條件為達到最大迭代次數;
CV_TERMCRIT_EPS或TermCriteria::EPS:表示迭代到特定的閾值就終止;
CV_TERMCRIT_ITER+CV_TERMCRIT_EPS:則表示兩者都作為迭代終止條件。 - 迭代的最大次數(maxCount):這是演算法可以執行的最大迭代次數。
- 特定的閾值(epsilon):當滿足這個精確度時,迭代演算法會停止。
OpenCV中用於精確化角點位置,其函式原型如下:
void cv::cornerSubPix(InputArray image,
InputOutputArray corners,
Size winSize,
Size zeroZone,
TermCriteria criteria);
引數解釋:
- image:輸入影像的畫素矩陣,最好是8位灰度影像,這樣檢測效率會更高。
- corners:初始的角點座標向量,同時作為亞畫素座標位置的輸出,因此需要是浮點型資料。
- winSize:搜尋視窗的大小,它表示的是搜尋視窗的一半尺寸。
- zeroZone:死區的一半尺寸,死區是搜尋視窗內不對中央位置做求和運算的區域。這是為了避免自相關矩陣出現某些可能的奇異性。
- criteria:角點搜尋的停止條件,通常包括迭代次數、角點位置變化量或角點誤差變化量等。
cornerSubPix函式用於在初步提取的角點資訊上進一步提取亞畫素資訊,從而提高相機標定的精度。在相機標定、目標跟蹤和三維重建等應用中,精確的角點位置是非常重要的,因此cornerSubPix函式在這些領域有廣泛的應用。
void OpenCVManager::testFindChessboardCorners()
{
#define FindChessboardCornersUseCamera 1
#if !FindChessboardCornersUseCamera
// 使用圖片
std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/chessboard.png";
// std::string srcFilePath = "D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/24.jpg";
cv::Mat srcMat = cv::imread(srcFilePath);
#else
// 使用攝像頭
cv::VideoCapture capture;
// 插入USB攝像頭預設為0
if(!capture.open(0))
{
qDebug() << __FILE__ << __LINE__ << "Failed to open camera: 0";
}else{
qDebug() << __FILE__ << __LINE__ << "Succeed to open camera: 0";
}
while(true)
{
cv::Mat srcMat;
capture >> srcMat;
#endif
int chessboardColCornerCount = 6;
int chessboardRowCornerCount = 9;
// 步驟一:讀取檔案
// cv::imshow("1", srcMat);
// cv::waitKey(0);
// 步驟二:縮放,太大了縮放下(可省略)
cv::resize(srcMat, srcMat, cv::Size(srcMat.cols / 2, srcMat.rows / 2));
cv::Mat srcMat2 = srcMat.clone();
cv::Mat srcMat3 = srcMat.clone();
// cv::imshow("2", srcMat);
// cv::waitKey(0);
// 步驟三:灰度化
cv::Mat grayMat;
cv::cvtColor(srcMat, grayMat, cv::COLOR_BGR2GRAY);
cv::imshow("3", grayMat);
// cv::waitKey(0);
// 步驟四:檢測角點
std::vector<cv::Point2f> vectorPoint2fCorners;
bool patternWasFound = false;
patternWasFound = cv::findChessboardCorners(grayMat,
cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
vectorPoint2fCorners,
cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_NORMALIZE_IMAGE);
/*
enum { CALIB_CB_ADAPTIVE_THRESH = 1, // 使用自適應閾值將影像轉化成二值影像
CALIB_CB_NORMALIZE_IMAGE = 2, // 歸一化影像灰度係數(用直方圖均衡化或者自適應閾值)
CALIB_CB_FILTER_QUADS = 4, // 在輪廓提取階段,使用附加條件排除錯誤的假設
CALIB_CB_FAST_CHECK = 8 // 快速檢測
};
*/
cvui::printf(srcMat, 0, 0, 1.0, 0xFF0000, "found = %s", patternWasFound ? "true" : "false");
cvui::printf(srcMat, 0, 24, 1.0, 0xFF0000, "count = %d", vectorPoint2fCorners.size());
qDebug() << __FILE__ << __LINE__ << vectorPoint2fCorners.size();
// 步驟五:繪製棋盤點
cv::drawChessboardCorners(srcMat2,
cv::Size(chessboardColCornerCount, chessboardRowCornerCount),
vectorPoint2fCorners,
patternWasFound)