欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

相机失真、内部和外部参照、不失真图像、OpenCV 中的相机校准

最编程 2024-04-07 20:39:55
...

OpenCV中的相机失真、内外参、不失真图像、相机校准

这篇博客将介绍相机的失真、相机的内在和外在参数等,并学习找到这些参数,不失真图像,并进行相机校准、立体成像等。

1. 效果图

原图 VS 棋盘模式效果图如下:

可以看到原图存在失真(径向扭曲,直线会显得弯曲;以及切向失真,这是因为图像拍摄镜头没有完全平行于成像平面与之对齐。因此图像中的某些区域可能看起来比预期的更近。) 在这里插入图片描述原图 VS 棋盘模式效果图2如下: 在这里插入图片描述原图 VS 图像校准后效果图如下:

可以看到校准后的右侧图中,棋盘的框线都变成非常直的了; 在这里插入图片描述

2. 原理

2.1 相机校准

将了解相机的失真、相机的内在和外在参数等。并学习找到这些参数,不失真图像等。

distortion, intrinsic parameters,extrinsic parameters 失真、内参、外参 radial distortion 径向扭曲,也称径向畸变或者径向失真; tangential distortion 切向扭曲,也称切向畸变或者切向失真;

廉价针孔相机给图像带来了很多失真,两种主要的畸变是径向扭曲和切向扭曲。 由于径向扭曲,直线会显得弯曲,当远离图像中心时,它的影响更大。 另一种失真是切向失真,这是因为图像拍摄镜头没有完全平行于成像平面与之对齐。因此图像中的某些区域可能看起来比预期的更近。

  • 需要找到五个参数,称为失真系数,以及相机的内在和外在参数。
  • 内在参数特定于相机,它包括焦距(f_x,f_y),光学中心(c_x,c_y)等信息,也称为相机矩阵。
  • 外部参数对应于将 3D 点的坐标转换为坐标系的旋转和平移向量。

对于立体声应用,首先需要校正这些失真。在棋盘中找到了一些特定的点(棋盘中的方角)。知道它在现实世界空间中的坐标,也知道它在图像中的坐标。使用这些数据,在后台计算以获得失真系数。

仅考虑棋盘的一张图像。相机校准所需的重要输入数据是一组 3D 现实世界点及其相应的 2D 图像点。

2D 图像点可以很容易地从图像中找到。 (这些图像点是棋盘中两个黑色方块相互接触的位置)

3D点假设z=0,正方形格子的边长,也可以得到点;

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

2.2 用到的方法

  • cv2.findChessboardCorners() 查找矩形的网格模式,返回角点和retval(找到模式返回True,角点按顺序从左到右,从上到下);
  • cv2.findCirclesGrid() 查找圆形的网格模型,据说使用圆形网格时,图像数量较少就足够了。
  • 找到角点后,可使用 cv2.cornerSubPix() 来提高它们的准确度
  • cv2.drawChessboardCorners() 使用该方法绘制图案

3. 源码

# 相机的内外参,失真系数,校准等。
# 拿到一组3D点对象,2D图像点,可以使用相应的方法进行校准;
import numpy as np
import cv2
import glob
import json


# 将相机矩阵、失真系数写入文件
def write2Npz(ret, mtx, dist, rvecs, tvecs):
    # print(ret)
    # print(mtx)
    # print(dist)
    # print(rvecs)
    # print(tvecs)
    np.savez('qpimgs/B.npz', mtx=mtx, dist=dist, rvecs=rvecs, tvecs=tvecs)


# 校准的终止准则
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

# 准备3D的对象点,如(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)

# 存储3D对象点、2D图像点
objpoints = []  # 现实世界3D点
imgpoints = []  # 图像平面2D点

images = glob.glob('qpimgs/*.jpg')

for fname in images:
    origin = cv2.imread(fname)
    img = origin.copy()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 查找网格模式,寻找角点
    ret, corners = cv2.findChessboardCorners(gray, (7, 6), None)

    # 如果找到了,添加对象点,以及经过细化后的图像点
    if ret == True:
        objpoints.append(objp)

        # 提高角点的准确度
        cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        imgpoints.append(corners)

        # 绘制模式角点和显示
        cv2.drawChessboardCorners(img, (7, 6), corners, ret)
        cv2.imshow('origin VS pattern', np.hstack([origin, img]))
        cv2.waitKey(0)

cv2.destroyAllWindows()

# 有了目标点和图像点可以开始校准了。为此使用函数 cv2.calibrateCamera()。它返回相机矩阵、失真系数、旋转和平移向量等。
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
write2Npz(ret, mtx, dist, rvecs, tvecs)

# cv2.getOptimalNewCameraMatrix() 获取基于*缩放参数细化相机矩阵。如果缩放参数 alpha=0,则返回具有最少不需要像素的未失真图像。所以它甚至可能会去除图像角落的一些像素。
# 如果 alpha=1,所有像素都会保留一些额外的黑色图像。它还返回一个图像 ROI,可用于裁剪结果。
img = cv2.imread('qpimgs/left12.jpg')
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))

# 法一:
# 不失真
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)

# 剪裁ROI图像
x, y, w, h = roi
dst = dst[y:y + h, x:x + w]
cv2.imwrite('qpimgs/calibresult1.jpg', dst)
cv2.imshow("origin", img)
cv2.imshow("caliresult", dst)

# 法二:
# 首先找到从失真图像到未失真图像的映射函数。然后使用重映射功能。
# 不失真
mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w, h), 5)
dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)

# 剪裁ROI
x, y, w, h = roi
dst = dst[y:y + h, x:x + w]
cv2.imwrite('qpimgs/calibresult2.jpg', dst)
cv2.imshow("caliresult2", dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 校准后可以看到棋盘的所有边缘都是直的。可以使用 Numpy 中的写入函数(np.savez、np.savetxt 等)存储相机矩阵和失真系数以备将来使用。

# 重投影误差
# 重新投影误差可以很好地估计找到的参数的精确程度,应该尽可能接近于零。
# 给定内在、扭曲、旋转和平移矩阵,首先使用 cv2.projectPoints() 将对象点转换为图像点。然后计算变换得到的和角点寻找算法之间的绝对范数。
# 为了找到平均误差,计算了为所有校准图像计算的误差的算术平均值。
mean_error = 0
tot_error = 0
for i in range(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))

参考

  • docs.opencv.org/3.0-beta/do…