From 61ca9d8a4c93bb8700189e1797a63efe3d19c9bc Mon Sep 17 00:00:00 2001 From: Rui Jie Li <rui-jie.li@polymtl.ca> Date: Thu, 25 Feb 2021 17:14:02 -0500 Subject: [PATCH] separated the files in black white, edges, and color detection --- .../find_contour_kmeans/black_and_white.py | 47 ++++++ .../find_contour_kmeans/edgeDetector.py | 134 ------------------ .../find_contour_kmeans/edge_detector.py | 103 ++++++++++++++ .../{hsvDetector.py => hsv_detector.py} | 37 ++--- 4 files changed, 170 insertions(+), 151 deletions(-) create mode 100644 robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/black_and_white.py delete mode 100644 robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/edgeDetector.py create mode 100644 robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/edge_detector.py rename robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/{hsvDetector.py => hsv_detector.py} (65%) diff --git a/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/black_and_white.py b/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/black_and_white.py new file mode 100644 index 0000000..7106586 --- /dev/null +++ b/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/black_and_white.py @@ -0,0 +1,47 @@ +import cv2 as cv +import numpy as np + + +def debug(image): + cv.imshow("debug", image) + cv.waitKey(0) +def debug_contours(contours, image): + mask = np.zeros((image.shape[0], image.shape[1])) + cv.drawContours(mask, contours, -1, (255), 1) + cv.imshow("debug", mask) + cv.waitKey(0) + +class BlackWhiteTransform: + # For iterations with kmeans + def __init__(self, percentage_led_area = 0.01, nb_color = 4, max_iter = 1, accuracy = 1): + self.percetage_led_area = percentage_led_area + self.nb_color = nb_color + self.max_iter = max_iter + self.accuracy = accuracy + + def convert_to_black_white(self, image, already_grayscale = False): + # Transform the image into grascale, then use kmeans to group the different shades of + # Grey + + min_area = image.shape[0] * image.shape[1] * self.percetage_led_area + + grey = cv.cvtColor(image, cv.COLOR_BGR2GRAY) + shape = grey.shape + + grey = grey.reshape(grey.shape[0] * grey.shape[1], 1) + grey = np.float32(grey) + + # Criteria is the number of iterations + # Format : (type, max_iteration, accuracy) + criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER,\ + self.max_iter, self.accuracy) + + ret,label,center=cv.kmeans(grey,self.nb_color,None,\ + criteria,1,cv.KMEANS_RANDOM_CENTERS) + + center = np.uint8(center) + res = center[label.flatten()] + kmeans = res.reshape(shape) + + black_white = cv.threshold(kmeans, np.average(grey), 255, cv.THRESH_BINARY)[1] + return black_white \ No newline at end of file diff --git a/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/edgeDetector.py b/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/edgeDetector.py deleted file mode 100644 index 3fca25f..0000000 --- a/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/edgeDetector.py +++ /dev/null @@ -1,134 +0,0 @@ -import numpy as np -import cv2 as cv -import matplotlib.pyplot as plt -from math import pi as PI -# for debugging purposes -gsize = None -ginertia = None -ls = [] - -class Colors: - RED = "RED" - BLUE = "BLUE" - GREY = "GREY" - -def debug(image): - cv.imshow("debug", image) - cv.waitKey(0) -def debug_contours(contours, image): - mask = np.zeros((image.shape[0], image.shape[1])) - cv.drawContours(mask, contours, -1, (255), 1) - cv.imshow("debug", mask) - cv.waitKey(0) - -class EdgeDetector: - # threshold for circularity (circle = 1); TBD - # circularity = 4 * PI * AREA / (PERIMETER ^ 2) - # TODO: change this according to the size of the image - # in general, decrease this more bigger images - CIRCLE_THRESHOLD = 0.4 # based on test values of 0.72, 0.8, 0.68 - - # threshold for concavity; the formula is area_of_blob / area_convexity_hull, TBD - CONVEX_THRESHOLD = 0.85 # based on values around 0.95 and 0.98 - - # threshold for how elongated it is (called intertia in opencv), TBD (circle = 1) - # TODO : change this according to the size of the image - # in general, inertia is bigger - MIN_INERTIA = 3 # 1.1 1.3 pour les petits images - MAX_INERTIA = 5.5 - - MIN_AREA = None # to be initialized to 0.02 * area of image - # for iterations with kmeans - NB_COLOR = 4 # allows for : black, white, blue, red - MAX_ITER = 1 # maximum of 1 iterations - ACCURACY = 1 # not sure what this does ... - - def __init__(self): - pass - - def calculate_inertia(self, image, shape) -> int: - pass - - # finds the contours of the LEDs and returns them - def find_contours(self, image, area = False, distance = False, already_grayscale = False): - # transform the image into black and white - # first go to grayscale then goto black white with a threshold - EdgeDetector.MIN_AREA = image.shape[0] * image.shape[1] * 0.01 - grey = image - if not already_grayscale: - grey = cv.cvtColor(image, cv.COLOR_BGR2GRAY) - shape = grey.shape - debug(grey) - # O(N) --> loop over every pixel - grey = grey.reshape(grey.shape[0] * grey.shape[1], 1) - # O(N) --> loop over every pixel - grey = np.float32(grey) - - # criteria is the number of iterations - # format : (type, max_iteration, accuracy) - criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER,\ - EdgeDetector.MAX_ITER, EdgeDetector.ACCURACY) - - ret,label,center=cv.kmeans(grey,EdgeDetector.NB_COLOR,None,\ - criteria,1,cv.KMEANS_RANDOM_CENTERS) - - center = np.uint8(center) - res = center[label.flatten()] - kmeans = res.reshape(shape) - - # O(N) --> iterate over every pixels - black_white = cv.threshold(kmeans, np.average(grey), 255, cv.THRESH_BINARY)[1] - contours, hierarchy = cv.findContours(black_white, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) - # note : other versions of OpenCV will require image, contours, hierarchy - # now for the characteristics of the contours - - retained_contours = [] - for contour in contours: - # TODO : something wrong here, probably with area - area = cv.contourArea(contour) - if area >= EdgeDetector.MIN_AREA: - # circularity < threshold : is the contour NOT circular? - circularity = 4*PI*area / (cv.arcLength(contour, True) * cv.arcLength(contour, True)) - # convexity > threashold : we want the shape to be convex - convexity = area / cv.contourArea(cv.convexHull(contour)) - # opencv docs aren't very helpful on this but looks like inertia is aspect ratio - (x,y),(width,height),theta = cv.minAreaRect(contour) - # this is basically aspect ratio - inertia = width / height - if inertia < 1: - inertia = 1/inertia - # note : the angle is pretty much useless - print("calculated circularity : ", circularity) - print("threshold circularity : ", EdgeDetector.CIRCLE_THRESHOLD) - print() - print("calculated converxity : ", convexity) - print("threshold converxity : ", EdgeDetector.CONVEX_THRESHOLD) - print() - print("calculated inertia : ", inertia) - print("threshold inertia : ", EdgeDetector.MIN_INERTIA, EdgeDetector.MAX_INERTIA) - print() - print("image area : ", image.shape[0] * image.shape[1]) - print("calculated area : ", area) - print() - print("detected angle : ", theta) - print("displaying contour, press any key to continue ... ") - debug_contours([contour], image) - if circularity > EdgeDetector.CIRCLE_THRESHOLD and \ - convexity > EdgeDetector.CONVEX_THRESHOLD and \ - inertia > EdgeDetector.MIN_INERTIA and \ - inertia < EdgeDetector.MAX_INERTIA: - retained_contours.append(contour) - print("retained !") - else: - print("rejected !") - print("----------------------------------") - # use HSV instead - # 1. color = red or blue - # use the other 2 to see if its gray - return retained_contours - -if __name__ == '__main__': - import os - image = cv.imread("test9.PNG") - gbd = EdgeDetector() - print(len(gbd.find_contours(image))) \ No newline at end of file diff --git a/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/edge_detector.py b/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/edge_detector.py new file mode 100644 index 0000000..1c94e7c --- /dev/null +++ b/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/edge_detector.py @@ -0,0 +1,103 @@ +import numpy as np +import cv2 as cv +import matplotlib.pyplot as plt +from math import pi as PI +from black_and_white import BlackWhiteTransform +import logging + +logger = logging.getLogger(__name__) + +# For debugging purposes +gsize = None +ginertia = None +ls = [] + +class Colors: + RED = "RED" + BLUE = "BLUE" + GREY = "GREY" + +def debug(image): + cv.imshow("debug", image) + cv.waitKey(0) + +def debug_contours(contours, image): + mask = np.zeros((image.shape[0], image.shape[1])) + cv.drawContours(mask, contours, -1, (255), 1) + cv.imshow("debug", mask) + cv.waitKey(0) + +class EdgeDetector: + # Threshold for circularity (circle = 1); to be determined + # Circularity = 4 * PI * AREA / (PERIMETER ^ 2) + # In general, decrease this more bigger images + # Based on test values of 0.72, 0.8, 0.68 seems like 0.4 is a good value + # Threshold for concavity; the formula is area_of_blob / area_convexity_hull + # Based on values around 0.95 and 0.98 + + def __init__(self, circle_threshold = 0.4, convex_threshold = 0.85, min_inertia = 3, max_intertia = 5.5, percentage_led_area = 0.01): + self.circle_threshold = circle_threshold + self.convex_threshold = convex_threshold + self.min_inertia = min_inertia + self.max_inertia = max_intertia + self.percentage_led_area = percentage_led_area + + # Finds the contours of the LEDs and returns them + def get_edges(self, black_white_image, area = False, distance = False, already_grayscale = False): + + contours, hierarchy = cv.findContours(black_white_image, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) + min_area = black_white_image.shape[0] * black_white_image.shape[1] * self.percentage_led_area + # Note : other versions of OpenCV will require image, contours, hierarchy + # Now for the characteristics of the contours + retained_contours = [] + for contour in contours: + # TODO : something wrong here, probably with area + area = cv.contourArea(contour) + if area >= min_area: + # Circularity < threshold : is the contour NOT circular? + circularity = 4*PI*area / (cv.arcLength(contour, True) * cv.arcLength(contour, True)) + # Convexity > threashold : we want the shape to be convex + convexity = area / cv.contourArea(cv.convexHull(contour)) + # Opencv docs aren't very helpful on this but looks like inertia is aspect ratio + (x,y),(width,height),theta = cv.minAreaRect(contour) + # This is basically aspect ratio + inertia = width / height + if inertia < 1: + inertia = 1/inertia + # Note : the angle is pretty much useless + logger.debug("calculated circularity : ", circularity) + logger.debug("threshold circularity : ", self.circle_threshold) + logger.debug("\n") + logger.debug("calculated converxity : ", convexity) + logger.debug("threshold converxity : ", self.convex_threshold) + logger.debug("\n") + logger.debug("calculated inertia : ", inertia) + logger.debug("threshold inertia : ", self.min_inertia, self.max_inertia) + logger.debug("\n") + logger.debug("image area : ", black_white_image.shape[0] * black_white_image.shape[1]) + logger.debug("calculated area : ", area) + logger.debug("\n") + logger.debug("detected angle : ", theta) + logger.debug("displaying contour, press any key to continue ... ") + debug_contours([contour], black_white_image) + # Make it able to switch on and off (ex. do not consider circularity) + if circularity > self.circle_threshold and \ + convexity > self.convex_threshold and \ + inertia > self.min_inertia and \ + inertia < self.max_inertia: + retained_contours.append(contour) + logger.debug("retained !") + else: + logger.debug("rejected !") + logger.debug("----------------------------------") + # Use HSV instead + # 1. color = red or blue + # Use the other 2 to see if its gray + return retained_contours + +if __name__ == '__main__': + import os + logger.setLevel("DEBUG") # Debug, info, warning, erreur + image = cv.imread("test11.PNG") + gbd = EdgeDetector() + logger.debug(len(gbd.find_contours(image))) \ No newline at end of file diff --git a/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/hsvDetector.py b/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/hsv_detector.py similarity index 65% rename from robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/hsvDetector.py rename to robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/hsv_detector.py index 3caec7d..6f0cdbe 100644 --- a/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/hsvDetector.py +++ b/robots-at-robots/research/robots_at_robots/armor_color/find_contour_kmeans/hsv_detector.py @@ -1,6 +1,10 @@ import cv2 as cv import numpy as np -import edgeDetector +from edge_detector import EdgeDetector +from black_and_white import BlackWhiteTransform +import logging + +logger = logging.getLogger(__name__) def debug(image): cv.imshow("debug", image) @@ -8,15 +12,12 @@ def debug(image): class HSVColorDetector: - RED = 180 - BLUE = 45 - - MIN_GRAY = 60 # not enough testing, but around 100 for normal, 40 for gray + def __init__(self, red = 180, blue = 45, min_gray = 60): + self.red = red + self.blue = blue + self.min_gray = min_gray - def __init__(self): - pass - - def detect_color(self, image, is_hsv = False): + def detect_color(self, image, contours, shape, is_hsv = False): # WARNING: greyscale is NOT the same as the value in HSV # the formula for BGR to grayscale is # Gray = 0.299*Red + 0.587*Green + 0.114*Blue @@ -32,10 +33,7 @@ class HSVColorDetector: # note : do not use sine or cosine for this, its like 10x slower than # just doing adding or substracting hue = np.abs(180 - hue) # due to angles looping back - edge = edgeDetector.EdgeDetector() - - contours = edge.find_contours(greyscale, already_grayscale=True) - mask = np.zeros(greyscale.shape) + mask = np.zeros(shape) mask = cv.drawContours(mask, contours, -1, (255), 1) hue_mask = np.ma.array(data=hue, mask=mask) sat_mask = np.ma.array(data=saturation, mask=mask) @@ -44,19 +42,24 @@ class HSVColorDetector: satvalue = sat_mask.mean() print(huevalue) print(satvalue) - if (abs(huevalue - HSVColorDetector.BLUE) < abs(huevalue - HSVColorDetector.RED)): + if (abs(huevalue - self.blue) < abs(huevalue - self.red)): print("this is probably blue") else: print("this is probably red") - if satvalue < HSVColorDetector.MIN_GRAY: + if satvalue < self.min_gray: print("this is probably gray") else: print("this is probably not gray") if __name__ == '__main__': - image = cv.imread("test8.PNG") + logger.setLevel("DEBUG") + image = cv.imread("test11.PNG") + bwt = BlackWhiteTransform() + black_white_image = bwt.convert_to_black_white(image) + ed = EdgeDetector() + edges = ed.get_edges(black_white_image) hsv = HSVColorDetector() - hsv.detect_color(image) + hsv.detect_color(image, edges, black_white_image.shape) -- GitLab