摄像头畸变矫正详解:Matlab与OpenCV两种实现方法
摄像头畸变矫正详解:Matlab与OpenCV两种实现方法
摄像头畸变矫正是一种常见的图像处理技术,主要用于解决广角镜头拍摄时出现的图像失真问题。本文将介绍两种常用的畸变矫正方法:使用Matlab的Camera Calibration Toolbox和使用OpenCV进行标定。
简单介绍
所谓畸变其实就是由摄像头引起的图片失真,一般在广角摄像头表现明显,原本平整的桌面通过镜头看像个球面,直观的解释直线被拍成了曲线。
去畸变的办法
首先我们需要一个标准棋盘(印有特定的标定图案),如图:
把它摊平放在桌子上,然后用需要去畸变的相机对它进行拍摄,拍几张(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 分析误差分布。
确认无误后可以点击 Export Camera Parameters导出数据,生成mat文件。也可以点击第二个按钮生成matlab代码进行后续操作。
2. 使用OpenCV进行标定
OpenCV是计算机视觉库,可以用来进行图像处理,里面有很多函数,我们可以借助它来进行畸变矫正。
首先将拍摄的照片全部保存到一个文件夹中,然后需要手动指定实际拍摄图片中有多少个棋盘角点。例如,一张9 X 5的棋盘图片。
对于所有的图片,都需要进行同样的操作,然后保存到列表中。
根据指定的大小获取三维(np.zeros((nx*ny, 3), np.float32)) 理论坐标:
# 每张图片的棋盘格参数, 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),
}
# 遍历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战队开源算法讲解(三)标定与透视变换
- 摄像头校准之白平衡&畸变&坏点