首页 > 其他分享 >opencv - py_calib3d - py_calibration 相机校准

opencv - py_calib3d - py_calibration 相机校准

时间:2024-07-25 17:29:27浏览次数:12  
标签:calibration py 失真 我们 相机 opencv 图像 棋盘 cv

文章目录

Camera Calibration 相机校准

目标

在本节中,我们将学习

  • 相机造成的失真类型
  • 如何找到相机的内在和外在属性
  • 如何根据这些属性消除图像失真

基础知识

一些针孔相机会给图像带来严重的失真。两种主要的失真是径向失真和切向失真。

径向失真会导致直线出现弯曲。点距离图像中心越远,径向失真就越大。例如,下面显示了一个图像,其中棋盘的两个边缘用红线标记。但是,您可以看到棋盘的边框不是直线,与红线不匹配。所有预期的直线都凸出。请访问 失真(光学) 了解更多详细信息。

在这里插入图片描述

径向畸变可以表示如下:

x d i s t o r t e d = x ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) y d i s t o r t e d = y ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) x_{distorted} = x( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \\ y_{distorted} = y( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6) xdistorted​=x(1+k1​r2+k2​r4+k3​r6)ydistorted​=y(1+k1​r2+k2​r4+k3​r6)

同样,切向畸变的发生是因为拍摄镜头与成像平面没有完全平行。因此,图像中的某些区域可能看起来比预期的更近。切向畸变量可以表示如下:

x d i s t o r t e d = x + [ 2 p 1 x y + p 2 ( r 2 + 2 x 2 ) ] y d i s t o r t e d = y + [ p 1 ( r 2 + 2 y 2 ) + 2 p 2 x y ] x_{distorted} = x + [ 2p_1xy + p_2(r^2+2x^2)] \\ y_{distorted} = y + [ p_1(r^2+ 2y^2)+ 2p_2xy] xdistorted​=x+[2p1​xy+p2​(r2+2x2)]ydistorted​=y+[p1​(r2+2y2)+2p2​xy]

简而言之,我们需要找到五个参数,称为畸变系数,如下所示:

D i s t o r t i o n    c o e f f i c i e n t s = ( k 1 k 2 p 1 p 2 k 3 ) Distortion \; coefficients=(k_1 \hspace{10pt} k_2 \hspace{10pt} p_1 \hspace{10pt} p_2 \hspace{10pt} k_3) Distortioncoefficients=(k1​k2​p1​p2​k3​)

除此之外,我们还需要一些其他信息,例如相机的内在参数和外在参数。内在参数特定于相机。它们包括焦距 ( f x , f y f_x,f_y fx​,fy​)和光学中心( c x , c y c_x, c_y cx​,cy​)等信息。焦距和光学中心可用于创建相机矩阵,该矩阵可用于消除由于特定相机的镜头而导致的失真。相机矩阵对于特定相机是唯一的,因此一旦计算出来,就可以在同一相机拍摄的其他图像上重复使用。它表示为 3x3 矩阵:

[ c a m e r a    m a t r i x = [ f x 0 c x 0 f y c y 0 0 1 ] ] [camera \; matrix = \left [ \begin{matrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{matrix} \right ]] [cameramatrix= ​fx​00​0fy​0​cx​cy​1​ ​]

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

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

代码

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

那么来自真实世界空间的 3D 点呢?这些图像是从静态相机拍摄的,棋盘放置在不同的位置和方向。所以我们需要知道 ( X , Y , Z ) (X,Y,Z) (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 图像点称为图像点

设置

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

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

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

一旦我们找到角点,我们就可以使用 cv.cornerSubPix() 来提高其准确性。我们还可以使用 cv.drawChessboardCorners() 绘制图案。所有这些步骤都包含在以下代码中:

import numpy as np
import cv2 as cv
import glob

# 终止标准
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 准备对象点,如 (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)
# 用于存储所有图像中的对象点和图像点的数组。
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(corners)
	
	    # Draw and display the corners 绘制并显示角
	    cv.drawChessboardCorners(img, (7,6), corners2, ret)
	    cv.imshow('img', img)
	    cv.waitKey(500)
		cv.destroyAllWindows()


下面是一张绘制有图案的图像:
在这里插入图片描述

校准

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

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

去失真

现在,我们可以拍摄一张图片并对其进行去失真处理。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. 使用 cv.undistort()

这是最简单的方法。只需调用该函数并使用上面获得的 ROI 来裁剪结果。

dst = cv.undistort(img, mtx, dist, None, newcameramtx)
# 裁剪图像
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv.imwrite('calibresult.png', dst)

2. 使用 remapping

这种方式稍微有点困难。首先,找到一个从失真图像到未失真图像的映射函数。然后使用重映射函数。

mapx, mapy = cv.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w,h), 5)
dst = cv.remap(img, mapx, mapy, cv.INTER_LINEAR)
# 裁剪图像
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)) )

标签:calibration,py,失真,我们,相机,opencv,图像,棋盘,cv
From: https://blog.csdn.net/yuxing55555/article/details/140693702

相关文章

  • 如何在 python 日志记录中插入换行符?
    importlogginglogging.basicConfig(level=logging.DEBUG,format='%(asctime)s%(levelname)s%(message)s',datefmt='%H:%M:%S')logging.info('hello')logging.warning('\nnewhello')11:15:01INFOhello11:16......
  • 7:Python 变量类型
    Python 变量类型变量是存储在内存中的值,这就意味着在创建变量时会在内存中开辟一个空间。基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中。因此,变量可以指定不同的数据类型,这些变量可以存储整数,小数或字符。变量赋值Python中的变量赋值不需要......
  • 使用 Scrapy 进行身份验证并登录用户会话的证书?
    在Scrapy文档中,有以下示例说明如何在Scrapy中使用带有登录数据的经过身份验证的会话:classLoginSpider(BaseSpider):name='example.com'start_urls=['http://www.example.com/users/login.php']defparse(self,response):return[FormRequest.from_response(......
  • 为什么 SteamworksPy 无法加载/检索数据?
    importctypesimportglobimportosfromsteamworksimportSTEAMWORKSfromsteamworks.exceptionsimportSteamNotRunningExceptionclassSteamClass:def__init__(self):#SpecifythedirectorycontainingtheDLLs(usecurrentworkingdirec......
  • 如何诊断和修复 python .\app.py 不工作
    我在vscode中激活了我的虚拟环境。在激活我的环境的PowerShell中,我成功安装了Flask。然后我在文件main.py中运行此代码:fromflaskimportFlaskapp=Flask(__name__)@app.route('/')defhello_world():return'Hello,World!'if__name__=="__main__":......
  • 【pywpsrpc/Python】改变图片宽高报错 对象无效属性: ‘tuple‘ object has no attrib
    概要开发UOS环境下,使用Python通过pywpsrpc库操作word文档在指定段落添加指定宽高的图片时意外发现的该问题,在原作者的github提问后获得解答后记录(源码地址、对应提问链接以及相关资料链接贴在文章末尾)。原因及解决方式首先贴一段简单的代码,作用是新建一个word文档并在其......
  • Python - 如何使用存储在类变量中的类型作为同一类方法的参数的类型提示?
    考虑下面的代码:fromtypingimportProtocol,ClassVar,AnyclassTool(Protocol):t:ClassVar#deff(self,params:self.t)->Any:#NameError:name'self'isnotdefineddeff(self,params)->Any:passclassSum:......
  • 从Python列表中提取值
    代码图片我只想从.dat文件中提取单个值,到目前为止,我已经成功提取了我想要的值,但我将各个字符串添加在一起。它非常笨重,所以寻找一种更平滑的方法,它只会给我行中的第一个值(-300)。谢谢代码:current_dir=os.getcwd()work_dir="\workDir"......
  • 如何从另一个 python 文件运行一个 python 文件
    我正在尝试从另一个python运行一个python文件,但是当涉及到调用另一个python文件中的方法时,它只会重新运行当前的python文件。我尝试了这段代码。我将导入放入log_user_in方法中,因为如果我将其导入到文件顶部,则会出现循环导入错误。这是我的代码片段fromtkinter......
  • 使用pymongo连接mongodb数据库与在mongodb数据库中查询数据
            MongoDB是一个开源的文档型数据库管理系统,采用BSON(BinaryJSON)格式存储数据。它以其灵活的数据模型、强大的查询语言和高性能的数据读写能力而广受欢迎。MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富、最像关系数据库的......