python cv2摄像头校准,坐标系转换

2024-06-19 09:32

本文主要是介绍python cv2摄像头校准,坐标系转换,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

代码

先上代码链接:
链接: https://pan.baidu.com/s/1vk1hYcOHdfadU-XwJQQS6g 提取码: cn2h

功能说明

  1. 摄像头校准:内参,外参获取,测试校准点
  2. 图片视频畸变还原
  3. 2D像素坐标坐标转3D世界坐标
  4. 3D世界坐标转2D像素坐标

流程分析

  1. 使用相机拍摄或直接使用现有的内参和外参图片
  2. 张友正标定法获取内参参数
  3. 获取外参标记点的世界坐标和像素坐标
  4. 使用PNP算法获取相机畸变系数
  5. 根据得到的参数做还原和坐标系转换

代码使用tkinter写成了一个小工具,有兴趣的尝试优化:
在这里插入图片描述

本例以摄像头为世界坐标系原点,以汽车车头中间作为测量点
offset是摄像头与测量点的偏移量
1. 校准

通过选取拍摄好的图片或直接调用摄像头拍照获取校准图片,然后将测得的基准点的世界坐标和像素坐标添加到基准点数据中后校准获得camera_params_XXX.xml文件

在这里插入图片描述
注意: 测量距离的时候先平行车和垂直车贴好标线方便测量标记点坐标
如图,根据图像在绿色线对应位置摆放多点然后沿多点贴上胶带即获得垂直坐标线
横坐标在纵坐标上取一点,取垂直纵坐标的坐标线(测试使用单位mm,垂直为x轴,横向左侧为正右侧为负)
在这里插入图片描述

2. 测试

将测得的3D坐标转为对应的像素坐标,然后与源像素坐标比较
如图,绿色是源像素点坐标6个像素点范围圆, 红色是通过对应的3D坐标转换的3个像素的实心点

如果红色实心点在绿色圈附近则认为相机参数基本符合要求(精度根据自己的需求调整,测试数据的准确性很重要,测试了很多组数据才将精度调整到需求)

在这里插入图片描述

3. 主要代码

校准部分

class CameraCalibrate(object):def __init__(self, image_size: tuple):super(CameraCalibrate, self).__init__()self.image_size = image_sizeself.matrix = np.zeros((3, 3), np.float)self.new_camera_matrix = np.zeros((3, 3), np.float)self.dist = np.zeros((1, 5))self.roi = np.zeros(4, np.int)self.element = ET.Element('cameraParams')self.elementTree = ET.ElementTree(self.element)self.time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())comment = ET.Element('about')comment.set('author', 'shadow')comment.set('email', 'yinlu@flyaudio.cn')comment.set('date', self.time)self.element.append(comment)self.add_param('imageSize', dict(zip(('width', 'height'), image_size)))# 添加保存摄像头参数def add_param(self, name, param):node = ET.Element(name)self.element.append(node)if isinstance(param, dict):for key, value in param.items():child = ET.Element(key)child.text = str(value)node.append(child)else:for i, elem in enumerate(param.flatten()):child = ET.Element(f'data{i}')child.text = str(elem)node.append(child)# 保存摄像头参数def save_params(self):save_path = 'camera_params_' + self.time.split()[0] + '.xml'self.elementTree.write(save_path, 'UTF-8')print("Saved params in {}.".format(save_path))# 校准角点def cal_real_corner(self, corner_height, corner_width, square_size):obj_corner = np.zeros([corner_height * corner_width, 3], np.float32)obj_corner[:, :2] = np.mgrid[0:corner_height, 0:corner_width].T.reshape(-1, 2)  # (w*h)*2return obj_corner * square_sizedef calibration(self, corner: dict, img_path='./image'):file_names = glob.glob(img_path+'/*.jpg') + glob.glob(img_path+'./*.png')if not file_names:showerror("ERROR", f'No picture find in {img_path}!')returnobjs_corner = []imgs_corner = []criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)obj_corner = self.cal_real_corner(corner['height'], corner['width'], corner['size'])for file_name in file_names:# read imagechess_img = cv.imread(file_name)assert (chess_img.shape[0] == self.image_size[1] and chess_img.shape[1] == self.image_size[0]), \"Image size does not match the given value {}.".format(self.image_size)# to graygray = cv.cvtColor(chess_img, cv.COLOR_BGR2GRAY)# find chessboard cornersret, img_corners = cv.findChessboardCorners(gray, (corner['height'], corner['width']))# append to img_cornersif ret:objs_corner.append(obj_corner)img_corners = cv.cornerSubPix(gray, img_corners, winSize=(corner['size']//2, corner['size']//2),zeroZone=(-1, -1), criteria=criteria)imgs_corner.append(img_corners)else:print("Fail to find corners in {}.".format(file_name))# calibrationret, self.matrix, self.dist, rvecs, tveces = cv.calibrateCamera(objs_corner, imgs_corner, self.image_size, None, None)if ret:self.add_param('camera_matrix', self.matrix)self.add_param('camera_distortion', self.dist)# self.add_param('roi', self.roi)return retdef get_camera_param(self, points):objPoints = np.array(points['worldPoints'], dtype=np.float64)imgPoints = np.array(points['imagePoints'], dtype=np.float64)retval, rvec, tvec = cv.solvePnP(objPoints, imgPoints, self.matrix, self.dist)if retval:# rotMatrix = cv.Rodrigues(rvec)[0]self.add_param('camera_rvec', rvec)self.add_param('camera_tvec', tvec)# self.add_param('rotation_matrix', rotMatrix)self.add_param('points', points)return retval

图像矫正, 坐标转换部分

class Rectify(object):def __init__(self, param_file='/home/fly/ros2_ws/src/camera_calibration/camera_params.xml'):super(Rectify, self).__init__()self.image_size = (1280, 720)self.camera_params = {'camera_matrix': None,  # 相机内参矩阵'camera_distortion': None,  # 畸变系数'camera_rvec': None,  # 旋转矢量'camera_tvec': None,  # 平移矢量}self.load_params(param_file)# 加载摄像头参数def load_params(self, param_file):if not os.path.exists(param_file):print("File {} does not exist.", format(param_file))exit(-1)tree = ET.parse(param_file)root = tree.getroot()# 获取到测试基准点数据p = root.find('points')if p:self.points = {data.tag: eval(data.text) for data in p}# print(self.points)# 获取摄像头内参。外参for i in self.camera_params.keys():datas = root.find(i)if datas:paras = [data.text for data in datas]self.camera_params[i] = np.array(paras, np.float)# print(i, paras)if i =='camera_matrix':self.camera_params[i] = mat(paras, np.float).reshape((3, 3))elif i in ['camera_tvec', 'camera_rvec']:self.camera_params[i] = mat(paras, np.float).reshape((3, 1))elif i == 'camera_distortion':self.camera_params[i] = mat(paras, np.float)else:print(i, self.camera_params)return Falseif root.find('imageSize'):params = {data.tag: int(data.text) for data in root.find('imageSize')}self.image_size = tuple((params['width'], params['height']))self.rvec = self.camera_params['camera_rvec']self.cam_mat = self.camera_params['camera_matrix']self.tvec = self.camera_params['camera_tvec']self.cam_dist = self.camera_params['camera_distortion']self.rot_mat = mat(cv.Rodrigues(self.rvec)[0])self.cam_mat_new, roi = cv.getOptimalNewCameraMatrix(self.cam_mat, self.cam_dist, self.image_size, 1, self.image_size)self.roi = np.array(roi)# for k, v in self.camera_params.items():#     print(k, v)return True# 矫正视频def rectify_video(self, video_in: str, video_out: str):cap = cv.VideoCapture(video_in)print(cap)if not cap.isOpened():print("Unable to open video.")return Falsefourcc = int(cap.get(cv.CAP_PROP_FOURCC))fps = int(cap.get(cv.CAP_PROP_FPS))out = cv.VideoWriter(filename=video_out, fourcc=fourcc, fps=fps, frameSize=self.image_size)frame_count = int(cap.get(cv.CAP_PROP_FRAME_COUNT))print('rectify_video start...')for _ in range(frame_count):ret, img = cap.read()if ret:dst = self.rectify_image(img)out.write(dst)cv.waitKey(1)cap.release()out.release()cv.destroyAllWindows()return True# 矫正摄像头def rectify_camera(self, camera: dict):try:cap = cv.VideoCapture(camera['id'])print(cap)except:print("Unable to open camera.")return Falseif not cap.isOpened():print("Unable to open camera.")return Falsewhile camera['run']:ret, img = cap.read()yield ret, self.rectify_image(img)cap.release()cv.destroyAllWindows()# 矫正图片def rectify_image(self, img):if not isinstance(img, np.ndarray) or not img.any():AssertionError("Image is None or type '{}' is not numpy.ndarray .".format(type(img)))return Noneimg = cv.resize(img, self.image_size)dst = cv.undistort(img, self.cam_mat, self.cam_dist, self.cam_mat_new)# x, y, w, h = self.roi# dst = dst[y:y + h, x:x + w]dst = cv.resize(dst, self.image_size)# cv.waitKey(0)return dst# @timefuncdef get_world_point(self, point):point = list(point) + [1]  # 注意要加1# print(point)# 计算参数Ss = (self.rot_mat.I * self.tvec)[2] / (self.rot_mat.I * self.cam_mat.I * mat(point).T)[2]# 计算世界坐标wcpoint = self.rot_mat.I * (s[0, 0] * self.cam_mat.I * mat(point).T - self.tvec)# print("sourcePoint:", point, "worldpoint:", wcpoint)return wcpoint.flatten().getA()[0]  # 转成列表输出# @timefuncdef wcpoint2point(self, wcpoints):points, _ = cv.projectPoints(wcpoints, self.rvec, self.tvec, self.cam_mat, 0)return points

另外做了在图像上直接选取像素点的功能但没有加到工具中,可以帮助省略通过其他图像工具获取像素点的步骤,有兴趣的同学可以自己尝试替换手动输入像素坐标的功能

# ======main.py
# 先开线程打开图片
def add():global Flagif not Flag:img_file = filedialog.askopenfile()threading.Thread(target=cb.point_get_from_image, args=(img_file.name, pos_im)).start()Flag = Trueelse:add_point()# ======calibration.py
# 显示图片,监控左键点击
def point_get_from_image(img_file, pos):# 打开要校准的基准图片,选取测试基准点flag = []img = cv.imread(img_file)cv.namedWindow("image")cv.setMouseCallback("image", on_mouse, param=(img, pos, flag))while True:cv.imshow("image", img)cv.line(img, (640, 0), (640, 720), (0, 255, 0), 1)if cv.waitKey(1) == ord('q') or flag:breakcv.destroyAllWindows()# 左键点击,在图像上标记,并将点传给标记点工具界面数据,右键退出
def on_mouse(event, x, y, flags, param):image, pos, flag = paramif event == cv.EVENT_LBUTTONDOWN:# print(event, x, y, param, flags)cv.circle(image, (x, y), 2, (0, 255, 0), -1)# cv.putText(image, f"pos:({x},{y})", (x, y - 10), cv.FONT_HERSHEY_SIMPLEX, 0.5, (200, 0, 0), 2)pos.set(f"{x}, {y}")elif event == cv.EVENT_RBUTTONDOWN:flag.append((x, y))

在这里插入图片描述

这篇关于python cv2摄像头校准,坐标系转换的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

一文详解Python如何开发游戏

《一文详解Python如何开发游戏》Python是一种非常流行的编程语言,也可以用来开发游戏模组,:本文主要介绍Python如何开发游戏的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、python简介二、Python 开发 2D 游戏的优劣势优势缺点三、Python 开发 3D

Python函数作用域与闭包举例深度解析

《Python函数作用域与闭包举例深度解析》Python函数的作用域规则和闭包是编程中的关键概念,它们决定了变量的访问和生命周期,:本文主要介绍Python函数作用域与闭包的相关资料,文中通过代码... 目录1. 基础作用域访问示例1:访问全局变量示例2:访问外层函数变量2. 闭包基础示例3:简单闭包示例4

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符

Python版本与package版本兼容性检查方法总结

《Python版本与package版本兼容性检查方法总结》:本文主要介绍Python版本与package版本兼容性检查方法的相关资料,文中提供四种检查方法,分别是pip查询、conda管理、PyP... 目录引言为什么会出现兼容性问题方法一:用 pip 官方命令查询可用版本方法二:conda 管理包环境方法

基于Python开发Windows自动更新控制工具

《基于Python开发Windows自动更新控制工具》在当今数字化时代,操作系统更新已成为计算机维护的重要组成部分,本文介绍一款基于Python和PyQt5的Windows自动更新控制工具,有需要的可... 目录设计原理与技术实现系统架构概述数学建模工具界面完整代码实现技术深度分析多层级控制理论服务层控制注

pycharm跑python项目易出错的问题总结

《pycharm跑python项目易出错的问题总结》:本文主要介绍pycharm跑python项目易出错问题的相关资料,当你在PyCharm中运行Python程序时遇到报错,可以按照以下步骤进行排... 1. 一定不要在pycharm终端里面创建环境安装别人的项目子模块等,有可能出现的问题就是你不报错都安装

Python打包成exe常用的四种方法小结

《Python打包成exe常用的四种方法小结》本文主要介绍了Python打包成exe常用的四种方法,包括PyInstaller、cx_Freeze、Py2exe、Nuitka,文中通过示例代码介绍的非... 目录一.PyInstaller11.安装:2. PyInstaller常用参数下面是pyinstal

Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题

《Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题》在爬虫工程里,“HTTPS”是绕不开的话题,HTTPS为传输加密提供保护,同时也给爬虫带来证书校验、... 目录一、核心问题与优先级检查(先问三件事)二、基础示例:requests 与证书处理三、高并发选型:

Python中isinstance()函数原理解释及详细用法示例

《Python中isinstance()函数原理解释及详细用法示例》isinstance()是Python内置的一个非常有用的函数,用于检查一个对象是否属于指定的类型或类型元组中的某一个类型,它是Py... 目录python中isinstance()函数原理解释及详细用法指南一、isinstance()函数