opencv-python光学畸变校准


前言

  开始练习opencv了,对于立体应用方面,这些畸变现象首先需要解决。所以第一个处理的问题就是对于光学畸变的照片进行畸变矫正。


一、光学畸变是什么?

  一些相机会有严重的图像畸变的问题。其中径向畸变和切向畸变是两种主要的畸变现象。径向畸变使得直线变得弯曲。切向畸变使得离图像中心点越远的点看上去更远。
径向畸变:
在这里插入图片描述

径向畸变可表示为如下公式:
在这里插入图片描述
切向畸变:
在这里插入图片描述
切向畸变可表示为如下公式:
在这里插入图片描述
简而言之,我们需要找到上面的五个参数,其被称为畸变系数,由下式给出:
在这里插入图片描述
  除此之外,我们还需要一些其他信息,像是相机的固有属性和可变属性。固有属性是每个相机的特有属性。其中包括像是焦距(fx,fy)和光心(Cx,Cy)。焦距和光心可以被用于创建相机矩阵,用于消除相机镜头特有属性造成的畸变。每个相机的相机矩阵都是独一无二的,所以一旦我们计算出来,便可以在同一相机所拍摄的其他图像上重复使用。其表示为 3×3 的矩阵:
在这里插入图片描述
  如果想要获取更好的畸变系数,我们需要至少 10 个测试图像。图片可以使用opencv自带案例(如何下载opencv自带案例,可以参考另一篇文章。)

二、校准步骤

相机校准所需要的重要输入数据便是 3D 真实世界点的集合以及在图像中这些点所对应的 2D 坐标。3D 点被称作对象点,2D 点被称作图像点。
(1)我们可以轻易从这些图像中寻找到 2D 图像点。(这些图像点是棋盘中两个黑色块相交的位置)。
(2)图像于同一相机静止拍摄,其中的棋盘放置于不同的位置与方向。为了简单起见,我们可以说棋盘在 XY 平面保持静止,(所以 Z 恒等于 0 )而相机是移动物件。现在对于 X,Y 的值,我们可以简单地传递像是(0,0), (1,0), (2,0), … 之类的点用于表示点的位置。在此之下,我们得到的结果将是棋盘方块相对的大小。但是如果我们知道棋盘方块的大小,(大约 30mm),我们便可以传递像(0,0), (30,0), (60,0), …这样的值。因此,我们的到的结果也是 mm 为单位的。(因为我们没有拍摄这些图像,我们不知道方块尺寸,所以我们将方块尺寸作为参数传入。

1.标定

  测量相机焦距和便宜主要的原理是张正友标定法,测量畸变参数是Brown算法。所以为了寻找到棋盘上的图案,我们可以使用一个函数,cv.findChessboardCorners()。该标定函数的一个输入参数是像点坐标,即在摄像机成像平面上对应角点相对于摄像机坐标系的二维坐标。而获得像点坐标的函数第一步就是找到角点坐标,函数是findChessboardCorners(image,patternSize,corners,flags = None)

cv.findChessboardCorners参数:
  image:输入原始的棋盘板图像。该图像必须是一张8位的灰度图或色彩图。
  patternSize:(w,h),棋盘上每一排和每一列的内角数。w=棋盘板一行上黑白块的数量-1,h=棋盘板一列上黑白块的数量-1,例如:10x6的棋盘板,则(w,h)=(9,5)。
  corners:array,检测到的角点的输出数组。
  flags:int,不同的操作标记,能够为0或者下述值的组合:
    CALIB_CB_ADAPTIVE_THRESH 使用自适应阈值法把图像转换为黑白图,而不是使用一个固定的阈值。
    CALIB_CB_NORMALIZE_IMAGE 在利用固定阈值或自适应阈值法二值化图像之前,利用直方图均衡化图像。
    CALIB_CB_FILTER_QUADS 使用额外的标准(如轮廓面积,周长,正方形形状)来过滤掉在轮廓检索阶段提取的假四边形。
    CALIB_CB_FAST_CHECK 对图像运行一个快速检查机制以查找棋盘板的角点,如果没有找到角点则返回一个快捷提醒。当没有观察到棋盘时,可以极大地加快在退化条件下的调用。
cv.cornerSubPix参数:
  image:输入图像
  corners:输入角点的初始坐标以及精准化后的坐标用于输出。
  winSize:搜索窗口边长的一半,例如如果winSize=Size(5,5),则一个大小为的搜索窗口将被使用。
  zeroZone:搜索区域中间的dead region边长的一半,有时用于避免自相关矩阵的奇异性。如果值设为(-1,-1)则表示没有这个区域。
  criteria:角点精准化迭代过程的终止条件。也就是当迭代次数超过criteria.maxCount,或者角点位置变化小于criteria.epsilon时,停止迭代过程。

import numpy as np
import cv2 as cv
import glob

# 终止标准
#当任何上面的条件满足就停止迭代
#cv2.TERM_CRITERIA_EPS - 如果满足了指定准确度,epsilon就停止算法迭代。
#cv2.TERM_CRITERIA_MAX_ITER - 在指定次数的迭代后就停止算法。
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 点
imgpoints = [] # 在图像平面中的 2d 点

images = glob.glob(r'D:\test\venv\data\*.jpg')

for fname in images:
    img = cv.imread(fname)
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) #从RBG和BGR颜色空间转换到灰度空间

    # 找到棋盘上所有的角点
    ret, corners = cv.findChessboardCorners(gray, (7,6), None)

    # 如果找到了,便添加对象点和图像点(在细化后)
    if ret == True:
        objpoints.append(objp)
 		#对检测到的角点作进一步的优化计算,可使角点的精度达到亚像素级别。
        corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
        imgpoints.append(corners)

        # 绘制角点
        cv.drawChessboardCorners(img, (7,6), corners2, ret)
        cv.imshow('img', img)
        cv.waitKey(500)

cv.destroyAllWindows()

在这里插入图片描述

2.校准

现在我们拥有了对象点与图像点,我们便可以准备开始校准了。我们使用函数返回相机矩阵,畸变系数,旋转和平移向量等等。
cv.calibrateCamera:
  ret也就是retval,表示的是重投影误差;
  mtx是相机的内参矩阵;
  dist表述的相机畸变参数;
  rvecs表示标定棋盘格世界坐标系到相机坐标系的旋转参数:rotation vectors,需要进行罗德里格斯转换;
  tvecs表示translation vectors,主要是平移参数。
代码如下 :

#通过多个视角的2D/3D对应,求解出该相机的内参数和每一个视角的外参数
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

在这里插入图片描述

3.矫正

(1)使用cv.getOptimalNewCameraMatrix()基于自由缩放参数来优化相机矩阵。如果缩放参数alpha = 0,则返回具有最少不需要像素的未失真图像。因此,它甚至可能会删除图像角落的一些像素。如果alpha = 1,则所有像素都保留有一些额外的黑色图像。此函数还返回可用于裁剪结果的图像ROI。
注:
  矩阵中,[0]表示行数,[1]表示列数
  img.shape[:2]取彩色图片的长、宽
  img.shape[:3]取彩色图片的长、宽、通道
  img.shape[0]图像的垂直尺寸(高度)
  img.shape[1]图像的水平尺寸(宽度)
  img.shape[2]图像的通道数

img = cv.imread('left12.jpg') #使用一张新图像做处理
h, w = img.shape[:2]
newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))

OpenCV提供了两种方法来对拍摄图像进行扭曲:

  1. 使用cv.undistort()
#有时不需要矫正整个图像,而仅仅计算图像中特定点的位置,这是可以使用undistortPoints函数
dst = cv.undistort(img, mtx, dist, None, newcameramtx) # 输入原图 输出矫正后的图像  内参矩阵 畸变系数
#剪裁图像
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv.imwrite('calibresult1.png', dst)

在这里插入图片描述

  1. 使用initUndistortRectifyMap()+remap()
    cv.initUndistortRectifyMap:用于计算无畸变和修正转换关系。
      cameraMatrix:相机矩阵
      distCoeffs:相机的畸变系数
      R:可选的修正变换矩阵,是个3*3的矩阵。通过stereoRectify计算得来的R1或R2可以放在这里。如果这个矩阵是空的,就假设为单位矩阵。在cvInitUndistortMap中,R被认为是单位矩阵。
      newCameraMatrix:新的相机矩阵
      size:未畸变的图像尺寸。
      m1type:第一个输出的映射的类型,可以为 CV_32FC1, CV_32FC2或CV_16SC2
    cv.remap:输入图像中各个像素按照一定的规则映射到另外一张图像的对应位置上去,形成一张新的图像。
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('calibresult2.png', dst)

在这里插入图片描述
两张照片似乎并无明显差异


4.重投影误差分析

重投影误差可以很好地估计找到的参数的精确程度。重投影误差越接近零,我们发现的参数越准确。
首先,使用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(error)
print( "total error: {}".format(mean_error/len(objpoints)) )

在这里插入图片描述

总结

刚刚入门,还是一直在参考各种大佬的文章。文章大部分是参考了极客笔记的《OpenCV-Python 相机校准和消除畸变》,大佬的笔记非常好,强烈推荐!还有一部分是opencv官方中文手册以及其他介绍opencv用法的文章!

  • 11
    点赞
  • 107
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小张Tt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值