简单介绍
所谓畸变其实就是由摄像头引起的图片失真, 一般在广角摄像头表现明显, 原本平整的桌面通过镜头看像个球面, 直观的解释直线被拍成了曲线, 这让我想起来了一个表情包.
去畸变的办法
首先我们需要一个标准棋盘(印有特定的标定图案), 如图:
把它摊平放在桌子上, 然后用需要去畸变的相机对它进行拍摄, 拍几张(10~20张差不多)不同角度的棋盘图片, 保存好. 然后我们有两种方法进行畸变矫正.
1. 借助matlab的Camera Calibration Toolbox工具
我们打开matlab,
在应用程序这里.
找到 Camera Calibration, 然后点击打开.
然后点击Add Images添加图片,
这里会弹出此弹框, 意思是选择我们棋盘格子的大小, 根据实际情况选择就行.
添加后会显示角点, 如果有不清楚的或者识别错误,红色边框, 可以删掉换一张图片就行.
导入多张图片后, 直接点击Calibrate 开始标定
完成后大致如上图, 会显示各个参数
- 重投影误差(Reprojection Error):理想情况下应 < 0.5 像素.
- 相机内参:
-
- 焦距(Focal Length)
- 主点(Principal Point)
- 畸变系数(Radial, Tangential)
新版有这两项
- 可视化:
- Show Undistorted Images 查看校正效果。
- Plot Reprojection Errors 分析误差分布。
我2016版本并没有这个框
确认无误后可以点击 Export Camera Parameters导出数据, 则会导出到workspace当中生成mat文件
当然也可以点击第二个生成matlab代码进行后续操作
2. 使用OpenCV进行标定
OpenCV是计算机视觉库, 可以用来进行图像处理, 里面有很多函数, 我们可以借助它来进行畸变矫正.
首先我们将拍摄的照片全部保存到一个文件夹中然后, 然后我们需要手动指定实际拍摄图片中有多少个棋盘角点.
比如说这张图片, 是 9 X 5 的棋盘
对于所有的图片, 我们都需要进行同样的操作, 然后保存到列表中.类似于
# 每张图片的棋盘格参数, 9 X 6 格大小
objp_dict = {
1: (9, 5),
2: (9, 6),
3: (9, 6),
4: (9, 6),
5: (9, 6),
6: (9, 6),
7: (9, 6),
8: (9, 6),
9: (9, 6),
10: (9, 6),
11: (9, 6),
12: (9, 6),
13: (9, 6),
14: (9, 6),
15: (9, 6),
16: (9, 6),
17: (9, 6),
18: (9, 6),
19: (9, 6),
20: (9, 6),
}
根据指定的大小获取三维(np.zeros((nx*ny, 3), np.float32)) 理论坐标
objp_list = [] # 存储对象点
corners_list = [] # 存储角点
# 遍历objp_dict的每个参数
for k in objp_dict:
nx, ny = objp_dict[k] # 取出长宽
# (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((nx*ny, 3), np.float32) # 生成三维坐标,即每个点理论坐标应该是多少
objp[:, : 2] = np.mgrid[0:nx, 0:ny].T.reshape(-1, 2) # 生成x和y的坐标,z坐标都是0
读取图片通过 cv2.cvtColor(灰度化) 和 cv2.findChessboardCorners(棋盘角点) 寻找真实的情况
# 对于识别的点应该的坐标序号初始化好后, 开始来读取图片中每个点的真实位置
fname = 'camera_cal/calibration%s.jpg' % str(k)
img = cv2.imread(fname) # 读取一张图片
# 转成灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 检测角点
ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)
将返回值保存到列表中
if ret == True: # 成功检测到了
# Save object points and corresponding corners
objp_list.append(objp) #
corners_list.append(corners)
将列表带入 cv2.calibrateCamera 中即可得到参数 内参矩阵(mtx)和畸变系数(dist)
img = cv2.imread('test_images/straight_lines1.jpg') # 这里得保证所有图像的大小一致
img_size = (img.shape[1], img.shape[0]) # 获取测试图片的尺寸
# 获取相关参数
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objp_list, corners_list, img_size,None,None)
最后使用 cv2.undistort 将图片进行去畸变即可
# 去畸变
img = mpimg.imread('camera_cal/calibration5.jpg') # 读取图像
dst = cv2.undistort(img, mtx, dist, None, mtx)
下面来康康完整代码:
import numpy as np
import cv2
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import pickle
def calibrate_camera():
# Mapping each calibration image to number of checkerboard corners
# Everything is (9,6) for now
# 每张图片的棋盘格参数, 9 X 6 格大小
objp_dict = {
1: (9, 5),
2: (9, 6),
3: (9, 6),
4: (9, 6),
5: (9, 6),
6: (9, 6),
7: (9, 6),
8: (9, 6),
9: (9, 6),
10: (9, 6),
11: (9, 6),
12: (9, 6),
13: (9, 6),
14: (9, 6),
15: (9, 6),
16: (9, 6),
17: (9, 6),
18: (9, 6),
19: (9, 6),
20: (9, 6),
}
# List of object points and corners for calibration
objp_list = [] # 存储对象点
corners_list = [] # 存储角点
# Go through all images and find corners
# 遍历objp_dict的每个参数
for k in objp_dict:
nx, ny = objp_dict[k] # 取出长宽
# Prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((nx*ny, 3), np.float32) # 生成三维坐标,即每个点理论坐标应该是多少
objp[:, : 2] = np.mgrid[0:nx, 0:ny].T.reshape(-1, 2) # 生成x和y的坐标,z坐标都是0
# 对于识别的点应该的坐标序号初始化好后, 开始来读取图片中每个点的真实位置
# Make a list of calibration images
fname = 'camera_cal/calibration%s.jpg' % str(k)
img = cv2.imread(fname) # 读取一张图片
# Convert to grayscale 转成灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Find the chessboard corners
# 检测角点
ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)
# If found, save & draw corners
if ret == True: # 成功检测到了
# Save object points and corresponding corners
objp_list.append(objp) #
corners_list.append(corners)
# Draw and display the corners
#cv2.drawChessboardCorners(img, (nx, ny), corners, ret)
#plt.imshow(img)
#plt.show()
#print('Found corners for %s' % fname)
else: # 否则没有检测到
print('Warning: ret = %s for %s' % (ret, fname))
# Calibrate camera and undistort a test image
img = cv2.imread('test_images/straight_lines1.jpg') # 这里得保证所有图像的大小一致
img_size = (img.shape[1], img.shape[0]) # 获取测试图片的尺寸
# 获取相关参数
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objp_list, corners_list, img_size,None,None)
return mtx, dist
if __name__ == '__main__':
mtx, dist = calibrate_camera()
save_dict = {'mtx': mtx, 'dist': dist} # 得到数据
# 将mtx和dist通过pickle保存至calibrate_camera.p中
# pickle库将此序列转化成二进制字节流存入.p文件中, wb:w--写入, b--以二进制形式
with open('calibrate_camera.p', 'wb') as f:
pickle.dump(save_dict, f)
# Undistort example calibration image
# 去畸变
img = mpimg.imread('camera_cal/calibration5.jpg') # 读取图像
dst = cv2.undistort(img, mtx, dist, None, mtx)
plt.imshow(dst) # 显示矫正结果
plt.savefig('example_images/undistort_calibration.png') # 然后保存图片
参考资料:
代码来自github的开源项目
Lane Detection with OpenCV
十年前的牢项目了, 还是很有参考意义的
上海交通大学AuTop战队开源算法讲解(三)标定与透视变换
摄像头校准之白平衡&畸变&坏点