지금 까지는 좌표 위의 임의의 점을 로봇이라 칭하면서 이동시키는 상상의(?) 로봇 프로그래밍을 하였었다. 위 사진을 보면 알 수 있겠지만, 이번에는 보다 로봇스러운 시스템을 구축해보려고 한다. 이 로봇 모델은 자전거와 비슷한 구조를 가지고 있다. 앞바퀴만 각도를 바꿀 수 있고, 뒷바퀴는 그저 따라오는 형식이다.
뒷바퀴 사이의 중점 좌표(X, Y)
앞바퀴에서 뒷바퀴 사이의 거리(L)
로봇 전체가 보고 있는 각도(Θ)
Θ를 기준으로 기울어진 앞바퀴의 각도()
이렇게 시스템을 구축한다. 다음으로 좀 복잡한 수식이 나오므로 미리 마음의 준비를 하시고,
짜잔... 앞바퀴가 기울어진 상태로 앞으로만 움직인다면, 로봇은 원의 궤도를 그리게 될 것이다. 그래서 그 원의 중심을 구할 수 있고, 움직인 뒤의 로봇의 위치를 앞서 구한 원의 중심을 이용해서 나타낼 수 있다. 사실 슥슥 수식을 쓰는데 내 머리속에서는 ?만 가득했다. 그런데 사실 다음 단원에서 도움을 주고 있다.
앞바퀴가 기울어진 정도가 alpha-steering angle이고 이렇게 앞바퀴만 기울어진 채로 앞으로 나아가면 로봇이 원의 궤도를 그리게 될 것이라고 말했었다. 그런데 앞바퀴와 뒷바퀴 사이의 거리 L이 존재함에 따라 위와 같이 각각 바퀴가 그리는 원은 다른 지름을 가지게 될 것이다.
뒷바퀴 중심의 좌표를 기준으로 한다고 했었다. 그래서 우리가 구해야 하는 것은 작은 원의 중심이다! 이를 r이라 하자! 위 그림에서 원에 접선과 중심이 이루는 각도는 항상 90도임을 이용해서 r을 구하는 것을 보여주고 있다. r을 구하는 것은 이제 해결이 되었고, 다음으로 이 원 중심의 좌표를 구해보자!!
우리는 지금 뒷바퀴 중심의 좌표와 원의 반지름을 알고 있는 상황이다. 이를 가지고 원중심의 좌표를 구해야 한다.
이번에도 원의 접선과 중심이 이루는 각도는 직각이라는 것을 이용해 볼 것이다. 그럼 위와 같이 로봇이 향하고 있는 각도 Θ를 이용해서 원의 중심을 구할 수 있음을 보인다. 현재 로봇의 위치 x,y에서 원의 반지름과 삼각함수를 이용해 구하는 것이다.
방금 구한 원의 중심을 이용해서 로봇이 다시 이동했을 때의 새로운 위치를 표현할 수도 있다!! 우선 steering angle을 유지한 채로 움직였다는 가정 하에, 원의 중심에 대해서 움직인 각도를 beta(B)라고 하겠다. 사진에서 새로이 그려진 파란 직각 삼각형에 대해서 새로운 x, y, Θ를 정의할 수 있다. 다만 마지막에 mod 2 pi 가 되어 있는데 이는 각도가 한 바퀴를 넘었을 경우에 대한 예외처리이다.
그럼 다시 돌아와서 원래 식을 본다. 아하!! 이해가 되었다. 그런데 밑에 B의 절댓값이 0.001보다 작을 때~ 이렇게 나와 있다. 이는 움직인 거리가 매우 작을 때에 대해서인데, R = d/B에 의해서 beta가 너무 작아 버리면 중심이 되는 원의 반지름이 너무나 커져 버린다. 이와 동시에 작게 움직이는 경우에는 움직임의 중심을 구하는 것의 의미가 거의 없어진다. 그래서 이러한 상황에서는 그냥 아래와 같은 식을 쓰는 것이 좋다고 이야기하고 있다.
지난 실습때 사용했던 robot()클래스에 이 수식을 사용해서 코드를 수정하라는 것이 실습이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | def move(self, motion): # Do not change the name of this function # ADD CODE HERE steering = motion[0] distance = motion[1] if abs(steering) > max_steering_angle: raise ValueError if distance < 0.0: raise ValueError result = robot(20) result.length = self.length result.bearing_noise = self.bearing_noise result.steering_noise = self.steering_noise result.distance_noise = self.distance_noise turn = tan(steering) * distance / self.length if abs(turn) < 0.001: result.x = self.x + distance * cos(self.orientation) result.y = self.y + distance * sin(self.orientation) result.orientation = (self.orientation + turn) % (2.0 * pi) else : R = distance / turn cx = self.x - R * sin(self.orientation) cy = self.y + R * cos(self.orientation) result.x = cx + R * sin(self.orientation + turn) result.y = cy - R * cos(self.orientation + turn) result.orientation = (self.orientation + turn) % (2. * pi) return result | cs |
뽀로롱~ 위와 같다. 사실 수식도 다 줬고, 그대로 옮기기만 하면 되는 것였는데 ㅠㅠ 나는 아직 멀었다. 실제로 output을 보고 싶으면 아래 풀 소스를 돌려 보면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | # ----------------- # USER INSTRUCTIONS # # Write a function in the class robot called move() # # that takes self and a motion vector (this # motion vector contains a steering* angle and a # distance) as input and returns an instance of the class # robot with the appropriate x, y, and orientation # for the given motion. # # *steering is defined in the video # which accompanies this problem. # # For now, please do NOT add noise to your move function. # # Please do not modify anything except where indicated # below. # # There are test cases which you are free to use at the # bottom. If you uncomment them for testing, make sure you # re-comment them before you submit. from math import * import random # -------- # # the "world" has 4 landmarks. # the robot's initial coordinates are somewhere in the square # represented by the landmarks. # # NOTE: Landmark coordinates are given in (y, x) form and NOT # in the traditional (x, y) format! landmarks = [[0.0, 100.0], [0.0, 0.0], [100.0, 0.0], [100.0, 100.0]] # position of 4 landmarks world_size = 100.0 # world is NOT cyclic. Robot is allowed to travel "out of bounds" max_steering_angle = pi/4 # You don't need to use this value, but it is good to keep in mind the limitations of a real car. # ------------------------------------------------ # # this is the robot class # class robot: # -------- # init: # creates robot and initializes location/orientation # def __init__(self, length = 10.0): self.x = random.random() * world_size # initial x position self.y = random.random() * world_size # initial y position self.orientation = random.random() * 2.0 * pi # initial orientation self.length = length # length of robot self.bearing_noise = 0.0 # initialize bearing noise to zero self.steering_noise = 0.0 # initialize steering noise to zero self.distance_noise = 0.0 # initialize distance noise to zero def __repr__(self): return '[x=%.6s y=%.6s orient=%.6s]' % (str(self.x), str(self.y), str(self.orientation)) # -------- # set: # sets a robot coordinate # def set(self, new_x, new_y, new_orientation): if new_orientation < 0 or new_orientation >= 2 * pi: raise ValueError self.x = float(new_x) self.y = float(new_y) self.orientation = float(new_orientation) # -------- # set_noise: # sets the noise parameters # def set_noise(self, new_b_noise, new_s_noise, new_d_noise): # makes it possible to change the noise parameters # this is often useful in particle filters self.bearing_noise = float(new_b_noise) self.steering_noise = float(new_s_noise) self.distance_noise = float(new_d_noise) ############# ONLY ADD/MODIFY CODE BELOW HERE ################### # -------- # move: # move along a section of a circular path according to motion # def move(self, motion): # Do not change the name of this function # ADD CODE HERE steering = motion[0] distance = motion[1] if abs(steering) > max_steering_angle: raise ValueError if distance < 0.0: raise ValueError result = robot(20) result.length = self.length result.bearing_noise = self.bearing_noise result.steering_noise = self.steering_noise result.distance_noise = self.distance_noise turn = tan(steering) * distance / self.length if abs(turn) < 0.001: result.x = self.x + distance * cos(self.orientation) result.y = self.y + distance * sin(self.orientation) result.orientation = (self.orientation + turn) % (2.0 * pi) else : R = distance / turn cx = self.x - R * sin(self.orientation) cy = self.y + R * cos(self.orientation) result.x = cx + R * sin(self.orientation + turn) result.y = cy - R * cos(self.orientation + turn) result.orientation = (self.orientation + turn) % (2. * pi) return result # make sure your move function returns an instance # of the robot class with the correct coordinates. ############## ONLY ADD/MODIFY CODE ABOVE HERE #################### ## IMPORTANT: You may uncomment the test cases below to test your code. ## But when you submit this code, your test cases MUST be commented ## out. Our testing program provides its own code for testing your ## move function with randomized motion data. ## -------- ## TEST CASE: ## ## 1) The following code should print: ## Robot: [x=0.0 y=0.0 orient=0.0] ## Robot: [x=10.0 y=0.0 orient=0.0] ## Robot: [x=19.861 y=1.4333 orient=0.2886] ## Robot: [x=39.034 y=7.1270 orient=0.2886] ## ## ##length = 20. ##bearing_noise = 0.0 ##steering_noise = 0.0 ##distance_noise = 0.0 ## ##myrobot = robot(length) ##myrobot.set(0.0, 0.0, 0.0) ##myrobot.set_noise(bearing_noise, steering_noise, distance_noise) ## ##motions = [[0.0, 10.0], [pi / 6.0, 10], [0.0, 20.0]] ## ##T = len(motions) ## ##print 'Robot: ', myrobot ##for t in range(T): ## myrobot = myrobot.move(motions[t]) ## print 'Robot: ', myrobot ## ## ## IMPORTANT: You may uncomment the test cases below to test your code. ## But when you submit this code, your test cases MUST be commented ## out. Our testing program provides its own code for testing your ## move function with randomized motion data. ## 2) The following code should print: ## Robot: [x=0.0 y=0.0 orient=0.0] ## Robot: [x=9.9828 y=0.5063 orient=0.1013] ## Robot: [x=19.863 y=2.0201 orient=0.2027] ## Robot: [x=29.539 y=4.5259 orient=0.3040] ## Robot: [x=38.913 y=7.9979 orient=0.4054] ## Robot: [x=47.887 y=12.400 orient=0.5067] ## Robot: [x=56.369 y=17.688 orient=0.6081] ## Robot: [x=64.273 y=23.807 orient=0.7094] ## Robot: [x=71.517 y=30.695 orient=0.8108] ## Robot: [x=78.027 y=38.280 orient=0.9121] ## Robot: [x=83.736 y=46.485 orient=1.0135] ## ## length = 20. bearing_noise = 0.0 steering_noise = 0.0 distance_noise = 0.0 myrobot = robot(length) myrobot.set(0.0, 0.0, 0.0) myrobot.set_noise(bearing_noise, steering_noise, distance_noise) motions = [[0.2, 10.] for row in range(10)] T = len(motions) print('Robot: ', myrobot) for t in range(T): myrobot = myrobot.move(motions[t]) print('Robot: ', myrobot) ## IMPORTANT: You may uncomment the test cases below to test your code. ## But when you submit this code, your test cases MUST be commented ## out. Our testing program provides its own code for testing your ## move function with randomized motion data. | cs |
이렇게만 봐서는 사실 결과가 어떤 의미인지 알기 힘들 것이다. 추후에 plot으로 나타내는 부분을 더 넣겠다. github에 다른 사람이 만들어 놓은 것도 있다.
아직 끝나지 않았다!! 이번에는 각 landmark들과 로봇에 대해서 이루는 각도들을 구해야 한다!! 사실 math라이브러리에 atan2함수를 써서 쉽게 할 수가 있다. 다만, 2pi를 넘어가고 하는 예외들을 조심스럽게 처리해 주어야 한다.
1 2 3 4 5 6 | def sense(self): #do not change the name of this function Z = [] for i in range(len(landmarks)): bearing = atan2(landmarks[i][0] - self.y, landmarks[i][1] - self.x) - self.orientation Z.append(bearing % (2. * pi)) | cs |
다소 간단하다!! atan2라는 함수는 파이썬 뿐만 아니라 보편적인 함수이므로 알아두면 좋다.
그럼 마지막 실습으로 이 모델을 이용해서 Particle Filter를 돌려보자!!!
2pi / 10 즉 36도를 돌고 20만큼 앞으로 가는 과정을 반복할 것이다. 그리고 motion을 취하면서 측정한 measurement는 아래와 같다고 할 것이다. 아무래도 직선 운동이 아니다 보니까 상당히 더러워 보이는 숫자들이 나왔다. 그리고 무엇보다 중요한 것으로 이번 실습에서는 노이즈를 고려해 줄 것이다.
현재 자신의 각도 Θ에 대한 bearing noise, 앞바퀴가 돌아간 정도에 대한 steering noise, 앞으로 간 거리에 대한 distance noise들을 설정해 준다. 그리고 max steering angle이 있는데, 사실 앞에서 설명했어야 하지만, 이는 앞바퀴가 최고로 돌 수 있는 각도이다. 실제 자동차라고 생각하기 위해서 한 건가... 본론으로 돌아가서 앞서 만들었던 두 가지 코드들을 기존 Particle filter코드와 병합하는 것이 과제이다.
그래서 위와 같은 main문이 작동하도록 해 주어야 한다. 사실 앞서 다 만들었어서 바로 원본을 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 | # -------------- # USER INSTRUCTIONS # # Now you will put everything together. # # First make sure that your sense and move functions # work as expected for the test cases provided at the # bottom of the previous two programming assignments. # Once you are satisfied, copy your sense and move # definitions into the robot class on this page, BUT # now include noise. # # A good way to include noise in the sense step is to # add Gaussian noise, centered at zero with variance # of self.bearing_noise to each bearing. You can do this # with the command random.gauss(0, self.bearing_noise) # # In the move step, you should make sure that your # actual steering angle is chosen from a Gaussian # distribution of steering angles. This distribution # should be centered at the intended steering angle # with variance of self.steering_noise. # # Feel free to use the included set_noise function. # # Please do not modify anything except where indicated # below. from math import * import random # -------- # # some top level parameters # max_steering_angle = pi / 4.0 # You do not need to use this value, but keep in mind the limitations of a real car. bearing_noise = 0.1 # Noise parameter: should be included in sense function. steering_noise = 0.1 # Noise parameter: should be included in move function. distance_noise = 5.0 # Noise parameter: should be included in move function. tolerance_xy = 15.0 # Tolerance for localization in the x and y directions. tolerance_orientation = 0.25 # Tolerance for orientation. # -------- # # the "world" has 4 landmarks. # the robot's initial coordinates are somewhere in the square # represented by the landmarks. # # NOTE: Landmark coordinates are given in (y, x) form and NOT # in the traditional (x, y) format! landmarks = [[0.0, 100.0], [0.0, 0.0], [100.0, 0.0], [100.0, 100.0]] # position of 4 landmarks in (y, x) format. world_size = 100.0 # world is NOT cyclic. Robot is allowed to travel "out of bounds" # ------------------------------------------------ # # this is the robot class # class robot: # -------- # init: # creates robot and initializes location/orientation # def __init__(self, length = 20.0): self.x = random.random() * world_size # initial x position self.y = random.random() * world_size # initial y position self.orientation = random.random() * 2.0 * pi # initial orientation self.length = length # length of robot self.bearing_noise = 0.0 # initialize bearing noise to zero self.steering_noise = 0.0 # initialize steering noise to zero self.distance_noise = 0.0 # initialize distance noise to zero # -------- # set: # sets a robot coordinate # def set(self, new_x, new_y, new_orientation): if new_orientation < 0 or new_orientation >= 2 * pi: raise ValueError, 'Orientation must be in [0..2pi]' self.x = float(new_x) self.y = float(new_y) self.orientation = float(new_orientation) # -------- # set_noise: # sets the noise parameters # def set_noise(self, new_b_noise, new_s_noise, new_d_noise): # makes it possible to change the noise parameters # this is often useful in particle filters self.bearing_noise = float(new_b_noise) self.steering_noise = float(new_s_noise) self.distance_noise = float(new_d_noise) # -------- # measurement_prob # computes the probability of a measurement # def measurement_prob(self, measurements): # calculate the correct measurement predicted_measurements = self.sense(0) # Our sense function took 0 as an argument to switch off noise. # compute errors error = 1.0 for i in range(len(measurements)): error_bearing = abs(measurements[i] - predicted_measurements[i]) error_bearing = (error_bearing + pi) % (2.0 * pi) - pi # truncate # update Gaussian error *= (exp(- (error_bearing ** 2) / (self.bearing_noise ** 2) / 2.0) / sqrt(2.0 * pi * (self.bearing_noise ** 2))) return error def __repr__(self): #allows us to print robot attributes. return '[x=%.6s y=%.6s orient=%.6s]' % (str(self.x), str(self.y), str(self.orientation)) ############# ONLY ADD/MODIFY CODE BELOW HERE ################### # -------- # move: # def move(self, motion): # Do not change the name of this function # ADD CODE HERE steering = motion[0] distance = motion[1] if abs(steering) > max_steering_angle: raise ValueError if distance < 0.0: raise ValueError result = robot(20) result.length = self.length result.bearing_noise = self.bearing_noise result.steering_noise = self.steering_noise result.distance_noise = self.distance_noise steering2 = random.gauss(steering, self.steering_noise) distance2 = random.gauss(distance, self.distance_noise) turn = tan(steering2) * distance2 / self.length if abs(turn) < 0.001: result.x = self.x + distance2 * cos(self.orientation) result.y = self.y + distance2 * sin(self.orientation) result.orientation = (self.orientation + turn) % (2.0 * pi) else : R = distance / turn cx = self.x - R * sin(self.orientation) cy = self.y + R * cos(self.orientation) result.x = cx + R * sin(self.orientation + turn) result.y = cy - R * cos(self.orientation + turn) result.orientation = (self.orientation + turn) % (2. * pi) return result # copy your code from the previous exercise # and modify it so that it simulates motion noise # according to the noise parameters # self.steering_noise # self.distance_noise # -------- # sense: # def sense(self, add_noise = 1): #do not change the name of this function Z = [] for i in range(len(landmarks)): bearing = atan2(landmarks[i][0] - self.y, landmarks[i][1] - self.x) - self.orientation if add_noise: bearing += random.gauss(0.0, self.bearing_noise) Z.append(bearing % (2. * pi)) # ENTER CODE HERE # HINT: You will probably need to use the function atan2() return Z #Leave this line here. Return vector Z of 4 bearings. # copy your code from the previous exercise # and modify it so that it simulates bearing noise # according to # self.bearing_noise ############## ONLY ADD/MODIFY CODE ABOVE HERE #################### # -------- # # extract position from a particle set # def get_position(p): x = 0.0 y = 0.0 orientation = 0.0 for i in range(len(p)): x += p[i].x y += p[i].y # orientation is tricky because it is cyclic. By normalizing # around the first particle we are somewhat more robust to # the 0=2pi problem orientation += (((p[i].orientation - p[0].orientation + pi) % (2.0 * pi)) + p[0].orientation - pi) return [x / len(p), y / len(p), orientation / len(p)] # -------- # # The following code generates the measurements vector # You can use it to develop your solution. # def generate_ground_truth(motions): myrobot = robot() myrobot.set_noise(bearing_noise, steering_noise, distance_noise) Z = [] T = len(motions) for t in range(T): myrobot = myrobot.move(motions[t]) Z.append(myrobot.sense()) #print 'Robot: ', myrobot return [myrobot, Z] # -------- # # The following code prints the measurements associated # with generate_ground_truth # def print_measurements(Z): T = len(Z) print 'measurements = [[%.8s, %.8s, %.8s, %.8s],' % \ (str(Z[0][0]), str(Z[0][1]), str(Z[0][2]), str(Z[0][3])) for t in range(1,T-1): print ' [%.8s, %.8s, %.8s, %.8s],' % \ (str(Z[t][0]), str(Z[t][1]), str(Z[t][2]), str(Z[t][3])) print ' [%.8s, %.8s, %.8s, %.8s]]' % \ (str(Z[T-1][0]), str(Z[T-1][1]), str(Z[T-1][2]), str(Z[T-1][3])) # -------- # # The following code checks to see if your particle filter # localizes the robot to within the desired tolerances # of the true position. The tolerances are defined at the top. # def check_output(final_robot, estimated_position): error_x = abs(final_robot.x - estimated_position[0]) error_y = abs(final_robot.y - estimated_position[1]) error_orientation = abs(final_robot.orientation - estimated_position[2]) error_orientation = (error_orientation + pi) % (2.0 * pi) - pi correct = error_x < tolerance_xy and error_y < tolerance_xy \ and error_orientation < tolerance_orientation return correct def particle_filter(motions, measurements, N=500): # I know it's tempting, but don't change N! # -------- # # Make particles # p = [] for i in range(N): r = robot() r.set_noise(bearing_noise, steering_noise, distance_noise) p.append(r) # -------- # # Update particles # for t in range(len(motions)): # motion update (prediction) p2 = [] for i in range(N): p2.append(p[i].move(motions[t])) p = p2 # measurement update w = [] for i in range(N): w.append(p[i].measurement_prob(measurements[t])) # resampling p3 = [] index = int(random.random() * N) beta = 0.0 mw = max(w) for i in range(N): beta += random.random() * 2.0 * mw while beta > w[index]: beta -= w[index] index = (index + 1) % N p3.append(p[index]) p = p3 return get_position(p) ## IMPORTANT: You may uncomment the test cases below to test your code. ## But when you submit this code, your test cases MUST be commented ## out. ## ## You can test whether your particle filter works using the ## function check_output (see test case 2). We will be using a similar ## function. Note: Even for a well-implemented particle filter this ## function occasionally returns False. This is because a particle ## filter is a randomized algorithm. We will be testing your code ## multiple times. Make sure check_output returns True at least 80% ## of the time. ## -------- ## TEST CASES: ## ##1) Calling the particle_filter function with the following ## motions and measurements should return a [x,y,orientation] ## vector near [x=93.476 y=75.186 orient=5.2664], that is, the ## robot's true location. ## ##motions = [[2. * pi / 10, 20.] for row in range(8)] ##measurements = [[4.746936, 3.859782, 3.045217, 2.045506], ## [3.510067, 2.916300, 2.146394, 1.598332], ## [2.972469, 2.407489, 1.588474, 1.611094], ## [1.906178, 1.193329, 0.619356, 0.807930], ## [1.352825, 0.662233, 0.144927, 0.799090], ## [0.856150, 0.214590, 5.651497, 1.062401], ## [0.194460, 5.660382, 4.761072, 2.471682], ## [5.717342, 4.736780, 3.909599, 2.342536]] ## ##print particle_filter(motions, measurements) ## 2) You can generate your own test cases by generating ## measurements using the generate_ground_truth function. ## It will print the robot's last location when calling it. ## ## ##number_of_iterations = 6 ##motions = [[2. * pi / 20, 12.] for row in range(number_of_iterations)] ## ##x = generate_ground_truth(motions) ##final_robot = x[0] ##measurements = x[1] ##estimated_position = particle_filter(motions, measurements) ##print_measurements(measurements) ##print 'Ground truth: ', final_robot ##print 'Particle filter: ', estimated_position ##print 'Code check: ', check_output(final_robot, estimated_position) | cs |
자, 너무나 친절한 주석질에 의해서 300줄이 넘어 버렸다... 우리가 주의 깊게 봐야 할 부분은 Particle filter함수이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | def particle_filter(motions, measurements, N=500): # I know it's tempting, but don't change N! # -------- # # Make particles # p = [] for i in range(N): r = robot() r.set_noise(bearing_noise, steering_noise, distance_noise) p.append(r) # -------- # # Update particles # for t in range(len(motions)): # motion update (prediction) p2 = [] for i in range(N): p2.append(p[i].move(motions[t])) p = p2 # measurement update w = [] for i in range(N): w.append(p[i].measurement_prob(measurements[t])) # resampling p3 = [] index = int(random.random() * N) beta = 0.0 mw = max(w) for i in range(N): beta += random.random() * 2.0 * mw while beta > w[index]: beta -= w[index] index = (index + 1) % N p3.append(p[index]) p = p3 return get_position(p) | cs |
노이즈를 설정하고, motion의 시행 횟수만큼 반복문을 돌린다. 사실 motion과 measurement는 우리가 사용하는 모델이 바뀜에 따른 것이고 주의깊게 봐야 할 것은 Particle Filter의 전체적인 흐름이 어떻게 흘러가는지를 이해하는 것이다.
1 2 3 4 5 | p = [] for i in range(N): r = robot() r.set_noise(bearing_noise, steering_noise, distance_noise) p.append(r) | cs |
N개의 Particle들을 생성하고 noise도 설정한다.
1 | for t in range(len(motions)): | cs |
Motion의 시행 횟수만큼 반복할 건데,
1 2 3 4 | p2 = [] for i in range(N): p2.append(p[i].move(motions[t])) p = p2 | cs |
로봇이 움직이는 만큼 각 Particle들도 움직여 준다.
1 2 3 4 | # measurement update w = [] for i in range(N): w.append(p[i].measurement_prob(measurements[t])) | cs |
landmark들에 대해서 거리와 각도를 구하고 그에 따라 weight를 부여한다. weight는 실제 로봇의 위치에 가까울 수록 큰 값을 가진다.
1 2 3 4 5 6 7 8 9 10 11 12 | # resampling p3 = [] index = int(random.random() * N) beta = 0.0 mw = max(w) for i in range(N): beta += random.random() * 2.0 * mw while beta > w[index]: beta -= w[index] index = (index + 1) % N p3.append(p[index]) p = p3 | cs |
이 weight값들을 가지고 N개의 Particle들을 Resampling한다.
다음은 Particle Filter에서의 질문들에 대한 Q & A 이다.
Q : 아래와 같이 룰렛 알고리즘을 할 때, 가장 많은 자리를 차지하는 부분의 2배 만큼 반복해서 움직이게 했는데 이렇게 한 이유가 있는가??
A : 모름, 그냥 적당한 수로 했다고 한다. 너무 크면 시간도 많이 걸리고 해서 적당히 했다고 한다.
Q : 언제 Particle Filter를 쓰고 언제 Kalman Filter를 쓰는가?
A : Particle Filter는 구현은 싑지만 차원이 늘어남에 따라 복잡도가 증가한다. 그래서 15차원 막 이러면 보통 Kalman Filter를 쓰는데, Kalman은 아시다시피 1개의 가장 높은 확률만 알 수 있는 것이 문제이다. 그래서 Extended된 Kalman Filter(mixed rough Kalman filter, multihypothesis Kalman filter)들을 쓰곤 한다.
multimodal distribution을 가지고 있다, 그럼 particle filters를 쓰고, continuous space이고 unimodal distribution을 가지고 있다, 그럼 Kalman filter를 써라 그리고 이 둘을 병합한 rao-blackwellized filter라는 것도 있다.
Q : Particle들의 개수 N을 어떤 기준으로??
A : 필터를 잘 설계했다면 적은 갯수로도 충분하다. 더욱이, 로봇 시스템은 Real-time이고, 특정 cycle안에 추적하지 않으면 의미가 없다. 보통 잘 추적되고 있는지 보고 싶을 때 Normalized되지 않은 weight값들을 보는데, 로봇의 실제 위치와 비슷한 Particle이 충분히 큰 weight를 가지고 있다면 잘 하고 있는 것이고, 그렇지 않다면 뽑는 Particle들의 수를 조금 더 늘려야 할 것이다.
Q : landmark들을 사용해서 실제 위치와 얼마나 유사한지 보았었는데 실제로 자율 주행 차량들에 이용한다고 할 때, 이 landmark들이 움직이는 사람인지, 가만히 있는 표지판인지 어떻게 알 수 있는가??
A : 우선, 자동차라고 가정했을 때, 자신이 얼마나 움직였는지는 알 수가 있다. 그리고 time step들을 계속 거치면서, time step 이전과 이후에 대해 어떠한 변화가 있었는지 알아볼 수가 있다. 이를 통해 움직인 물체인지, 아닌지 알 수가 있다.
예를 들면, 도로 위의 표지판이라고 하면 내가 움직인 그 정도만 보이는 위치가 달라진다. 그런데 만약, 나에게 달려오는 고라니라고 하면, 갑자기 크기가 커지는 등으로 변화가 있을 것이다. 사실 움직임과 measurement의 노이즈에 의해서 구별하기 쉽지 않을까 싶기도 한데, 그 노이즈를 고려하더라고 확실하게 변화가 큰 것들은 알아챌 수가 있을 것이다.
길고 긴 포스트가 끝났다... 사진도 많고 양도 너무 많아서 글을 쓰는데 버퍼링이 걸릴 정도... 다음에는 보다 순수 알고리즘적인 Search에 대해서 다루어 본다!!
'공부' 카테고리의 다른 글
Search 공부했다. (2) (0) | 2019.01.05 |
---|---|
Search - 공부했다. (0) | 2019.01.05 |
Particle Filter 공부했다. (0) | 2018.12.31 |
Kalman Filter 공부했다. - 2 차원 이동 실습 (0) | 2018.12.28 |
Kalman Filter 공부했다. (1) | 2018.12.23 |