全景图像畸变校正

全景图像畸变校正

1.简介

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

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

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

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

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。这样标定虽然有些误差但是也能有不错的效果。