相机造成的失真类型

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

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

基础

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

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



径向畸变可以表示如下:

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

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



除此之外,我们还需要一些其他信息,比如相机的内在和外在参数。固有参数是特定于相机的。它们包括像焦距(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. Nebula Graph 源码解读系列 | Vol.03 Planner 的实现

    上篇我们讲到 Validator 会将由 Parser 生成的抽象语法树(AST)转化为执行计划,这次,我们来讲下执行计划是如何生成的. 概述 Planner 是执行计划(Execution Plan ...

  2. C++ 深拷贝浅拷贝

    C++ 深拷贝浅拷贝 C++默认生成的拷贝构造函数,他的行为就是浅拷贝,他只会复制一个一模一样的的指针,并不会操作指针指向的东西. 要想实现我们的逻辑需求,就要自定义拷贝构造函数,实现深拷贝. 我们来 ...

  3. 摆脱鼠标系列 Trigger Suggest 快捷键 改成 Shift + Space

    摆脱鼠标系列 Trigger Suggest 快捷键 改成 Shift + Space 看marp插件的时候,发现用ctrl + space 自动提示,但是我这里是输入法

  4. github git clone下载加速 && npm install 下载加速

    git clone https://pd.zwc365.com/seturl/< https 开头的项目地址> npm install --registry=https://registr ...

  5. 手撕fft系列之频移fftshift源码解析

    壹: fft在数字信号处理领域是一个神一样的存在.要好好熟悉一下.这里给出频移的算法源码解析. 所谓的频移,就是把数字信号的频频顺序打乱,移动一些.这个在防止啸叫和辅听领域应用十分广泛. 贰: 这个源 ...

  6. 四种方式实现点击chrome链接在ie中显示页面

    1.c++ socket通过浏览器在ie中打开指定url github源码:https://github.com/iamzken/cpp-open-ie 2.vb生成exe,url访问exe启动ie并 ...

  7. Ubuntu 14.04 Intel 处理器 硬编解码配置(Intel® Media Server Studio)

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

  8. 记录--让URL地址都变成了"ooooooooo"

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 发现一个很有创意的小工具网站,如封面图所示功能很简单,就是将一个URL地址转换为都是 ooooooooo 的样子,通过转换后的地址访问可以 ...

  9. JavaScript知识总结 闭包篇

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 1. 对闭包的理解 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问 ...

  10. 记录--通过手写,分析Promise核心原理

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 1. 定义整体结构 先写出构造函数,将Promise向外暴露 /* 自定义Promise函数模块:IIFE */ (function (w ...