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