网上大部分的镜头畸变矫正的例子都是给的小图片的测试效果,照搬他们的代码测试的话会发现,在他们给的小图片上效果良好,但是应用到自己的 超大图片(4000×3000像素) 上后会发现边缘效果很不理想!效果如下:
原图(4000×3000像素)(实验室镜头较好,看不出明显的畸变)
矫正后的效果(4000×3000像素)(仔细对比观察中间4×4的方格可以发现更加垂直了,但边缘部分形变严重)
接下来将先从理论方面为大家分析后,再附上代码,如果对理论不感兴趣也可以直接看代码
镜头畸变分为径向畸变和切向畸变两部分
其中径向畸变的修正采用主点周围的泰勒级数展开式的前几项进行描述,下面的公式中采用了 k1,k2,k3 三项来进行描述
注意:使用的展开项越多,镜头畸变矫正越准确!
使用cv2.calibrateCamera函数默认只返回 k1,k2,k3!!!
这将导致当 r 增大时,矫正时的误差会越来越大,对于100×100像素的小图片来说 r 最大也只有 50 2 50\sqrt2 502 ,而对于4000×3000的超大图片而言,r 最大可以达到 2500,公式中当取到 r 3 r^3 r3 时,误差将呈几何式增长
查看OpenCV官网中cv2.calibrateCamera函数说明文档,发现该函数有一个参数flags,可以通过该参数控制返回的畸变系数的数量等高级设置
可以通过设置 flags=cv2.CALIB_RATIONAL_MODEL来控制最后返回的畸变系数数量,除了返回 k1,k2,k3,p1,p2 外,多返回 k4,k5,k6 的值
通过这样一个小的修改即可解决超大图片畸变矫正边缘形变的问题
import cv2
import numpy as np
import glob
# 阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# w h分别是棋盘格模板长边和短边规格(角点个数)
w = 5
h = 5
dir = 'train_image/*.jpg' # 所有图片数据
re_im = 'test_image/Image.jpg' # 要矫正的图像
sign_im = 'sign_image/' + 'sign.jpg'
sv_im = 'adjust_image/' + 'adjust.jpg'
# 世界坐标系中的棋盘格点
objp = np.zeros((w*h, 3), np.float32) # 构造0矩阵,用于存放角点的世界坐标
objp[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2) # 三维网格坐标划分
# 储存棋盘格角点的世界坐标和图像坐标对
objpoints = [] # 在世界坐标系中的三维点
imgpoints = [] # 在图像平面的二维点
images = glob.glob(dir)
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# cv2.imshow('picture', gray)
# cv2.waitKey()
# cv2.destroyAllWindows()
# 粗略找到棋盘格角点 这里找到的是这张图片中角点的亚像素点位置,共7×7 = 49个点,gray必须是8位灰度或者彩色图,(w,h)为角点规模
ret, corners = cv2.findChessboardCorners(gray, (w, h))
# 如果找到足够点对,将其存储起来
if ret is True:
# 精确找到角点坐标
corners = cv2.cornerSubPix(gray, corners, (3, 3), (-1, -1), criteria)
# 将正确的objp点放入objpoints中
objpoints.append(objp)
imgpoints.append(corners)
# 将角点在图像上显示
cv2.drawChessboardCorners(img, (w, h), corners, ret)
cv2.imwrite(sign_im, img) # 保存图片
# cv2.imshow('findCorners', img)
# cv2.waitKey()
# cv2.destroyAllWindows()
# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None, flags=cv2.CALIB_RATIONAL_MODEL)
# ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# 去畸变
img2 = cv2.imread(re_im)
h, w = img2.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h)) # 自由比例参数
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)
cv2.imwrite(sv_im, dst) # 保存图片
# cv2.imshow('undistort_picture', dst)
# cv2.waitKey()
# cv2.destroyAllWindows()
# 反投影误差
total_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)
total_error += error
print("total error: ", total_error/len(objpoints))
原图(4000×3000像素)
矫正后的效果(4000×3000像素)(仔细对比观察可以看到矫正后的图片左侧有一条黑线,图像也略微向右聚拢了一些)