python cv2摄像头校准,坐标系转换
生活随笔
收集整理的這篇文章主要介紹了
python cv2摄像头校准,坐标系转换
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
代碼
先上代碼鏈接:
鏈接: https://pan.baidu.com/s/1vk1hYcOHdfadU-XwJQQS6g 提取碼: cn2h
功能說明
流程分析
代碼使用tkinter寫成了一個小工具,有興趣的嘗試優化:
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摄像头校准,坐标系转换的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Karto的后端优化与回环检测功能对比测
- 下一篇: 高德开放平台实现区域地图+云图标记