欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

鱼眼镜头标定及畸变校正

程序员文章站 2022-03-07 12:49:24
...

鱼眼摄像头畸变校正的方法:

  1. 棋盘矫正法
  2. 经纬度矫正法。

离线图片实现摄像头标定和矫正
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 <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include "opencv2/imgproc.hpp"
#include "opencv2/calib3d.hpp"
#include <opencv2/highgui.hpp>
#include <string>
#include <vector>
#include <fstream>
using namespace std;
using namespace cv;
#define PATH "/camera_calibration/image_my/"
#define NUM 30
int main() {
    // 定义用来保存导入的图片
    Mat image_in; 
    // 定义用来保存文件路径的容器
    vector<string> filelist;
    // 定义用来保存旋转和平移矩阵的容器
    vector<Mat> rvecs, tvecs;
    // 定义相机矩阵,畸变矩阵
    Mat cameraMatrix;
    Mat distCoeffs;
    int flags = 0;
    // 定义保存图像二维角点的容器
    vector<Point2f> corners;
    // 定义保存图像三维角点的容器
    vector<vector<Point2f> > corners2;
    // 定义保存图像二维和三维角点的容器
    vector<Point3f> worldPoints;
    vector<vector<Point3f> > worldPoints2;
//***************读取一个文件夹中的所有图片(所有标定图片)**********************
    for(int i=1; i<NUM;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<filelist.size();i++){
       //cout <<filelist[i]<<endl;
       // 一张张读入图片;
       image_in = imread(filelist[i]);
       // 找图片的角点,参数分别为:
       // 输入图片,图片内角点数(不算棋盘格最外层的角点),输出角点,求解方式
       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> 或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<double>(0,0) << " " << cameraMatrix.at<double>(0,1) << " " << cameraMatrix.at<double>(0,2) << endl;
    cout << cameraMatrix.at<double>(1,0) << " " << cameraMatrix.at<double>(1,1) << " " << cameraMatrix.at<double>(1,2) << endl;
    cout << cameraMatrix.at<double>(2,0) << " " << cameraMatrix.at<double>(2,1) << " " << cameraMatrix.at<double>(2,2) << endl;
    cout << distCoeffs.rows << "x" <<distCoeffs.cols << endl;
    cout << distCoeffs << endl;
    for(int i = 0;i < distCoeffs.cols;i++)
    {
        cout << distCoeffs.at<double>(0,i) << " " ;
    }
    cout <<endl;

鱼眼镜头标定及畸变校正

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

 //*********************畸变矫正**************************
// 导入要矫正的图片
Mat test_image2 = imread("/camera_calibration/test_image.jpg");
Mat show_image;
undistort(test_image2, show_image, cameraMatrix, distCoeffs);

鱼眼镜头标定及畸变校正
鱼眼镜头标定及畸变校正

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

#include <iostream>
#include <opencv2/opencv.hpp>
#include <string>
#include <stdio.h>

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 <<key <<endl;
        if (key == 's') {
            sprintf(filename, "%s%d%s", "../image/", i++, ".jpg");
            imwrite(filename, img_flip);
     }
    }
    return 0;
}

相机为什么会出现畸变?
当前相机的畸变主要分为径向畸变和切向畸变两种。
径向畸变产生的原因:相机的光学镜头厚度不均匀,离镜头越远场景的光线就越弯曲从而产生径向畸变。
切向畸变产生的原因:镜头与图像传感器不完全平行造成的。
鱼眼镜头标定及畸变校正
鱼眼镜头标定及畸变校正

相机参数有哪些?
相机内参:主要包括相机矩阵(包括焦距,光学中心,这些都是相机本身属性)和畸变系数(畸变数学模型的5个参数 D = {K1,K2,K3,P1,P2})。
相机外参:通过旋转和平移将实际场景3D映射到相机的2D坐标过程中的旋转和平移就是外参。(他描述的是世界坐标转化成相机坐标的过程)

相机的标定流程
机标定流程就是4个坐标系在转换过程中求出计算机的外参和内参的过程。四个坐标系分别是:世界坐标系(真实的实际场景),相机坐标系(摄像头镜头中心),图像坐标系(图像传感器成像中心,图片中心,影布中心),像素坐标系(图像左上角为原点)。如图三所示,O1是图像坐标系,O0 是像素坐标系,两者之间的区别只是原点发生了变化。
世界坐标系 ->相机坐标系 求解外参(旋转和平移)
相机坐标系 ->图像坐标系 求解内参(摄像头矩阵,畸变系数)
图像坐标系 ->像素坐标系 求解像素转化矩阵(可简单理解为原点从图片中心到左上角,单位厘米变行列)
鱼眼镜头标定及畸变校正

一、基于棋盘的相机畸变校正方法
打印棋盘并采集鱼眼摄像头下的棋盘图片:
1.棋盘获取:链接: https://pan.baidu.com/s/14qB3kQ_MbWORay1i0GFm1A 提取码: ksqw 复制这段内容后打开百度网盘手机App,操作更方便哦
2.采集图片,采集图片如下图所示,可以多采集一些,标注的会更加准确。
鱼眼镜头标定及畸变校正

3.使用采集的图片求出相机的内参和矫正系数(DIM, K, D),然后使用得到的(DIM, K, D)再进行测试,代码如下。

import cv2
import numpy as np
import glob

def get_K_and_D(checkerboard, imgsPath):
 
    CHECKERBOARD = checkerboard
    subpix_criteria = (cv2.TERM_CRITERIA_EPS+cv2.TERM_CRITERIA_MAX_ITER, 30, 0.1)
    calibration_flags = cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC+cv2.fisheye.CALIB_CHECK_COND+cv2.fisheye.CALIB_FIX_SKEW
    objp = np.zeros((1, CHECKERBOARD[0]*CHECKERBOARD[1], 3), np.float32)
    objp[0,:,:2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2)
    _img_shape = None
    objpoints = []
    imgpoints = []
    images = glob.glob(imgsPath + '/*.png')
    for fname in images:
        img = cv2.imread(fname)
        if _img_shape == None:
            _img_shape = img.shape[:2]
        else:
            assert _img_shape == img.shape[:2], "All images must share the same size."
 
        gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
        ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD,cv2.CALIB_CB_ADAPTIVE_THRESH+cv2.CALIB_CB_FAST_CHECK+cv2.CALIB_CB_NORMALIZE_IMAGE)
        if ret == True:
            objpoints.append(objp)
            cv2.cornerSubPix(gray,corners,(3,3),(-1,-1),subpix_criteria)
            imgpoints.append(corners)
    N_OK = len(objpoints)
    K = np.zeros((3, 3))
    D = np.zeros((4, 1))
    rvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(N_OK)]
    tvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(N_OK)]
    rms, _, _, _, _ = cv2.fisheye.calibrate(
        objpoints,
        imgpoints,
        gray.shape[::-1],
        K,
        D,
        rvecs,
        tvecs,
        calibration_flags,
        (cv2.TERM_CRITERIA_EPS+cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6)
    )
    DIM = _img_shape[::-1]
    print("Found " + str(N_OK) + " valid images for calibration")
    print("DIM=" + str(_img_shape[::-1]))
    print("K=np.array(" + str(K.tolist()) + ")")
    print("D=np.array(" + str(D.tolist()) + ")")
    return DIM, K, D
 
 
def undistort(img_path,K,D,DIM,scale=0.6,imshow=False):
    img = cv2.imread(img_path)
    dim1 = img.shape[:2][::-1]  #dim1 is the dimension of input image to un-distort
    assert dim1[0]/dim1[1] == DIM[0]/DIM[1], "Image to undistort needs to have same aspect ratio as the ones used in calibration"
    if dim1[0]!=DIM[0]:
        img = cv2.resize(img,DIM,interpolation=cv2.INTER_AREA)
    Knew = K.copy()
    if scale:#change fov
        Knew[(0,1), (0,1)] = scale * Knew[(0,1), (0,1)]
    map1, map2 = cv2.fisheye.initUndistortRectifyMap(K, D, np.eye(3), Knew, DIM, cv2.CV_16SC2)
    undistorted_img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT)
    if imshow:
        cv2.imshow("undistorted", undistorted_img)
    return undistorted_img
 
if __name__ == '__main__':
 
   # 开始使用图片来获取内参和畸变系数
    DIM, K, D = get_K_and_D((6,9), '')
 
   # 得到内参和畸变系数畸变矫正进行测试
    '''
    DIM=(2560, 1920)
    K=np.array([[652.8609862494474, 0.0, 1262.1021584894233], [0.0, 653.1909758659955, 928.0871455436396], [0.0, 0.0, 1.0]])
    D=np.array([[-0.024092199861108887], [0.002745976275100771], [0.002545415522352827], [-0.0014366825722748522]])
    img = undistort('../imgs/pig.jpg',K,D,DIM)
    cv2.imwrite('../imgs/pig_checkerboard.jpg', img)
    '''

二、基于经纬度的矫正方法
1.算法原理:经纬度矫正法, 可以把鱼眼图想象成半个地球, 然后将地球展开成地图,经纬度矫正法主要是利用几何原理, 对图像进行展开矫正。(此算法操作简单不需要使用棋盘来进行数据采集)
2.代码实现

import cv2
import numpy as np
import time
 
# 鱼眼有效区域截取
def cut(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    (_, thresh) = cv2.threshold(img_gray, 20, 255, cv2.THRESH_BINARY)
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = sorted(contours, key=cv2.contourArea, reverse=True)[0]
    x,y,w,h = cv2.boundingRect(cnts)
    r = max(w/ 2, h/ 2)
    # 提取有效区域
    img_valid = img[y:y+h, x:x+w]
    return img_valid, int(r)
 
# 鱼眼矫正
def undistort(src,r):
    # r: 半径, R: 直径
    R = 2*r
    # Pi: 圆周率
    Pi = np.pi
    # 存储映射结果
    dst = np.zeros((R, R, 3))
    src_h, src_w, _ = src.shape
 
    # 圆心
    x0, y0 = src_w//2, src_h//2
 
    # 数组, 循环每个点
    range_arr = np.array([range(R)])
 
    theta = Pi - (Pi/R)*(range_arr.T)
    temp_theta = np.tan(theta)**2
 
    phi = Pi - (Pi/R)*range_arr
    temp_phi = np.tan(phi)**2
 
    tempu = r/(temp_phi + 1 + temp_phi/temp_theta)**0.5
    tempv = r/(temp_theta + 1 + temp_theta/temp_phi)**0.5
 
    # 用于修正正负号
    flag = np.array([-1] * r + [1] * r)
 
    # 加0.5是为了四舍五入求最近点
    u = x0 + tempu * flag + 0.5
    v = y0 + tempv * np.array([flag]).T + 0.5
 
    # 防止数组溢出
    u[u<0]=0
    u[u>(src_w-1)] = src_w-1
    v[v<0]=0
    v[v>(src_h-1)] = src_h-1
 
    # 插值
    dst[:, :, :] = src[v.astype(int),u.astype(int)]
    return dst
 
if __name__ == "__main__":
    t = time.perf_counter()
    frame = cv2.imread('../imgs/pig.jpg')
    cut_img,R = cut(frame)
    t = time.perf_counter()
    result_img = undistort(cut_img,R)
    cv2.imwrite('../imgs/pig_vector_nearest.jpg',result_img)
    print(time.perf_counter()-t)

图像算法中会经常用到摄像机的畸变校正,有必要总结分析OpenCV中畸变校正方法,其中包括普通针孔相机模型和鱼眼相机模型fisheye两种畸变校正方法。
普通相机模型畸变校正函数针对OpenCV中的cv::initUndistortRectifyMap(),鱼眼相机模型畸变校正函数对应OpenCV中的cv::fisheye::initUndistortRectifyMap()。两种方法算出映射Mapx和Mapy后,统一用cv::Remap()函数进行插值得到校正后的图像。
1. FishEye模型的畸变校正。
方便起见,直接贴出OpenCV源码,我在里面加了注释说明。建议参考OpenCV官方文档看畸变模型原理会更清楚:https://docs.opencv.org/3.0-beta/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#fisheye-initundistortrectifymap

简要流程就是:
1.求内参矩阵的逆,由于摄像机坐标系的三维点到二维图像平面,需要乘以旋转矩阵R和内参矩阵K。那么反向投影回去则是二维图像坐标乘以K*R的逆矩阵。
2.将目标图像中的每一个像素点坐标(j,i),乘以1中求出的逆矩阵iR,转换到摄像机坐标系(_x,_y,_w),并归一化得到z=1平面下的三维坐标(x,y,1);
3.求出平面模型下像素点对应鱼眼半球模型下的极坐标(r, theta)。
4.利用鱼眼畸变模型求出拥有畸变时像素点对应的theta_d。
鱼眼镜头标定及畸变校正

5.利用求出的theta_d值将三维坐标点重投影到二维图像平面得到(u,v),(u,v)即为目标图像对应的畸变图像中像素点坐标
6.使用cv::Remap()函数,根据mapx,mapy取出对应坐标位置的像素值赋值给目标图像,一般采用双线性插值法,得到畸变校正后的目标图像。

#include <opencv2\opencv.hpp>

void cv::fisheye::initUndistortRectifyMap( InputArray K, InputArray D, InputArray R, InputArray P,
    const cv::Size& size, int m1type, OutputArray map1, OutputArray map2 )
{
    CV_Assert( m1type == CV_16SC2 || m1type == CV_32F || m1type <=0 );
    map1.create( size, m1type <= 0 ? CV_16SC2 : m1type );
    map2.create( size, map1.type() == CV_16SC2 ? CV_16UC1 : CV_32F );

    CV_Assert((K.depth() == CV_32F || K.depth() == CV_64F) && (D.depth() == CV_32F || D.depth() == CV_64F));
    CV_Assert((P.empty() || P.depth() == CV_32F || P.depth() == CV_64F) && (R.empty() || R.depth() == CV_32F || R.depth() == CV_64F));
    CV_Assert(K.size() == Size(3, 3) && (D.empty() || D.total() == 4));
    CV_Assert(R.empty() || R.size() == Size(3, 3) || R.total() * R.channels() == 3);
    CV_Assert(P.empty() || P.size() == Size(3, 3) || P.size() == Size(4, 3));

    //从内参矩阵K中取出归一化焦距fx,fy; cx,cy
    cv::Vec2d f, c;
    if (K.depth() == CV_32F)
    {
        Matx33f camMat = K.getMat();
        f = Vec2f(camMat(0, 0), camMat(1, 1));
        c = Vec2f(camMat(0, 2), camMat(1, 2));
    }
    else
    {
        Matx33d camMat = K.getMat();
        f = Vec2d(camMat(0, 0), camMat(1, 1));
        c = Vec2d(camMat(0, 2), camMat(1, 2));
    }
    //从畸变系数矩阵D中取出畸变系数k1,k2,k3,k4
    Vec4d k = Vec4d::all(0);
    if (!D.empty())
        k = D.depth() == CV_32F ? (Vec4d)*D.getMat().ptr<Vec4f>(): *D.getMat().ptr<Vec4d>();

    //旋转矩阵RR转换数据类型为CV_64F,如果不需要旋转,则RR为单位阵
    cv::Matx33d RR  = cv::Matx33d::eye();
    if (!R.empty() && R.total() * R.channels() == 3)
    {
        cv::Vec3d rvec;
        R.getMat().convertTo(rvec, CV_64F);
        RR = Affine3d(rvec).rotation();
    }
    else if (!R.empty() && R.size() == Size(3, 3))
        R.getMat().convertTo(RR, CV_64F);
    
    //新的内参矩阵PP转换数据类型为CV_64F
    cv::Matx33d PP = cv::Matx33d::eye();
    if (!P.empty())
        P.getMat().colRange(0, 3).convertTo(PP, CV_64F);

    //关键一步:新的内参矩阵*旋转矩阵,然后利用SVD分解求出逆矩阵iR,后面用到
    cv::Matx33d iR = (PP * RR).inv(cv::DECOMP_SVD);

    //反向映射,遍历目标图像所有像素位置,找到畸变图像中对应位置坐标(u,v),并分别保存坐标(u,v)到mapx和mapy中
    for( int i = 0; i < size.height; ++i)
    {
        float* m1f = map1.getMat().ptr<float>(i);
        float* m2f = map2.getMat().ptr<float>(i);
        short*  m1 = (short*)m1f;
        ushort* m2 = (ushort*)m2f;

        //二维图像平面坐标系->摄像机坐标系
        double _x = i*iR(0, 1) + iR(0, 2),
               _y = i*iR(1, 1) + iR(1, 2),
               _w = i*iR(2, 1) + iR(2, 2);

        for( int j = 0; j < size.width; ++j)
        {
            //归一化摄像机坐标系,相当于假定在Z=1平面上
            double x = _x/_w, y = _y/_w;

            //求鱼眼半球体截面半径r
            double r = sqrt(x*x + y*y);
            //求鱼眼半球面上一点与光心的连线和光轴的夹角Theta
            double theta = atan(r);
            //畸变模型求出theta_d,相当于有畸变的角度值
            double theta2 = theta*theta, theta4 = theta2*theta2, theta6 = theta4*theta2, theta8 = theta4*theta4;
            double theta_d = theta * (1 + k[0]*theta2 + k[1]*theta4 + k[2]*theta6 + k[3]*theta8);
            //利用有畸变的Theta值,将摄像机坐标系下的归一化三维坐标,重投影到二维图像平面,得到(j,i)对应畸变图像中的(u,v)
            double scale = (r == 0) ? 1.0 : theta_d / r;
            double u = f[0]*x*scale + c[0];
            double v = f[1]*y*scale + c[1];

            //保存(u,v)坐标到mapx,mapy
            if( m1type == CV_16SC2 )
            {
                int iu = cv::saturate_cast<int>(u*cv::INTER_TAB_SIZE);
                int iv = cv::saturate_cast<int>(v*cv::INTER_TAB_SIZE);
                m1[j*2+0] = (short)(iu >> cv::INTER_BITS);
                m1[j*2+1] = (short)(iv >> cv::INTER_BITS);
                m2[j] = (ushort)((iv & (cv::INTER_TAB_SIZE-1))*cv::INTER_TAB_SIZE + (iu & (cv::INTER_TAB_SIZE-1)));
            }
            else if( m1type == CV_32FC1 )
            {
                m1f[j] = (float)u;
                m2f[j] = (float)v;
            }

            //这三条语句是上面 ”//二维图像平面坐标系->摄像机坐标系“的一部分,是矩阵iR的第一列,这样写能够简化计算
            _x += iR(0, 0);
            _y += iR(1, 0);
            _w += iR(2, 0);
        }
    }
}

opencv-python 实现鱼眼矫正 棋盘矫正法
鱼眼矫正有很多的方法, 比较常用的有:
1、棋盘标定法
2、经纬度法
opencv自带鱼眼矫正算法, 也就是第一种, 棋盘矫正法。

第一步:制作棋盘格
用A4纸打印一张棋盘格, 固定到硬纸板上, 然后用鱼眼镜头对着拍摄。 保留拍到的图片, 如下图所示:
鱼眼镜头标定及畸变校正

可以从不同角度拍摄, 多保存一些。

第二步: 计算内参和矫正系数
棋盘标定法, 必须要先计算出鱼眼的内参和矫正系数, 可直接调用以下函数计算

import cv2
assert cv2.__version__[0] == '3'
import numpy as np
import os
import glob

def get_K_and_D(checkerboard, imgsPath):
    CHECKERBOARD = checkerboard
    subpix_criteria = (cv2.TERM_CRITERIA_EPS+cv2.TERM_CRITERIA_MAX_ITER, 30, 0.1)
    calibration_flags = cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC+cv2.fisheye.CALIB_CHECK_COND+cv2.fisheye.CALIB_FIX_SKEW
    objp = np.zeros((1, CHECKERBOARD[0]*CHECKERBOARD[1], 3), np.float32)
    objp[0,:,:2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2)
    _img_shape = None
    objpoints = [] 
    imgpoints = [] 
    images = glob.glob(imgsPath + '/*.png')
    for fname in images:
        img = cv2.imread(fname)
        if _img_shape == None:
            _img_shape = img.shape[:2]
        else:
            assert _img_shape == img.shape[:2], "All images must share the same size."
        
        gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
        ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD,cv2.CALIB_CB_ADAPTIVE_THRESH+cv2.CALIB_CB_FAST_CHECK+cv2.CALIB_CB_NORMALIZE_IMAGE)
        if ret == True:
            objpoints.append(objp)
            cv2.cornerSubPix(gray,corners,(3,3),(-1,-1),subpix_criteria)
            imgpoints.append(corners)
    N_OK = len(objpoints)
    K = np.zeros((3, 3))
    D = np.zeros((4, 1))
    rvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(N_OK)]
    tvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(N_OK)]
    rms, _, _, _, _ = \
    cv2.fisheye.calibrate(
                                objpoints,
                                imgpoints,
                                gray.shape[::-1],
                                K,
                                D,
                                rvecs,
                                tvecs,
                                calibration_flags,
                                (cv2.TERM_CRITERIA_EPS+cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6)
                                )
    DIM = _img_shape[::-1]
    print("Found " + str(N_OK) + " valid images for calibration")
    print("DIM=" + str(_img_shape[::-1]))
    print("K=np.array(" + str(K.tolist()) + ")")
    print("D=np.array(" + str(D.tolist()) + ")")
    
    return DIM, K, D

# 计算内参和矫正系数
'''
# checkerboard: 棋盘格的格点数目
# imgsPath: 存放鱼眼图片的路径
'''
get_K_and_D((6,9), 'fisheyeImage')

第三步: 根据计算的K和D, 矫正鱼眼图
保存第二步计算的DIM, K, D, 利用一下程序, 矫正鱼眼图

def undistort(img_path):
    img = cv2.imread(img_path)
    img = cv2.resize(img, DIM)
    map1, map2 = cv2.fisheye.initUndistortRectifyMap(K, D, np.eye(3), K, DIM,cv2.CV_16SC2)
    undistorted_img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT)    
    cv2.imwrite('unfisheyeImage.png', undistorted_img)

DIM, K, D是固定不变的,因此,map1和map2也是不变的, 当你有大量的数据需要矫正时, 应当避免map1和map2的重复计算, 只需要计算remap即可。

矫正效果
鱼眼镜头标定及畸变校正

后续
矫正结束, 对于上面的矫正结果, 应该适用于很多场景了, 但是矫正之后, 很多有效区域被截掉了, 能否矫正出更大的有效面积呢? 答案当然是有的, 具体的细节, 去github上看项目里面的pdf吧, 这里不再介绍了。
https://github.com/HLearning/fisheye

相关标签: 传感器融合