├── .ipynb_checkpoints └── Convert_Velo_2_Pano-checkpoint.ipynb ├── Convert_Velo_2_Pano.ipynb ├── Convert_Velo_2_Pano_detail.ipynb ├── Convert_Velo_2_Topview.ipynb ├── Convert_Velo_2_Topview_detail.ipynb ├── README.md ├── display_groundtruth.ipynb ├── images ├── pano.jpg ├── projection.jpg ├── topview.jpg └── tracklet.jpg ├── kitti_foundation.py ├── src ├── __init__.py └── parseTrackletXML.py ├── velo2cam_projection.ipynb └── velo2cam_projection_detail.ipynb /.ipynb_checkpoints/Convert_Velo_2_Pano-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# **Convert Velodyne-data to Panoramic image** \n", 8 | "***\n", 9 | "\n", 10 | "## Dataset\n", 11 | "[KITTI 2011_09_26_drive_0005 dataset](http://www.cvlibs.net/datasets/kitti/raw_data.php?type=city)\n", 12 | "\n", 13 | "\n", 14 | "## Objective\n", 15 | "\n", 16 | "Convert Velodyne data(model : HDL-64E) to panoramic image.\n", 17 | "In this code, Every setting value related HW is set for [HDL-64E](http://velodynelidar.com/docs/datasheet/63-9194%20Rev-E_HDL-64E_S3_Spec%20Sheet_Web.pdf).(e.g. Angular Resolution, FOV, etc)\n", 18 | "Therefore, if you are using different 3D-Lidar model, you need to replace them by the corresponding values. \n", 19 | " \n", 20 | " \n", 21 | "\n", 22 | "| Channe| FOV(V) | Angular Resolution(V) | FOV(H) |Angular Resolution(H) |\n", 23 | "|:-----:|:----------------:|:------------------------:|:--------:|:--------------------------:| \n", 24 | "| 64 | +2˚ to - 24.9˚| 0.4˚ | 360˚ | 0.08˚- 0.35˚(5Hz - 20Hz) |\n", 25 | "\n", 26 | "The rotation rate is assumed as 20Hz. SO, the horizontal angular resolution is set to 0.35˚. \n", 27 | " \n", 28 | " " 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 1, 34 | "metadata": { 35 | "collapsed": false 36 | }, 37 | "outputs": [ 38 | { 39 | "name": "stdout", 40 | "output_type": "stream", 41 | "text": [ 42 | "(123397, 4)\n" 43 | ] 44 | } 45 | ], 46 | "source": [ 47 | "import numpy as np\n", 48 | "import matplotlib.pyplot as plt\n", 49 | "%matplotlib inline\n", 50 | "\n", 51 | "def load_from_bin(bin_path):\n", 52 | " obj = np.fromfile(bin_path, dtype=np.float32).reshape(-1, 4)\n", 53 | " return obj\n", 54 | "\n", 55 | "# bin file -> numpy array\n", 56 | "velo_points = load_from_bin('./velodyne_points/data/0000000000.bin')\n", 57 | "\n", 58 | "print(velo_points.shape)" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "metadata": {}, 64 | "source": [ 65 | "Points from a Velodyne scan can be roughly projected and discretized into a 2D point map. \n", 66 | " \n", 67 | "The related projection function is refered to **[this paper](http://www.roboticsproceedings.org/rss12/p42.pdf)**.(Vehicle Detection from 3D Lidar Using Fully Convolutional Network(2016))" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": 2, 73 | "metadata": { 74 | "collapsed": false 75 | }, 76 | "outputs": [ 77 | { 78 | "name": "stdout", 79 | "output_type": "stream", 80 | "text": [ 81 | "(66, 1030)\n" 82 | ] 83 | }, 84 | { 85 | "data": { 86 | "image/png": "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\n9zIAAAAASUVORK5CYII=\n", 87 | "text/plain": [ 88 | "" 89 | ] 90 | }, 91 | "metadata": {}, 92 | "output_type": "display_data" 93 | } 94 | ], 95 | "source": [ 96 | "def normalize_depth(val, min_v, max_v):\n", 97 | " \"\"\" \n", 98 | " print 'nomalized depth(distance) value' \n", 99 | " nomalize values to 0-255 & close distance value has high value. (similar to stereo vision's disparity map)\n", 100 | " \"\"\"\n", 101 | " return (((max - a) / float(max - min)) * 255).astype(np.uint8)\n", 102 | "\n", 103 | "def velo_points_2_pano(points, v_res, h_res, v_fov):\n", 104 | "\n", 105 | " # Projecting to 2D\n", 106 | " x = points[:, 0]\n", 107 | " y = points[:, 1]\n", 108 | " z = points[:, 2]\n", 109 | " dist = np.sqrt(x ** 2 + y ** 2)\n", 110 | "\n", 111 | " # project point cloud to 2D point map\n", 112 | " x_img = np.arctan2(-y, x) / (h_res * (np.pi / 180))\n", 113 | " y_img = -(np.arctan2(z, dist) / (v_res * (np.pi / 180)))\n", 114 | "\n", 115 | " x_size = int(np.ceil(360/h_res))\n", 116 | " y_size = int(np.ceil((v_fov[1] - v_fov[0])/v_res))\n", 117 | " \n", 118 | " # shift negative points to positive points (shift minimum value to 0)\n", 119 | " x_offset = (360 / 2) / h_res\n", 120 | " x_img = np.trunc(x_img + x_offset).astype(np.int32)\n", 121 | " y_offset = ((v_fov[1] / v_res))\n", 122 | " y_fine_tune = 1\n", 123 | " y_img = np.trunc(y_img + y_offset + y_fine_tune).astype(np.int32)\n", 124 | "\n", 125 | " # array to img\n", 126 | " img = np.zeros([y_size + 1, x_size + 1], dtype=np.uint8)\n", 127 | " img[y_img, x_img] = dist\n", 128 | " \n", 129 | " return img\n", 130 | "\n", 131 | "pano_img = velo_points_2_pano(velo_points, v_res=0.42, h_res=0.35, v_fov=(-24.9, 2.0))\n", 132 | "\n", 133 | "# display result image\n", 134 | "plt.subplots(1,1, figsize = (13,3) )\n", 135 | "plt.imshow(pano_img)\n", 136 | "plt.axis('off')\n", 137 | "\n", 138 | "print(pano_img.shape)" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "metadata": {}, 144 | "source": [ 145 | "## save panorama video" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 3, 151 | "metadata": { 152 | "collapsed": false 153 | }, 154 | "outputs": [], 155 | "source": [ 156 | "import glob\n", 157 | "from PIL import Image\n", 158 | "import cv2\n", 159 | "\n", 160 | "lidar_points = glob.glob('./velodyne_points/data/*.bin')\n", 161 | "\n", 162 | "\"\"\" save panorama video \"\"\"\n", 163 | "fourcc = cv2.VideoWriter_fourcc(*'XVID')\n", 164 | "vid = cv2.VideoWriter('pano.mp4', fourcc, 25.0, (1030, 66), False)\n", 165 | "\n", 166 | "for point in lidar_points:\n", 167 | " velo = load_from_bin(point)\n", 168 | " img = velo_points_2_pano(velo, v_res=0.42, h_res=0.35, v_fov=(-24.9, 2.0))\n", 169 | " vid.write(img)\n", 170 | "\n", 171 | "vid.release() " 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": null, 177 | "metadata": { 178 | "collapsed": true 179 | }, 180 | "outputs": [], 181 | "source": [] 182 | } 183 | ], 184 | "metadata": { 185 | "anaconda-cloud": {}, 186 | "kernelspec": { 187 | "display_name": "Python [conda root]", 188 | "language": "python", 189 | "name": "conda-root-py" 190 | }, 191 | "language_info": { 192 | "codemirror_mode": { 193 | "name": "ipython", 194 | "version": 3 195 | }, 196 | "file_extension": ".py", 197 | "mimetype": "text/x-python", 198 | "name": "python", 199 | "nbconvert_exporter": "python", 200 | "pygments_lexer": "ipython3", 201 | "version": "3.5.2" 202 | } 203 | }, 204 | "nbformat": 4, 205 | "nbformat_minor": 1 206 | } 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KITTI Tutorial 2 | 3 | ## Introduction 4 | 5 | >This is personal result for studying Self-Driving Techs. In this tutorial, I'll upload various codes from basic methods(e.g. lidar point projection) to state-of-the-art techs(e.g. deeplearning-based vehicle detection). Mainly, 'velodyne, camera' data-based approach will be discussed but when the time allows, I'll treat stereo vision, too. Also, Kitti-dataset-related simple codes(e.g. load tracklet or velodyne points) are in [`kitti_foundation.py`](kitti_foundation.py) coded by myself. 6 | 7 | Before start, 8 | 9 | * [KITTI site](www.cvlibs.net/datasets/kitti/) 10 | * refer to [KITTI Dataset Paper](http://www.cvlibs.net/publications/Geiger2013IJRR.pdf) for the details of data measurement environment 11 | 12 | 13 | ## Dataset 14 | 15 | [KITTI 2011_09_26_drive_0005 dataset](http://www.cvlibs.net/datasets/kitti/raw_data.php?type=city) 16 | 17 | ## tutorials 18 | 19 | [`Velodyne -> Panoramic Image`](Convert_Velo_2_Pano.ipynb) : Convert Velodyne data(model : HDL-64E) to panoramic image. 20 |

21 | panorama_image
22 |

23 | 24 | [`Velodyne -> Top-View Image`](Convert_Velo_2_Topview.ipynb) : Convert Velodyne data(model : HDL-64E) to Top-view image. 25 |

26 | topview_image
27 |

28 | 29 | [`Velodyne to Image Projection`](velo2cam_projection.ipynb) : Project Velodyne points(model : HDL-64E) to camera Image. 30 |

31 | projection_image
32 |

33 | 34 | [`Display 3D Tracklet`](display_groundtruth.ipynb) : Display 3D Tracklet on image 35 |

36 | tracklet_image
37 |

38 | 39 | 40 | 41 | ## Contributions / Comments 42 | always welcome any kind of comments and pull-requests -------------------------------------------------------------------------------- /images/pano.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windowsub0406/KITTI_Tutorial/a53c99b7ba301b78f125ad075de115803b7f43a6/images/pano.jpg -------------------------------------------------------------------------------- /images/projection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windowsub0406/KITTI_Tutorial/a53c99b7ba301b78f125ad075de115803b7f43a6/images/projection.jpg -------------------------------------------------------------------------------- /images/topview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windowsub0406/KITTI_Tutorial/a53c99b7ba301b78f125ad075de115803b7f43a6/images/topview.jpg -------------------------------------------------------------------------------- /images/tracklet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/windowsub0406/KITTI_Tutorial/a53c99b7ba301b78f125ad075de115803b7f43a6/images/tracklet.jpg -------------------------------------------------------------------------------- /kitti_foundation.py: -------------------------------------------------------------------------------- 1 | """ 2017.07.19 2 | made by changsub Bae 3 | github.com/windowsub0406 4 | """ 5 | import numpy as np 6 | import glob 7 | import cv2 8 | from src import parseTrackletXML as pt_XML 9 | 10 | class Kitti: 11 | 12 | """ 13 | frame : specific frame number or 'all' for whole dataset. default = 'all' 14 | velo_path : velodyne bin file path. default = None 15 | camera_path : left-camera image file path. default = None 16 | img_type : image type info 'gray' or 'color'. default = 'gray' 17 | v2c_path : Velodyne to Camera calibration info file path. default = None 18 | c2c_path : camera to Camera calibration info file path. default = None 19 | xml_path : XML file having tracklet info 20 | """ 21 | def __init__(self, frame='all', velo_path=None, camera_path=None, \ 22 | img_type='gray', v2c_path=None, c2c_path=None, xml_path=None): 23 | self.__frame_type = frame 24 | self.__img_type = img_type 25 | self.__num_frames = None 26 | self.__cur_frame = None 27 | 28 | if velo_path is not None: 29 | self.__velo_path = velo_path 30 | self.__velo_file = self.__load_from_bin() 31 | else: 32 | self.__velo_path, self.__velo_file = None, None 33 | 34 | if camera_path is not None: 35 | self.__camera_path = camera_path 36 | self.__camera_file = self.__load_image() 37 | else: 38 | self.__camera_path, self.__camera_file = None, None 39 | if v2c_path is not None: 40 | self.__v2c_path = v2c_path 41 | self.__v2c_file = self.__load_velo2cam() 42 | else: 43 | self.__v2c_path, self.__v2c_file = None, None 44 | if c2c_path is not None: 45 | self.__c2c_path = c2c_path 46 | self.__c2c_file = self.__load_cam2cam() 47 | else: 48 | self.__c2c_path, self.__c2c_file = None, None 49 | if xml_path is not None: 50 | self.__xml_path = xml_path 51 | self.__tracklet_box, self.__tracklet_type = self.__load_tracklet() 52 | else: 53 | self.__xml_path = None 54 | self.__tracklet_box, self.__tracklet_type = None, None 55 | 56 | @property 57 | def frame_type(self): 58 | return self.__frame_type 59 | 60 | @property 61 | def image_type(self): 62 | return self.__img_type 63 | 64 | @property 65 | def num_frame(self): 66 | return self.__num_frames 67 | 68 | @property 69 | def cur_frame(self): 70 | return self.__cur_frame 71 | 72 | @property 73 | def img_size(self): 74 | return self.__img_size 75 | 76 | @property 77 | def velo_file(self): 78 | return self.__velo_file 79 | 80 | @property 81 | def velo_d_file(self): 82 | x = self.__velo_file[:, 0] 83 | y = self.__velo_file[:, 1] 84 | z = self.__velo_file[:, 2] 85 | d = np.sqrt(x ** 2 + y ** 2 + z ** 2) 86 | return np.hstack((self.__velo_file, d[:, None])) 87 | 88 | @property 89 | def camera_file(self): 90 | return self.__camera_file 91 | 92 | @property 93 | def v2c_file(self): 94 | return self.__v2c_file 95 | 96 | @property 97 | def c2c_file(self): 98 | return self.__c2c_file 99 | 100 | @property 101 | def tracklet_info(self): 102 | return self.__tracklet_box, self.__tracklet_type 103 | 104 | def __get_velo(self, files): 105 | """ Convert bin to numpy array for whole dataset""" 106 | 107 | for i in files.keys(): 108 | points = np.fromfile(files[i], dtype=np.float32).reshape(-1, 4) 109 | self.__velo_file = points[:, :3] 110 | self.__cur_frame = i 111 | yield self.__velo_file 112 | 113 | def __get_velo_frame(self, files): 114 | """ Convert bin to numpy array for one frame """ 115 | points = np.fromfile(files[self.__frame_type], dtype=np.float32).reshape(-1, 4) 116 | return points[:, :3] 117 | 118 | def __get_camera(self, files): 119 | """ Return image for whole dataset """ 120 | 121 | for i in files.keys(): 122 | self.__camera_file = files[i] 123 | self.__cur_frame = i 124 | frame = cv2.imread(self.__camera_file) 125 | if i == 0: 126 | self.__img_size = frame.shape 127 | yield frame 128 | 129 | def __get_camera_frame(self, files): 130 | """ Return image for one frame """ 131 | frame = cv2.imread(files[self.__frame_type]) 132 | self.__img_size = frame.shape 133 | return frame 134 | 135 | def __load_from_bin(self): 136 | """ Return numpy array including velodyne's all 3d x,y,z point cloud """ 137 | 138 | velo_bins = glob.glob(self.__velo_path + '/*.bin') 139 | velo_bins.sort() 140 | self.__num_frames = len(velo_bins) 141 | velo_files = {i: velo_bins[i] for i in range(len(velo_bins))} 142 | 143 | if self.__frame_type in velo_files: 144 | velo_xyz = self.__get_velo_frame(velo_files) 145 | else: 146 | velo_xyz = self.__get_velo(velo_files) 147 | 148 | return velo_xyz 149 | 150 | def __load_image(self): 151 | """ Return camera image """ 152 | 153 | image_path = glob.glob(self.__camera_path + '/*.png') 154 | image_path.sort() 155 | self.__num_frames = len(image_path) 156 | image_files = {i: image_path[i] for i in range(len(image_path))} 157 | 158 | if self.__frame_type in image_files: 159 | image = self.__get_camera_frame(image_files) 160 | else: 161 | image = self.__get_camera(image_files) 162 | 163 | return image 164 | 165 | def __load_velo2cam(self): 166 | """ load Velodyne to Camera calibration info file """ 167 | with open(self.__v2c_path, "r") as f: 168 | file = f.readlines() 169 | return file 170 | 171 | def __load_cam2cam(self): 172 | """ load Camera to Camera calibration info file """ 173 | with open(self.__c2c_path, "r") as f: 174 | file = f.readlines() 175 | return file 176 | 177 | def __load_tracklet(self): 178 | """ extract tracklet's 3d box points and type """ 179 | 180 | # read info from xml file 181 | tracklets = pt_XML.parseXML(self.__xml_path) 182 | 183 | f_tracklet = {} 184 | f_type = {} 185 | 186 | # refered to parseTrackletXML.py's example function 187 | # loop over tracklets 188 | for tracklet in tracklets: 189 | 190 | # this part is inspired by kitti object development kit matlab code: computeBox3D 191 | h, w, l = tracklet.size 192 | trackletBox = np.array([ # in velodyne coordinates around zero point and without orientation yet\ 193 | [-l / 2, -l / 2, l / 2, l / 2, -l / 2, -l / 2, l / 2, l / 2], \ 194 | [w / 2, -w / 2, -w / 2, w / 2, w / 2, -w / 2, -w / 2, w / 2], \ 195 | [0.0, 0.0, 0.0, 0.0, h, h, h, h]]) 196 | 197 | # loop over all data in tracklet 198 | for translation, rotation, state, occlusion, truncation, amtOcclusion, amtBorders, absoluteFrameNumber in tracklet: 199 | 200 | # determine if object is in the image; otherwise continue 201 | if truncation not in (pt_XML.TRUNC_IN_IMAGE, pt_XML.TRUNC_TRUNCATED): 202 | continue 203 | 204 | # re-create 3D bounding box in velodyne coordinate system 205 | yaw = rotation[2] # other rotations are 0 in all xml files I checked 206 | assert np.abs(rotation[:2]).sum() == 0, 'object rotations other than yaw given!' 207 | rotMat = np.array([ \ 208 | [np.cos(yaw), -np.sin(yaw), 0.0], \ 209 | [np.sin(yaw), np.cos(yaw), 0.0], \ 210 | [0.0, 0.0, 1.0]]) 211 | 212 | cornerPosInVelo = np.dot(rotMat, trackletBox) + np.tile(translation, (8, 1)).T 213 | 214 | if absoluteFrameNumber in f_tracklet: 215 | f_tracklet[absoluteFrameNumber] += [cornerPosInVelo] 216 | f_type[absoluteFrameNumber] += [tracklet.objectType] 217 | else: 218 | f_tracklet[absoluteFrameNumber] = [cornerPosInVelo] 219 | f_type[absoluteFrameNumber] = [tracklet.objectType] 220 | 221 | # fill none in non object frame 222 | if self.num_frame is not None: 223 | for i in range(self.num_frame): 224 | if i not in f_tracklet: 225 | f_tracklet[i] = None 226 | f_type[i] = None 227 | 228 | return f_tracklet, f_type 229 | 230 | def __del__(self): 231 | pass 232 | 233 | class Kitti_util(Kitti): 234 | 235 | def __init__(self, frame='all', velo_path=None, camera_path=None, \ 236 | img_type='gray', v2c_path=None, c2c_path=None, xml_path=None): 237 | super(Kitti_util, self).__init__(frame, velo_path, camera_path, img_type, v2c_path, c2c_path, xml_path) 238 | self.__h_min, self.__h_max = -180, 180 239 | self.__v_min, self.__v_max = -24.9, 2.0 240 | self.__v_res, self.__h_res = 0.42, 0.35 241 | self.__x, self.__y, self.__z, self.__d = None, None, None, None 242 | self.__h_fov, self.__v_fov = None, None 243 | self.__x_range, self.__y_range, self.__z_range = None, None, None 244 | self.__get_sur_size, self.__get_top_size = None, None 245 | 246 | @property 247 | def surround_size(self): 248 | return self.__get_sur_size 249 | 250 | @property 251 | def topview_size(self): 252 | return self.__get_top_size 253 | 254 | def __calib_velo2cam(self): 255 | """ 256 | get Rotation(R : 3x3), Translation(T : 3x1) matrix info 257 | using R,T matrix, we can convert velodyne coordinates to camera coordinates 258 | """ 259 | if self.v2c_file is None: 260 | raise NameError("calib_velo_to_cam file isn't loaded.") 261 | 262 | for line in self.v2c_file: 263 | (key, val) = line.split(':', 1) 264 | if key == 'R': 265 | R = np.fromstring(val, sep=' ') 266 | R = R.reshape(3, 3) 267 | if key == 'T': 268 | T = np.fromstring(val, sep=' ') 269 | T = T.reshape(3, 1) 270 | return R, T 271 | 272 | def __calib_cam2cam(self): 273 | """ 274 | If your image is 'rectified image' : 275 | get only Projection(P : 3x4) matrix is enough 276 | but if your image is 'distorted image'(not rectified image) : 277 | you need undistortion step using distortion coefficients(5 : D) 278 | 279 | In this code, only P matrix info is used for rectified image 280 | """ 281 | if self.c2c_file is None: 282 | raise NameError("calib_velo_to_cam file isn't loaded.") 283 | 284 | mode = '00' if self.image_type == 'gray' else '02' 285 | 286 | for line in self.c2c_file: 287 | (key, val) = line.split(':', 1) 288 | if key == ('P_rect_' + mode): 289 | P_ = np.fromstring(val, sep=' ') 290 | P_ = P_.reshape(3, 4) 291 | # erase 4th column ([0,0,0]) 292 | P_ = P_[:3, :3] 293 | return P_ 294 | 295 | def __upload_points(self, points): 296 | self.__x = points[:, 0] 297 | self.__y = points[:, 1] 298 | self.__z = points[:, 2] 299 | self.__d = np.sqrt(self.__x ** 2 + self.__y ** 2 + self.__z ** 2) 300 | 301 | def __point_matrix(self, points): 302 | """ extract points corresponding to FOV setting """ 303 | 304 | # filter in range points based on fov, x,y,z range setting 305 | self.__points_filter(points) 306 | 307 | # Stack arrays in sequence horizontally 308 | xyz_ = np.hstack((self.__x[:, None], self.__y[:, None], self.__z[:, None])) 309 | xyz_ = xyz_.T 310 | 311 | # stack (1,n) arrays filled with the number 1 312 | one_mat = np.full((1, xyz_.shape[1]), 1) 313 | xyz_ = np.concatenate((xyz_, one_mat), axis=0) 314 | 315 | # need dist info for points color 316 | color = self.__normalize_data(self.__d, min=1, max=70, scale=120, clip=True) 317 | 318 | return xyz_, color 319 | 320 | def __normalize_data(self, val, min, max, scale, depth=False, clip=False): 321 | """ Return normalized data """ 322 | if clip: 323 | # limit the values in an array 324 | np.clip(val, min, max, out=val) 325 | if depth: 326 | """ 327 | print 'normalized depth value' 328 | normalize values to (0 - scale) & close distance value has high value. (similar to stereo vision's disparity map) 329 | """ 330 | return (((max - val) / (max - min)) * scale).astype(np.uint8) 331 | else: 332 | """ 333 | print 'normalized value' 334 | normalize values to (0 - scale) & close distance value has low value. 335 | """ 336 | return (((val - min) / (max - min)) * scale).astype(np.uint8) 337 | 338 | def __hv_in_range(self, m, n, fov, fov_type='h'): 339 | """ extract filtered in-range velodyne coordinates based on azimuth & elevation angle limit 340 | horizontal limit = azimuth angle limit 341 | vertical limit = elevation angle limit 342 | """ 343 | 344 | if fov_type == 'h': 345 | return np.logical_and(np.arctan2(n, m) > (-fov[1] * np.pi / 180), \ 346 | np.arctan2(n, m) < (-fov[0] * np.pi / 180)) 347 | elif fov_type == 'v': 348 | return np.logical_and(np.arctan2(n, m) < (fov[1] * np.pi / 180), \ 349 | np.arctan2(n, m) > (fov[0] * np.pi / 180)) 350 | else: 351 | raise NameError("fov type must be set between 'h' and 'v' ") 352 | 353 | def __3d_in_range(self, points): 354 | """ extract filtered in-range velodyne coordinates based on x,y,z limit """ 355 | return points[np.logical_and.reduce((self.__x > self.__x_range[0], self.__x < self.__x_range[1], \ 356 | self.__y > self.__y_range[0], self.__y < self.__y_range[1], \ 357 | self.__z > self.__z_range[0], self.__z < self.__z_range[1]))] 358 | 359 | def __points_filter(self, points): 360 | """ 361 | filter points based on h,v FOV and x,y,z distance range. 362 | x,y,z direction is based on velodyne coordinates 363 | 1. azimuth & elevation angle limit check 364 | 2. x,y,z distance limit 365 | """ 366 | 367 | # upload current points 368 | self.__upload_points(points) 369 | 370 | x, y, z = points[:, 0], points[:, 1], points[:, 2] 371 | d = np.sqrt(x ** 2 + y ** 2 + z ** 2) 372 | 373 | if self.__h_fov is not None and self.__v_fov is not None: 374 | if self.__h_fov[1] == self.__h_max and self.__h_fov[0] == self.__h_min and \ 375 | self.__v_fov[1] == self.__v_max and self.__v_fov[0] == self.__v_min: 376 | pass 377 | elif self.__h_fov[1] == self.__h_max and self.__h_fov[0] == self.__h_min: 378 | con = self.__hv_in_range(d, z, self.__v_fov, fov_type='v') 379 | lim_x, lim_y, lim_z, lim_d = self.__x[con], self.__y[con], self.__z[con], self.__d[con] 380 | self.__x, self.__y, self.__z, self.__d = lim_x, lim_y, lim_z, lim_d 381 | elif self.__v_fov[1] == self.__v_max and self.__v_fov[0] == self.__v_min: 382 | con = self.__hv_in_range(x, y, self.__h_fov, fov_type='h') 383 | lim_x, lim_y, lim_z, lim_d = self.__x[con], self.__y[con], self.__z[con], self.__d[con] 384 | self.__x, self.__y, self.__z, self.__d = lim_x, lim_y, lim_z, lim_d 385 | else: 386 | h_points = self.__hv_in_range(x, y, self.__h_fov, fov_type='h') 387 | v_points = self.__hv_in_range(d, z, self.__v_fov, fov_type='v') 388 | con = np.logical_and(h_points, v_points) 389 | lim_x, lim_y, lim_z, lim_d = self.__x[con], self.__y[con], self.__z[con], self.__d[con] 390 | self.__x, self.__y, self.__z, self.__d = lim_x, lim_y, lim_z, lim_d 391 | else: 392 | pass 393 | 394 | if self.__x_range is None and self.__y_range is None and self.__z_range is None: 395 | pass 396 | elif self.__x_range is not None and self.__y_range is not None and self.__z_range is not None: 397 | # extract in-range points 398 | temp_x, temp_y = self.__3d_in_range(self.__x), self.__3d_in_range(self.__y) 399 | temp_z, temp_d = self.__3d_in_range(self.__z), self.__3d_in_range(self.__d) 400 | self.__x, self.__y, self.__z, self.__d = temp_x, temp_y, temp_z, temp_d 401 | else: 402 | raise ValueError("Please input x,y,z's min, max range(m) based on velodyne coordinates. ") 403 | 404 | def __surround_view(self, points, depth): 405 | """ convert coordinates for panoramic image """ 406 | 407 | # upload current points 408 | self.__points_filter(points) 409 | # project point cloud to 2D point map 410 | x_img = np.arctan2(-self.__y, self.__x) / (self.__h_res * (np.pi / 180)) 411 | y_img = -(np.arctan2(self.__z, self.__d) / (self.__v_res * (np.pi / 180))) 412 | # filter in range points based on fov, x,y,z range setting 413 | 414 | x_size = int(np.ceil((self.__h_fov[1] - self.__h_fov[0]) / self.__h_res)) 415 | y_size = int(np.ceil((self.__v_fov[1] - self.__v_fov[0]) / self.__v_res)) 416 | self.__get_sur_size = (x_size + 1, y_size + 1) 417 | 418 | # shift negative points to positive points (shift minimum value to 0) 419 | x_offset = self.__h_fov[0] / self.__h_res 420 | x_img = np.trunc(x_img - x_offset).astype(np.int32) 421 | y_offset = self.__v_fov[1] / self.__v_res 422 | y_fine_tune = 1 423 | y_img = np.trunc(y_img + y_offset + y_fine_tune).astype(np.int32) 424 | dist = self.__normalize_data(self.__d, min=0, max=120, scale=255, depth=depth) 425 | 426 | # array to img 427 | img = np.zeros([y_size + 1, x_size + 1], dtype=np.uint8) 428 | img[y_img, x_img] = dist 429 | return img 430 | 431 | def __topview(self, points, scale): 432 | """ convert coordinates for top-view (bird's eye view) image """ 433 | 434 | # filter in range points based on fov, x,y,z range setting 435 | self.__points_filter(points) 436 | 437 | x_size = int(np.ceil(self.__y_range[1] - self.__y_range[0])) 438 | y_size = int(np.ceil(self.__x_range[1] - self.__x_range[0])) 439 | self.__get_sur_size = (x_size * scale + 1, y_size * scale + 1) 440 | 441 | # convert 3D lidar coordinates(vehicle coordinates) to 2D image coordinates 442 | # Velodyne coordinates info : http://www.cvlibs.net/publications/Geiger2013IJRR.pdf 443 | # scale - for high resolution 444 | x_img = -(self.__y * scale).astype(np.int32) 445 | y_img = -(self.__x * scale).astype(np.int32) 446 | 447 | # shift negative points to positive points (shift minimum value to 0) 448 | x_img += int(np.trunc(self.__y_range[1] * scale)) 449 | y_img += int(np.trunc(self.__x_range[1] * scale)) 450 | 451 | # normalize distance value & convert to depth map 452 | max_dist = np.sqrt((max(self.__x_range) ** 2) + (max(self.__y_range) ** 2)) 453 | dist_lim = self.__normalize_data(self.__d, min=0, max=max_dist, scale=255, depth=True) 454 | # array to img 455 | img = np.zeros([y_size * scale + 1, x_size * scale + 1], dtype=np.uint8) 456 | img[y_img, x_img] = dist_lim 457 | return img 458 | 459 | def __velo_2_img_projection(self, points): 460 | """ convert velodyne coordinates to camera image coordinates """ 461 | 462 | # rough velodyne azimuth range corresponding to camera horizontal fov 463 | if self.__h_fov is None: 464 | self.__h_fov = (-50, 50) 465 | if self.__h_fov[0] < -50: 466 | self.__h_fov = (-50,) + self.__h_fov[1:] 467 | if self.__h_fov[1] > 50: 468 | self.__h_fov = self.__h_fov[:1] + (50,) 469 | 470 | # R_vc = Rotation matrix ( velodyne -> camera ) 471 | # T_vc = Translation matrix ( velodyne -> camera ) 472 | R_vc, T_vc = self.__calib_velo2cam() 473 | 474 | # P_ = Projection matrix ( camera coordinates 3d points -> image plane 2d points ) 475 | P_ = self.__calib_cam2cam() 476 | 477 | """ 478 | xyz_v - 3D velodyne points corresponding to h, v FOV limit in the velodyne coordinates 479 | c_ - color value(HSV's Hue vaule) corresponding to distance(m) 480 | 481 | [x_1 , x_2 , .. ] 482 | xyz_v = [y_1 , y_2 , .. ] 483 | [z_1 , z_2 , .. ] 484 | [ 1 , 1 , .. ] 485 | """ 486 | xyz_v, c_ = self.__point_matrix(points) 487 | 488 | """ 489 | RT_ - rotation matrix & translation matrix 490 | ( velodyne coordinates -> camera coordinates ) 491 | 492 | [r_11 , r_12 , r_13 , t_x ] 493 | RT_ = [r_21 , r_22 , r_23 , t_y ] 494 | [r_31 , r_32 , r_33 , t_z ] 495 | """ 496 | RT_ = np.concatenate((R_vc, T_vc), axis=1) 497 | 498 | # convert velodyne coordinates(X_v, Y_v, Z_v) to camera coordinates(X_c, Y_c, Z_c) 499 | for i in range(xyz_v.shape[1]): 500 | xyz_v[:3, i] = np.matmul(RT_, xyz_v[:, i]) 501 | 502 | """ 503 | xyz_c - 3D velodyne points corresponding to h, v FOV in the camera coordinates 504 | [x_1 , x_2 , .. ] 505 | xyz_c = [y_1 , y_2 , .. ] 506 | [z_1 , z_2 , .. ] 507 | """ 508 | xyz_c = np.delete(xyz_v, 3, axis=0) 509 | 510 | # convert camera coordinates(X_c, Y_c, Z_c) image(pixel) coordinates(x,y) 511 | for i in range(xyz_c.shape[1]): 512 | xyz_c[:, i] = np.matmul(P_, xyz_c[:, i]) 513 | 514 | """ 515 | xy_i - 3D velodyne points corresponding to h, v FOV in the image(pixel) coordinates before scale adjustment 516 | ans - 3D velodyne points corresponding to h, v FOV in the image(pixel) coordinates 517 | [s_1*x_1 , s_2*x_2 , .. ] 518 | xy_i = [s_1*y_1 , s_2*y_2 , .. ] ans = [x_1 , x_2 , .. ] 519 | [ s_1 , s_2 , .. ] [y_1 , y_2 , .. ] 520 | """ 521 | xy_i = xyz_c[::] / xyz_c[::][2] 522 | ans = np.delete(xy_i, 2, axis=0) 523 | 524 | return ans, c_ 525 | 526 | def velo_2_pano(self, h_fov=None, v_fov=None, x_range=None, y_range=None, z_range=None, depth=False): 527 | """ panoramic image for whole velo dataset """ 528 | 529 | self.__v_fov, self.__h_fov = v_fov, h_fov 530 | self.__x_range, self.__y_range, self.__z_range = x_range, y_range, z_range 531 | 532 | velo_gen = self.velo_file 533 | if velo_gen is None: 534 | raise ValueError("Velo data is not included in this class") 535 | for points in velo_gen: 536 | res = self.__surround_view(points, depth) 537 | yield res 538 | 539 | def velo_2_pano_frame(self, h_fov=None, v_fov=None, x_range=None, y_range=None, z_range=None, depth=False): 540 | """ panoramic image for one frame """ 541 | 542 | self.__v_fov, self.__h_fov = v_fov, h_fov 543 | self.__x_range, self.__y_range, self.__z_range = x_range, y_range, z_range 544 | 545 | velo_gen = self.velo_file 546 | if velo_gen is None: 547 | raise ValueError("Velo data is not included in this class") 548 | res = self.__surround_view(velo_gen, depth) 549 | return res 550 | 551 | def velo_2_topview(self, h_fov=None, v_fov=None, x_range=None, y_range=None, z_range=None, scale=10): 552 | """ Top-view(Bird's eye view) image for whole velo dataset """ 553 | 554 | self.__v_fov, self.__h_fov = v_fov, h_fov 555 | self.__x_range, self.__y_range, self.__z_range = x_range, y_range, z_range 556 | 557 | if scale <= 0: 558 | raise ValueError("scale value must be positive. default value is 10.") 559 | elif float(scale).is_integer() is False: 560 | scale = round(scale) 561 | 562 | velo_gen = self.velo_file 563 | if velo_gen is None: 564 | raise ValueError("Velo data is not included in this class") 565 | for points in velo_gen: 566 | res = self.__topview(points, scale) 567 | yield res 568 | 569 | def velo_2_topview_frame(self, h_fov=None, v_fov=None, x_range=None, y_range=None, z_range=None, scale=10): 570 | """ Top-view(Bird's eye view) image for one frame """ 571 | self.__v_fov, self.__h_fov = v_fov, h_fov 572 | self.__x_range, self.__y_range, self.__z_range = x_range, y_range, z_range 573 | 574 | if scale <= 0: 575 | raise ValueError("scale value must be positive. default value is 10.") 576 | elif float(scale).is_integer() is False: 577 | scale = round(scale) 578 | 579 | velo_gen = self.velo_file 580 | if velo_gen is None: 581 | raise ValueError("Velo data is not included in this class") 582 | res = self.__topview(velo_gen, scale) 583 | return res 584 | 585 | def velo_projection(self, h_fov=None, v_fov=None, x_range=None, y_range=None, z_range=None): 586 | """ print velodyne 3D points corresponding to camera 2D image """ 587 | 588 | self.__v_fov, self.__h_fov = v_fov, h_fov 589 | self.__x_range, self.__y_range, self.__z_range = x_range, y_range, z_range 590 | velo_gen, cam_gen = self.velo_file, self.camera_file 591 | 592 | if velo_gen is None: 593 | raise ValueError("Velo data is not included in this class") 594 | if cam_gen is None: 595 | raise ValueError("Cam data is not included in this class") 596 | for frame, points in zip(cam_gen, velo_gen): 597 | res, c_ = self.__velo_2_img_projection(points) 598 | yield [frame, res, c_] 599 | 600 | def velo_projection_frame(self, h_fov=None, v_fov=None, x_range=None, y_range=None, z_range=None): 601 | """ print velodyne 3D points corresponding to camera 2D image """ 602 | 603 | self.__v_fov, self.__h_fov = v_fov, h_fov 604 | self.__x_range, self.__y_range, self.__z_range = x_range, y_range, z_range 605 | velo_gen, cam_gen = self.velo_file, self.camera_file 606 | 607 | if velo_gen is None: 608 | raise ValueError("Velo data is not included in this class") 609 | if cam_gen is None: 610 | raise ValueError("Cam data is not included in this class") 611 | res, c_ = self.__velo_2_img_projection(velo_gen) 612 | return cam_gen, res, c_ 613 | 614 | def __del__(self): 615 | pass 616 | 617 | def print_projection_cv2(points, color, image): 618 | """ project converted velodyne points into camera image """ 619 | hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) 620 | 621 | for i in range(points.shape[1]): 622 | cv2.circle(hsv_image, (np.int32(points[0][i]), np.int32(points[1][i])), 2, (np.int(color[i]), 255, 255), -1) 623 | 624 | return cv2.cvtColor(hsv_image, cv2.COLOR_HSV2BGR) 625 | 626 | def print_projection_plt(points, color, image): 627 | """ project converted velodyne points into camera image """ 628 | hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) 629 | 630 | for i in range(points.shape[1]): 631 | cv2.circle(hsv_image, (int(points[0][i]), int(points[1][i])), 2, (int(color[i]), 255, 255), -1) 632 | 633 | return cv2.cvtColor(hsv_image, cv2.COLOR_HSV2RGB) 634 | 635 | def pano_example1(): 636 | """ save one frame image about velodyne dataset converted to panoramic image """ 637 | velo_path = './velodyne_points/data' 638 | v_fov, h_fov = (-10.5, 2.0), (-60, 80) 639 | velo = Kitti_util(frame=89, velo_path=velo_path) 640 | 641 | frame = velo.velo_2_pano_frame(h_fov, v_fov, depth=False) 642 | 643 | cv2.imshow('panoramic result', frame) 644 | cv2.waitKey(0) 645 | 646 | def pano_example2(): 647 | """ save video about velodyne dataset converted to panoramic image """ 648 | velo_path = './velodyne_points/data' 649 | v_fov, h_fov = (-24.9, 2.0), (-180, 160) 650 | 651 | velo2 = Kitti_util(frame='all', velo_path=velo_path) 652 | pano = velo2.velo_2_pano(h_fov, v_fov, depth=False) 653 | 654 | velo = Kitti_util(frame=0, velo_path=velo_path) 655 | velo.velo_2_pano_frame(h_fov, v_fov, depth=False) 656 | size = velo.surround_size 657 | 658 | fourcc = cv2.VideoWriter_fourcc(*'XVID') 659 | vid = cv2.VideoWriter('pano_result.avi', fourcc, 25.0, size, False) 660 | 661 | for frame in pano: 662 | vid.write(frame) 663 | 664 | print('video saved') 665 | vid.release() 666 | 667 | def topview_example1(): 668 | """ save one frame image about velodyne dataset converted to topview image """ 669 | velo_path = './velodyne_points/data' 670 | x_range, y_range, z_range = (-15, 15), (-10, 10), (-2, 2) 671 | velo = Kitti_util(frame=89, velo_path=velo_path) 672 | 673 | frame = velo.velo_2_topview_frame(x_range=x_range, y_range=y_range, z_range=z_range) 674 | 675 | cv2.imshow('panoramic result', frame) 676 | cv2.waitKey(0) 677 | 678 | def topview_example2(): 679 | """ save video about velodyne dataset converted to topview image """ 680 | velo_path = './velodyne_points/data' 681 | x_range, y_range, z_range, scale = (-20, 20), (-20, 20), (-2, 2), 10 682 | size = (int((max(y_range) - min(y_range)) * scale), int((max(x_range) - min(x_range)) * scale)) 683 | 684 | velo2 = Kitti_util(frame='all', velo_path=velo_path) 685 | topview = velo2.velo_2_topview(x_range=x_range, y_range=y_range, z_range=z_range, scale=scale) 686 | 687 | fourcc = cv2.VideoWriter_fourcc(*'XVID') 688 | vid = cv2.VideoWriter('topview_result.avi', fourcc, 25.0, size, False) 689 | 690 | for frame in topview: 691 | vid.write(frame) 692 | 693 | print('video saved') 694 | vid.release() 695 | 696 | def projection_example1(): 697 | """ save one frame about projecting velodyne points into camera image """ 698 | image_type = 'gray' # 'gray' or 'color' image 699 | mode = '00' if image_type == 'gray' else '02' # image_00 = 'graye image' , image_02 = 'color image' 700 | 701 | image_path = 'image_' + mode + '/data' 702 | velo_path = './velodyne_points/data' 703 | 704 | v_fov, h_fov = (-24.9, 2.0), (-90, 90) 705 | 706 | v2c_filepath = './calib_velo_to_cam.txt' 707 | c2c_filepath = './calib_cam_to_cam.txt' 708 | 709 | res = Kitti_util(frame=89, camera_path=image_path, velo_path=velo_path, \ 710 | v2c_path=v2c_filepath, c2c_path=c2c_filepath) 711 | 712 | img, pnt, c_ = res.velo_projection_frame(v_fov=v_fov, h_fov=h_fov) 713 | 714 | result = print_projection_cv2(pnt, c_, img) 715 | 716 | cv2.imshow('projection result', result) 717 | cv2.waitKey(0) 718 | 719 | def projection_example2(): 720 | """ save video about projecting velodyne points into camera image """ 721 | image_type = 'gray' # 'gray' or 'color' image 722 | mode = '00' if image_type == 'gray' else '02' # image_00 = 'graye image' , image_02 = 'color image' 723 | 724 | image_path = 'image_' + mode + '/data' 725 | 726 | velo_path = './velodyne_points/data' 727 | v_fov, h_fov = (-24.9, 2.0), (-90, 90) 728 | 729 | v2c_filepath = './calib_velo_to_cam.txt' 730 | c2c_filepath = './calib_cam_to_cam.txt' 731 | 732 | temp = Kitti(frame=0, camera_path=image_path) 733 | img = temp.camera_file 734 | size = (img.shape[1], img.shape[0]) 735 | 736 | """ save result video """ 737 | fourcc = cv2.VideoWriter_fourcc(*'XVID') 738 | vid = cv2.VideoWriter('projection_result.avi', fourcc, 25.0, size) 739 | test = Kitti_util(frame='all', camera_path=image_path, velo_path=velo_path, \ 740 | v2c_path=v2c_filepath, c2c_path=c2c_filepath) 741 | 742 | res = test.velo_projection(v_fov=v_fov, h_fov=h_fov) 743 | 744 | for frame, point, cc in res: 745 | image = print_projection_cv2(point, cc, frame) 746 | vid.write(image) 747 | 748 | print('video saved') 749 | vid.release() 750 | 751 | def xml_example(): 752 | 753 | xml_path = "./tracklet_labels.xml" 754 | xml_check = Kitti_util(xml_path=xml_path) 755 | 756 | tracklet_, type_ = xml_check.tracklet_info 757 | print(tracklet_[0]) 758 | 759 | if __name__ == "__main__": 760 | 761 | # pano_example1() 762 | # pano_example2() 763 | topview_example1() 764 | # topview_example2() 765 | # projection_example1() 766 | # projection_example2() 767 | 768 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/parseTrackletXML.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | parse XML files containing tracklet info for kitti data base (raw data section) 4 | (http://cvlibs.net/datasets/kitti/raw_data.php) 5 | 6 | No guarantees that this code is correct, usage is at your own risk! 7 | 8 | created by Christian Herdtweck, Max Planck Institute for Biological Cybernetics 9 | (christian.herdtweck@tuebingen.mpg.de) 10 | 11 | requires numpy! 12 | 13 | example usage: 14 | import parseTrackletXML as xmlParser 15 | kittiDir = '/path/to/kitti/data' 16 | drive = '2011_09_26_drive_0001' 17 | xmlParser.example(kittiDir, drive) 18 | or simply on command line: 19 | python parseTrackletXML.py 20 | """ 21 | 22 | # Version History: 23 | # 4/7/12 Christian Herdtweck: seems to work with a few random test xml tracklet files; 24 | # converts file contents to ElementTree and then to list of Tracklet objects; 25 | # Tracklet objects have str and iter functions 26 | # 5/7/12 ch: added constants for state, occlusion, truncation and added consistency checks 27 | # 30/1/14 ch: create example function from example code 28 | 29 | from sys import argv as cmdLineArgs 30 | from xml.etree.ElementTree import ElementTree 31 | import numpy as np 32 | import itertools 33 | from warnings import warn 34 | 35 | STATE_UNSET = 0 36 | STATE_INTERP = 1 37 | STATE_LABELED = 2 38 | stateFromText = {'0':STATE_UNSET, '1':STATE_INTERP, '2':STATE_LABELED} 39 | 40 | OCC_UNSET = 255 # -1 as uint8 41 | OCC_VISIBLE = 0 42 | OCC_PARTLY = 1 43 | OCC_FULLY = 2 44 | occFromText = {'-1':OCC_UNSET, '0':OCC_VISIBLE, '1':OCC_PARTLY, '2':OCC_FULLY} 45 | 46 | TRUNC_UNSET = 255 # -1 as uint8, but in xml files the value '99' is used! 47 | TRUNC_IN_IMAGE = 0 48 | TRUNC_TRUNCATED = 1 49 | TRUNC_OUT_IMAGE = 2 50 | TRUNC_BEHIND_IMAGE = 3 51 | truncFromText = {'99':TRUNC_UNSET, '0':TRUNC_IN_IMAGE, '1':TRUNC_TRUNCATED, \ 52 | '2':TRUNC_OUT_IMAGE, '3': TRUNC_BEHIND_IMAGE} 53 | 54 | 55 | class Tracklet(object): 56 | r""" representation an annotated object track 57 | 58 | Tracklets are created in function parseXML and can most conveniently used as follows: 59 | 60 | for trackletObj in parseXML(trackletFile): 61 | for translation, rotation, state, occlusion, truncation, amtOcclusion, amtBorders, absoluteFrameNumber in trackletObj: 62 | ... your code here ... 63 | #end: for all frames 64 | #end: for all tracklets 65 | 66 | absoluteFrameNumber is in range [firstFrame, firstFrame+nFrames[ 67 | amtOcclusion and amtBorders could be None 68 | 69 | You can of course also directly access the fields objType (string), size (len-3 ndarray), firstFrame/nFrames (int), 70 | trans/rots (nFrames x 3 float ndarrays), states/truncs (len-nFrames uint8 ndarrays), occs (nFrames x 2 uint8 ndarray), 71 | and for some tracklets amtOccs (nFrames x 2 float ndarray) and amtBorders (nFrames x 3 float ndarray). The last two 72 | can be None if the xml file did not include these fields in poses 73 | """ 74 | 75 | objectType = None 76 | size = None # len-3 float array: (height, width, length) 77 | firstFrame = None 78 | trans = None # n x 3 float array (x,y,z) 79 | rots = None # n x 3 float array (x,y,z) 80 | states = None # len-n uint8 array of states 81 | occs = None # n x 2 uint8 array (occlusion, occlusion_kf) 82 | truncs = None # len-n uint8 array of truncation 83 | amtOccs = None # None or (n x 2) float array (amt_occlusion, amt_occlusion_kf) 84 | amtBorders = None # None (n x 3) float array (amt_border_l / _r / _kf) 85 | nFrames = None 86 | 87 | def __init__(self): 88 | r""" create Tracklet with no info set """ 89 | self.size = np.nan*np.ones(3, dtype=float) 90 | 91 | def __str__(self): 92 | r""" return human-readable string representation of tracklet object 93 | 94 | called implicitly in 95 | print trackletObj 96 | or in 97 | text = str(trackletObj) 98 | """ 99 | return '[Tracklet over {0} frames for {1}]'.format(self.nFrames, self.objectType) 100 | 101 | def __iter__(self): 102 | r""" returns an iterator that yields tuple of all the available data for each frame 103 | 104 | called whenever code iterates over a tracklet object, e.g. in 105 | for translation, rotation, state, occlusion, truncation, amtOcclusion, amtBorders, absoluteFrameNumber in trackletObj: 106 | ...do something ... 107 | or 108 | trackDataIter = iter(trackletObj) 109 | """ 110 | if self.amtOccs is None: 111 | return itertools.izip(self.trans, self.rots, self.states, self.occs, self.truncs, \ 112 | itertools.repeat(None), itertools.repeat(None), range(self.firstFrame, self.firstFrame+self.nFrames)) 113 | else: 114 | return itertools.izip(self.trans, self.rots, self.states, self.occs, self.truncs, \ 115 | self.amtOccs, self.amtBorders, range(self.firstFrame, self.firstFrame+self.nFrames)) 116 | #end: class Tracklet 117 | 118 | 119 | def parseXML(trackletFile): 120 | r""" parse tracklet xml file and convert results to list of Tracklet objects 121 | 122 | :param trackletFile: name of a tracklet xml file 123 | :returns: list of Tracklet objects read from xml file 124 | """ 125 | 126 | # convert tracklet XML data to a tree structure 127 | eTree = ElementTree() 128 | print ('parsing tracklet file', trackletFile) 129 | with open(trackletFile) as f: 130 | eTree.parse(f) 131 | 132 | # now convert output to list of Tracklet objects 133 | trackletsElem = eTree.find('tracklets') 134 | tracklets = [] 135 | trackletIdx = 0 136 | nTracklets = None 137 | for trackletElem in trackletsElem: 138 | #print 'track:', trackletElem.tag 139 | if trackletElem.tag == 'count': 140 | nTracklets = int(trackletElem.text) 141 | print ('file contains', nTracklets, 'tracklets') 142 | elif trackletElem.tag == 'item_version': 143 | pass 144 | elif trackletElem.tag == 'item': 145 | #print 'tracklet {0} of {1}'.format(trackletIdx, nTracklets) 146 | # a tracklet 147 | newTrack = Tracklet() 148 | isFinished = False 149 | hasAmt = False 150 | frameIdx = None 151 | for info in trackletElem: 152 | #print 'trackInfo:', info.tag 153 | if isFinished: 154 | raise ValueError('more info on element after finished!') 155 | if info.tag == 'objectType': 156 | newTrack.objectType = info.text 157 | elif info.tag == 'h': 158 | newTrack.size[0] = float(info.text) 159 | elif info.tag == 'w': 160 | newTrack.size[1] = float(info.text) 161 | elif info.tag == 'l': 162 | newTrack.size[2] = float(info.text) 163 | elif info.tag == 'first_frame': 164 | newTrack.firstFrame = int(info.text) 165 | elif info.tag == 'poses': 166 | # this info is the possibly long list of poses 167 | for pose in info: 168 | #print 'trackInfoPose:', pose.tag 169 | if pose.tag == 'count': # this should come before the others 170 | if newTrack.nFrames is not None: 171 | raise ValueError('there are several pose lists for a single track!') 172 | elif frameIdx is not None: 173 | raise ValueError('?!') 174 | newTrack.nFrames = int(pose.text) 175 | newTrack.trans = np.nan * np.ones((newTrack.nFrames, 3), dtype=float) 176 | newTrack.rots = np.nan * np.ones((newTrack.nFrames, 3), dtype=float) 177 | newTrack.states = np.nan * np.ones(newTrack.nFrames, dtype='uint8') 178 | newTrack.occs = np.nan * np.ones((newTrack.nFrames, 2), dtype='uint8') 179 | newTrack.truncs = np.nan * np.ones(newTrack.nFrames, dtype='uint8') 180 | newTrack.amtOccs = np.nan * np.ones((newTrack.nFrames, 2), dtype=float) 181 | newTrack.amtBorders = np.nan * np.ones((newTrack.nFrames, 3), dtype=float) 182 | frameIdx = 0 183 | elif pose.tag == 'item_version': 184 | pass 185 | elif pose.tag == 'item': 186 | # pose in one frame 187 | if frameIdx is None: 188 | raise ValueError('pose item came before number of poses!') 189 | for poseInfo in pose: 190 | #print 'trackInfoPoseInfo:', poseInfo.tag 191 | if poseInfo.tag == 'tx': 192 | newTrack.trans[frameIdx, 0] = float(poseInfo.text) 193 | elif poseInfo.tag == 'ty': 194 | newTrack.trans[frameIdx, 1] = float(poseInfo.text) 195 | elif poseInfo.tag == 'tz': 196 | newTrack.trans[frameIdx, 2] = float(poseInfo.text) 197 | elif poseInfo.tag == 'rx': 198 | newTrack.rots[frameIdx, 0] = float(poseInfo.text) 199 | elif poseInfo.tag == 'ry': 200 | newTrack.rots[frameIdx, 1] = float(poseInfo.text) 201 | elif poseInfo.tag == 'rz': 202 | newTrack.rots[frameIdx, 2] = float(poseInfo.text) 203 | elif poseInfo.tag == 'state': 204 | newTrack.states[frameIdx] = stateFromText[poseInfo.text] 205 | elif poseInfo.tag == 'occlusion': 206 | newTrack.occs[frameIdx, 0] = occFromText[poseInfo.text] 207 | elif poseInfo.tag == 'occlusion_kf': 208 | newTrack.occs[frameIdx, 1] = occFromText[poseInfo.text] 209 | elif poseInfo.tag == 'truncation': 210 | newTrack.truncs[frameIdx] = truncFromText[poseInfo.text] 211 | elif poseInfo.tag == 'amt_occlusion': 212 | newTrack.amtOccs[frameIdx,0] = float(poseInfo.text) 213 | hasAmt = True 214 | elif poseInfo.tag == 'amt_occlusion_kf': 215 | newTrack.amtOccs[frameIdx,1] = float(poseInfo.text) 216 | hasAmt = True 217 | elif poseInfo.tag == 'amt_border_l': 218 | newTrack.amtBorders[frameIdx,0] = float(poseInfo.text) 219 | hasAmt = True 220 | elif poseInfo.tag == 'amt_border_r': 221 | newTrack.amtBorders[frameIdx,1] = float(poseInfo.text) 222 | hasAmt = True 223 | elif poseInfo.tag == 'amt_border_kf': 224 | newTrack.amtBorders[frameIdx,2] = float(poseInfo.text) 225 | hasAmt = True 226 | else: 227 | raise ValueError('unexpected tag in poses item: {0}!'.format(poseInfo.tag)) 228 | frameIdx += 1 229 | else: 230 | raise ValueError('unexpected pose info: {0}!'.format(pose.tag)) 231 | elif info.tag == 'finished': 232 | isFinished = True 233 | else: 234 | raise ValueError('unexpected tag in tracklets: {0}!'.format(info.tag)) 235 | #end: for all fields in current tracklet 236 | 237 | # some final consistency checks on new tracklet 238 | if not isFinished: 239 | warn('tracklet {0} was not finished!'.format(trackletIdx)) 240 | if newTrack.nFrames is None: 241 | warn('tracklet {0} contains no information!'.format(trackletIdx)) 242 | elif frameIdx != newTrack.nFrames: 243 | warn('tracklet {0} is supposed to have {1} frames, but perser found {1}!'.format(\ 244 | trackletIdx, newTrack.nFrames, frameIdx)) 245 | if np.abs(newTrack.rots[:,:2]).sum() > 1e-16: 246 | warn('track contains rotation other than yaw!') 247 | 248 | # if amtOccs / amtBorders are not set, set them to None 249 | if not hasAmt: 250 | newTrack.amtOccs = None 251 | newTrack.amtBorders = None 252 | 253 | # add new tracklet to list 254 | tracklets.append(newTrack) 255 | trackletIdx += 1 256 | 257 | else: 258 | raise ValueError('unexpected tracklet info') 259 | #end: for tracklet list items 260 | 261 | print ('loaded', trackletIdx, 'tracklets') 262 | 263 | # final consistency check 264 | if trackletIdx != nTracklets: 265 | warn('according to xml information the file has {0} tracklets, but parser found {1}!'.format(nTracklets, trackletIdx)) 266 | 267 | return tracklets 268 | #end: function parseXML 269 | 270 | 271 | def example(kittiDir=None, drive=None): 272 | 273 | from os.path import join, expanduser 274 | import readline # makes raw_input behave more fancy 275 | # from xmlParser import parseXML, TRUNC_IN_IMAGE, TRUNC_TRUNCATED 276 | 277 | DEFAULT_DRIVE = '2011_09_26_drive_0001' 278 | twoPi = 2.*np.pi 279 | 280 | # get dir names 281 | if kittiDir is None: 282 | kittiDir = expanduser(raw_input('please enter kitti base dir (e.g. ~/path/to/kitti): ').strip()) 283 | if drive is None: 284 | drive = raw_input('please enter drive name (default {0}): '.format(DEFAULT_DRIVE)).strip() 285 | if len(drive) == 0: 286 | drive = DEFAULT_DRIVE 287 | 288 | # read tracklets from file 289 | myTrackletFile = join(kittiDir, drive, 'tracklet_labels.xml') 290 | tracklets = parseXML(myTrackletFile) 291 | 292 | # loop over tracklets 293 | for iTracklet, tracklet in enumerate(tracklets): 294 | print ('tracklet {0: 3d}: {1}'.format(iTracklet, tracklet)) 295 | 296 | # this part is inspired by kitti object development kit matlab code: computeBox3D 297 | h,w,l = tracklet.size 298 | trackletBox = np.array([ # in velodyne coordinates around zero point and without orientation yet\ 299 | [-l/2, -l/2, l/2, l/2, -l/2, -l/2, l/2, l/2], \ 300 | [ w/2, -w/2, -w/2, w/2, w/2, -w/2, -w/2, w/2], \ 301 | [ 0.0, 0.0, 0.0, 0.0, h, h, h, h]]) 302 | 303 | # loop over all data in tracklet 304 | for translation, rotation, state, occlusion, truncation, amtOcclusion, amtBorders, absoluteFrameNumber \ 305 | in tracklet: 306 | 307 | # determine if object is in the image; otherwise continue 308 | if truncation not in (TRUNC_IN_IMAGE, TRUNC_TRUNCATED): 309 | continue 310 | 311 | # re-create 3D bounding box in velodyne coordinate system 312 | yaw = rotation[2] # other rotations are 0 in all xml files I checked 313 | assert np.abs(rotation[:2]).sum() == 0, 'object rotations other than yaw given!' 314 | rotMat = np.array([\ 315 | [np.cos(yaw), -np.sin(yaw), 0.0], \ 316 | [np.sin(yaw), np.cos(yaw), 0.0], \ 317 | [ 0.0, 0.0, 1.0]]) 318 | cornerPosInVelo = np.dot(rotMat, trackletBox) + np.tile(translation, (8,1)).T 319 | 320 | # calc yaw as seen from the camera (i.e. 0 degree = facing away from cam), as opposed to 321 | # car-centered yaw (i.e. 0 degree = same orientation as car). 322 | # makes quite a difference for objects in periphery! 323 | # Result is in [0, 2pi] 324 | x, y, z = translation 325 | yawVisual = ( yaw - np.arctan2(y, x) ) % twoPi 326 | 327 | #end: for all frames in track 328 | #end: for all tracks 329 | #end: function example 330 | 331 | # when somebody runs this file as a script: 332 | # run example if no arg or only 'example' was given as arg 333 | # otherwise run parseXML 334 | if __name__ == "__main__": 335 | # cmdLineArgs[0] is 'parseTrackletXML.py' 336 | if len(cmdLineArgs) < 2: 337 | example() 338 | elif (len(cmdLineArgs) == 2) and (cmdLineArgs[1] == 'example'): 339 | example() 340 | else: 341 | parseXML(*cmdLineArgs[1:]) 342 | 343 | # (created using vim - the world's best text editor) 344 | --------------------------------------------------------------------------------