全景图像畸变校正

1.简介

理想的相机基本上是小孔成像的,在小孔成像模型中,如果焦距一定,那么图像传感器像素平面的面积直接决定了相机视场角的大小,超过这个视场角范围的物体不会被镜头获取到。因此基于透镜成像原理的相机,视场角无法做到足够大,水平视场角一般小于140°。

但是在一些领域,比如气象科学,科技工作者需要对天空天象变化进行观测,需要有一种相机能将整个半球形天空一次性拍摄下来。而在安防监控领域,安保团队期望有一种相机能从俯视角度一次性拍摄整个监控区域。而为了实现这些目的,就需要相机具有水平180°甚至更大的视场角能力。

就在科研人员陷入苦苦思索的时候,这个时候仿生学义无反顾站了出来。科学家们发现鱼的眼睛在往上看的时候,可以看到水面上整个半球形空间。细究原因,科学家发现由于水的折射率比空气大,光线由空气进入水中后会发生折射,且折射角比入射角要小。同时随着入射角增加,折射角变小的程度也增加。基于这个特性,水面上180°半球形空间的物体就可以被扭曲、压缩到一个有限的成像平面上。
 

cc86a94cf53de5a2320bdda0e2a2dab5.png

虽然这样大大的增加了视场角,能够看到的角度更大,但是这样也产生了图像畸变的问题。

2.图像畸变

相机的成像过程实质上是坐标系的转换。首先空间中的点由 “世界坐标系” 转换到 “像机坐标系”,然后再将其投影到成像平面 ( 图像物理坐标系 ) ,最后再将成像平面上的数据转换到 图像像素坐标系。但是由于透镜制造精度以及组装工艺的偏差会引入畸变,导致原始图像的失真。镜头的畸变分为径向畸变和切向畸变两类。参见:

http://blog.csdn.net/dcrmg/article/details/52950141

http://blog.csdn.net/waeceo/article/details/50580808

由于切向畸变是由于组装工艺的偏差,所以我们大部分主要解决的是图像径向畸变的校正。

3.畸变校正

想要对图像进行畸变校正先要知道几个参数,分别是相机的内参,包括相机的焦距、成像中心和畸变参数;相机的外参,包括旋转矩阵和平移矩阵。如果你详细的知道相机的内参,那么图像校正就很容易,但大多数情况你是不知道的,这样就需要通过相机的标定来得到相机的内参和外参了。

现在最常用的方法是张正友标定法,不知道的去搜一下就了解了。具体的原理有能力的可以了解下,但最重要的还是知道如何使用就行了。

最常用的就是用棋盘格对相机参数进行标定,步骤如下:

1.需要一个棋盘格,精度越高越好,最好整个棋盘格都处于同一平面中,没有凹凸。

2.从多个视角拍摄棋盘格,棋盘格必须全部出现在图像中,最好能出现在图像的各个位置,比如一张图像中棋盘格出现在图像的左上角,一张出现在右上角。最好能够拍摄10~15张图像。

3.用opencv自带的包进行棋盘格角点的检测,得到世界坐标系下角点的坐标objpoint和像素坐标系下角点的像素坐标imgpoint。

4.利用objpoint和imgpoint来标定相机参数,得到相机的内参、畸变系数、旋转矩阵和平移矩阵。

5.利用得到的相机参数对图像进行畸变校正。

4.畸变校正代码

# coding:utf-8
import cv2
import numpy as np
import glob

# 找棋盘格角点
# 阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 棋盘格模板规格
w = 9
h = 6
# 世界坐标系中的棋盘格点,例如(0,0,0), (1,0,0), (2,0,0) ....,(8,5,0),去掉Z坐标,记为二维矩阵
objp = np.zeros((w * h, 3), np.float32)
objp[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)
# 储存棋盘格角点的世界坐标和图像坐标对
objpoints = []  # 在世界坐标系中的三维点
imgpoints = []  # 在图像平面的二维点

images = glob.glob('D:images\\*.jpg')
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 找到棋盘格角点
    ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
    # 如果找到足够点对,将其存储起来
    if ret == True:
        cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        objpoints.append(objp)
        imgpoints.append(corners)
        # 将角点在图像上显示
        cv2.drawChessboardCorners(img, (w, h), corners, ret)
        cv2.imshow('findCorners', img)
        # cv2.imwrite('D:images\\grid_out.png', img)
        cv2.waitKey(1)
cv2.destroyAllWindows()

# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# print(mtx)
# print(dist)
# print(rvecs)
# print(tvecs)
# 去畸变
img2 = cv2.imread('D:images\\10.jpg')
h, w = img2.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))  # 自由比例参数
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)
# 根据前面ROI区域裁剪图片
# x,y,w,h = roi
# dst = dst[y:y+h, x:x+w]
cv2.imwrite('D:images\\grid_out.png', dst)

# 反投影误差
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))

# 校正视频
cap = cv2.VideoCapture('D:video\\video.mp4')
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
fps = int(cap.get(cv2.CAP_PROP_FPS))
frame_size = (width, height)
video_writer = cv2.VideoWriter('D:video\\result2.mp4', cv2.VideoWriter_fourcc(*"mp4v"), fps, frame_size)
for frame_idx in range(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))):
    ret, frame = cap.read()
    if ret:
      image_ = cv2.undistort(frame, mtx, dist, None, newcameramtx)
      cv2.imshow('jiaozheng', image_)
      # gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
      video_writer.write(image_)
    if cv2.waitKey(10) & 0xFF== ord('q'):
        break
cap.release()
# cv2.destroyALLWindows()

5.其他补充

如果没有标定板或者不方便使用标定板的情况,我们想要标定摄像机参数可以不用角点检测来得到objpoint,可以自己手动标记,最好是在图像中放一些标志物,最好是矩形表格状,如4x4。这样标定虽然有些误差但是也能有不错的效果。

猜你喜欢

转载自blog.csdn.net/Orange_sparkle/article/details/130102267