照片畸变校正 python_(五)单目摄像头标定与畸变矫正(C++,opencv)

本文将梳理一种单目摄像头标定和矫正的方法,在梳理的过程中,首先使用网上离线的图片数据跑通流程,然后接入自己的camera,手动采集标定图像,实时矫正相机的畸变,然后输出矫正后的图像。全文基于Opencv使用C++实现,文末附带相应的python代码。

1. 基本概念

1.1 什么是畸变

下面两张示意图可以让大家直观的感受摄像头的畸变效果,简单的讲摄像头的畸变会让人们看到的图像出现“拉伸”或“扭曲”的直观感受,出现“横不平,竖不直”的现象。虽然畸变现象改变了图像原有的面貌,但在日常生活中也有很多应用之处,比如转弯路口的凸透镜,汽车的左右后视镜等,利用畸变效果扩大视野。但自动驾驶中,往往需要利用图像进行测量或者识别,为了保证精度,在这种场景下,就需要尽量还原图像,也就是所说的“畸变矫正”。畸变效果示意图一畸变效果示意图二

1.2 为什么会产生畸变

在进行畸变矫正之前,我们需要简单的理解产生畸变的原因。通常畸变可以分为两种,一种是径向畸变,一种是切向畸变,如下面两张图所示。

径向畸变有两种形态,即桶形畸变和枕形畸变,从效果上看一个突出,一个内凹。产生径像畸变的原因是光学镜头在生产制造的过程中,很难保证厚度的均匀,离透镜中心越远的地方光线弯曲越大,从而产生径向畸变。径向畸变(图片来源https://blog.csdn.net/waeceo/article/details/50580808)

切向畸变如下图所示,从效果上看,一个平直的物体在照片中看上去会有“倾斜”,“大小不一”的现象。出现切向畸变的原因是由于镜头与图像传感器不完全平行造成的(可理解为投影仪与影布不平行)。切向畸变(图片来源https://blog.csdn.net/waeceo/article/details/50580808)

1.3 什么是摄像头参数

1)相机矩阵:包括焦距(fx,fy),光学中心(Cx,Cy),完全取决于相机本身,是相机的固有属性,只需要计算一次,可用矩阵表示如下:[fx, 0, Cx; 0, fy, cy; 0,0,1];

2) 畸变系数:畸变数学模型的5个参数 D = (k1,k2, P1, P2, k3);

3)相机内参:相机矩阵和畸变系数统称为相机内参,在不考虑畸变的时候,相机矩阵也会被称为相机内参;

4) 相机外参:通过旋转和平移变换将3D的坐标转换为相机2维的坐标,其中的旋转矩阵和平移矩阵就被称为相机的外参;描述的是将世界坐标系转换成相机坐标系的过程。

1.4 摄像头标定的流程

相机的标定过程实际上就是在4个坐标系转化的过程中求出相机的内参和外参的过程。这4个坐标系分别是:世界坐标系(描述物体真实位置),相机坐标系(摄像头镜头中心),图像坐标系(图像传感器成像中心,图片中心,影布中心,单位mm),像素坐标系(图像左上角为原点,描述像素的位置,单位是多少行,多少列)。

(1)世界坐标系

相机坐标系:求解摄像头外参(旋转和平移矩阵);

(2)相机坐标系

图像坐标系:求解相机内参(摄像头矩阵和畸变系数);

(3) 图像坐标系

像素坐标系:求解像素转化矩阵(可简单理解为原点从图片中心到左上角,单位厘米变行列)

2. 摄像头标定与矫正实践

2.1 离线图片实现摄像头标定和矫正

1)Cmakelist 配置Opencv

//要求cmake最低版本

cmake_minimum_required(VERSION 3.1)

// 工程名

project(camera_calibration)

set(CMAKE_CXX_STANDARD 11)

add_executable(camera_calibration main.cpp)

// 配置opencv

find_package(OpenCV REQUIRED)

target_link_libraries(camera_calibration ${OpenCV_LIBS}))

2)导入棋盘格图片;

在标定过程中,需要使用棋盘格,拍摄棋盘格在多个角度的图片,这里省去了拍摄的过程,直接使用网上下载的棋盘格图片。

#include

#include

#include

#include "opencv2/imgproc.hpp"

#include "opencv2/calib3d.hpp"

#include

#include

#include

#include

using namespace std;

using namespace cv;

#define PATH "/camera_calibration/image_my/"

#define NUM 30

int main() {

// 定义用来保存导入的图片

Mat image_in;

// 定义用来保存文件路径的容器

vector filelist;

// 定义用来保存旋转和平移矩阵的容器

vector rvecs, tvecs;

// 定义相机矩阵,畸变矩阵

Mat cameraMatrix;

Mat distCoeffs;

int flags = 0;

// 定义保存图像二维角点的容器

vector corners;

// 定义保存图像三维角点的容器

vector > corners2;

// 定义保存图像二维和三维角点的容器

vector worldPoints;

vector > worldPoints2;

//***************读取一个文件夹中的所有图片(所有标定图片)**********************

for(int i=1; i

stringstream str;

str << PATH << setw(2) << setfill('0') << i << ".jpg";

// 保存所有图片的路径,放入容器filelist中

filelist.push_back(str.str());

image_in = imread(str.str());

}棋盘格标定示意图一棋盘格标定示意图二

3)找角点

标定前需要找到棋盘格中黑白框结合的角点,opencv提供了findChessboardCorners函数来完成这个工作。这个函数的输入参数为:输入图片,图片的内角点数,输出角点,求解方式;

//***************************找角点×××××××××××××××××××××××××××××××× for(int i=0;i

//cout <

// 找图片的角点,参数分别为: // 输入图片,图片内角点数(不算棋盘格最外层的角点),输出角点,求解方式 bool found = findChessboardCorners(image_in, Size(8,6),corners,CALIB_CB_ADAPTIVE_THRESH|CALIB_CB_NORMALIZE_IMAGE);

// 将找到的角点放入容器中; corners2.push_back(corners);

//画出角点 drawChessboardCorners(image_in,Size(9,6),corners, found);

//显示图像 imshow("test",image_in);

// 图像刷新等待时间,单位ms waitKey(100);

// 世界坐标系的二维vector 放入三维vector worldPoints2.push_back(worldPoints);

}角点检测示意图一角点检测示意图二

4)生成世界坐标系下三维空间点

畸变矫正的本质是通过寻找棋盘格上角点,在图像中和真实世界中的对应关系,来计算相机参数。因此我们需要生成真实世界中的棋盘格坐标点。由于矫正的过程与标定过程的比例尺一样,实际是等比例缩放,因此这些点可以不与真实的尺寸对应,只要成比例就行。

//***********************生成一组object_points*************************

for(int j = 0;j<6;j++){

for(int k = 0; k<8;k++){

worldPoints.push_back(Point3f(j*1.0 ,k*1.0 ,0.0f));

}

}

5)标定

采用calibrateCamera函数能够计算出相应的相机参数,实现相机的标定,这个函数的输入参数依次为:世界坐标系内角点, 图像的角点, 图像的尺寸,相机矩阵,畸变矩阵,旋转矩阵,平移矩阵,求解方式。 其中需要注意的是,世界坐标系内的角点和图像的角点 二者的维度一定要对应,要么全是二维Vector,要么全是三维Vector 即Vector> 或vector

calibrateCamera(worldPoints2,corners2,image_in.size(),cameraMatrix,distCoeffs,

rvecs,tvecs,CV_CALIB_FIX_PRINCIPAL_POINT);

查看对应的相机参数:

//*************************************查看参数*****************************************

cout << " Camera intrinsic: " << cameraMatrix.rows << "x" << cameraMatrix.cols << endl;

cout << cameraMatrix.at(0,0) << " " << cameraMatrix.at(0,1) << " " << cameraMatrix.at(0,2) << endl;

cout << cameraMatrix.at(1,0) << " " << cameraMatrix.at(1,1) << " " << cameraMatrix.at(1,2) << endl;

cout << cameraMatrix.at(2,0) << " " << cameraMatrix.at(2,1) << " " << cameraMatrix.at(2,2) << endl;

cout << distCoeffs.rows << "x" <

cout << distCoeffs << endl;

for(int i = 0;i < distCoeffs.cols;i++)

{

cout << distCoeffs.at(0,i) << " " ;

}

cout <

相机参数

6)矫正

opencv提供了多种畸变矫正的函数,这里使用最基本的undistort, 输入参数分别为:输入图像,矫正后图像,相机矩阵,畸变矩阵。

//*********************畸变矫正**************************

// 导入要矫正的图片

Mat test_image2 = imread("/camera_calibration/test_image.jpg");

Mat show_image;

undistort(test_image2, show_image, cameraMatrix, distCoeffs);矫正前的图片矫正后的图片

2.2 在线Camera数据采集

打印一张棋盘格,固定在木板上,使用摄像头从各个角度拍摄棋盘格图像。本文使用一个外接Usb罗技摄像头,以下是采集摄像头图像时所使用的程序,按下S键保存当帧的图像。这里需要注意的是waitKey()这个函数,其作用对象是显示的图像窗口,不能对控制台起作用,也就是说在使用waitKey这个函数时,必须在前面显示图片,然后才起作用。

#include

#include

#include

#include

using namespace std;

using namespace cv;

int main() {

VideoCapture capture(1);

Mat img,img_flip;

char filename[200];

int i =0;

int key = 0;

while (capture.isOpened()) {

capture >> img;

flip(img, img_flip, 0);

// imshow("test0",img);

imshow("test1",img_flip);

//char key_board = waitKey(10);

key = waitKey(30);

cout <

if (key == 's') {

sprintf(filename, "%s%d%s", "../image/", i++, ".jpg");

imwrite(filename, img_flip);

}

}

return 0;

}

2.3 在线摄像头畸变矫正

// 当摄像头打开时,实时矫正图片,并实时输出;

while(capture.isOpened()){

capture >> frame;

flip(frame,fz,-1);//1代表水平方向旋转180度

undistort(fz,show, cameraMatrix,distCoeffs);

imshow("raw",fz);

waitKey(30);

imshow("corrected2",show);

waitKey(30);

3. 其他

3.1 python 代码

import numpy as np

import cv2

import glob

import matplotlib.pyplot as plt

%matplotlib

# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)

objp = np.zeros((6*8,3), np.float32)

objp[:,:2] = np.mgrid[0:8, 0:6].T.reshape(-1,2)

# Arrays to store object points and image points from all the images.

objpoints = [] # 3d points in real world space

imgpoints = [] # 2d points in image plane.

# Make a list of calibration images

images = glob.glob('calibration_wide/GO*.jpg')

# Step through the list and search for chessboard corners

for idx, fname in enumerate(images):

img = cv2.imread(fname)

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Find the chessboard corners

ret, corners = cv2.findChessboardCorners(gray, (8,6), None)

# If found, add object points, image points

if ret == True:

objpoints.append(objp)

imgpoints.append(corners)

# Draw and display the corners

cv2.drawChessboardCorners(img, (8,6), corners, ret)

#write_name = 'corners_found'+str(idx)+'.jpg'

#cv2.imwrite(write_name, img)

cv2.imshow('img', img)

cv2.waitKey(500)

cv2.destroyAllWindows()

import pickle

%matplotlib inline

# Test undistortion on an image

img = cv2.imread('calibration_wide/test_image.jpg')

img_size = (img.shape[1], img.shape[0])

# Do camera calibration given object points and image points

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img_size,None,None)

dst = cv2.undistort(img, mtx, dist, None, mtx)

cv2.imwrite('calibration_wide/test_undist.jpg',dst)

# Save the camera calibration result for later use (we won't worry about rvecs / tvecs)

dist_pickle = {}

dist_pickle["mtx"] = mtx

dist_pickle["dist"] = dist

pickle.dump( dist_pickle, open( "calibration_wide/wide_dist_pickle.p", "wb" ) )

#dst = cv2.cvtColor(dst, cv2.COLOR_BGR2RGB)

# Visualize undistortion

f, (ax1, ax2) = plt.subplots(1, 2, figsize=(20,10))

ax1.imshow(img)

ax1.set_title('Original Image', fontsize=30)

ax2.imshow(dst)

ax2.set_title('Undistorted Image', fontsize=30)

3.2 参考资料

3)OpenCv camera calibration with C++ (Unsupported format or combination of formats error)?stackoverflow.com