简记摄像机标定

《简记摄像机标定》

  CV的数据源头是摄像机,我们根据不同的场景需要选用不同的摄像机,如果视野范围优先,我们考虑使用广角;如果精度优先,我们考虑使用无畸变的相机,或者微畸变的相机再进行图像的矫正;由于透镜制造精度以及组装工艺的偏差会引入畸变,就会导致原始图像的失真,而我们的任务是想大概知道一个像素对应多少mm,所以需要畸变矫正。

Key Words:相机标定、畸变、OpenCV


Beijing, 2020

作者:RaySue

Code:

Agile Pioneer  


前言

  相机的畸变主要取决于镜头,短焦镜头的视野更广,但更容易产生畸变。而长焦镜头产生很少的畸变,但是视野却更小了,所以畸变的矫正有时候是一个相对折中的办法。

  如果把工作距离(距离目标的高度)设置太高,那么精度就会下降,精度=分辨率/范围。


相机的内参、外参、畸变系数

  1. 6个外部参数(取决于相机在世界坐标系的位置)

    • 3个旋转参数R
    • 3个平移参数T
  2. 5个内部矩阵参数K:

    • f f f - 焦距(单位mm)
    • d x d_x dx - 1像素对应的宽(单位mm/pixel)
    • d y d_y dy - 1像素对应的长(单位mm/pixel)
    • u 0 u_0 u0, v 0 v_0 v0 - 相机主点(光学中心)
    • 内部矩阵参数也可以用四个参数表示为:
      • f x = f d x f_x = \frac{f}{d_x} fx=dxf - 焦距1
      • f y = f d y f_y = \frac{f}{d_y} fy=dyf - 焦距2
      • u 0 u_0 u0, v 0 v_0 v0 - 相机主点(光学中心)
  3. 5个畸变参数D:

    • k 1 , k 2 , k 3 k_1,k_2,k_3 k1,k2,k3 - 径向畸变
    • p 1 , p 2 p_1,p_2 p1,p2 - 切向畸变

准备工作

  准备一个棋盘格图像,如下图,然后,指定程序中内角点的size,比如下图的内角点size是(7, 9)。对棋盘格子拍照的时候尽量多样一些,取15~20张图左右。在标定相机的时候,可以将事先知道的相机矩阵或畸变矩阵输入进函数calibrateCamera。


相机标定流程

  相机的标定过程实际上就是在4个坐标系转化的过程中求出相机的内参和外参的过程。这4个坐标系分别是:世界坐标系(描述物体真实位置),相机坐标系(摄像头镜头中心),图像坐标系(图像传感器成像中心,图片中心,影布中心,单位mm),像素坐标系(图像左上角为原点,描述像素的位置,单位是多少行,多少列,单位pixel)。

  1. 世界坐标系 -> 相机坐标系:求解摄像头外参(旋转和平移矩阵);

  2. 相机坐标系 -> 图像坐标系:求解相机内参(摄像头矩阵和畸变系数);

  3. 图像坐标系 -> 像素坐标系:求解像素转化矩阵(可简单理解为原点从图片中心到左上角,单位mm变行列)


参考代码

import cv2
import glob
import pickle as pkl
import numpy as np

# 一个用于测试的畸变图像路径
distort_image_path = "/Users/Pictures/test.png"

# 用于存放采集的棋盘格子的数据,尽可能的保证距离不要有太大变化
calibration_images = glob.glob("/Users/Pictures/calibration_td/*.png")
distort_image = cv2.imread(distort_image_path)

# 指定棋盘格子内角点的个数
patternSize = (7, 9)

# termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.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((patternSize[0] * patternSize[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:patternSize[0], 0:patternSize[1]].T.reshape(-1, 2)
# Arrays to store object points and image points from all the images.
objpoints = []  # 真实世界的3维坐标
imgpoints = []  # 图像的二维平面

# 开始通过角点来计算图像的二维平面坐标
i = 0
for fname in calibration_images:
    img = cv2.imread(fname)
    print(img.shape)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # Find the chess board corners
    try:
        ret, corners = cv2.findChessboardCorners(gray, patternSize, None)
    except:
        ret, corners = cv2.findChessboardCorners(gray, patternSize[::-1], None)
    if ret: print(fname)
    # If found, add object points, image points (after refining them)
    if ret:
        i += 1
        objpoints.append(objp)
        corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        imgpoints.append(corners)
        # 把找到的角点画出来
        cv2.drawChessboardCorners(img, patternSize, corners2, ret)
        cv2.imshow('img', img)
        # 指定存储路径
        cv2.imwrite('/Users/surui/Pictures/calibration_show/' + str(i) + '.jpg', img)
        # cv2.waitKey(50)
# cv2.destroyAllWindows()

# print(objpoints)
# print(imgpoints)

## 缓存中间结果
pkl.dump([objpoints, imgpoints], open("./calibration.pkl", "wb"))
objpoints, imgpoints = pkl.load(open("./calibration.pkl", "rb"))

print(gray.shape[::-1])
# 利用三维坐标和二维坐标来标定相机
retval, cameraMatrix, distCoeffs, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

# print(cameraMatrix)
# print(distCoeffs)

# 畸变图像矫正
h, w = distort_image.shape[:2]
print(h, w)
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, (w, h), 1, (w, h))
dst = cv2.undistort(distort_image, cameraMatrix, distCoeffs, None, newcameramtx)
x, y, w, h = roi
print(roi)  # 如果roi都是0的话那么就把这行注释掉,不用ROI进行裁剪
dst = dst[y:y + h, x:x + w]
cv2.imwrite('calibresult.png', dst)

# undistort
mapx, mapy = cv2.initUndistortRectifyMap(cameraMatrix, distCoeffs, None, newcameramtx, (w, h), 5)
dst = cv2.remap(distort_image, mapx, mapy, cv2.INTER_LINEAR)
# crop the image
x, y, w, h = roi  # 如果roi都是0的话那么就把这行注释掉,不用ROI进行裁剪
dst = dst[y:y + h, x:x + w]
cv2.imwrite('calibresult2.png', dst)


# 计算反向投影误差
mean_error = 0
for i in range(len(objpoints)):
    imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs)
    error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
    mean_error += error
print("total error: {}".format(mean_error / len(objpoints)))

效果

  • 检测棋盘格角点结果
  • 畸变前
  • 矫正后

建议

  • 尽量保证摄像头的工作距离变化不要太大(距离棋盘格子的距离)
  • 尽量保证摄像头垂直于棋盘格子移动
  • 采集棋盘格子数据20张左右

参考

[1]https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_calib3d/py_calibration/py_calibration.html

[2]镜头畸变现象及其校正方法

[3]单目摄像头标定与畸变矫正(C++,opencv)

[4]第35章:摄像头标定

[5]Python使用OpenCV进行标定

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值