鱼眼镜头标定及畸变校正
鱼眼摄像头畸变校正的方法:
- 棋盘矫正法
- 经纬度矫正法。
离线图片实现摄像头标定和矫正
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
上一篇: 小米终于在高端市场打开局面,高端手机销量可望超越华为
下一篇: Unity实现倒计时功能