(九)相机内参、外参、反透视变换python opencv
背景知识
任务需求:将相机上的一个点投影到真实世界平面上去。
原则上单目相机是不可以的,因为只记录了二维信息,真实世界是三维的,双目相机可以通过视差,或者单目+IMU组合,但是由于特征点在地面上的先验知识,因此可以进行反透视变换。方法有很多种那个,这里采用计算相机的内参和外参的方法。基础知识理论在视觉slam14讲中有详细说明,但是其代码是c++这里采用python opencv实现。
相机内参外参标定:https://blog.csdn.net/qq_29931565/article/details/119395353
逆投影,roadmap:https://blog.csdn.net/qq_53086461/article/details/128028199
需要安装提前安装好numpy、glob、cv2
pip install numpy
pip install opencv-python
pip install glob
2、内参标定
2.1打印一张棋盘格
2.2拍摄50张照片
import cv2
camera = cv2.VideoCapture(0)
i = 1
while i < 50:_, frame = camera.read()cv2.imwrite("E:/images/"+str(i)+'.png', frame, [int(cv2.IMWRITE_PNG_COMPRESSION), 0]) cv2.imshow('frame', frame)i += 1if cv2.waitKey(200) & 0xFF == 27: # 按ESC键退出break
cv2.destroyAllWindows()
2.3 计算内参、畸变
需要更改的地方
第4行:调整为实际的棋盘格数量,格子数-1(格子内部交点数量)
第5行:调整为实际的棋盘格数量
第6行:调整为实际的棋盘格大小
第9行:上一步的存储位置
import cv2
import numpy as np
import glob
objp = np.zeros((6 * 9, 3), np.float32)
objp[:, :2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2) # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
objp = 2.6 * objp # 打印棋盘格一格的边长为2.6cm
obj_points = [] # 存储3D点
img_points = [] # 存储2D点
images = glob.glob("images/*.png") # 黑白棋盘的图片路径for fname in images:img = cv2.imread(fname)gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)size = gray.shape[::-1]ret, corners = cv2.findChessboardCorners(gray, (9, 6), None)if ret:obj_points.append(objp)corners2 = cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1),(cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 30, 0.001))if [corners2]:img_points.append(corners2)else:img_points.append(corners)cv2.drawChessboardCorners(img, (9, 6), corners, ret) # 记住,OpenCV的绘制函数一般无返回值cv2.waitKey(1)
_, mtx, dist, _, _ = cv2.calibrateCamera(obj_points, img_points, size, None, None)# 内参数矩阵
Camera_intrinsic = {"mtx": mtx, "dist": dist, }
print("内参")
print(mtx)
print("畸变")
print(dist)
2.4 计算外参
固定相机!!!(之后就不要动了)
因为笔记本自带了一个相机,所以这里是用第二个,根据自己情况修改数字
camera = cv2.VideoCapture(1)
查看参考资料1:原作者代码显示俯仰角距离等信息,打开line 65、66 ,注释掉 line 67、68即可,效果如下。
外参包含两部分,旋转矩阵rvec_matrix(公式中的)和平移向量tvec(公式中的),我们通过参考资料2的方式反透视变化,为相机的投影模型,简化为内参矩阵。
的标定我们在图中选取特定的点,进行运算。
代码中为#####部分,uv是相机图像中的坐标值的点,xy1是结算出来对应真实世界的点。
以该图片举例:
红色是七个点在图像中的位置,坐标原点在左上角
绿色是七个点在真实世界中的位置,坐标原点在右下角0号点位置
输入:点在图片中的位置,输出点在真实坐标系的位置
import cv2
import numpy as np
import glob
import math
objp = np.zeros((6 * 9, 3), np.float32)
objp[:, :2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2) # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
objp = 2.5 * objp # 打印棋盘格一格的边长为2.6cm
obj_points = [] # 存储3D点
img_points = [] # 存储2D点
images = glob.glob("images/*.png") # 黑白棋盘的图片路径mtx=[[444.26980017,0.0,330.00914635], [ 0.0,442.49395109,239.56771487], [ 0.0,0.0,1.0]]
mtx=np.array(mtx)dist = [[ 1.47649702e-01,-2.72487592e-01 ,7.01492649e-05 ,2.74167922e-03, 1.84670647e-01]]
dist=np.array(dist)
# 内参数矩阵
Camera_intrinsic = {"mtx": mtx, "dist": dist, }
print("内参")
print(mtx)
print("畸变")
print(dist)obj_points = objp # 存储3D点
img_points = [] # 存储2D点# 从摄像头获取视频图像
camera = cv2.VideoCapture(1)while True:_, frame = camera.read()gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)size = gray.shape[::-1]ret, corners = cv2.findChessboardCorners(gray, (9, 6), None)if ret: # 画面中有棋盘格img_points = np.array(corners)cv2.drawChessboardCorners(frame, (9, 6), corners, ret)# rvec: 旋转向量 tvec: 平移向量_, rvec, tvec = cv2.solvePnP(obj_points, img_points, Camera_intrinsic["mtx"], Camera_intrinsic["dist"]) # 解算位姿distance = math.sqrt(tvec[0] ** 2 + tvec[1] ** 2 + tvec[2] ** 2) # 计算距离rvec_matrix = cv2.Rodrigues(rvec)[0] # 旋转向量->旋转矩阵print("旋转矩阵")print(rvec_matrix)print("tvec")print(tvec)#####################rt =np.array([[rvec_matrix[0][0],rvec_matrix[0][1],tvec[0]],[rvec_matrix[1][0],rvec_matrix[1][1],tvec[1]],[rvec_matrix[2][0],rvec_matrix[2][1],tvec[2]]], dtype=np.float)rt_i=np.linalg.inv(rt)pi_i = np.linalg.inv(Camera_intrinsic["mtx"])uv = np.array([[434,396,362,331,338,376,480],[349,331,315,300,347,368,371],[1,1,1,1,1,1,1]])xy1 = 0.340909*rt_i.dot(pi_i.dot(uv))print("xy1")print(xy1)#####################proj_matrix = np.hstack((rvec_matrix, tvec)) # hstack: 水平合并eulerAngles = cv2.decomposeProjectionMatrix(proj_matrix)[6] # 欧拉角pitch, yaw, roll = eulerAngles[0], eulerAngles[1], eulerAngles[2]#cv2.putText(frame, "dist: %.2fcm, yaw: %.2f, pitch: %.2f, roll: %.2f" % (distance, yaw, pitch, roll),#(10, frame.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)cv2.putText(frame, "dist: %.2fcm,x : %.2f,y : %.2f, z: %.2f" % (distance, tvec[0], tvec[1], tvec[2]),(10, frame.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)cv2.imshow('frame', frame)if cv2.waitKey(1) & 0xFF == 27: # 按ESC键退出breakelse: # 画面中没有棋盘格cv2.putText(frame, "Unable to Detect Chessboard", (20, frame.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX, 1.3,(0, 0, 255), 3)cv2.imshow('frame', frame)if cv2.waitKey(1) & 0xFF == 27: # 按ESC键退出break
cv2.destroyAllWindows()
这里是结果,可以看到还是有一定误差,系数是根据4号点算的。
3、yolo中实现
这里我们更改了yolov5 detect.py中的Add bbox to image部分,让结果加在识别框上。思路是提取下边缘中点进行反透视投影。
if save_img or save_crop or view_img: # Add bbox to imagec = int(cls) # integer classlabel = None if hide_labels else (names[c] if hide_conf else f' {names[c]}{conf:.2f}')#########mtx = [[444.26980017, 0.0, 330.00914635], [0.0, 442.49395109, 239.56771487], [0.0, 0.0, 1.0]]mtx = np.array(mtx)dist = [[1.47649702e-01, -2.72487592e-01, 7.01492649e-05, 2.74167922e-03, 1.84670647e-01]]dist = np.array(dist)Camera_intrinsic = {"mtx": mtx, "dist": dist, }rvec_matrix= np.array([[-0.99611232,-0.05914762,-0.06528244],[-0.04042639,-0.35150008,0.9353146 ],[-0.07826841,0.93431753,0.34774244]])tvec=np.array([[8.0553314],[8.50746768],[34.52738218]])rt = np.array([[rvec_matrix[0][0], rvec_matrix[0][1], tvec[0]],[rvec_matrix[1][0], rvec_matrix[1][1], tvec[1]],[rvec_matrix[2][0], rvec_matrix[2][1], tvec[2]]], dtype=np.float)rt_i = np.linalg.inv(rt)pi_i = np.linalg.inv(Camera_intrinsic["mtx"])x1, y1, x2, y2 = xyxyxx = (x1 + x2) / 2yy = max(y1, y2);uv = np.array([[xx.cpu()],[yy.cpu()],[1]])xy1 =rt_i.dot(pi_i.dot(uv))xy1[0]=xy1[0]*0.43xy1[1]=xy1[1]*0.43xxx="%.3f" % xy1[0]yyy="%.2f" % xy1[1]label =xxx+" "+yyy#########annotator.box_label(xyxy, label, color=colors(c, True))
可以看到,误差似乎比单点更大了一点,怀疑是投影模型没有考虑畸变,更改如下:
before=np.array([[[xx.cpu(),yy.cpu()]]])end = cv.undistortPoints(before,mtx, dist, P=mtx)xx = end[0][0][0]yy = end[0][0][1]uv = np.array([[xx],[yy],[1]])
但是,后来发现差不多,测试了下去畸变函数,可以发现并没有什么区别。
import cv2 as cv
import numpy as np
mtx = [[444.26980017, 0.0, 330.00914635], [0.0, 442.49395109, 239.56771487], [0.0, 0.0, 1.0]]
mtx = np.array(mtx)
dist = [[1.47649702e-01, -2.72487592e-01, 7.01492649e-05, 2.74167922e-03, 1.84670647e-01]]
dist = np.array(dist)
img = cv.imread('imagess/1.png')
img_undistored = cv.undistort(img, mtx, dist)
cv.imwrite('imagess/11.png', img_undistored)
4、总结
原理上可行,根据棋盘格算距离也比较准确。但最终精度不高,可能是yolo本身下边缘也不准。
这种方法本来精度就有限,尤其上车后,还存在俯仰角度变化等,所以后续还要优化。
计算内参时候发现,选优不同数量的图片和角度时,结果有时候差距也比较大,可能是影响结果来源之一。另外一个打印的纸张后来发现不是严格的2.5cm,实际要大一点点,因此量格子的时候十个一量算平均数可能结果更加精确。
4、留坑:后续可能和(八)做联动
(九)相机内参、外参、反透视变换python opencv相关推荐
- 计算机视觉——棋盘格标定法获取相机内参外参
计算机视觉--棋盘格标定法获取相机内参外参 一.原理 相机标定目的 相机标定的输入 相机标定的输出 相机标定策略 相机拍摄图像变换过程 相机内参 相机外参 二.环境 三.数据集 四.运行结果与分析 角 ...
- 相机的内参会改变吗_相机内参外参及成像过程
前段时间有人问我怎么由点的世界坐标计算计算对应的像素坐标,我详细的推导了下,现在把整个过程写篇博客.虽然网上有很多相关的文章,但是我可能会写的更详细些. 一.小孔成像模型 1.基本概念及公式 如图所示 ...
- JointCalib-雷达与相机的外参标定
0. 简介 作为自动驾驶行业最头疼的问题之一,外参标定一直以来受到广泛的关注,尤其是最常使用的激光雷达与相机的外参标定.之前在文章:3D雷达与相机的标定方法详细教程与多传感器融合感知 --传感器外参标 ...
- Ubuntu18系统下采集点云数据(速腾16线激光雷达)与ZED 2 相机进行外参标定(Matlab的LCC工具箱)
这篇是最近的实验记录:在Ubuntu系统下读取点云数据并在rviz中显示,录制自己的rosbag数据包并离线播放,将rosbag文件转化为pcd格式导入MATLAB中,和双目相机进行外参标定. 目录 ...
- 安卓手机标定相机IMU外参过程
本文阐述安卓手机标定相机IMU外参过程,包括以下主要流程: 1. 制作标定板 2. 单独标定单目相机的外参 3. 单独标定IMU的外参 4. 相机IMU的联合标定 1.为什么要进行相机标定? 在图像测 ...
- 多激光雷达与相机的外参快速精准标定(arxiv 2021)
点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 作者丨paopaoslam 来源丨 泡泡机器人SLAM 标题:Fast and Accurate Ex ...
- 激光雷达和相机的外参标定
激光雷达和相机的外参标定 我两年前做的本科毕设课题是激光雷达与相机的外参标定,最近抽空整理了一下当时完成的标定工具,这篇博客的目的也是来介绍一下这个标定工具(目前已经开源). 介绍 在当时的研究现状( ...
- 生成黑白棋盘标定图和单目相机标定(一)(python+opencv实现)
学习记录. 事实上很早就接触过视觉定位这东西,但是到现在才返回头学习一下相机的标定,真是可耻啊!我把想法和过程记录一下. 相机成像 相机的成像原理--小孔成像 然而,在实际由于设计工艺问题.相机安装环 ...
- velo2cam_calibration——最新最准确的激光雷达Lidar和相机Camera外参标定算法实现
目录 写在前面 参考资料 算法原理 准备工作 开始使用 检测结果 总结 写在前面 因为实验需求,要实现相机和雷达之间的融合,因此需要完成相机内参标定和雷达与相机外参标定. 相机内参标定使用ros自带的 ...
最新文章
- ExtJS 4.2 教程-08:布局系统详解
- PHP求并集,交集,差集
- springweb拦截器
- Android开发之recyclerview布局加载不全的问题
- linux关闭4750 端口,【ubuntu分享帖】acer 4750G ubuntu安装后的一些设置
- 计算机数据库管理基本知识,2015年计算机四级考试《数据库技术》基础知识:概念篇...
- RabbitMQ实现生产者发送消息异步confirm
- 【数据格式】Jackson 美化输出JSON,优雅的输出JSON数据,格式化输出JSON数据
- Python--day26--封装和@property
- Disunity_V0.5.0 提取Unity生成的Apk的资源
- Cannot connect to the Maven process. Try again later. If the problem persists, check the Maven Impor
- oracle中那个日期怎么相减_oracle 日期相减
- idea 如何修改主题
- 头的各个部位示意图_人体头部结构图解剖图 人体头部结构及功能
- 计算机和信息技术革命,人类历史上的四次信息技术革命
- 起步晚了20年,韩国芯片凭什么打破美日封锁,做到世界第1?
- 服务器是嵌入式系统吗,常见的几种嵌入式web服务器
- Bootloader 的作用
- 争议最大的神经网络:绝顶聪明or傻透顶了 ?
- 《机电一体化系统设计》
热门文章
- 日常开发中,你需要掌握的git使用技巧
- 金融:一级市场和二级市场的区别
- 计算动态优先级 -- effective_prio()
- c#: Newtonsoft.Json 高级用法一(不创建类,动态解析和构造json、JObject/JArray)
- TextView属性详细
- 选对群控系统究竟能不能吃香,未来能不能赚钱?
- Python数据爬虫学习笔记:爬取豆瓣阅读的出版社名称数据
- 现在计算机大厂985本科是不是难进了?
- Spring中Quartz调度器的使用 ----继承QuartzJobBean,不继承QuartzJobBean两种方式
- 《影响力》第三章:承诺和一致 读书笔记