鱼眼相机标定-基于张正友标定法

2023-11-02 16:10
文章标签 相机 标定 鱼眼 张正友

本文主要是介绍鱼眼相机标定-基于张正友标定法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

鱼眼相机标定

前段时间曾经做过一段时间的摄像头标定,这里对以前做的事情做一个总结。这里介绍一下鱼眼相机的标定吧,也是相机标定的第二部分,主要还是代码解析和一些细节说明,为了让自己更好的理解相机标定,标定目的是为了实现坐标转换,通过摄像头测定相机的内参和外参之后,需要基于公式得到精确的坐标转换矩阵。(涉及公司项目,这里就不贴图了)

思路详解

相机代码见 https://github.com/wisdom-bob/Camera_calibration
基于张正友标定法,通过opencv的cv2.fisheye完成摄像头标定(matlab对于鱼眼标定是没什么办法了),但单纯的鱼眼标定似乎结果误差还是比较大的,均方根误差近似3像素,于是采用了二次标定的方法,提高标定精度,最终降低均方根误差到0.07。这里把摄像头标定分为以下几个步骤:
Step1:采集若干张图片,筛选照片
Step2:基于图片利用cv2.fisheye.calibrate计算摄像头内参
Step3:基于鱼眼内参对图像进行undistort,基于undistort_img进行标定,得到需要的内外参
 (对比鱼眼内外参和二次标定内外参结果有细微差别,但这一点点就是误差校正量)
Step4:取出摄像头实际工作环境的外参,这里由于我默认只求地面的点,则应该取图片平放置地面的那组参数
Step5:基于单应性标定法,利用dist,mtx以及外参求出对应坐标变换矩阵
这里我就直接开始配合着代码讲内容吧!

# 声明全局变量和一些调试参数
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 300, 0.00001)
find_flag = cv2.CALIB_CB_ADAPTIVE_THRESH+cv2.CALIB_CB_FAST_CHECK+cv2.CALIB_CB_NORMALIZE_IMAGE
calib_flags = cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC+cv2.fisheye.CALIB_CHECK_COND+cv2.fisheye.CALIB_FIX_SKEW
global mtx, dist

采集照片,筛选

张正友标定法是通过一种机器学习的方法,利用大量匹配的数据点像素坐标、世界坐标,基于极大似然估计拟合得到一个最优解,所以一定程度上,数据越多,结果就越准确,但是这里也需要注意,采集数据应该要分布均匀,尽可能均匀分布相机视界的所有位置,也就是尽量满足机器学习数据的独立同分布要求~~
如下图所示,就差不多这样,但是要多拍一些,大概60张左右,可以让自己从容的筛照片。(鱼眼相机标定条件比针孔相机更加严格,图片的不合格率更高,所以照片要拍的更多一些,此外由于畸变,实际上有一些图像在去畸变后,视界范围发生变化,影响二次标定,也是不合格率高的原因)
在这里插入图片描述

# input fisheye imgs, catch objpoints and imgpoints, the same as pinhole camera
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((1, 6 * 9, 3), np.float32)
objp[0,:,:2] = 40*np.mgrid[0:9, 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.
img_with_corners = []# Make a list of calibration images
images = glob.glob('./other/fish*.jpg')# Step through the list and search for chessboard corners
for i in range(len(images)):img = cv2.imread(images[i])gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  ret, corners = cv2.findChessboardCorners(gray, (9, 6), None)#find_flag# If found, add object points, image pointsif ret == True:  cv2.cornerSubPix(gray,corners,(15,15),(-1,-1),criteria)img = cv2.drawChessboardCorners(img, (9, 6), corners, ret)img_with_corners.append(img)objpoints.append(objp)imgpoints.append(corners)

以上的函数就是为了创造数据,用于标定摄像头的。样本数据是cv2.findChessboard以及cv2.cornerSubPix函数在图像上找到的精确标定板的黑白格子交点集X,ground_truth为世界坐标点集,即objp
ps.cv2.cornerSubPix作用是基于确定的像素坐标,在其一定范围内进行亚像素级搜索,计算出更合适,拟合度更高的像素坐标值。(可见,在统计学角度上来说,这个结果是更加准确,但是如果单张有效点数太少,那统计结果的可靠性也不高)
在这里插入图片描述

基于图像计算摄像头内参和外参

# calculate the internal parameters by cv2.fisheye.calibrate. 
# Extrinsic parameter is useless
num = len(objpoints)
mtxfish = np.zeros((3, 3))
distfish = np.zeros((4, 1))
rvecs = [np.zeros((1, 1, 3), dtype=np.float32) for i in range(num)]
tvecs = [np.zeros((1, 1, 3), dtype=np.float32) for i in range(num)]
img = cv2.imread('calibration/RT.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img_size = gray.shape[::-1]rms, mtxfish,distfish, rvecs, tvecs = cv2.fisheye.calibrate(objpoints, imgpoints, img_size, mtxfish,distfish, rvecs, tvecs, calib_flags, criteria)

基于cv2.fisheye.calibrate得到鱼眼相机的内参与图像匹配外参。这里,应该大部分人都会遇到ILL_Condition的问题,这就表示在这张图片上对应的objpX并不能计算出鱼眼内参或者说计算出的内参与其他图片差别太大,总之就是这张图片不能用,把它删了就好了。这个过程会循环很多次,直到跑通,你就得到了fishmtx,fishdist。

二次标定取得内参和外参

对于不太精确要求的话,这样的结果已经足够了,如果需要坐标标定的,那么二次标定我觉得还是非常必要的。

# undist imgs and based on undist_imgs to try calibration
def __get_short_name(fname):"""该函数用于获取文件前缀名,输入包含路径的全名"""fpath, tempfname = os.path.split(fname)shortname, extension = os.path.splitext(tempfname)  return shortname# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6 * 9, 3), np.float32)
objp[:,:2] = 40*np.mgrid[0:9, 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.
img_with_corners = []# Make a list of calibration images
images = glob.glob('./other/8*.jpg')# Step through the list and search for chessboard corners
for i in range(len(images)):img = cv2.imread(images[i])map1, map2 = cv2.fisheye.initUndistortRectifyMap(mtxfish, distfish, np.eye(3), mtxfish, img_size, cv2.CV_16SC2)undistorted_img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT)gray = cv2.cvtColor(undistorted_img,cv2.COLOR_BGR2GRAY)ret, corners = cv2.findChessboardCorners(gray, (9,6), None)
#     print(images[i],ret)# If found, add object points, image pointsif ret == True:cv2.cornerSubPix(gray,corners,(15,15),(1,-1),criteria)imge = cv2.drawChessboardCorners(undistorted_img, (9,6), corners, ret)img_with_corners.append(imge)objpoints.append(objp)imgpoints.append(corners)#去畸变图片存储shortname = __get_short_name(images[i])    # 获取文件前缀dst_fname = './calibration/undistort_img' + '/' + shortname + '.jpg'cv2.imwrite(dst_fname, imge)     # 生成图片# calculate the internal parameters and extrinsic parameters....
img = cv2.imread('calibration/undistort_img/cRT.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img_size = gray.shape[::-1]
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img_size, None, None)print("dist",dist)
print("mtx",mtx)

基于fishmtx,fishdist,把原始图片去畸变,再基于针孔相机标定的方法标定一下,采集objpX。再次标定,得到mtx和dist。此时你也可以用cv2.projectPoints判断一下,均方根误差为0.045,原来为3.453。

取出摄像头实际工作环境的外参

# undist imgs and based on undist_imgs to try calibration
def __get_short_name(fname):"""该函数用于获取文件前缀名,输入包含路径的全名"""fpath, tempfname = os.path.split(fname)shortname, extension = os.path.splitext(tempfname)  return shortname# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((3 * 4, 3), np.float32)
objp[:,:2] = 900*np.mgrid[0:4, 0:3].T.reshape(-1, 2)# Arrays to store object points and image points from all the images.
img = cv2.imread('./other/crt.jpg')
map1, map2 = cv2.fisheye.initUndistortRectifyMap(mtxfish, distfish, np.eye(3), mtxfish, img_size, cv2.CV_16SC2)
undistorted_img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT)
gray = cv2.cvtColor(undistorted_img,cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (4,3), None)
print(ret)# If found, add object points, image points
if ret == True:cv2.cornerSubPix(gray,corners,(15,15),(1,-1),criteria)imge = cv2.drawChessboardCorners(undistorted_img, (4,3), corners, ret)#去畸变图片存储dst_fname = './other/undistort_img/crt.jpg'cv2.imwrite(dst_fname, imge)     # 生成图片# 对于结果不太理想的corners,通过人为测定,修正结果
corners[0]=[1214.56,649.829]
corners[2]=[848.306,659.974]
corners[1]=[1034.87,655.358]
corners[3]=[670.579,663.482]
retval,rvec,tvec = cv2.solvePnP(objp, corners, mtx, dist)

同样,这里也需要一张平铺在地面的标定板,基于cv2.solvePnP计算出目标外参。

基于单适性因子求出坐标转换矩阵

整理一下,目前我们已经得到摄像头fishmtx、fishdist,内参mtx,畸变参数dist,对应的外参rvec,tvec。现在我们要基于这些得到坐标变换矩阵。

map1, map2 = cv2.fisheye.initUndistortRectifyMap(mtxfish, distfish, np.eye(3), mtxfish, img_size, cv2.CV_16SC2)
def cal_undistort(img):# convert image into undistort scaleundistorted_img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT)undist = cv2.undistort(undistorted_img , mtx, dist, None, mtx)return undistrvec_test=rvec
tvec_test=np.mat(tvec)
mtx_test=np.hstack((mtx,np.mat([0,0,0]).T))rmat,_ =cv2.Rodrigues(rvec_test)
RT=np.hstack((rmat,tvec_test))
KRT=np.vstack((RT,[0,0,0,1]))temp = KRT
temp=np.delete(temp,2,axis=1)
temp=np.delete(temp,3,axis=0)
temp=mtx*temp
tempI=temp.I# 以下代码不要同时跑,x,y为补偿量(x横向补偿量,y纵向补偿量)
# 图像转世界
stemp=np.array(tempI*tpts.T).T
results=[]
for i in range(len(stemp)):TTemp=stemp[i]/stemp[i][2]TTemp[0] = -(TTemp[0]+x)TTemp[1] = (TTemp[1]+y)results.append(TTemp)
print(np.mat(results))# 世界转图像
results=[]
for i in range(len(apts)):TTemp=apts[i]TTemp[0]=-1*(TTemp[0]-1190)TTemp[1]-=3490TTemp=temp*np.array([TTemp]).TTTemp/=TTemp[2]results.append(TTemp)

基于张正友单应性方法,通过矩阵操作,得到最后的变换矩阵temp(世界->图像)和tempI(图像->世界)。关于具体的理论见[^1],里面讲的很详细。

如有侵权,请私戳,感谢~
[^1]:https://blog.csdn.net/u010128736/article/details/52860364

这篇关于鱼眼相机标定-基于张正友标定法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/331767

相关文章

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(五):Blender锥桶建模

前言 本系列教程旨在使用UE5配置一个具备激光雷达+深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2和UE5仿真的通讯,达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博客Nav2代价地图实现和原理–Nav2源码解读之CostMap2D(上)-CSDN博客往期教程: 第一期:基于UE5和ROS2的激光雷达+深度RG

海鸥相机存储卡格式化如何恢复数据

在摄影的世界里,‌每一张照片都承载着独特的记忆与故事。‌然而,‌当我们不慎将海鸥相机的存储卡格式化后,‌那些珍贵的瞬间似乎瞬间消逝,‌让人心急如焚。‌但请不要绝望,‌数据恢复并非遥不可及。‌本文将详细介绍在海鸥相机存储卡格式化后,‌如何高效地恢复丢失的数据,‌帮助您重新找回那些宝贵的记忆。‌ 图片来源于网络,如有侵权请告知 一、‌回忆备份情况 ‌海鸥相机存储卡格式化如何恢复数据?在意

解析apollo纵向控制标定表程序

百度apollo采用标定表描述车辆速度、加速度与油门/刹车之间的关系。该表可使无人车根据当前车速与期望加速度得到合适的油门/刹车开合度。除了文献《Baidu Apollo Auto-Calibration System - An Industry-Level Data-Driven and Learning based Vehicle Longitude Dynamic Calibrating

机器视觉硬件选型根据某项目相机镜头

一 项目总需求 1、大视野检测需求: (1)大视野: ①产品尺寸15.6寸屏幕,产品大小:350mm x 225mm; ②产品料盘尺寸大小:565mm x 425mm; ③工作距离:880mm;检测精度:500μm; 1、大视野检测需求: (1)大视野: ①产品尺寸15.6寸屏幕,产品大小:350mm x 225mm; ②产品料盘尺寸大小:565mm x 425mm; 工作距离:

005:VTK世界坐标系中的相机和物体

VTK医学图像处理---世界坐标系中的相机和物体 左侧是成像结果                                                    右侧是世界坐标系中的相机与被观察物体 目录 VTK医学图像处理---世界坐标系中的相机和物体 简介 1 在三维空间中添加坐标系 2 世界坐标系中的相机 3 世界坐标系中vtkImageData的参数 总结:

独立双端App《瓦格相机》的开发过程分享

前言 Hello大家好,我是灯灯,独立开发者灯灯,也是天天学藏语的灯灯,哈哈哈... 好了屁话少说,今天和大家分享一下最近自己独立制作一款应用的经验历程,希望能对刚刚起步的新手们、还有独立开发者们有所帮助。 什么样的应用 我想做的是一款能够将照片转换成文字拼成的图片应用,也就是,图片中的每一个像素点都将会被文字取代,同时对应色彩、密集程度等。 之所以想做这样的应用是因为早在我高中的时候,

相机检查内参 外参

目录 检查内参 外参 像素点投影到世界坐标系,再投回到2d坐标系: 检查内参 外参 import cv2import numpy as np# 假设我们有以下相机内参K = np.array([[418.96369417, 0.0, 489.16315478],[0.0, 419.04813353, 267.88796254],[0.0, 0.0, 1.0]], dtype=n

相机拍摄时最重要的三个参数——光圈、快门、ISO

如果你对相机只有很少了解,那么看这篇文章再好不过啦,我结合很多资料,力图用最通俗易懂的方式进行讲解。 相机拍摄时最重要的3个参数就是——光圈、快门、ISO 次重要的参数有——焦距、景深、曝光   在介绍光圈、快门、ISO之前,必须先介绍曝光。曝光准确的照片:   过曝的照片:   欠曝的照片:   我们把一张完美曝光的照片理解成一桶刚刚装满的水,不

halcon 的图像坐标转到实际的机械坐标的标定

所谓手眼系统,就是人眼睛看到一个东西的时候要让手去抓取,就需要大脑知道眼睛和手的坐标关系。如果把大脑比作B,把眼睛比作A,把手比作C,如果A和B的关系知道,B和C的关系知道,那么C和A的关系就知道了,也就是手和眼的坐标关系也就知道了。 相机知道的是像素坐标,机械手是空间坐标系,所以手眼标定就是得到像素坐标系和空间机械手坐标系的坐标转化关系。 在实际控制中,相机检测到目标在图像中的像

猫猫学iOS之二维码学习,快速打开相机读取二维码

猫猫分享,必须精品 原创文章,欢迎转载。转载请注明:翟乃玉的博客 地址:http://blog.csdn.net/u013357243 上一篇文章写了怎么生成二维码,这儿就说说怎么读取吧,反正也很简单,iOS封装的太强大了 步骤呢就是这样: 读取二维码需要导入AVFoundation框架#import <AVFoundation/AVFoundation.h> 1:利用摄像头识别二维码