opencv 畸变校正立体校正和畸变矫正有什么区别

上一篇博客简要介绍了一下常用的张正友标定法的流程,其中获取了摄像机的内参矩阵K,和畸变系数D。
1.在普通相机cv模型中,畸变系数主要有下面几个:(k1; k2; p1; p2[; k3[; k4; k5; k6]] ,其中最常用的是前面四个,k1,k2为径向畸变系数,p1,p2为切向畸变系数。
2.在fisheye模型中,畸变系数主要有下面几个(k1,k2,k3,k4).
因为cv和fisheye的镜头畸变模型不一样,所以畸变系数也会有所不同,具体在畸变校正时的公式也不同,具体公式请参见opencv2.0和3.0的官方文档。OpenCV中对畸变图像进行畸变校正主要用的函数有UndistortImage()函数,以及initUndistortRectifyMap()结合remap()函数。其实UndistortImage()就是initUndistortRectifyMap()和remap()的简单组合,效果是一样的。
但是有一点是:当你有很多畸变图像需要较正时,用UndistortImage()函数的缺点就暴露了。因为畸变坐标映射矩阵mapx和mapy只需要计算一次就足够了,而重复调用UndistortImage()只会重复计算mapx和mapy,严重影响程序效率。因此当有多张图片要畸变校正时,建议使用一次initUndistortRectifyMap(),获取畸变坐标映射矩阵mapx和mapy后,作为remap函数的输入,多次调用remap函数进行畸变校正。
今天要说的第二点就是做过畸变校正的同学都知道,畸变校正后的图像会损失很多像素,这是为什么呢?接下来就以常见的桶形畸变为例分析一下:由于我目前手头的相机畸变程度并不明显(之前用广角镜头的时候畸变程度相当明显)。因此就从网上找一些图片作为例子以便说明,这里引用一下图片来源
http://www.developersite.org/904-45591-%E6%A0%87%E5%AE%9A。
畸变原图如下:
畸变校正后的图如下:
相信大家已经可以看到了,由于桶形畸变的特征是,远离图像中心的地方成像放大率小,因此越远离图像中心的位置畸程度越明显,像点越向内移动。畸变校正后,原本挤在一起的像素点们被校正到原来的位置,就得到上面的图像。同时由于四周的像素被拉伸,会造成四周出现模糊的情况。
得到上述图像后很自然想到的是把四周的黑色区域裁掉,只留下中间的图像区域。如下图(红色框):
那么问题来了,这样做的话输出图像的长宽比和输入图像的长宽比就不一致了。因此opencv畸变校正函数内部做法是:在保证长宽比不变的情况下,对上面的图像取中间的ROI区域出来,类似进行“裁剪”操作,那么就会得到损失更多像素的输出图像啦,如下图(蓝色框)!
好了,说到这里,我们就把代码中的罪魁祸首找出来吧!先看一下initUndistortRectifyMap()函数的原型,如下图左,默认情况下,我们通常不会求取新的CameraMatrix,这样代码中会默认使用标定得到的CameraMatrix。而这个摄像机矩阵是在理想情况下没有考虑畸变得到的,所以并不准确,重要的是fx和fy的值会比考虑畸变情况下的偏大,会损失很多有效像素。我们可以通过这个函数getOptimalNewCameraMatrix ()求取一个新的摄像机内参矩阵,函数原型如下图右,注意函数上面的备注” Return the new camera matrix based on the free scaling parameter“,通过这个函数可以自行调整缩放比例。
在getOptimalNewCameraMatrix ()函数中,其中的一个输入参数为alpha∈(0,1),alpha的意义见上图,调节alpha的值能够控制得到的新矩阵中的fx和fy的大小,当alpha=1的时候,原图像中的所有像素能够得到保留,也就出现了上面校正后图像中的那些黑色的空洞区域。【注:cv模型中为alpha,fisheye模型中为balance,意义是一样的】
那么alpha是怎么样改变 f 值的,主要是getOptimalNewCameraMatrix ()中又调用了一个函数undistortPoints(),这个函数会在畸变图像中选取上下左右四个点进行畸变校正,具体请看源码。下面图片中我圈出的代码就在这个函数里面,里面具体的f1,f2,f3,f4的意思就与取的上下左右四个点有关,可以去看源代码,当然不明白了也可以问我。总之是在fmin和fmax之间进行插值计算 f,而通常balance(alpha)默认为0,f=fmax,焦距越大,视场越小,损失的有效像素越多。
好了,夜已深,今天就到这里。
阅读(...) 评论()2009年1月 VC/MFC大版内专家分月排行榜第三
本帖子已过去太久远了,不再提供回复功能。2974人阅读
OpenCV(54)
C++/C(74)
http://blog.csdn.net/wangxiaokun671903/article/details/
分类:&&647人阅读&&&
&&& 通过相机标定的程序获取了两个相机各自的内参矩阵和畸变系数,以及两个相机达到平行时各自的旋转矩阵。Opencv和Matlab都给了我们现成的函数,可以利用这些数据进行去畸变或者双目平行校正,因为有需求要将去畸变和平行校正移植到硬件上,那么自己如何利用这些参数和矩阵写去畸变的程序和双目平行校正的程序呢?我本人发现的网上这方面资料较少。在此总结一下。
去畸变的过程就是针对单目相机进行的变换,平行校正就是针对双目相机的操作。
本文针对单目去畸变的方法展开叙述:
和“从世界坐标系到相机坐标系再到图像物理坐标系最后到图像像素坐标系”过程相逆,我们做去畸变时,是已经通过相机标定获取了相机的内参矩阵和畸变系数的,另外,我们已知的就是相机拍摄出来一副图像。
内参矩阵:
&畸变系数:
&则各参数如下:
&去畸变的操作步骤如下:
(1),将图像的像素坐标系通过内参矩阵转换到相机坐标系:
(2),在相机坐标系下进行去畸变操作。
(3),去畸变操作结束后,将相机坐标系重新转换到图像像素坐标系。
(4),并用源图像的像素值对新图像的像素点进行插值。
插值算法如下:
1、[x+1]-x;2、[y+1]-y;3、I1([x],[y]);4、y-[y];5、I1([x],[y+1]);6、x-[x];7、I1([x+1],[y]);8、I1([x+1],[y+1]);
&I2(u,v)=1*2*3+1*4*5+6*2*7+6*4*8
编程实例:
去畸变之前的图像:
去畸变之后的图片:
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:623665次
积分:7327
积分:7327
排名:第3195名
原创:29篇
转载:372篇
评论:51条
(1)(2)(1)(1)(1)(1)(1)(3)(30)(47)(17)(16)(34)(38)(18)(41)(27)(17)(32)(15)(21)(11)(7)(5)(10)(4)(1)
(window.slotbydup = window.slotbydup || []).push({
id: '4740887',
container: s,
size: '250,250',
display: 'inlay-fix'基于OpenCV的非线性图像畸变校正研究_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
基于OpenCV的非线性图像畸变校正研究
阅读已结束,下载文档到电脑
想免费下载本文?
定制HR最喜欢的简历
下载文档到电脑,方便使用
还剩1页未读,继续阅读
定制HR最喜欢的简历
你可能喜欢对倾斜的图像进行修正——基于opencv 透视变换 - 简书
对倾斜的图像进行修正——基于opencv 透视变换
这篇文章主要解决这样一个问题:
有一张倾斜了的图片(当然是在Z轴上也有倾斜,不然直接旋转得了o(╯□╰)o),如何尽量将它纠正到端正的状态。
而要解决这样一个问题,可以用到透视变换。
关于透视变换的原理,网上已经有一大推了,这里就不再做介绍了。
这篇文章的干货是:
对OpenCV晦涩难懂的透视变换接口的使用细节的描述;
基于两套自己提出的自动选择顶点进行透视变换的可以运行的
关于干货的第1点,相信很多同学在使用OpenCV透视变换接口的时候,一定google了不少东西吧。。。
而关于干货的第2点,应该更能引起大家的共鸣吧。就像我当初想做这个的时候,信心满满地去搜了很多博客,然而发现绝大部分博客或者教程中,关于透视变换的举例无非是如下两种:
是把一张端正的图像进行扭曲,比如下面这样:
可以说对要做的工作毫无卵用。。。
把上图中变换后的图片恢复成原图。
可以说刚看到可以这样子的时候,大家应该是非常激动的。。。赶紧去看看代码里面用了什么方法,然后看啊看,发现仿射变换的4个关键点是手动确定的。。。又可以说毫无卵用了。。毕竟每张图片都要通过手动的方法来确定4个关键点,还是很容易让人崩溃的。。。
于是乎,我决定,自己设计一套算法,来自动确定这4个关键点的坐标。当然,由于才疏学浅,我的这套算法当然可谓是漏洞百出,权当抱砖引玉,欢迎大家提出更好的思路,一起交流~~
干货来啦~~~
OpenCV的透视变换接口
void warpPerspective(InputArray src,
OutputArray dst,
InputArray M,Size dsize,
intflags=INTER_LINEAR,
int borderMode=BORDER_CONSTANT,
const Scalar&borderValue=Scalar()
参数含义:InputArray src:输入的图像;OutputArray dst:输出的图像;InputArray M:透视变换的矩阵;Size dsize:输出图像的大小;int flags=INTER_LINEAR:输出图像的插值方法。
其中的透视变换矩阵还需要函数findHomography的计算来得到一个单映射矩阵。findHomography的函数接口如下:
Mat findHomography(InputArray srcPoints,
InputArray dstPoints,
int method=0,
doubleransacReprojThreshold=3,
OutputArray mask=noArray()
参数含义:InputArray srcPoints:输入图像的顶点;InputArray dstPoints:输出图像的顶点。
关于自动计算仿射变换顶点的两种算法实现
以下处理的原图如下:
基于边缘提取
在OpenCV中,表示直线的数据结构一般是Vec4i,这本身是一个vector[1]结构,包含了4个元素,分别对应直线起点和终点的横纵坐标,在工程代码里,用vector&Vec4i&来表示经过直线提取后的的直线簇:
vector&Vec4i&
首先,对原图进行边缘检测,为了使边缘检测和直线提取的结果尽可能主要体现在轮廓方面,工程代码里,将Canny边缘检测的threshold1设定为一个带初值的变量,并设置最多检测出的直线条数,迭代地通过增加threshold1的值,去减少每次检测出的直线条数,通过工程代码也能体现出来:
const int maxLinesNum = 12;//最多检测出的直线条数
while (this-&lines.size() &= maxLinesNum)
this-&cannyThreshold += 2;
Canny(this-&srcImage, this-&midImage,this-&cannyThreshold,
this-&cannyThreshold * factor);
threshold(this-&midImage, this-&midImage, 128,255, THRESH_BINARY);
cvtColor(this-&midImage, this-&edgeDetect,CV_GRAY2RGB);
HoughLinesP(this-&midImage, this-&lines, 1,CV_PI / 180, 50, 100, 100);
可以看出,只要本次检测出的直线条数大于12条,那么就增加Canny函数的threshold1的值,使下次检测出的直线条数减少,知道第一次小于12条,才退出循环。另外,由于一些照片拍摄的情形过于复杂,有许多环境噪声的干扰不可避免,因此,算法里还加入了一个滤波器,这个滤波器可以有效地对过于贴近图像边缘的平行直线进行过滤:
lines.erase(remove_if(
lines.begin(),lines.end(),
[](Vec4i line)
{return abs(line[0] - line[2]) & 10 ||abs(line[1] - line[3]) & 10; }
lines.end());
通过以上步骤的处理后,就可以得到下图:
至此,左上、右上、左下、右下这四个顶点已经被包含在了紫色的线条之中,下一步的工作就是从这些紫色的线条中解析出这四个顶点。
在解析这四个点之前,还需要对这些紫色的线条进行一次处理:将所有点从这些线段中剥离出来。剥离的方法很直观:由于每条线段包含了两个点,因此点的个数最多是线段数的两倍(考虑到有的线段共用了顶点),因此新建一个用于存储所有点的vector,将他的大小初始化为lines这个vector大小的两倍:
vector&Point& points(lines.size() * 2);//各个线段的起止点,然后根据对应关系直接将直线的起始点存入
points这个vector[3]中:
for (size_t i = 0; i & lines.size(); ++i)//将Vec4i转为point
points[i * 2].x =
points[i * 2].y =
points[i * 2 + 1].x =
points[i * 2 + 1].y =
这样就完成了对各个起始点的剥离。为了提高之后计算的效率,并且合并一些由于直线提取的误差所产生的同一个点分离的情况,再对这些已经剥离了的点进行一次过滤:
vector&Point& candidates(candidate);
vector&Point& filter(candidate);
for (auto i = candidates.begin(); i !=candidates.end();)
for (auto j = filter.begin(); j != filter.end(); ++j)
if(abs((i).x - (j).x) & 5 &&
abs((i).y - (j).y) & 5 &&
abs((i).x - (j).x) & 0 &&
abs((i).y - (j).y) & 0
i= filter.erase(i);
这次过滤是非常有必要进行的,由于直线提取的阈值不可能适用于各种情形下拍摄的照片,因此有些照片的直线提取结果中,某些看上去是一条线段,实际上是由两条甚至更多条线段合并而成,如果直接把他们剥离成点用于算法后面的计算的话,由于后面的计算时间复杂度是O(N^2),盲目的计算会消耗非常多的时间,而这些消耗是没有必要的。这次过滤后,重合的点将被删除,而原本逻辑上是同一个点而计算后成为不同点的那些点将被合并为一个点。在经过这次过滤后,再对剩余点进行一次排序,排序的依据是这些点到(0,0)点的距离(图像处理中的(0,0)点一般是左上角的点,横坐标向右增加,纵坐标向下增加):
sort(points.begin(), points.end(),
[](const Point& lhs, const Point& rhs)
{return lhs.x + lhs.y & rhs.x + rhs.y; }
经过这次处理后,points中的所有点都是有序排列了。
为了保证对左上、右上、左下、右下这四个点计算结果的精确性,我设计了两种方法来分别计算这四个点的坐标,并且在保证经过两种方法的计算后,各自的误差满足一定条件后,取两种计算结果的平均值,作为最终的计算结果。这两种方法中有部分思想是一致的:在绝大多数正常拍摄的照片中,左上、和右下这两个顶点是容易提取的。不难发现,左上这个顶点是距离原点最近的点,右下这个顶点是距离原点最远的点。在经过上述过滤和排序步骤后,我们得到过滤后的点,就可以直接从中取出左上、右下这两个点:
vector&Point& temp = this-&axisSort(lines);
Point leftTop, rightD //左上和右下可以直接判断
leftTop.x = temp[0].x;
leftTop.y = temp[0].y;
rightDown.x = temp[temp.size() - 1].x;
rightDown.y = temp[temp.size() - 1].y;
下面分别介绍两种方法计算左下和右上这两个点的思路。
第一种思路相对简单。
具体思想是,将“右上”、“左下”定义为点簇而非具体的某个点。在除开左上和右下这两个点外的所有点中,经行两次过滤:第一次过滤可以选出右上的点簇,利用的是在剩余的点中,如果某个点的横坐标大于左上点的横坐标并且纵坐标小于右下点的纵坐标,那么将这个点归到“右上”这个点簇中,如下图所示;如果某个点的纵坐标大于左上点的纵坐标并且横坐标小于右下点的横坐标,那么将这个点归到“左下”这个点簇中,如下图所示。
工程中的代码如下:
vector&Point&rightTop(temp.size());
vector&Point&leftDown(temp.size());//左下和右上有多个点可能符合
for (auto & i : temp)[2]
if (i.x & leftTop.x&& i.y & rightDown.y)
rightTop.push_back(i);
for (auto & i : temp)
if (i.y & leftTop.y&& i.x & rightDown.x)
leftDown.push_back(i);
经过这个步骤后,就将所有满足条件的点分别归到了“左下”和“右上”这两个点簇中。那么接下来,如何从这两个点簇中选出真正的左上点和右下点呢。这就要用到一个矩形中最长的线段是对角线这个性质了。即使原图由于拍摄原因可能已经产生了畸变,但是在“左下”和“右上”这两个点簇中,能构成最长线段的点仍然是真正的右上点和左下点。于是在“左下”和“右上”这两个点簇中从容器起始位置进行遍历,不断更新最长距离和此距离对应的两个容器中的元素位置,直到这两个位置到达两个容器的末尾,就停止更新。此时记录下的元素位置所对应的点,就是真正的左下点和右上点,如工程代码所示:
int maxDistance = (rightTop[0].x - leftDown[0].x) *(rightTop[0].x - leftDown[0].x)
+ (rightTop[0].y - leftDown[0].y) *(rightTop[0].y - leftDown[0].y);
for (size_t i = 0; i & rightTop.size(); ++i)
for (size_t j = 0; j & leftDown.size(); ++j)
(rightTop[i].x - leftDown[j].x) * (rightTop[i].x -leftDown[j].x)
+ (rightTop[i].y - leftDown[j].y) * (rightTop[i].y -leftDown[j].y)
& maxDistance
maxDistance = (rightTop[i].x - leftDown[j].x) * (rightTop[i].x - leftDown[j].x)
+ (rightTop[i].y - leftDown[j].y) *(rightTop[i].y - leftDown[j].y);
rightTopFlag=
leftDownFlag=
下面介绍第二种方法。
通常,输入图像在视觉直观上可以分成端正、向左倾斜、向右倾斜这三种状态。之所以很难通过通常的想法来确定一个图像的左下点和右上点,是因为通常的想法下,左下点应该是横坐标最小且纵坐标最大,右上点应该是横坐标最大且纵坐标最小。然而,这种判断只适用于“端正”这种状态,如下图所示。但是对于“向右倾斜”和“向左倾斜”这两种状态,这种直观的判断就失效了,如下图所示。在“向右倾斜”这种状态下,左下点实际上是横坐标最小而纵坐标却不是最小,右上点实际上是横坐标最大而纵坐标不是最小;在“向左倾斜”这种状态下,左下点实际上是纵坐标最大而横坐标却不是最小,右上点实际上是纵坐标最小而横坐标却不是最大。
端正状态下的左下点和右上点
向右倾斜状态下的左下点和右上点
向左倾斜状态下的左下点和右上点
如果不对图像的状态进行区分就直接计算左下点和右上点,是非常困难的。但是,如果将图片分成上述三种状态后再对左下点和右上点进行计算,那么将会容易得多。如果输入图片本身就是“端正”状态,可以对左上点和右下点进行直接判断,下面介绍在“向右倾斜”和“想做倾斜”这两种状态下,对这两个点计算的方法。在介绍根据不同倾斜状况对两个顶点的计算方法之前,先介绍一下如何确定右上点簇和左下点簇。在图片处于端正状态下,位于右上点两侧边缘上的点就被定义为“右上点簇”,位于左下点两侧边缘上的点就被定义为“左下点簇”。在此之后,无论这张图片如何倾斜,“右上点簇”和“左下点簇”的相对位置都不会改变。如何区分图片是“向右倾斜”还是“向左倾斜”呢?首先,按照第一种方法的思路,将除开左上点和右下点的其余所有点归类进“左下”和“右上”这两个点簇中。如果某张图片的“右上”点簇中的所有点的纵坐标都大于左上点的纵坐标,就说明这张图是“向右倾斜”;否则这张图就是“向左倾斜”。上述思路的工程代码如下:
enum imageStyle { normal, leanToRight, leanToLeft };
if (rightTop.end() == find_if(
rightTop.begin(), rightTop.end(),
[leftTop, rightTop](Point p)
{return p.y & leftTop.y; }
))//如果所有右上点的y值都 & 左上点的y值,说明图像向右倾斜
imageState = imageStyle::leanToR
imageState = imageStyle::leanToL
在“向右倾斜”状态下,对“右上”点簇中的所有点按照横坐标降序排列,横坐标最大的点就是真正的右上点,如图所示;对“左下”点簇中的所有点按照横坐标升序排列,横坐标最小的点就是真正的左下点,如图所示。在“向左倾斜”状态下,对“右上”点簇中的所有点按照纵坐标升序排列,纵坐标最小的点就是真正的右上点,如图所示;对“左下”点簇中的所有点按照纵坐标降序排列,纵坐标最大的点就是真正的左下点,如图所示。工程代码如下:
if (imageState == imageStyle::leanToRight)//向右倾斜
sort(rightTop.begin(), rightTop.end(),
[rightTop](Point p1, Point p2){return p1.x & p2.x; });//对所有右上点按X值排序,X最大的就是真正的右上点
rightTop.erase(remove(rightTop.begin(), rightTop.end(), Point(0, 0)), rightTop.end());
trueRightTop = rightTop[0];
sort(leftDown.begin(), leftDown.end(),
[leftDown](Point p1, Point p2){return p1.x & p2.x; });//对所有左下点按X值排序,X最小的就是真正的左下点
leftDown.erase(remove(leftDown.begin(), leftDown.end(), Point(0, 0)), leftDown.end());
trueLeftDown = leftDown[0];
else //向左倾斜
sort(rightTop.begin(), rightTop.end(),
[rightTop](Point p1, Point p2){return p1.y & p2.y; });//对所有右上点按Y值排序,Y最小的就是真正的右上点
rightTop.erase(remove(rightTop.begin(), rightTop.end(), Point(0, 0)), rightTop.end());
trueRightTop = rightTop[0];
sort(leftDown.begin(), leftDown.end(),
[leftDown](Point p1, Point p2){return p1.y & p2.y; });//对所有左下点按Y值排序,Y最大的就是真正的左下点
leftDown.erase(remove(leftDown.begin(), leftDown.end(), Point(0, 0)), leftDown.end());
trueLeftDown = leftDown[0];
基于轮廓提取
轮廓提取的思路和边缘提取基本相同,就是预处理中,将提边缘换成体轮廓。当初想到基于轮廓提取是为了互相验证这两种方法的可靠性~~就不再详述这种方法了~~~
在文章的最后,当然还是要放几张效果图啦~~~
当然,还是存在一些显而易见的问题:
如果输入图像的顶点本身已经缺失过多,那我提出的两种顶点计算方法都不可能完全还原出该图本身的缺失顶点(因为该顶点已处于图像像素范围之外,无法计算);
另外,边缘提取和轮廓提取的参数也不可能做到完全的自适应。}

我要回帖

更多关于 opencv 鱼眼畸变校正 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信