柱面投影的C++实现(一)
本文介绍柱面投影具体做法:若要实现柱面投影变换,除了需要获取源图像数据以外,还需要已知相机镜头的一些信息——
(信息A:相机焦距+目标距离)
(信息B:图像变换尺度s,与相机焦距f成正比)
(信息C:相机水平方向的视角,即相机到源图像两边延伸点的射线之间的夹角)
以上三种信息获取其中一种,即可求出平面图像的对应柱面投影。其中,信息A除特殊情况基本不考虑,信息B的正向变换及原理在【注1】中老哥的博客里有详细介绍,本文主要介绍已知相机视场角情况(α = pi / 6)下的实现方法。
首先,已知柱面半径 r = 相机焦距 f,通过l=α·r和源图像水平像素个数就可以得到 s = W / (tan(α)),所以目标柱面的水平方向弧长 L = 2sα, 任一在目标图像的像素点根据这些值来进行坐标变换。
讨论以下四种情况,以图像中心(相机的光心)将水平竖直方向分割图片:
x < W / 2, y < H / 2.
x < W / 2, y > H / 2.
x > W / 2, y < H / 2.
x > W / 2, y > H / 2.
所以根据公式,设源图像像素(x0, y0),柱面变换后目标图像像素(x1, y1),则有:
x1 = (L / 2) - s * arctan((W / 2 - x0) / s) , x0 <= (W / 2)
x1 = (L / 2) + s * arctan((x0 - W / 2) / s) , (W / 2) < x0 <= W
同样不难推出:
y1 = (H / 2) - s * (H / 2 - y0) /√ ((W / 2 - x0)^ 2 + s ^ 2) , y0 <= (H / 2)
y1 = (H / 2) + s * (y0 - H / 2) /√ ((W / 2 - x0)^ 2 + s ^ 2) , (H / 2) < y0 <= H
#include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"#define PI 3.141592653using namespace std;
using namespace cv;int main()
{Mat img0, img1;img0 = imread("cathedrale.jpg");//img1 = img0.clone();//img1.setTo(Scalar::all(0));int W = img0.cols; //W为图像的平面宽度int H = img0.rows; //H为图像的柱面高度float L; //目标柱面的宽度float a = PI / 6; //已知相机能看到的半角大小float s = W / 2 / (tan(a)); //s为相机的焦距int x0, y0; //图像平面上某一点的横纵坐标(x0, y0)float x1, y1; //目标曲面上对应点的横纵坐标(x1, y1)int zsx, zsy; //整数x1和y1L = 2 * s * a; //求解L的总长度int springX = (int)L * tan(a) / a +1;img1.create(cvSize((int)L,H), img0.type());img1.setTo(Scalar::all(0));//cout << img0.size() << endl;//cout << springX << endl;double t0 = (double)getTickCount();//分类讨论x0<=(W/2)和(W/2)<x0<=(W)以及y0<=(H/2)和(H/2)<y0<=(H)的情况,之后指令向量化优化for (y0 = 1; y0 <= H; y0++){for (x0 = 1; x0 <= W; x0++){if (x0 <= (W / 2))x1 = (L / 2) - s * cvFastArctan((float)(W / 2 - x0), s) * PI / 180;elseif (x0 > (W / 2))x1 = (L / 2) + s *cvFastArctan((float)(x0 - (W / 2)), s) * PI / 180;if (y0 <= (H / 2))y1 = (H / 2) - s * (H / 2 - y0) / sqrt(pow((W / 2) - x0, 2) + pow(s, 2));elseif (y0 > (H / 2))y1 = (H / 2) + s * (y0 - H / 2) / sqrt(pow((W / 2) - x0, 2) + pow(s, 2));//化为整数坐标zsx = (int)(x1);zsy = (int)(y1);//cout << L << endl;//cout << zsx << endl;if (zsx < W && zsy < H && x0<W && y0<H){//cout << zsy << endl;//cout << zsx << endl;//cout << y0 << endl;//cout << x0 << endl;img1.at<Vec3b>(zsy, zsx)[0] = img0.at<Vec3b>(y0, x0)[0];img1.at<Vec3b>(zsy, zsx)[1] = img0.at<Vec3b>(y0, x0)[1];img1.at<Vec3b>(zsy, zsx)[2] = img0.at<Vec3b>(y0, x0)[2];}}}double t_proc = ((double)getTickCount() - t0) / getTickFrequency();cout << "柱面投影:" << t_proc << endl;cout << "变换尺度:" << s << endl;cout << "柱面变换结束..." << endl;imshow("cylindralCathedrale.jpg", img1);imwrite("cylindralCathedrale.jpg", img1);waitKey();/* 向img1赋值B0 = *(img0.data + img0.step[0] * y0 + img0.step[1] * x0 + img0.elemSize1() * 0);*(img1.data + img1.step[0] * zsy + img1.step[1] * zsx + img1.elemSize1() * 0) = B0;G0 = *(img0.data + img0.step[0] * y0 + img0.step[1] * x0 + img0.elemSize1() * 1);*(img1.data + img1.step[0] * zsy + img1.step[1] * zsx + img1.elemSize1() * 1) = G0;R0 = *(img0.data + img0.step[0] * y0 + img0.step[1] * x0 + img0.elemSize1() * 2);*(img1.data + img1.step[0] * zsy + img1.step[1] * zsx + img1.elemSize1() * 2) = R0;*//*//测试cvFastArctan(分子, 分母),可用,得到的是角度制需要乘以PI/180转化为弧度制算弧长;float testsum;float testitem = 1.7320508075689 * W;testsum = cvFastArctan(W, testitem);cout << "结果在这里: " << endl;cout << testsum << endl;*/
}
注1:这篇文章介绍了已知尺度(s for scale) s=1000情况下的前向映射做法
https://blog.csdn.net/czl389/article/details/54599253
注2:这篇文章介绍了柱面投影变换的计算过程
https://www.cnblogs.com/cheermyang/p/5431170.html
注3:在做完投影变换后会有空洞出现,但是本文由于作者很懒 篇幅问题不做赘述
注4:柱面投影的后向变换下期再见