Search부분은 Path Planning과 관련이 깊은 단원이었다. 지도가 주어졌을 때, 어떻게 해야 목적지까지 갈 수 있는지, 길을 계산하는 것이다. 그리고 더불어 최적의 루트를 찾기 위해서는 어떻게 해야 하는지 등등을 배운다. 알고리즘적일 측면이 강한 단원이었다. 그래서 오래 걸리기도 했다. 대부분이 프로그래밍 실습으로 이루어져 있어서 미리 리 마음의 준비를 하기 바란다.
우리가 로봇을 실질적으로 움직이게 하기 위해서는 위와 같이 Motion Planning을 해주어야 한다. (물론 움직이는 로봇이라는 가정 하에) 이는 인간이 보기에는 위와 같이 최단 경로를 찾는 것이 쉽고 당연하게 느껴지겠지만, 로봇을 위해서 알고리즘을 짜는 것은 또다른 문제이다.
위는 운전을 하는 상황을 나타내고 있다. 수많은 차들이 지나다니고, 신호와 도로를 꿰고 있어야 최선의 경로를 찾을 수 있는 운전은 인간에게도 어려운 과제이다. 더욱이 위의 경우 차선 변경을 하는 묘사가 되어 있는데, 차선 변경을 위해서는 앞, 뒤 차량과의 간격도 봐야 하고 커다란 트럭이 가로막고 있다면 시야도 좁아지고... 여간 어려운 것이 아니다.
그래서 차라리 조금 돌아 가더라도 우측 위의 ㅁ자형 도로를 거쳐 목적지에 도달하는 것이 더 좋은 선택이 될 수가 있다. 이렇게 상황을 고려한 최적의 경로(Optimal Path)는 단순한 직선 최단거리가 아니다.
경로를 계산하는 과정에서 우리가 알고 있는 것은 무엇이고 이들을 통해 우리가 해주어야 할 것들은 무엇인지에 대해서 정리가 되어 있다. 우선 우리는 시작지점과 목적지점을 알고 있어야 하고, 더불어 자신의 위치도 알고 있어야 할 것이다. 그리고 Cost라고 나와 있는데 이는 움직임에 대한 비용이다.
예를 들어 이 Cost를 시간이라고 하자. 위의 4거리에서 운전하는 상황이라고 하였을 때, 직선으로 돌아 간다면 신호를 기다리지도 않고, 차선 변경을 하느라 소비하는 시간도 없다. 그래서 이러한 예외들을 계산에 사용하기 위해 모두 수치화한 것을 Cost라고 한다.
마지막으로 우리의 목적은?? 위 조건들을 가지고 최소한의 Cost를 가지는 경로를 찾는 것이다! 시작부터 어려운 프로그래밍을 하기는 힘드니 쉬운 예제들로 시작을 해 보자.\
4거리 문제에서, 도로를 타일과 같이 잘게 쪼개고, 전진, 좌회전, 우회전, 이렇게 상황을 나누었다. 그리고 각 행동에 소요되는 Cost는 모두 1로 같다고 하였다. 시작 지점에서 목표 지점까지 최단 경로로 간다고 했을 때, 최단 경로가 소모하는 Cost는 얼마일까?? 그렇다 놀랍게도(?) 6이다. ㄱ자로 이동하면 된다. 그럼 아래의 상황을 보자.
엇! 이번에는 좌회전을 하는데 소모되는 Cost가 20이나 된다! 신호도 기다리고 차선도 변경한다고 생각해서 이렇게 Cost를 높게 주었다고 생각하자, 그러면 최단 경로가 바뀌어 버린다. 4거리에서 좌회전을 하지 않고 그대로 직진하는 길이 최단경로가 되는 것이다. 그래서 16이라는 값이 들었다.
이번에는 좀 더 확장을 해 보았다. 위 경우에는 최단 경로가 하나가 아니고 여러 경우가 있을 수 있다. 다만 이 최단 경로들 모두 사용된 Cost는 11로 같을 것이다. 이렇게 타일처럼 나뉘어진 상황이 주어져 있을 때, 최소 비용의 최단 경로를 찾아내는 알고리즘을 작성해 보는 것이 과제이다.
어떻게 코드를 작성할지에 대한 보충 설명이다.
1. 지금 위치에서 상하좌우로 갈 수 있는 경우가 있는지 찾아 본다. (지도의 끝이거나 장애물이 있으면 불가능) 이 지점을 OPEN 되었다고 하자. 각 OPEN된 위치들은 Cost(g value라고 하자)도 담고 있다. 한 칸 움직이는데 비용은 1이 든다.
2. OPEN을 시켰다면 원래 위치는 CLOSE를 한다. (사진에서 빗금을 친 것처럼)
3. OPEN된 위치들 중에서 최소의 g-value를 갖는 것으로 다시 상하좌우를 살핀다.
상하좌우를 살피는 것으로 인해 지도에서 거미줄처럼 탐색을 하게 된다. 위 사진에서는 4, 4 지점에서 막다른 길이 나타나게 된다. 이러한 경우 상, 우는 벽이 있어서 못가고 하는 존재하기 않아서 못가고 좌는 이미 CLOSE가 되어 버려서 가지 못하므로 그냥 자신도 CLOSE한 채로 죽어버리게 된다.
이렇게 CLOSE를 하는 과정이 반드시 있어야 같은 지점을 두 번 반복해서 지나치는 일이 없어진다. 그리고 최소한의 g-value를 가지는 값을 선택해서 살피는 과정을 통해, Path를 찾았다고 하더라도 최단거리를 가지지 않으면 죽어버린다.
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 | # ---------- # User Instructions: # # Define a function, search() that returns a list # in the form of [optimal path length, row, col]. For # the grid shown below, your function should output # [11, 4, 5]. # # If there is no valid path from the start point # to the goal, your function should return the string # 'fail' # ---------- # Grid format: # 0 = Navigable space # 1 = Occupied space grid = [[0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0], [0, 0, 1, 1, 1, 0], [0, 0, 0, 0, 1, 0]] init = [0, 0] goal = [len(grid)-1, len(grid[0])-1] cost = 1 delta = [[-1, 0], # go up [ 0,-1], # go left [ 1, 0], # go down [ 0, 1]] # go right delta_name = ['^', '<', 'v', '>'] def search(grid,init,goal,cost): # ---------------------------------------- # insert code here # ---------------------------------------- return path | cs |
체점을 하기 위한 grader파일을 올려 두니, 코드 제일 위에 import grader를 삽입해서 체점도 받아볼 수 있다.
이렇게 정답으로 가는 과정들을 보면서 디버깅도 하기 바란다. 그럼 아래는 정답 공개
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 | # ---------- # User Instructions: # # Define a function, search() that returns a list # in the form of [optimal path length, row, col]. For # the grid shown below, your function should output # [11, 4, 5]. # # If there is no valid path from the start point # to the goal, your function should return the string # 'fail' # ---------- # Grid format: # 0 = Navigable space # 1 = Occupied space import sys grid = [[0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0], [0, 0, 1, 1, 1, 0], [0, 0, 0, 0, 1, 0]] expand = [[0, 0, -1, 0, 0, 0], [0, 0, -1, 0, 0, 0], [0, 0, 0, 0, -1, 0], [0, 0, -1, -1, -1, 0], [0, 0, 0, 0, -1, 0]] init = [0, 0] goal = [len(grid)-1, len(grid[0])-1] cost = 1 delta = [[-1, 0], # go up [ 0,-1], # go left [ 1, 0], # go down [ 0, 1]] # go right delta_name = ['^', '<', 'v', '>'] def search(grid,init,goal,cost): closed = [[0 for row in range(len(grid[0]))] for col in range(len(grid))] closed[init[0]][init[1]] = 1 x = init[0] y = init[1] g = 0 open = [[g, x, y]] found = False # flag that is set when search complete resign = False # flag set if we can't find expand count= 0 while found is False and resign is False: #check if we still have elements on the open list if len(open) == 0: resign = True print('fail') else: open.sort() open.reverse() next = open.pop() x = next[1] y = next[2] g = next[0] #check if we are done if x == goal[0] and y == goal[1]: found = True path = next print(next) else: # expand winning element and add to new open list for i in range(len(delta)): x2 = x + delta[i][0] y2 = y + delta[i][1] if x2 >=0 and x2 < len(grid) and y2 >= 0 and y2 < len(grid[0]): if closed[x2][y2] == 0 and grid[x2][y2] == 0: g2 = g + cost open.append([g2, x2, y2]) closed[x2][y2] = 1 return path ##### Do Not Modify ###### #import grader print('search',search(grid,init,goal,cost)) #try: # response = grader.run_grader(search) # print('response', response) #except Exception as err: # print(str(err)) | cs |
사실 쉽지는 않다. 나도 알고리즘에는 익숙하지가 않아서 시간이 많이 들었다.
1 2 3 4 | if x == goal[0] and y == goal[1]: found = True path = next print(next) | cs |
목표 지점에 도달하기 전까지 반복을 계속하고,
1 2 3 4 5 6 7 8 | for i in range(len(delta)): x2 = x + delta[i][0] y2 = y + delta[i][1] if x2 >=0 and x2 < len(grid) and y2 >= 0 and y2 < len(grid[0]): if closed[x2][y2] == 0 and grid[x2][y2] == 0: g2 = g + cost open.append([g2, x2, y2]) closed[x2][y2] = 1 | cs |
현제 OPEN된 지점에서 상하좌우를 살핀다. 상하좌우에 있는 위치들 중에서
지도상에 존재하고 (x2 >=0 and x2 < len(grid) and y2 >= 0 and y2 < len(grid[0]):)
그 위치가 CLOSE되어있지 않고, (closed[x2][y2] == 0
장애물이 없다면 (and grid[x2][y2] == 0:
g-value에 1(한 번 움직이는데 드는 비용)을 더해주고 ( g2 = g + cost
open된 것들에 append를 시킨다음 - 순서는 g-value가 제일 먼저!! ( open.append([g2, x2, y2])
원래 지점을 CLOSE시켜 준다. ( closed[x2][y2] = 1
1 2 3 | open.sort() open.reverse() next = open.pop() | cs |
그리고 최소한의 g-value를 가지는 open된 지점을 다시 탐색해야 하는데, 그래서 append시킬 때 g-value를 제일 앞에 두었던 것이다.
정렬을 하는 것은 파이썬에서 원래 제공하는 sort함수를 사용하고 open.sort()
제일 작은 것을 꺼내서 사용할 것이므로 순서를 뒤집어서 open.reverse()
꺼내준다. next = open.pop()
휴, 만만치가 않다. 그런데 끝이 아니다. 다음 과제~
=======================================================================
이번에는 총 몇 번의 Expand가 이루어졌는가에 대해서 계산해 보라는 과제이다. Expand라는 것이 무엇인가 하면, 앞서 말했던 상하좌우가 바로 그것이다.
쉽게 말해서 open된 지점이 있다면 그 지점이 몇 번째 탐색이었는지 보여달라는 것이다. 아래의 사진을 보면 이해가 될 것이다.
애초에 목적지로 갈 수가 없는 경우인데 그래서 아래로 Expand만 반복하고 실질적인 길은 찾지 못하였다. 참고로 장애물이 있거나 가지 못한 부분은 -1로 표시가 되고, 이 숫자들의 순서는 사람마다 다를 수 있다. 그럼 문제는 다음과 같다.
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 | # ----------- # User Instructions: # # Modify the function search so that it returns # a table of values called expand. This table # will keep track of which step each node was # expanded. # # Make sure that the initial cell in the grid # you return has the value 0. # ---------- grid = [[0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 1, 0], [0, 0, 1, 0, 1, 0], [0, 0, 1, 0, 1, 0]] init = [0, 0] goal = [len(grid)-1, len(grid[0])-1] cost = 1 delta = [[-1, 0], # go up [ 0,-1], # go left [ 1, 0], # go down [ 0, 1]] # go right delta_name = ['^', '<', 'v', '>'] def search(grid,init,goal,cost): # ---------------------------------------- # modify code below # ---------------------------------------- expand = None return expand print search(grid,init,goal,cost) | cs |
앞선 문제를 풀었다면 이는 사실 쉽다.
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 | # ----------- # User Instructions: # # Modify the function search so that it returns # a table of values called expand. This table # will keep track of which step each node was # expanded. # # Make sure that the initial cell in the grid # you return has the value 0. # ---------- grid = [[0, 1, 0, 0, 0, 1, 0], [0, 1, 0, 1, 0, 1, 0], [0, 1, 0, 1, 0, 1, 0], [0, 1, 0, 1, 0, 1, 1], [0, 0, 0, 1, 0, 0, 0]] init = [0, 0] goal = [len(grid)-1, len(grid[0])-1] cost = 1 delta = [[-1, 0], # go up [ 0,-1], # go left [ 1, 0], # go down [ 0, 1]] # go right delta_name = ['^', '<', 'v', '>'] def search(grid,init,goal,cost): # ---------------------------------------- # modify code below # ---------------------------------------- closed = [[0 for row in range(len(grid[0]))] for col in range(len(grid))] closed[init[0]][init[1]] = 1 x = init[0] y = init[1] g = 0 open = [[g, x, y]] expand = [[-1 for i in range(len(grid[0]))] for j in range(len(grid))] found = False # flag that is set when search is complete resign = False # flag set if we can't find expand step = 0 while not found and not resign: if len(open) == 0: resign = True else: open.sort() open.reverse() next = open.pop() x = next[1] y = next[2] g = next[0] expand[x][y] = step step += 1 if x == goal[0] and y == goal[1]: found = True else: for i in range(len(delta)): x2 = x + delta[i][0] y2 = y + delta[i][1] if x2 >= 0 and x2 < len(grid) and y2 >=0 and y2 < len(grid[0]): if closed[x2][y2] == 0 and grid[x2][y2] == 0: g2 = g + cost open.append([g2, x2, y2]) closed[x2][y2] = 1 return expand print search(grid,init,goal,cost) | cs |
1 2 3 4 5 6 | expand = [[-1 for i in range(len(grid[0]))] for j in range(len(grid))] step = 0 expand[x][y] = step step += 1 | cs |
위의 부분들만 수정이 되었다. 전체 -1을 가지는 지도와 동일한 사이즈의 행렬을 만들어 주고, 탐색을 할 때마다 step이라는 변수를 증가시켜 준다. 그리고, 이 step을 open된 위치에 대입해 주는 것이다. 휴, 쉬워서 다행이다.
=======================================================================
바로 다음 문제이다. (문제가 정말 많다.) 이번에는 최적의 루트를 보여주는 것이 과제이다. 위와 같이 말이다. 목적지에는 *을 쳐주고, 나머지는 빈칸으로 둔 다음, 화살표를 이용해서 위와 같이 경로를 보여주는 것이다.
상 하 좌 우에 대항되는 움직임을 delta라는 이름으로 설정하였고, delta_name이라는 이름으로 이를 화살표로 나타낸 문자열을 제공해 주었다. 다시 말하지만, 이 화살표는 최적의 경로를 찾은 다음, 그걸 보여주는 것임을 잊지 말자!! 그럼 문제 나간다.
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 | # ----------- # User Instructions: # # Modify the the search function so that it returns # a shortest path as follows: # # [['>', 'v', ' ', ' ', ' ', ' '], # [' ', '>', '>', '>', '>', 'v'], # [' ', ' ', ' ', ' ', ' ', 'v'], # [' ', ' ', ' ', ' ', ' ', 'v'], # [' ', ' ', ' ', ' ', ' ', '*']] # # Where '>', '<', '^', and 'v' refer to right, left, # up, and down motions. Note that the 'v' should be # lowercase. '*' should mark the goal cell. # # You may assume that all test cases for this function # will have a path from init to goal. # ---------- grid = [[0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 1, 0], [0, 0, 1, 0, 1, 0], [0, 0, 1, 0, 1, 0]] init = [0, 0] goal = [len(grid)-1, len(grid[0])-1] cost = 1 delta = [[-1, 0 ], # go up [ 0, -1], # go left [ 1, 0 ], # go down [ 0, 1 ]] # go right delta_name = ['^', '<', 'v', '>'] def search(grid,init,goal,cost): # ---------------------------------------- # modify code below # ---------------------------------------- closed = [[0 for row in range(len(grid[0]))] for col in range(len(grid))] closed[init[0]][init[1]] = 1 x = init[0] y = init[1] g = 0 open = [[g, x, y]] found = False # flag that is set when search is complete resign = False # flag set if we can't find expand while not found and not resign: if len(open) == 0: resign = True return 'fail' else: open.sort() open.reverse() next = open.pop() x = next[1] y = next[2] g = next[0] if x == goal[0] and y == goal[1]: found = True else: for i in range(len(delta)): x2 = x + delta[i][0] y2 = y + delta[i][1] if x2 >= 0 and x2 < len(grid) and y2 >=0 and y2 < len(grid[0]): if closed[x2][y2] == 0 and grid[x2][y2] == 0: g2 = g + cost open.append([g2, x2, y2]) closed[x2][y2] = 1 return expand # make sure you return the shortest path | cs |
이전에 했던 expand에서 좀 더 손을 보면 되기에 이전의 답을 디폴트로 준다.
생각의 시간...
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 | # ----------- # User Instructions: # # Modify the the search function so that it returns # a shortest path as follows: # # [['>', 'v', ' ', ' ', ' ', ' '], # [' ', '>', '>', '>', '>', 'v'], # [' ', ' ', ' ', ' ', ' ', 'v'], # [' ', ' ', ' ', ' ', ' ', 'v'], # [' ', ' ', ' ', ' ', ' ', '*']] # # Where '>', '<', '^', and 'v' refer to right, left, # up, and down motions. Note that the 'v' should be # lowercase. '*' should mark the goal cell. # # You may assume that all test cases for this function # will have a path from init to goal. # ---------- grid = [[0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 1, 0], [0, 0, 1, 0, 1, 0], [0, 0, 1, 0, 1, 0]] init = [0, 0] goal = [len(grid)-1, len(grid[0])-1] cost = 1 delta = [[-1, 0 ], # go up [ 0, -1], # go left [ 1, 0 ], # go down [ 0, 1 ]] # go right delta_name = ['^', '<', 'v', '>'] def search(grid,init,goal,cost): closed = [[0 for row in range(len(grid[0]))] for col in range(len(grid))] closed[init[0]][init[1]] = 1 action = [[' ' for row in range(len(grid[0]))] for col in range(len(grid))] policy = [[' ' for row in range(len(grid[0]))] for col in range(len(grid))] x = init[0] y = init[1] g = 0 open = [[g, x, y]] found = False # flag that is set when search complete resign = False # flag set if we can't find expand while not found and not resign: if len(open) == 0: resign = True return 'fail' else: open.sort() open.reverse() next = open.pop() x = next[1] y = next[2] g = next[0] if x == goal[0] and y == goal[1]: found = True else: # expand winning element and add to new open list for i in range(len(delta)): x2 = x + delta[i][0] y2 = y + delta[i][1] if x2 >= 0 and x2 < len(grid) and y2 >= 0 and y2 < len(grid[0]): if closed[x2][y2] == 0 and grid[x2][y2] == 0: g2 = g + cost open.append([g2, x2, y2]) closed[x2][y2] = 1 action[x2][y2] = i #action[x][y] = i #shows all routes, not shortest x = goal[0] y = goal[1] while x != 0 or y != 0: x2 = x - delta[action[x][y]][0] y2 = y - delta[action[x][y]][1] policy[x2][y2] = delta_name[action[x][y]] x = x2 y = y2 policy[goal[0]][goal[1]] = '*' #for i in range(len(policy)): # print(policy[i]) return policy | cs |
이게 답이다... 바뀐 부분들에 대해서만 설명을 조금 해보겠다.
1 2 | action = [[' ' for row in range(len(grid[0]))] for col in range(len(grid))] policy = [[' ' for row in range(len(grid[0]))] for col in range(len(grid))] | cs |
ACTION과 POLICY라는 배열(원본 지도와 크기가 같은)을 두개 설정해 주었다. POLICY는 최종적으로 화살표와 *표가 표시될 지도이고, ACTION은 각 위치에서 어떠한 위치가 행해졌는지를 저장해 둔 것이다. 실제로 ACTION을 보면
이렇게 생겼다. 각 숫자가 무엇을 의미하는지는 DELTA_NAME을 통해서 되짚어보자.
delta_name = ['^', '<', 'v', '>']
0은 위로, 1은 왼쪽... 이러한 움직임을 담고 있는 것이 ACTION이다. 이것이 어떻게 만들어지는지 보자면,
1 2 3 4 5 6 7 8 9 | for i in range(len(delta)): x2 = x + delta[i][0] y2 = y + delta[i][1] if x2 >= 0 and x2 < len(grid) and y2 >= 0 and y2 < len(grid[0]): if closed[x2][y2] == 0 and grid[x2][y2] == 0: g2 = g + cost open.append([g2, x2, y2]) closed[x2][y2] = 1 action[x2][y2] = i | cs |
마지막에 ACTION의 각 위치에 i를 집어넣어 준 것이 보인다. i가 무엇인가 하면, 위 for을 통해서 어떠한 delta(움직임)값이 시행되고 있었는지를 보이는 변수이다. 그래서 이렇게 action지도를 만들어 주면 이제 화살표만 policy에 그려주면 된다.
여기에서 나는 막혔다. ㅠ 모든 action들에 대해서 화살표를 그려주는 것이 아니라 최적의 경로에 대해서만 해주어야 함을 잊지 말자! 이를 하는 방법은 최종 위치에서부터 거슬러 올라가자 라는 것이다.
1 2 3 4 5 6 7 8 | x = goal[0] y = goal[1] while x != 0 or y != 0: x2 = x - delta[action[x][y]][0] y2 = y - delta[action[x][y]][1] policy[x2][y2] = delta_name[action[x][y]] x = x2 y = y2 | cs |
이렇게 목표 지점에서부터 거슬러 올라가야 한다. 참고로 나는 x2 y2와 x y사이에 무척이나 혼란이 왔었는데 그에 유의하길 바란다.
요즘 사놨던 게임들을 하다 보니까 포스트가 너무 밀렸다. ㅠㅠ search부분은 코드 과제가 너무 많고 설명해야 할 것도 많아 나눠서 포스트를 한다.
'공부' 카테고리의 다른 글
Control 공부했다. - PID Control (0) | 2019.01.08 |
---|---|
Search 공부했다. (2) (0) | 2019.01.05 |
Particle Filter 공부했다 (2) (0) | 2019.01.02 |
Particle Filter 공부했다. (0) | 2018.12.31 |
Kalman Filter 공부했다. - 2 차원 이동 실습 (0) | 2018.12.28 |