相机造成的失真类型

如何找到相机的内在和外在特性

如何基于这些特性来消除图像失真

基础

一些针孔相机会对图像造成严重失真。两种主要的畸变是径向畸变和切向畸变。

径向变形会使直线看起来是弯曲的。点离图像中心越远,径向失真就越大。例如,下面显示了一个图像,其中棋盘的两个边缘用红线标记。但是,你可以看到棋盘的边界不是一条直线,与红线不匹配。所有预期的直线都凸起了。



径向畸变可以表示如下:

类似地,由于图像拍摄透镜没有完全平行于成像平面对准,所以发生切向失真。因此,图像中的某些区域看起来可能比预期的更近。切向畸变的量可以表示如下:

简而言之,我们需要找到五个参数,即失真系数,由下式给出:



除此之外,我们还需要一些其他信息,比如相机的内在和外在参数。固有参数是特定于相机的。它们包括像焦距(fx,fy)和光学中心(cx,cy)这样的信息。焦距和光学中心可用于创建相机矩阵,该矩阵可用于消除特定相机镜头造成的失真。相机矩阵对于特定的相机是唯一的,因此一旦计算出来,它就可以在同一相机拍摄的其他图像上重复使用。它表示为3x3矩阵。



外部参数对应于将3D点的坐标转换为坐标系的旋转和平移矢量。

对于立体应用,需要首先校正这些失真。为了找到这些参数,我们必须提供一些定义良好的模式(例如棋盘)的样本图像。我们找到了一些我们已经知道相对位置的特定点(例如棋盘上的直角)。我们知道这些点在现实世界空间中的坐标,也知道图像中的坐标。所以我们可以求解失真系数。为了获得更好的结果,我们至少需要10个测试模式。

如上所述,我们需要至少10个测试模式来进行相机校准。OpenCV附带了一些棋盘的图像(请参阅samples/data/left01.jpg–left14.jpg),因此我们将使用这些图像。考虑一个棋盘的图像。相机校准所需的重要输入数据是3D真实世界点的集合以及图像中这些点的对应2D坐标。2D图像点是可以的,我们可以很容易地从图像中找到。(这些图像点是棋盘中两个黑色方块相互接触的位置)

那么来自真实世界空间的3D点呢?这些图像是从静态相机中拍摄的,棋盘被放置在不同的位置和方向。所以我们需要知道(X,Y,Z)值。但为了简单起见,我们可以说棋盘在XY平面上保持静止(所以Z=0总是),相机也相应地移动了。这种考虑有助于我们只找到X,Y值。现在,对于X,Y值,我们可以简单地将点传递为(0,0),(1,0),(2,0)。。。其表示点的位置。在这种情况下,我们得到的结果将以棋盘正方形的大小为尺度。但是,如果我们知道正方形的大小(比如30毫米),我们可以将值传递为(0,0)、(30,0),(60,0)、。因此,我们得到了以毫米为单位的结果。(在这种情况下,我们不知道正方形的大小,因为我们没有拍摄这些图像,所以我们以正方形的大小通过)。

3D点称为对象点,2D图像点称为图像点。

Setup

因此,要在棋盘中找到图案,我们可以使用函数cv.findChessboardCorners()。我们还需要传递我们正在寻找的图案类型,如8x8网格、5x5网格等。在本例中,我们使用7x6网格。(通常棋盘有8x8个方格和7x7个内角)。它返回角点和retval,如果获得图案,该值将为True。这些角将按顺序(从左到右,从上到下)放置

Note

此功能可能无法在所有图像中找到所需的图案。因此,一个好的选择是编写代码,这样,它启动相机并检查每一帧所需的模式。获得图案后,找到角落并将其存储在列表中。此外,在阅读下一帧之前提供一些间隔,以便我们可以将棋盘调整到不同的方向。继续此过程,直到获得所需数量的良好图案。即使在这里提供的例子中,我们也不确定给出的14个图像中有多少是好的。因此,我们必须阅读所有的图像,只拍摄好的图像。

我们可以使用圆形网格来代替棋盘。在这种情况下,我们必须使用函数cv.findCirclesGrid()来查找模式。较少的图像足以使用圆形网格执行相机校准。

一旦我们找到了角,我们可以使用cv.canerSubPix()来提高它们的准确性。我们也可以使用cv.drawChessbordCorners()来绘制图案。所有这些步骤都包含在下面的代码中:

点击查看代码
import numpy as np
import cv2 as cv
import glob
# termination criteria
criteria = (cv.TERM_CRITERIA_EPS + cv.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 = cv.imread(fname)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# Find the chess board corners
ret, corners = cv.findChessboardCorners(gray, (7,6), None)
# If found, add object points, image points (after refining them)
if ret == True:
objpoints.append(objp)
corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
imgpoints.append(corners2)
# Draw and display the corners
cv.drawChessboardCorners(img, (7,6), corners2, ret)
cv.imshow('img', img)
cv.waitKey(500)
cv.destroyAllWindows()

一张画有图案的图片如下所示:



校准

现在我们有了目标点和图像点,我们就可以进行校准了。我们可以使用函数cv.calirateCamera(),它返回相机矩阵、失真系数、旋转和平移矢量等。

ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

Undistortion

现在,我们可以拍摄一张图片并不侵权。OpenCV提供了两种方法。然而,首先,我们可以使用cv.getOptimalNewCameraMatrix()基于自由缩放参数来细化相机矩阵。如果缩放参数alpha=0,它将返回具有最小不需要像素的未失真图像。因此,它甚至可以去除图像角落的一些像素。如果alpha=1,则所有像素都会保留一些额外的黑色图像。此函数还返回可用于裁剪结果的图像ROI。

因此,我们拍摄一张新的图像(在本例中为left12.jpg。这是本章的第一张图像)

点击查看代码
img = cv.imread('left12.jpg')
h, w = img.shape[:2]
newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))

1. Using cv.undistort()
这是最简单的方法。只需调用函数并使用上面获得的ROI来裁剪结果。

点击查看代码
# undistort
dst = cv.undistort(img, mtx, dist, None, newcameramtx)
# crop the image
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv.imwrite('calibresult.png', dst)

**2.Using remapping**
这条路有点难。首先,找到一个从失真图像到未失真图像的映射函数。然后使用重映射函数

点击查看代码
# undistort
mapx, mapy = cv.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w,h), 5)
dst = cv.remap(img, mapx, mapy, cv.INTER_LINEAR)
# crop the image
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv.imwrite('calibresult.png', dst)

尽管如此,两种方法都给出了相同的结果。结果如下:

你可以从结果中看到,所有的边都是直的。

现在,您可以使用NumPy中的写入函数(np.savez、np.savetxt等)存储相机矩阵和失真系数,以备将来使用。

重投影误差

重新投影误差给出了对所找到的参数的精确程度的良好估计。重新投影误差越接近零,我们发现的参数就越准确。给定固有矩阵、失真矩阵、旋转矩阵和平移矩阵,我们必须首先使用cv.projectPoints()将对象点转换为图像点。然后,我们可以计算通过转换和角点查找算法得到的值之间的绝对范数。为了找到平均误差,我们计算为所有校准图像计算的误差的算术平均值。

点击查看代码
mean_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv.norm(imgpoints[i], imgpoints2, cv.NORM_L2)/len(imgpoints2)
mean_error += error
print( "total error: {}".format(mean_error/len(objpoints)) )

**总结来说畸变矫正的过程是根据现有二维像素坐标反推三维坐标,然后再投影到二维坐标,再矫正。**

cv.calibrateCamera的更多相关文章

  1. 我的复杂的OpenCV编译之路(OpenCV3.1.0 + VS2010 + Win7)

    教程:www.cnblogs.com/jliangqiu2016/p/5597501.html 这里主要记载我编译遇到的错误及解决方法. OpenCV3.1软件下载:https://sourcefor ...

  2. 相机标定问题-Matlab & Py-Opencv

    一.相机标定基本理论 1.相机成像系统介绍 图中总共有4个坐标系: 图像坐标系:Op    坐标表示方法(u,v)                 Unit:Dots(个) 成像坐标系:Oi      ...

  3. 基于OpenCV做“三维重建”(2)--封装标定过程

    既然已经能够找到了标定点,那么下边的工作就是使用标定结果了.[这本书在这里的内容组织让人莫名其妙]但是通过阅读代码能够很方便地串起来. /*------------------------------ ...

  4. 【双目备课】《学习OpenCV第18章》相机模型与标定整编

    一.相机模型 针孔模型.在这个简单模型中,想象光线是从场景或一个很远的物体发射过来的,但只有一条光线从该场景中的任意特定点进入针孔. 我们将这个图像进行抽象,就能够得到这样的结果: 其中,f为像到针孔 ...

  5. 车道线识别/Opencv/传统方法

    车道检测(Advanced Lane Finding Project) 实现步骤: 使用提供的一组棋盘格图片计算相机校正矩阵(camera calibration matrix)和失真系数(disto ...

  6. 采用QHD分辨率使用kinect2_calibration,完成QHD图像校正

    //.................................................................................//采用QHD分辨率使用kinec ...

  7. Camera Calibration 相机标定:Opencv应用方法

    本系列文章由 @YhL_Leo 出品,转载请注明出处. 文章链接: http://blog.csdn.net/yhl_leo/article/details/49427383 Opencv中Camer ...

  8. OpenCV-Python 相机校准 | 四十九

    目标 在本节中,我们将学习 由相机引起的失真类型, 如何找到相机的固有和非固有特性 如何根据这些特性使图像不失真 基础 一些针孔相机会给图像带来明显的失真.两种主要的变形是径向变形和切向变形. 径向变 ...

  9. [OpenCV实战]50 用OpenCV制作低成本立体相机

    本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...

  10. [OpenCV实战]38 基于OpenCV的相机标定

    文章目录 1 什么是相机标定? 2 图像形成几何学 2.1 设定 2.1.1 世界坐标系 2.1.2 相机坐标系 2.1.3 图像坐标系 2.2 图像形成方法总结 3 基于OpenCV的相机标定原理 ...

随机推荐

  1. 【Azure 应用服务】App Service 默认开放端口说明, 如何禁用Web app的端口号? 

    问题描述 基于安全的角度来考虑,在网站上线之前用户会对自己的网站进行安全扫描,以防网站因为某些漏洞而被非法攻击. 而在扫描过程中,会发现除了 80 和 443 之外的一些其他端口也被开放了.例如:45 ...

  2. C++ //类模板与函数模板的区别 //1.类模板没有自动类型推导的使用方式 //2.类模板子模板参数中可以有默认参数

    1 //类模板与函数模板的区别 2 //1.类模板没有自动类型推导的使用方式 3 //2.类模板子模板参数中可以有默认参数 4 5 #include <iostream> 6 #inclu ...

  3. Python(上机题) 通俗易懂的基础题目解析

    python 题目 文章目录 python 题目 题目一:幸运数对 题目二:lambda 函数找最大值 题目三:n个数前后互换 (切片) 题目四:字符串相减(删除指定字符) 方法一:可以用空字符来替换 ...

  4. Kafka的Controller

    控制器组件(Controller),是 Apache Kafka 的核心组件.它的主要作用是在 Apache ZooKeeper 的帮助下管理和协调整个 Kafka 集群.集群中任意一台 Broker ...

  5. kafka---面经

    kafka深入理解 消息队列 作用,优点? 异步:比如查看文章,点赞收藏评论等操作,提升文章热度,提升个人社区贡献度,提升个人社区积分,刷新社区贡献度排行榜.将其他操作放到消息队列,相应的模块从消息队 ...

  6. (二)Git 学习之基础篇

    一.理论基础 1.1 Git 记录的是什么? Git 和其它版本控制系统(如 SVN)的主要差别在于 Git 对待数据的方式. 1.1.1 SVN 记录差异比较 从概念上来说,SVN 以文件变更列表的 ...

  7. notion database 必知必会

    notion database 必知必会 用过 mysql 的同学一定很容易上手 notion .在 notion 中,掌握好 database,基本上就掌握了 notion 最核心的概念. noti ...

  8. [模板]01trie,维护异或最大值

    // 查询异或最大值,每次插入和查询时间都是log(C) template<class T> class trie01 { vector<vector<T>> tr ...

  9. webserver总结

    可设置参数:连接池最大连接数,最大线程数,任务队列最大值,timeslot epoll epoll监听listen_fd(接受新客户端和断开连接), pipefd(将信号输入到管道用epoll统一管理 ...

  10. C程序问题归纳(static,auto,register,extern,程序内存分布图,linux下程序的执行过程......)(二)

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明   本文作为本人csdn blog的主站的备份.(Bl ...