利用C++?OpenCV?實(shí)現(xiàn)從投影圖像恢復(fù)仿射特性
原理
我們通過相機(jī)拍攝的圖片存在各種畸變,其中投影畸變使得原本平行的直線不再平行,就會(huì)產(chǎn)生照片中近大遠(yuǎn)小的效果,要校正這一畸變,書中給了很多方法,這里是其中的一種。
我們可以將投影變換拆分成相似變換、仿射變換和投影變換三部分, 如下圖,
其中相似變換和仿射變換不會(huì)改變infinite line,只有投影變換會(huì)改變。因此只要找到畸變圖像中的這條線,就能夠恢復(fù)圖像的仿射特性(相當(dāng)于逆轉(zhuǎn)投影變換)。而要確定這條線的位置,就得至少知道線上的兩個(gè)點(diǎn)。我們知道,所有平行線的交點(diǎn)都在infinite line上面,因此,我們只需要找到圖像上的兩對(duì)平行線(原本是平行,圖像上不再平行),求出對(duì)應(yīng)的兩個(gè)交點(diǎn),就能找到infinite line了,如下圖
進(jìn)而可以圖像的恢復(fù)仿射特性。
實(shí)現(xiàn)思路
首先我們的畸變圖像如下圖,
利用公式:
l = x1 × x2
可以通過x1、x2的齊次坐標(biāo)求出兩點(diǎn)連線l的齊次坐標(biāo)。在圖中我們找到兩對(duì)平行線l1、l2和l3、l4,如下圖
利用公式:
x = l1 × l2
可以通過l1、l2以及l(fā)3、l4的齊次坐標(biāo)分別求出兩對(duì)平行線的交點(diǎn)A12、A34,直線A12A34就是我們要找的infinite line。假設(shè)該直線的齊次坐標(biāo)為(l1,l2,l3),那么通過矩陣:
H = ((1,0,0),(0,1,0),(l1,l2,l3))
就能夠?qū)⒅本€(l1,l2,l3)變換成(0,0,1),即將該直線還原成為infinite line。同理我們也可以利用H矩陣,通過公式:
x = Hx'
還原投影畸變。
主要代碼
代碼一共需要運(yùn)行兩次
第一次運(yùn)行的主函數(shù):
int main() { Mat src = imread("distortion.jpg", IMREAD_GRAYSCALE); IplImage *src1 = cvLoadImage("distortion.jpg"); //第一步,通過鼠標(biāo)獲取圖片中某個(gè)點(diǎn)的坐標(biāo),運(yùn)行第一步時(shí)注釋掉Rectify(points_3d, src, src1);,將獲取到的八個(gè)點(diǎn)寫入 //points_3d[8]坐標(biāo)數(shù)組中,因?yàn)槭驱R次坐標(biāo),x3 = 1 GetMouse(src1); //輸入畸變圖上的8個(gè)關(guān)鍵點(diǎn) Point3d points_3d[8] = { Point3d(99, 147, 1), Point3d(210, 93, 1), Point3d(144, 184, 1), Point3d(261, 122, 1), Point3d(144, 184, 1), Point3d(99, 147, 1), Point3d(261, 122, 1), Point3d(210, 93, 1) }; //第二步,校正圖像,運(yùn)行此步驟時(shí)注釋掉GetMouse(src1);,解除注釋Rectify(points_3d, src, src1); //Rectify(points_3d, src, src1); imshow("yuantu", src); waitKey(0); }
其他函數(shù):
void on_mouse(int event, int x, int y, int flags, void* ustc) { CvFont font; cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX, 0.5, 0.5, 0, 1, CV_AA); if (event == CV_EVENT_LBUTTONDOWN) { CvPoint pt = cvPoint(x, y); char temp[16]; sprintf(temp, "(%d,%d)", pt.x, pt.y); cvPutText(src, temp, pt, &font, cvScalar(255, 255, 255, 0)); cvCircle(src, pt, 2, cvScalar(255, 0, 0, 0), CV_FILLED, CV_AA, 0); cvShowImage("src", src); } } void GetMouse(IplImage *img) { src = img; cvNamedWindow("src", 1); cvSetMouseCallback("src", on_mouse, 0); cvShowImage("src", src); waitKey(0); }
在彈出來的圖片中點(diǎn)擊任意地方可獲得改點(diǎn)的圖像坐標(biāo)(x1,x2),如下圖:
我選取了a、b、c、d四個(gè)點(diǎn),其中:
ab // cd ac // bd
將這四個(gè)點(diǎn)的坐標(biāo)按照a、b、c、d、c、a、d、b的順序填入points_3d[8]坐標(biāo)數(shù)組中,第一次運(yùn)行結(jié)束。
第二次運(yùn)行的主函數(shù):
int main() { Mat src = imread("distortion.jpg", IMREAD_GRAYSCALE); IplImage *src1 = cvLoadImage("distortion.jpg"); //第一步,通過鼠標(biāo)獲取圖片中某個(gè)點(diǎn)的坐標(biāo),運(yùn)行第一步時(shí)注釋掉Rectify(points_3d, src, src1);,將獲取到的八個(gè)點(diǎn)寫入 //points_3d[8]矩陣中,因?yàn)槭驱R次坐標(biāo),x3 = 1 //GetMouse(src1); //輸入畸變圖上的8個(gè)關(guān)鍵點(diǎn) Point3d points_3d[8] = { Point3d(99, 147, 1), Point3d(210, 93, 1), Point3d(144, 184, 1), Point3d(261, 122, 1), Point3d(144, 184, 1), Point3d(99, 147, 1), Point3d(261, 122, 1), Point3d(210, 93, 1) }; //第二步,校正圖像,運(yùn)行此步驟時(shí)注釋掉GetMouse(src1);,解除注釋Rectify(points_3d, src, src1); Rectify(points_3d, src, src1); imshow("yuantu", src); waitKey(0); }
校正函數(shù):
void Rectify(Point3d* points, Mat src, IplImage* img) { //通過輸入的8個(gè)點(diǎn)得到4條連線 vector<vector<float>> lines; int num_lines = 4; for(int i = 0; i < num_lines; i++) { //獲取兩點(diǎn)連線 GetLineFromPoints(points[2 * i], points[2 * i + 1], lines); } //分別求取兩個(gè)交點(diǎn) vector<Point3f> intersect_points; int num_intersect_points = 2; for (int i = 0; i < num_intersect_points; i++) { //計(jì)算交點(diǎn) GetIntersectPoint(lines[2 * i], lines[2 * i + 1], intersect_points); } //通過兩個(gè)交點(diǎn)連線求消失線 vector<vector<float>> vanishing_line; GetLineFromPoints(intersect_points[0], intersect_points[1], vanishing_line); //恢復(fù)矩陣 float H[3][3] = {{1, 0, 0}, {0, 1, 0}, {vanishing_line[0][0], vanishing_line[0][1], vanishing_line[0][2]}}; Mat image = Mat::zeros(src.rows, src.cols, CV_8UC1); GetRectifingImage(vanishing_line[0], src, image); int i = 0; } void GetLineFromPoints(Point3d point1, Point3d point2, vector<vector<float>> &lines) { vector<float> line; //定義直線的三個(gè)齊次坐標(biāo) float l1 = 0; float l2 = 0; float l3 = 0; l1 = (point1.y * point2.z - point1.z * point2.y); l2 = (point1.z * point2.x - point1.x * point2.z); l3 = (point1.x * point2.y - point1.y * point2.x); //歸一化 l1 = l1 / l3; l2 = l2 / l3; l3 = 1; line.push_back(l1); line.push_back(l2); line.push_back(l3); lines.push_back(line); } void GetIntersectPoint(vector<float> line1, vector<float> line2, vector<Point3f> &intersect_points) { Point3f intersect_point; //定義交點(diǎn)的三個(gè)齊次坐標(biāo) float x1 = 0; float x2 = 0; float x3 = 0; x1 = (line1[1] * line2[2] - line1[2] * line2[1]); x2 = (line1[2] * line2[0] - line1[0] * line2[2]); x3 = (line1[0] * line2[1] - line1[1] * line2[0]); //歸一化 x1 = x1 / x3; x2 = x2 / x3; x3 = 1; intersect_point.x = x1; intersect_point.y = x2; intersect_point.z = x3; intersect_points.push_back(intersect_point); } int Round(float x) { return (x > 0.0) ? floor(x + 0.5) : ceil(x - 0.5); } void GetRectifingImage(vector<float> line, Mat src, Mat dst) { Size size_src = src.size(); for (int i = 0; i < size_src.height; i++) { for (int j = 0; j < size_src.width; j++) { float x3 = line[0] * j + line[1] * i + line[2] * 1; int x1 = Round(j / x3); int x2 = Round(i / x3); if (x1 < size_src.width && x1 >= 0 && x2 < size_src.height && x2 >= 0) { dst.at<uint8_t>(x2, x1) = src.at<uint8_t>(i, j); } } } imshow("src", src); imshow("dst", dst); waitKey(0); }
運(yùn)行結(jié)果如下圖:
校正效果和點(diǎn)的選取有關(guān),因?yàn)槭髽?biāo)點(diǎn)擊的那個(gè)點(diǎn)不一定是我們真正想要的點(diǎn),建議一條直線的的兩個(gè)點(diǎn)間距盡量大一些。
完整代碼鏈接??提取碼:qltt?
到此這篇關(guān)于利用C++ OpenCV 實(shí)現(xiàn)從投影圖像恢復(fù)仿射特性的文章就介紹到這了,更多相關(guān)C++ OpenCV 投影圖像恢復(fù)仿射特性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)希爾排序(ShellSort)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)希爾排序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04C語言用棧實(shí)現(xiàn)十進(jìn)制轉(zhuǎn)換為二進(jìn)制的方法示例
這篇文章主要介紹了C語言用棧實(shí)現(xiàn)十進(jìn)制轉(zhuǎn)換為二進(jìn)制的方法,結(jié)合實(shí)例形式分析了C語言棧的定義及進(jìn)制轉(zhuǎn)換使用技巧,需要的朋友可以參考下2017-06-06C++右值引用與move和forward函數(shù)的使用詳解
為了支持移動(dòng)操作,新標(biāo)準(zhǔn)引入了一種新的引用類型——右值引用(rvalue reference)。所謂右值引用就是必須綁定到右值的引用,這篇文章主要介紹了C++右值引用與move和forward的使用2022-08-08C++?Qt實(shí)現(xiàn)動(dòng)態(tài)增加垂直滾動(dòng)條
本博文源于筆者正在工作的一個(gè)小內(nèi)容,內(nèi)容涉及到為qt動(dòng)態(tài)增加垂直滾動(dòng)條,文章分為三個(gè)部分,問題起源,問題解決方案,問題解決成功效果,思路清晰,文章干貨滿滿,復(fù)制源碼即可使用,需要的朋友可以參考下2023-08-08