├── screenshot.png ├── test_images ├── test_image1.jpg ├── test_image2.jpg ├── test_image3.jpg └── test_image4.jpg ├── main.py ├── traversal.py ├── LICENSE.md ├── astarsearch.py ├── README.md └── process_image.py /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aniruddha-Tapas/Obstacle-Detection-and-Path-Planning/HEAD/screenshot.png -------------------------------------------------------------------------------- /test_images/test_image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aniruddha-Tapas/Obstacle-Detection-and-Path-Planning/HEAD/test_images/test_image1.jpg -------------------------------------------------------------------------------- /test_images/test_image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aniruddha-Tapas/Obstacle-Detection-and-Path-Planning/HEAD/test_images/test_image2.jpg -------------------------------------------------------------------------------- /test_images/test_image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aniruddha-Tapas/Obstacle-Detection-and-Path-Planning/HEAD/test_images/test_image3.jpg -------------------------------------------------------------------------------- /test_images/test_image4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aniruddha-Tapas/Obstacle-Detection-and-Path-Planning/HEAD/test_images/test_image4.jpg -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import process_image 2 | occupied_grids, planned_path = process_image.main("test_images/test_image3.jpg") 3 | print "Occupied Grids : " 4 | print occupied_grids 5 | print "Planned Path :" 6 | print planned_path 7 | 8 | -------------------------------------------------------------------------------- /traversal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | #Traversing through the image to perform image processing 4 | 5 | def sliding_window(image, stepSize, windowSize): 6 | # slide a window across the image 7 | for y in xrange(0, image.shape[0], stepSize): 8 | for x in xrange(0, image.shape[1], stepSize): 9 | # yield the current window 10 | yield (x, y, image[y:y + windowSize[1], x:x + windowSize[0]]) 11 | 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Aniruddha Tapas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /astarsearch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | #A* Search algorithm implementation to find the minimum path between 2 points 4 | def astar(m,startp,endp): 5 | w,h = 10,10 # 10x10(blocks) is the dimension of the input images 6 | sx,sy = startp #Start Point 7 | ex,ey = endp #End Point 8 | #[parent node, x, y, g, f] 9 | node = [None,sx,sy,0,abs(ex-sx)+abs(ey-sy)] 10 | closeList = [node] 11 | createdList = {} 12 | createdList[sy*w+sx] = node 13 | k=0 14 | while(closeList): 15 | node = closeList.pop(0) 16 | x = node[1] 17 | y = node[2] 18 | l = node[3]+1 19 | k+=1 20 | #find neighbours 21 | if k!=0: 22 | neighbours = ((x,y+1),(x,y-1),(x+1,y),(x-1,y)) 23 | else: 24 | neighbours = ((x+1,y),(x-1,y),(x,y+1),(x,y-1)) 25 | for nx,ny in neighbours: 26 | if nx==ex and ny==ey: 27 | path = [(ex,ey)] 28 | while node: 29 | path.append((node[1],node[2])) 30 | node = node[0] 31 | return list(reversed(path)) 32 | if 0<=nx>1 41 | if closeList[i][4]>nn[4]: 42 | closeList[i],closeList[nni] = nn,closeList[i] 43 | nni = i 44 | else: 45 | break 46 | return [] 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Obstacle Detection and Path Planning 4 | Path planning is a technique used to find the shortest path between a source and destination. 5 | Path planning ensures that navigation is done in least time and in most optimized way, saving energy and providing a optimized way of the doing task. 6 | 7 | ### Given: 8 | 9 | A set of test images, each containing 10 | 11 | 1. 10x10 grid, making 100 squares 12 | 2. Obstacles marked as black square 13 | 3. Objects defined by three features, viz. Shape, Size and Color 14 | 15 | 16 | 17 | The squares are identified by the coordinate (x,y) where x is the column and y is the row to which the square belongs. Each square 18 | can be empty or have an Obstacle or have an Object. 19 | 20 | ### The program returns 2 major findings: 21 | 22 | 23 | 24 | 25 | 1. The coordinates of occupied grid: 26 | 27 | The code returns a python list having ‘n’ python tuples, where ‘n’ denotes number of occupied grid in test image. Grid is to be considered occupied if either grid has an Obstacle or an Object. Each tuple has two elements, first element is the x-coordinate of an Obstacle/Object and second element is the y-coordinate of the Obstacle. 28 | 29 | 2. The minimum path: 30 | 31 | For each object in the test images, a matching object which is nearest to it is found using `compare_ssim` function from `scikit-image`. Object is said to be nearest to another Object, if length of path traversed between two objects is smallest. Traversal is done by moving either horizontally or vertically. The length of the path is determined by the number of moves made during traversal. [A* search](https://en.wikipedia.org/wiki/A*_search_algorithm) is used to find this shortest path. 32 | 33 | 34 | The code return a python dictionary. Format for creating dictionary is as follows: 35 | * Key for dictionary is a tuple - (x,y) coordinate of an Object 36 | * first element of dictionary is a tuple - (x,y) coordinate of an object nearest to it 37 | * second element is a list of tuples having (x,y) coordinate of all grids traversed i.e all route path 38 | * third element of dictionary should be number of moves taken for traversal 39 | 40 | 41 | ## Basic Usage 42 | 43 | Run `main.py` to check the results. 44 | You can edit the test image from main.py to see different results. 45 | 46 | The `process_image.py` contains the major code. 47 | Check that script to see the main functionality. 48 | Follow the comments to undertand the code better. 49 | 50 | `astarsearch.py` contains the implementation of A* search algo. 51 | 52 | `traversal.py` contains the script to traverse through the image to find objects/min path. 53 | 54 | ## Dependencies 55 | 56 | [Use pip to install.](https://pypi.python.org/pypi/pip) 57 | 58 | 1. Install OpenCV for Python 59 | 60 | [`For Windows`](http://docs.opencv.org/3.1.0/d5/de5/tutorial_py_setup_in_windows.html) 61 | 62 | [`For Ubuntu`](http://www.pyimagesearch.com/2015/06/22/install-opencv-3-0-and-python-2-7-on-ubuntu/) 63 | 64 | 65 | 2. Install skimage (or scikit-image) 66 | 67 | Open command prompt and type in: 68 | ```pip install scikit-image``` 69 | 70 | 3. Install numpy 71 | 72 | Open command prompt and type in: 73 | ```pip install numpy``` 74 | 75 | 76 |
77 | -------------------------------------------------------------------------------- /process_image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import cv2 4 | import numpy as np 5 | import time 6 | from skimage.measure import compare_ssim as ssim #to compare 2 images 7 | 8 | import astarsearch 9 | import traversal 10 | 11 | 12 | def main(image_filename): 13 | ''' 14 | Returns: 15 | 1 - List of tuples which is the coordinates for occupied grid. 16 | 2 - Dictionary with information of path. 17 | ''' 18 | 19 | occupied_grids = [] # List to store coordinates of occupied grid 20 | planned_path = {} # Dictionary to store information regarding path planning 21 | 22 | # load the image and define the window width and height 23 | image = cv2.imread(image_filename) 24 | (winW, winH) = (60, 60) # Size of individual cropped images 25 | 26 | obstacles = [] # List to store obstacles (black tiles) 27 | index = [1,1] 28 | blank_image = np.zeros((60,60,3), np.uint8) 29 | list_images = [[blank_image for i in xrange(10)] for i in xrange(10)] #array of list of images 30 | maze = [[0 for i in xrange(10)] for i in xrange(10)] #matrix to represent the grids of individual cropped images 31 | 32 | for (x, y, window) in traversal.sliding_window(image, stepSize=60, windowSize=(winW, winH)): 33 | # if the window does not meet our desired window size, ignore it 34 | if window.shape[0] != winH or window.shape[1] != winW: 35 | continue 36 | 37 | # print index 38 | clone = image.copy() 39 | cv2.rectangle(clone, (x, y), (x + winW, y + winH), (0, 255, 0), 2) 40 | crop_img = image[x:x + winW, y:y + winH] #crop the image 41 | list_images[index[0]-1][index[1]-1] = crop_img.copy() #Add it to the array of images 42 | 43 | average_color_per_row = np.average(crop_img, axis=0) 44 | average_color = np.average(average_color_per_row, axis=0) 45 | average_color = np.uint8(average_color) #Average color of the grids 46 | # print (average_color) 47 | 48 | if (any(i <= 240 for i in average_color)): #Check if grids are colored 49 | maze[index[1]-1][index[0]-1] = 1 #ie not majorly white 50 | occupied_grids.append(tuple(index)) #These grids are termed as occupied_grids 51 | # print ("occupied") #and set the corresponding integer in the maze as 1 52 | 53 | if (any(i <= 20 for i in average_color)): #Check if grids are black in color 54 | # print ("black obstacles") 55 | obstacles.append(tuple(index)) #add to obstacles list 56 | 57 | 58 | cv2.imshow("Window", clone) 59 | cv2.waitKey(1) 60 | time.sleep(0.025) 61 | 62 | #Iterate 63 | index[1] = index[1] + 1 64 | if(index[1]>10): 65 | index[0] = index[0] + 1 66 | index[1] = 1 67 | 68 | 69 | """ 70 | ########## 71 | #Uncomment this portion to print the occupied_grids (First Part Solution) 72 | 73 | print "Occupied Grids : " 74 | print occupied_grids 75 | """ 76 | 77 | 78 | """ 79 | #Printing other info 80 | print "Total no of Occupied Grids : " 81 | print len(occupied_grids) 82 | print "Obstacles : " 83 | print obstacles 84 | 85 | print "Map list: " 86 | print maze 87 | 88 | print "Map : " 89 | for x in xrange(10): 90 | for y in xrange(10): 91 | if(maze[x][y] == -1): 92 | print str(maze[x][y]), 93 | else: 94 | print " " + str(maze[x][y]), 95 | print "" 96 | """ 97 | 98 | #First part done 99 | ############################################################################## 100 | 101 | list_colored_grids = [n for n in occupied_grids if n not in obstacles] #Grids with objects (not black obstacles) 102 | """ 103 | print "Colored Occupied Grids : " 104 | print list_colored_grids 105 | print "Total no of Colored Occupied Grids : " + str(len(list_colored_grids)) 106 | """ 107 | 108 | #Compare each image in the list of objects with every other image in the same list 109 | #Most similar images return a ssim score of > 0.9 110 | #Find the min path from the startimage to this similar image u=by calling astar function 111 | 112 | for startimage in list_colored_grids: 113 | key_startimage = startimage 114 | img1 = list_images[startimage[0]-1][startimage[1]-1] 115 | for grid in [n for n in list_colored_grids if n != startimage]: 116 | img = list_images[grid[0]-1][grid[1]-1] 117 | # print "for {0} , other images are {1}".format(key_startimage, grid) 118 | image = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) 119 | image2 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 120 | s = ssim(image, image2) 121 | if s > 0.9: 122 | result = astarsearch.astar(maze,(startimage[0]-1,startimage[1]-1),(grid[0]-1,grid[1]-1)) 123 | # print result 124 | list2=[] 125 | for t in result: 126 | x,y = t[0],t[1] 127 | list2.append(tuple((x+1,y+1))) #Contains min path + startimage + endimage 128 | result = list(list2[1:-1]) #Result contains the minimum path required 129 | 130 | # print "similarity :" + str(s) 131 | if not result: #If no path is found; 132 | planned_path[startimage] = list(["NO PATH",[], 0]) 133 | planned_path[startimage] = list([str(grid),result,len(result)+1]) 134 | 135 | 136 | #print "Dictionary Keys pf planned_path:" 137 | #print planned_path.keys() 138 | 139 | for obj in list_colored_grids: 140 | if not(planned_path.has_key(obj)): #If no matched object is found; 141 | planned_path[obj] = list(["NO MATCH",[],0]) 142 | 143 | 144 | """ 145 | ########## 146 | #Uncomment this portion to print the planned_path (Second Part Solution) 147 | 148 | print "Planned path :" 149 | print planned_path 150 | """ 151 | 152 | #Second part done 153 | ############################################################################## 154 | 155 | return occupied_grids, planned_path 156 | 157 | 158 | 159 | if __name__ == '__main__': 160 | 161 | # change filename to check for other images 162 | image_filename = "test_images/test_image1.jpg" 163 | 164 | main(image_filename) 165 | 166 | cv2.waitKey(0) 167 | cv2.destroyAllWindows() 168 | --------------------------------------------------------------------------------