前言:前段时间在做鱼眼展开的的程序,本来以为超简单,毕竟好多人都已经做过了不是,肯定有许多资料和参考。然而,做什么事情之前都不能眼高手低。我做了好久,还得不出想要的结果,是很烦。前两天导师没办法了给我安排了其他的工作,但我这个还没有得出想要的结果,强迫症的我心里好难受。花两天时间把能想到的全部整理如下,理一理思路,看能不能有所突破。
鱼眼镜头参数是:f=1.5mm,fov=240度
1、常见的三种鱼眼图像
(1)圆形鱼眼图 (2)全帧鱼眼图 (3)鼓形鱼眼图
对于圆形鱼眼图像来说,由于它在各个方向上都能达到视域180度(其实说这个也不完全正确,因为我拿到的鱼眼图是超过180度的),所以仅需要水平拍摄两到三张就可用于全景图生成,并且拍摄技术简单。全帧鱼眼图需要水平拍摄一圈四张外加朝天朝地两张共六张图片才能生成全景图,操作很复杂。至于鼓形鱼眼图,由于它在垂直方向上可以达到视域180度,所以需要水平一圈拍摄四张图像才能生成一张全景图。
2、鱼眼图像的畸变形式
径向畸变(Radial Distortion),即从圆心开始各个放射方向上,在图像点到圆心的距离上所产生的偏差。(主要是针对径向畸变的)
偏心畸变(Decentering Distortion),图像的中心不在鱼眼镜头的光轴上所引发的图像畸变。
切向畸变(Tangential Distortion),从圆心到图像点的向量在其切向上发生的偏离,也就是向量的角度偏差引起的畸变。
3、摄像机镜头模型
常见的模型有:透视投影模型(perspective projection)、立体投影模型(stereographic projection)、等距投影(equidistant projection)、正弦投影(sine-law projection)以及等立体角投影(equi-solid angle projection)。
我查到的资料是:在鱼眼镜头的生产商中,大部分采用等距映射模型。
投影模型如下:鱼眼图到单位视球面之间的转换
具体的解释在下面矫正的部分。
下面正式开始对图片的处理:
4、鱼眼有效区域的提取:目的是为了得到圆心坐标和半径。(老师说学习人工智能任何一种算法,首先要明确它的objective和optimistic。这里虽不是人工智能上的算法,但这种理念是通用的不是么)
对鱼眼有效区域的提取,我得到的最有效的方法是变角度先扫描法。原理很简单,是从线扫描法改进而来的。线扫描法就是从上下左右四个方向用扫描线的方式逼近圆,当扫描线上的像素差大于一个阈值时 ,我们认为该扫描线是鱼眼镜头有效区域的切线。改进的变角度扫描线法就是从多个角度逼近,而不单单是上下左右四条线了。
剔除无效切点的办法是:
对所有切点对计算出的半径取平均值,然后把那些半径大于均值的切点对坐标剔除。
这时我们得到许多切点对。根据这些切点对来拟合圆。
Kasa圆拟合法:
假设 是用于圆拟合的切点坐标数据,对其进行列填充扩展,构造下面的两个矩阵:
vector<Point3i> extendA; vector<int> extendB; vector<Point>::iterator iter = validPoints.begin(); while (iter != validPoints.end()) { extendA.push_back(Point3i((*iter).x, (*iter).y, 1)); extendB.push_back((pow((*iter).x, 2) + pow((*iter).y, 2))); iter++; } Mat A = Mat(extendA).reshape(1); Mat B = Mat(extendB).reshape(1); Mat_<double> dA, dB; Mat_<double> P(3, 1, CV_64F); A.convertTo(dA, CV_64F); B.convertTo(dB, CV_64F); P = dA.inv(CV_SVD)*dB; double p1, p2, p3; p1 = P.at<double>(0, 0); p2 = P.at<double>(1, 0); p3 = P.at<double>(2, 0); center.x = p1 / 2; center.y = p2 / 2; radius = sqrt((pow(p1, 2.0) + pow(p2, 2.0)) / 4 + p3);
我没有去推这个拟合算法具体的过程,实现部分见上面这一段圆拟合代码。
到这里我们就已经得到了鱼眼图片的圆心坐标和半径。(这部分非常重要,因为如果圆心和半径求得不对的话,严重影响后面的矫正部分)
5、对鱼眼图片进行矫正(目标是求出 鱼眼镜头图片和展开的图片像素坐标之间一一映射的关系)
矫正分成两个部分:
(1)利用鱼眼镜头的成像模型将畸变的图像还原到单位视球面上;
(2) 将单位视球面通过特定的映射关系,变换成为我们通常所见的二维图像。映射关系有以下几种:
其中采用的是等距投影模型。。。。。。
5.1、鱼眼镜头图到视球面的转化
(1)图像坐标uv和笛卡尔集坐标系(直角坐标系xy)之间的转换: (a)中o(u0,v0)是鱼眼镜头图有效区域的圆心图像坐标
x=u-u0
y=-(v-v0)
(2)笛卡尔(直角坐标系)转化成平面极坐标
根据等距映射模型:加上鱼眼镜头区域半径R和半视场角,可以求出f(镜头光心到图像主点的距离)。
所以
由此我们就得到了单位半球面上任意一点的球面坐标参数(,),其中是平面某一点与x轴正向之间的夹角,是平面上一点与镜头光心之间的夹角,也是镜头光轴与入射光线之间的夹角。
//双线性插值 int iu = (floor(u)>0 ? floor(u) : 0); //左上点x int iv = (floor(v)>0 ? floor(v) : 0); //左上点y pt = Point(iu + 1, iv + 1);//为了防止指针越界 if (!pt.inside(imgArea)) { continue; } Vec3b temp1, temp2; temp1 = _imgOrg.ptr<Vec3b>(iv)[iu] * (1 - abs(u - iu)) + _imgOrg.ptr<Vec3b>(iv)[iu + 1] * (1 - abs(u - (iu + 1))); temp2 = _imgOrg.ptr<Vec3b>(iv + 1)[iu] * (1 - abs(u - iu)) + _imgOrg.ptr<Vec3b>(iv + 1)[iu + 1] * (1 - abs(u - (iu + 1))); _retImg.ptr<Vec3b>(j)[i] = temp1*(1 - abs(v - iv)) + temp2*(abs(v - iv));