发布时间:2023-07-27 15:30
目标
我们将了解导致相机失真、扭曲的内因与外因
我们将试着找到这些畸变参数,并消除畸变
基础
如今大量廉价的摄像机导致了很多照片畸变。两个主要的畸变是径向畸变和切向畸变。
由于径向畸变,直线会变弯。距离图片中心越远,它的影响越大。如下面这张图片,棋盘格中被红线标记的边缘。你会发现棋盘格的边缘并不与直红线重合,而是变弯了。可以到维基百科查看更多细节Distortion (optics) 。
这种畸变可以用如下公式消除:
同样的,另一种畸变-切向畸变是由于相机镜头没有与被拍摄物体平行造成的。因为有些区域会看起来更近。这可以通过下面公式消除:
总的来说,我们需要找到5个参数,即畸变系数:
除此之外,我们还需要找到一些另外的信息,如相机的内在和外部参数。内在参数是由相机内部决定的,它取决于特定的相机。它包括焦距(
),光心(
)等。这些也称作相机矩阵。它们只取决于相机本身,因此只需要计算一次。
外部参数对应于旋转和平移向量,它将三维点的坐标转换为坐标系统。
对于三维应用程序,上面这些畸变需要先被计算。要找到所有这些参数,我们需要做的是提供一些定义好的模式的示例图像(如棋盘格)。我们找到模式中特定的点(格子的交点)。这样,我们知道这些点在真实世界中的坐标,也知道它们在图片中的坐标。有了这些数据,就可以解决一些数学问题,从而得到畸变系数。这就是解决整个问题的思路。为了得到更好的结果,我们至少需要10张测试模式图片。
代码
如上所述,我们至少需要10张测试模板图片来进行相机标定。OpenCV提供了一些棋盘格图片(samples/cpp/left01.jpg -- left14.jpg)。为了便于理解,只考虑一张棋盘格的图片。相机标定需要的重要数据是一系列3D世界的点和它相对的2D图片上的点。2D图片上的点我们很容易从图片上计算它们的位置。(这些图像点是两个黑色块相接触的地方)
那么3D世界的点怎么得到呢?这些图像是静态的摄像机拍摄放置在不同的位置和方向的棋盘格得到的。所以我们需要知道(X,Y,Z)的值。为了简单起见,我们假设图片就放在XY片面上,(因此Z值总是等于0)同时相机相对移动。这种简化之后,我们只需要找出X,Y的值。对于X,Y的值我们可以简单的用(0,0),(1,0),(2,0),...来代表点的位置。这样,我们得到的结果就是与棋盘格等比例的大小。如果我们知道了格子的大小,(比如30mm),我们就可以传递(0,0),(30,0),(60,0),...这样的值。然后以mm为单位。(在这种情况下,我们不知道正方形的大小,因为我们没有采集照片,所以,我们只是就正方形大小而言)。
3D点称作物体点,2D点称作图片点。
设置
为了找到棋盘格的样式,我们使用cv2.findChessboardCorners()这个函数。我们同样需要传递我们要找什么样式,比如你这个棋盘格是8*8的,或是5*5的。在这个例子中,我们使用7*6的棋盘格。(通常棋盘格有8*8个方格,有7*7个内格点)。它返回每一一个角点,如果匹配到了模式,它将返回是True。这些角点将按一定顺序标注出来(从左到右,从上到下)
注意
这个函数并不能识别每张图片的样式。因此一个更好的选择是按照如下的思路写代码,打开摄像头检查每一帧图片是否包含样式。如果包含了需要的样式,找到角点并把他们存到一个列表中。同时提供一些间隔的时间,以便于我们调整棋盘格到不同的方向。重复这些步骤,直到足够多的图片被采集。即使在我们提供的例子中,我们也不确定14张图片中有多少合格的图片。因此我们要使用所有的图片,然后在其中找合适的一些。
注意
除了使用棋盘格,我们也可以使用圆形格子,这就需要使用cv2.findCirclesGrid()来寻找样式。据说,使用圆形网格的时候可以减少图片的采样数。
当我们找到角点之后,我们可以使用cv2.cornerSubPix()这个函数来增加坐标精度。也可以使用cv2.drawChessboardCorners()将角点标注出来。所有这些步骤都包含在以下代码中:
import numpy as np
import cv2
import glob
# termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
images = glob.glob(\'*.jpg\')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# Find the chess board corners
ret, corners = cv2.findChessboardCorners(gray, (7,6),None)
# If found, add object points, image points (after refining them)
if ret == True:
objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
imgpoints.append(corners2)
# Draw and display the corners
img = cv2.drawChessboardCorners(img, (7,6), corners2,ret)
cv2.imshow(\'img\',img)
cv2.waitKey(500)
cv2.destroyAllWindows()
一张标记了角点的图片如下所示:
标定
现在我们有了物体坐标和图片坐标,是时候开始标定相机了。我们使用cv2.calibrateCamera()这个函数。它返回相机矩阵、畸变系数、旋转和平移向量等。
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None)
消除畸变
我们已经得到了所有数据。现在我们可以拿一张图片来消除它的畸变。OpenCV中有两种方法,我们一一来看。在那之前,我们可以根据一个自由尺度参数来改进相机矩阵。cv2.getOptimalNewCameraMatrix()变换参数alpha=0,它使用最小不需要的像素返回校正的图像。如果alpha=1 所有的像素都保留下来,并且包括一些额外的黑色图像。它还返回一个图像ROI,可以用来裁剪结果。
我们选取一张新图片(left2.jpg)
img = cv2.imread(\'left12.jpg\')
h, w = img.shape[:2]
newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
1. Using cv2.undistort()
这是最简单的办法,通过调用函数,传递ROI参数就可以复制结果。
# undistort
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# crop the image
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite(\'calibresult.png\',dst)
2. Using remapping
这种方法麻烦一点。首先找到原图片与校正图片之间映射函数。然后使用重映射函数。
# undistort
mapx,mapy = cv2.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w,h),5)
dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
# crop the image
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite(\'calibresult.png\',dst)
两种方法都可以得到同样的结果,如下图:
你会看到结果中,所有的边都是直的。
现在你可以使用Numpy的函数(np.savez,np.savetxt etc)将相机矩阵和畸变系数存起来,以便以后使用。
重投影误差
重投影误差给出了一个很好的判别方式来检测找到的畸变参数的准确度。它尽可能的趋近于0. 考虑到固有的,扭曲的,旋转和平移矩阵,我们首先将物体点坐标变换到图片点坐标,使用cv2.projectPoints()函数。然后我们计算我们变换后得到的图片点和我们通过算法得到的角点坐标间的绝对范数。为了找到平均误差,我们计算了所有校准图像的误差的算术平均值。
mean_error = 0
for i in xrange(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i],imgpoints2, cv2.NORM_L2)/len(imgpoints2)
tot_error += error
print \"total error: \", mean_error/len(objpoints)
张正友相机标定Opencv实现以及标定流程&&标定结果评价&&图像矫正流程解析(附标定程序和棋盘图)
使用Opencv实现张正友法相机标定之前,有几个问题事先要确认一下,那就是相机为什么需要标定,标定需要的输入和输出分别是哪些? 相机标定的目的:获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的 ...
【视频开发】【计算机视觉】相机标定(Camera calibration)原理、步骤
相机标定(Camera calibration)原理.步骤 author@jason_ql(lql0716) http://blog.csdn.net/lql0716 在图像测量过程以及机器视觉应用 ...
SLAM入门之视觉里程计(6):相机标定 张正友经典标定法详解
想要从二维图像中获取到场景的三维信息,相机的内参数是必须的,在SLAM中,相机通常是提前标定好的.张正友于1998年在论文:\"A Flexible New Technique fro Cam ...
相机标定:PNP基于单应面解决多点透视问题
利用二维视野内的图像,求出三维图像在场景中的位姿,这是一个三维透视投影的反向求解问题.常用方法是PNP方法,需要已知三维点集的原始模型. 本文做了大量修改,如有不适,请移步原文: ...
相机标定 matlab opencv ROS三种方法标定步骤(3)
三 , ROS 环境下 如何进行相机标定 刚开始做到的时候遇到一些问题没有记录下来,现在回头写的时候都是没有错误的结果了,首先使用ROS标定相机, 要知道如何查看节点之间的流程图 rosrun r ...
相机标定 matlab opencv ROS三种方法标定步骤(1)
一 . 理解摄像机模型,网上有很多讲解的十分详细,在这里我只是记录我的整合出来的资料和我的部分理解 计算机视觉领域中常见的三个坐标系:图像坐标系,相机坐标系,世界坐标系,实际上就是要用矩阵来表 示各个 ...
使用OpenCV进行相机标定
1. 使用OpenCV进行标定 相机已经有很长一段历史了.但是,伴随着20世纪后期的廉价针孔照相机的问世,它们已经变成我们日常生活的一种常见的存在.不幸的是,这种廉价是由代价的:显著的变形.幸运的是, ...
opencv 角点检测+相机标定+去畸变+重投影误差计算
https://blog.csdn.net/u010128736/article/details/52875137 https://blog.csdn.net/h532600610/article/d ...
OpenCV相机标定和姿态更新
原帖地址: http://blog.csdn.net/aptx704610875/article/details/48914043 http://blog.csdn.net/aptx704610875 ...
相机标定 matlab opencv ROS三种方法标定步骤(2)
二 ubuntu下Opencv的相机标定 一般直接用Opencv的源码就可以进行相机的标定,但是可能只是会实现结果,却不懂实现的过程,我也是模模糊糊的看了以及 ...
随机推荐
图解Android - Android GUI 系统 (5) - Android的Event Input System
Android的用户输入处理 Android的用户输入系统获取用户按键(或模拟按键)输入,分发给特定的模块(Framework或应用程序)进行处理,它涉及到以下一些模块: Input Reader: ...
python 类型之 set
python的set和其他语言类似, 是一个无序不重复元素集, 基本功能包括关系测试和消除重复元素. 集合对象还支持union(联合), intersection(交), difference(差)和 ...
HDU OJ 5437 Alisha’s Party 2015online A
题目:click here 题意: 邀请k个朋友,每个朋友带有礼物价值不一,m次开门,每次开门让一定人数p(如果门外人数少于p,全都进去)进来,当最后所有人都到了还会再开一次门,让还没进来的人进来,每 ...
[妙味DOM]第四课:Event-事件详解2
知识点总结 事件捕获 obj.addEventListener(\'click\',fn,true) 从外往里 obj.addEventListener(\'click\',fn,false) 从里往外(冒泡 ...
We Chall-Training: Crypto - Caesar I-Writeup
MarkdownPad Document html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,ab ...
前端魔法堂:onsubmit和submit事件处理函数怎么不生效呢?
前言 最近在用Polymer增强form,使其支持表单的异步提交,但发现明明订阅了onsubmit和submit事件,却怎么也触发不了.下面我们将一一道来. 提交表单的方式 表单仅含一个以下的元素时 ...
Java爬取网络博客文章
前言 近期本人在某云上购买了个人域名,本想着以后购买与服务器搭建自己的个人网站,由于需要筹备的太多,暂时先搁置了,想着先借用GitHub Pages搭建一个静态的站,搭建的过程其实也曲折,主要是域名地 ...
浏览器的F5和Ctrl+F5
在浏览器里中,按F5键和按F5同时按住Ctrl键(简称Ctrl+F5),效果是不同,到底两者有什么区别呢? 假如我第一次访问过http://localhost/home,这个网页是个动态网页,每次访问 ...
PHP上传文件参考配置大文件上传
PHP用超级全局变量数组$_FILES来记录文件上传相关信息的. 1.file_uploads=on/off 是否允许通过http方式上传文件 2.max_execution_time=30 允许脚本 ...
基于 Docker 的现代软件供应链
[编者按]本文作者为 Marc Holmes,主要介绍一项关于现代软件供应链的调查结果.本文系国内 ITOM 管理平台 OneAPM 编译呈现,以下为正文. 3 月初,为了了解软件供应链的现状以及 D ...