如何利用C++ OpenCV 实现从投影图像恢复仿射特性
如何利用C++ OpenCV 实现从投影图像恢复仿射特性,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
原理
我们通过相机拍摄的图片存在各种畸变,其中投影畸变使得原本平行的直线不再平行,就会产生照片中近大远小的效果,要校正这一畸变,书中给了很多方法,这里是其中的一种。
我们可以将投影变换拆分成相似变换、仿射变换和投影变换三部分, 如下图,
其中相似变换和仿射变换不会改变infinite line,只有投影变换会改变。因此只要找到畸变图像中的这条线,就能够恢复图像的仿射特性(相当于逆转投影变换)。而要确定这条线的位置,就得至少知道线上的两个点。我们知道,所有平行线的交点都在infinite line上面,因此,我们只需要找到图像上的两对平行线(原本是平行,图像上不再平行),求出对应的两个交点,就能找到infinite line了,如下图
进而可以图像的恢复仿射特性。
实现思路
首先我们的畸变图像如下图,
利用公式:
l = x1 × x2
可以通过x1、x2的齐次坐标求出两点连线l的齐次坐标。在图中我们找到两对平行线l1、l2和l3、l4,如下图
利用公式:
x = l1 × l2
可以通过l1、l2以及l3、l4的齐次坐标分别求出两对平行线的交点A12、A34,直线A12A34就是我们要找的infinite line。假设该直线的齐次坐标为(l1,l2,l3),那么通过矩阵:
H = ((1,0,0),(0,1,0),(l1,l2,l3))
就能够将直线(l1,l2,l3)变换成(0,0,1),即将该直线还原成为infinite line。同理我们也可以利用H矩阵,通过公式:
x = Hx'
还原投影畸变。
主要代码
代码一共需要运行两次
第一次运行的主函数:
int main(){ Mat src = imread("distortion.jpg", IMREAD_GRAYSCALE); IplImage *src1 = cvLoadImage("distortion.jpg"); //第一步,通过鼠标获取图片中某个点的坐标,运行第一步时注释掉Rectify(points_3d, src, src1);,将获取到的八个点写入 //points_3d[8]坐标数组中,因为是齐次坐标,x3 = 1 GetMouse(src1); //输入畸变图上的8个关键点 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) }; //第二步,校正图像,运行此步骤时注释掉GetMouse(src1);,解除注释Rectify(points_3d, src, src1); //Rectify(points_3d, src, src1); imshow("yuantu", src); waitKey(0); }
其他函数:
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);}
在弹出来的图片中点击任意地方可获得改点的图像坐标(x1,x2),如下图:
我选取了a、b、c、d四个点,其中:
ab // cd ac // bd
将这四个点的坐标按照a、b、c、d、c、a、d、b的顺序填入points_3d[8]坐标数组中,第一次运行结束。
第二次运行的主函数:
int main(){ Mat src = imread("distortion.jpg", IMREAD_GRAYSCALE); IplImage *src1 = cvLoadImage("distortion.jpg"); //第一步,通过鼠标获取图片中某个点的坐标,运行第一步时注释掉Rectify(points_3d, src, src1);,将获取到的八个点写入 //points_3d[8]矩阵中,因为是齐次坐标,x3 = 1 //GetMouse(src1); //输入畸变图上的8个关键点 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) }; //第二步,校正图像,运行此步骤时注释掉GetMouse(src1);,解除注释Rectify(points_3d, src, src1); Rectify(points_3d, src, src1); imshow("yuantu", src); waitKey(0); }
校正函数:
void Rectify(Point3d* points, Mat src, IplImage* img){ //通过输入的8个点得到4条连线 vector> lines; int num_lines = 4; for(int i = 0; i < num_lines; i++) { //获取两点连线 GetLineFromPoints(points[2 * i], points[2 * i + 1], lines); } //分别求取两个交点 vector intersect_points; int num_intersect_points = 2; for (int i = 0; i < num_intersect_points; i++) { //计算交点 GetIntersectPoint(lines[2 * i], lines[2 * i + 1], intersect_points); } //通过两个交点连线求消失线 vector > vanishing_line; GetLineFromPoints(intersect_points[0], intersect_points[1], vanishing_line); //恢复矩阵 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 > &lines){ vector line; //定义直线的三个齐次坐标 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 line1, vector line2, vector &intersect_points){ Point3f intersect_point; //定义交点的三个齐次坐标 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 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 (x2, x1) = src.at (i, j); } } } imshow("src", src); imshow("dst", dst); waitKey(0);}
运行结果如下图:
校正效果和点的选取有关,因为鼠标点击的那个点不一定是我们真正想要的点,建议一条直线的的两个点间距尽量大一些。
关于如何利用C++ OpenCV 实现从投影图像恢复仿射特性问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注行业资讯频道了解更多相关知识。