title: Shape Detection in python using OpenCV
body: <p>I'm working on a project in which I use OpenCV to detect shapes and their colors.</p>
  
  <p>There are 5 colors (red, green, yellow, blue and white) and 4 shapes (rectangle, star, circle and heart). I've been able to reliably discern the colors and I can detect the shapes when the image used is a drawn image like <a href="https://i.stack.imgur.com/kapSd.png" rel="nofollow noreferrer">this</a>
  using this code. Note, the image is for demonstration only, the range values in my code are not for these colors.</p>
  
  <pre><code>import cv2
  import numpy as np
  class Shape():
  
      def __init__(self, color, shape, x, y, approx):
          self.color = color
          self.shape = shape
          self.x = x
          self.y = y
          self.approx = approx
  def closing(mask):
  kernel = np.ones((7,7),np.uint8) 
  closing = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
  return closing
  
  def opening(mask):
      kernel = np.ones((6,6),np.uint8)
      opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
      return opening
  
  #Define Red
  lower_red = np.array([0, 90, 60], dtype=np.uint8)
  upper_red = np.array([10, 255, 255], dtype=np.uint8)
  red = [lower_red, upper_red, 'red']
  
  #Define Green
  lower_green = np.array([60, 55, 0], dtype=np.uint8)
  upper_green = np.array([100, 255, 120], dtype=np.uint8)
  green = [lower_green, upper_green, 'green']
  
  #Define Blue
  lower_blue = np.array([90, 20, 60], dtype=np.uint8)
  upper_blue = np.array([130, 255, 180], dtype=np.uint8)
  blue = [lower_blue, upper_blue, 'blue']
  
  #Define Yellow
  lower_yellow = np.array([5, 110, 200], dtype=np.uint8)
  upper_yellow = np.array([50, 255, 255], dtype=np.uint8)
  yellow = [lower_yellow, upper_yellow, 'yellow']
  
  #Define White
  lower_white = np.array([0, 90, 60], dtype=np.uint8)
  upper_white = np.array([10, 255, 255], dtype=np.uint8)
  white = [lower_white, upper_white ,'white']
  
  colors = [red, green, blue, yellow, white]
  
  def detect_shapes(image_location):
      #Open image
      img = cv2.imread(image_location)
  
      #Convert to hsv
      hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
  
      #Shape list
      shapes = []
  
      #Lay over masks and detect shapes
      for color in colors:
          mask = cv2.inRange(hsv, color[0], color[1])
          mask = closing(mask)
          mask = opening(mask)
          contours, h = cv2.findContours(mask, 1, cv2.CHAIN_APPROX_SIMPLE)
          contours.sort(key = len)
          for contour in contours[-3:]:
              #Amount of edges
              approx = cv2.approxPolyDP(contour, 0.01*cv2.arcLength(contour, True), True)
              #Center locations
              M = cv2.moments(contour)
              if M['m00'] == 0.0:
                  continue
              centroid_x = int(M['m10']/M['m00'])
              centroid_y = int(M['m01']/M['m00'])
  
              if len(approx) == 4:
                  shape_name = 'rectangle'
              elif len(approx) == 10:
                  shape_name = 'star'
              elif len(approx) &gt;= 11:
                  shape_name = 'oval'
              else:
                  shape_name ='undefined'
  
              shape = Shape(color[2], shape_name, centroid_x, centroid_y, len(approx))
              shapes.append(shape)
  
      return shapes
  </code></pre>
  
  <p>This is largely based off the answers on <a href="https://stackoverflow.com/questions/11424002/how-to-detect-simple-geometric-shapes-using-opencv">this question</a>.</p>
  
  <p>However, when I try to detect the shapes on an actual photo, I cannot reliably use this. The amount of edges I get varies wildly. <a href="https://imgur.com/JI4IXsH" rel="nofollow noreferrer">This</a> is an example of a photo I need to recognize the shapes on. 
  I'm guessing this happens because of small imperfections on the edges of the shapes, but I can't figure out how I would approximate those edges with straight lines, or how I would reliably recognize circles. What would I need to change in the code to do this? Intensive googling hasn't given me an answer yet but that might be because I'm not using the correct terminology in the searches...</p>
  
  <p>Also, if this question is not formatted correctly, let me know!</p>