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结合PyWebView库打造跨平台桌面应用

《Python结合PyWebView库打造跨平台桌面应用》随着Web技术的发展,将HTML/CSS/JavaScript与Python结合构建桌面应用成为可能,本文将系统讲解如何使用PyWebView... 目录一、技术原理与优势分析1.1 架构原理1.2 核心优势二、开发环境搭建2.1 安装依赖2.2 验

C#实现将Excel表格转换为图片(JPG/ PNG)

《C#实现将Excel表格转换为图片(JPG/PNG)》Excel表格可能会因为不同设备或字体缺失等问题,导致格式错乱或数据显示异常,转换为图片后,能确保数据的排版等保持一致,下面我们看看如何使用C... 目录通过C# 转换Excel工作表到图片通过C# 转换指定单元格区域到图片知识扩展C# 将 Excel

一文详解如何在Python中从字符串中提取部分内容

《一文详解如何在Python中从字符串中提取部分内容》:本文主要介绍如何在Python中从字符串中提取部分内容的相关资料,包括使用正则表达式、Pyparsing库、AST(抽象语法树)、字符串操作... 目录前言解决方案方法一:使用正则表达式方法二:使用 Pyparsing方法三:使用 AST方法四:使用字

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

Python列表去重的4种核心方法与实战指南详解

《Python列表去重的4种核心方法与实战指南详解》在Python开发中,处理列表数据时经常需要去除重复元素,本文将详细介绍4种最实用的列表去重方法,有需要的小伙伴可以根据自己的需要进行选择... 目录方法1:集合(set)去重法(最快速)方法2:顺序遍历法(保持顺序)方法3:副本删除法(原地修改)方法4:

Python运行中频繁出现Restart提示的解决办法

《Python运行中频繁出现Restart提示的解决办法》在编程的世界里,遇到各种奇怪的问题是家常便饭,但是,当你的Python程序在运行过程中频繁出现“Restart”提示时,这可能不仅仅是令人头疼... 目录问题描述代码示例无限循环递归调用内存泄漏解决方案1. 检查代码逻辑无限循环递归调用内存泄漏2.

Python中判断对象是否为空的方法

《Python中判断对象是否为空的方法》在Python开发中,判断对象是否为“空”是高频操作,但看似简单的需求却暗藏玄机,从None到空容器,从零值到自定义对象的“假值”状态,不同场景下的“空”需要精... 目录一、python中的“空”值体系二、精准判定方法对比三、常见误区解析四、进阶处理技巧五、性能优化

使用Python构建一个Hexo博客发布工具

《使用Python构建一个Hexo博客发布工具》虽然Hexo的命令行工具非常强大,但对于日常的博客撰写和发布过程,我总觉得缺少一个直观的图形界面来简化操作,下面我们就来看看如何使用Python构建一个... 目录引言Hexo博客系统简介设计需求技术选择代码实现主框架界面设计核心功能实现1. 发布文章2. 加

python logging模块详解及其日志定时清理方式

《pythonlogging模块详解及其日志定时清理方式》:本文主要介绍pythonlogging模块详解及其日志定时清理方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录python logging模块及日志定时清理1.创建logger对象2.logging.basicCo

Python如何自动生成环境依赖包requirements

《Python如何自动生成环境依赖包requirements》:本文主要介绍Python如何自动生成环境依赖包requirements问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录生成当前 python 环境 安装的所有依赖包1、命令2、常见问题只生成当前 项目 的所有依赖包1、