milind-u | 7d834eb | 2021-11-20 08:46:11 -0800 | [diff] [blame] | 1 | import cv2 as cv |
| 2 | import enum |
| 3 | import numpy as np |
| 4 | |
| 5 | |
| 6 | class Rect: |
| 7 | """ |
| 8 | Holds points for a rectangle in an image. |
| 9 | This section of the image is where to expect a ball. |
| 10 | """ |
| 11 | |
| 12 | # x1 and y1 are top left corner, x2 and y2 are bottom right |
| 13 | def __init__(self, x1, y1, x2, y2): |
| 14 | self.x1 = x1 |
| 15 | self.y1 = y1 |
| 16 | self.x2 = x2 |
| 17 | self.y2 = y2 |
| 18 | |
| 19 | def __str__(self): |
| 20 | return "({}, {}), ({}, {})".format(self.x1, self.y1, self.x2, self.y2) |
| 21 | |
| 22 | |
| 23 | class Alliance(enum.Enum): |
| 24 | RED = enum.auto() |
| 25 | BLUE = enum.auto() |
| 26 | UNKNOWN = enum.auto() |
| 27 | |
| 28 | |
| 29 | class Letter(enum.Enum): |
| 30 | A = enum.auto() |
| 31 | B = enum.auto() |
| 32 | |
| 33 | |
| 34 | class Path: |
| 35 | """ |
| 36 | Each path (ex. Red A, Blue B, etc.) contains a Letter, Alliance, and |
| 37 | 2-3 rectangles (the places to expect balls in). |
| 38 | There may be only 2 rectangles if there isn't a clear view at all of the balls. |
| 39 | """ |
| 40 | |
| 41 | def __init__(self, letter, alliance, rects): |
| 42 | self.letter = letter |
| 43 | self.alliance = alliance |
| 44 | self.rects = rects |
| 45 | |
| 46 | def __str__(self): |
| 47 | return "%s %s: " % (self.alliance.value, self.letter.value) |
| 48 | |
| 49 | |
| 50 | # TODO: view each of the 4 images in this folder by running `./img_viewer.py <image_file>`, |
| 51 | # and figure out the retangle bounds for each of the 3 balls in each of the 4 paths. |
| 52 | # You can move your cursor to the endpoints of the rectangle, and it will show |
| 53 | # the coordinates. |
| 54 | # Note that in some images, there might not be a good view of 3 balls and you might have to just use rects of 2. |
| 55 | # That is ok. |
| 56 | # Add a new Path to this list for each image. |
| 57 | PATHS = [] |
| 58 | |
| 59 | # TODO: fill out the other constants below as you are writing the code in functions |
| 60 | # galactic_search_path and _pct_yellow |
| 61 | |
| 62 | # TODO: figure out the bounds for filtering just like in the video for the red hat. |
| 63 | # Instead of how the person in the video figured them out, run `./img_viewer.py --hsv <image_file>` |
| 64 | # to view the images in hsv. |
| 65 | # Then, move your cursor around the image and it will display the hue, saturation, and value |
| 66 | # of the pixel you are hovering over. Record the mininum and maximum h, s, and v of all the balls |
| 67 | # in all photos here. |
| 68 | LOWER_YELLOW = np.array([0, 0, 0], dtype=np.uint8) |
| 69 | HIGHER_YELLOW = np.array([255, 255, 255], dtype=np.uint8) |
| 70 | |
| 71 | # TODO: once you get to the eroding/dilating step below, |
| 72 | # tune the kernel by trying different sizes (3, 5 ,7). |
| 73 | # You can see if your kernel erodes and dilates properly, |
| 74 | # because when you run the test it will write the image to test_<alliance>_<letter>.png |
| 75 | # which you can view using img_viewer.py |
| 76 | # If needed, you can also use different kernels for eroding and dilating. |
| 77 | KERNEL = np.ones((0, 0), np.uint8) |
| 78 | |
| 79 | # Portion of yellow in a rectangle (0 to 1) required for it to be considered as containing a ball. |
| 80 | # TODO: Try different values for this until it correctly reflects whether a ball is in an rectangle |
| 81 | # or not. |
| 82 | BALL_PCT_THRESHOLD = 0 |
| 83 | |
| 84 | |
| 85 | def galactic_search_path(img_path): |
| 86 | # TODO: read image from img_path into the img variable |
| 87 | img = None |
| 88 | |
| 89 | # TODO: convert img into hsv |
| 90 | hsv = None |
| 91 | |
| 92 | # TODO: filter yellow using your bounds for yellow and cv.inRange, creating a binary mask |
| 93 | mask = None |
| 94 | |
| 95 | # TODO: erode and dilate the mask, and maybe try different numbers of iterations |
| 96 | mask = None |
| 97 | mask = None |
| 98 | |
| 99 | correct_path = None |
| 100 | for path in PATHS: |
| 101 | # TODO: If all the percentages are atleast BALL_PCT_THRESHOLD, |
| 102 | # then you can say that this path is present on the field and store it. |
| 103 | pcts = _pct_yellow(mask, path.rects) |
| 104 | |
| 105 | # TODO: make sure that a path was found, and if not |
| 106 | # make sure that correct_path has Alliance.UNKNOWN |
| 107 | |
| 108 | return mask, correct_path |
| 109 | |
| 110 | |
| 111 | # This function finds the percentage of yellow pixels in the rectangles |
| 112 | # given that are regions of the given image. This allows us to determine |
| 113 | # whether there is a ball in those rectangles |
| 114 | def _pct_yellow(mask, rects): |
| 115 | pcts = np.zeros(len(rects)) |
| 116 | for i in range(len(rects)): |
| 117 | # TODO: set pcts[i] to be the ratio of the number of yellow pixels in the current rectangle |
| 118 | # to the total number of pixels in it. |
| 119 | # You can take the section of the mask that is the rectangle, and then count the number of pixels |
| 120 | # that aren't zero there with np.count_nonzero to do so, |
| 121 | # since mask is a 2d array of either 0 or 255. |
| 122 | pass |
| 123 | |
| 124 | return pcts |