3

screenshotI'm trying to run this project on my Mac.

There are two main python files in this project I have to run and that's calibrate.py, to open the camera and get the images, and detectCoins.py, but I'm getting this error every time when I try to build detectCoins.py:

File "detectCoins.py", line 226, in <module>
    scale = finalHeight / im.shape[0]
AttributeError: 'NoneType' object has no attribute 'shape'

Can anyone tell me how to run this project properly? Thanks. the code

import numpy as np
import logging, sys
import yaml

## =========================================================================
# select video & camera and whether to use calibration
VIDEO = 0
camera = 1
CAL = True
# Beaware that camera calibration can move some objects out of the frame
# and as a result contours will be incomplete and return a very small area
#
## ==========================================================================

#qIDref = 2.00*25.4
# ID-1 85.60 x 53.98mm
IDrefW = 53.98
IDrefL = 85.60

def adjust_gamma(image, gamma=1.0):
  # build a lookup table mapping the pixel values [0, 255] to
  # their adjusted gamma values
  invGamma = 1.0 / gamma
  table = np.array([((i / 255.0) ** invGamma) * 255
      for i in np.arange(0, 256)]).astype("uint8")

  # apply gamma correction using the lookup table
  return cv2.LUT(image, table)

def getBoxDim(box1):
  # return length and width of rotated rect
  L = box1[1][1]
  W = box1[1][0]

  return max(L,W), min(L,W)

def checkCoinDia(rc, Wp, Lp, cent):
  # check if radius is a valid coin
  ## absolute size using L and W, dia (mm)
  ##
  ## dime:        17.91
  ## penny:       19.05
  ## nickel:      21.21
  ## quarter:     24.26
  ## fifty-cent:  30.61
  ## ID-1:        (85.6 x 53.98)
  ## business crd ( 3.5*25.4 x 2.0*25.4 )
  dia = rc * 2.0 * (IDrefW) / Wp

  # check coin dia in mm and color flag, penny == True
  result = -1.0
  if dia< 16.2:
      result = -1.0
  elif dia < 18.48:
      if penny == True:
         result = 1.0
      else:
          result = 10.0
  elif dia < 20.13:
      if penny == True:
         result = 1.0
      else:
         result = 10.0
  elif dia < 22.8:
      if penny == True:
          result = 1.0
      else:
          result = 5.0
  elif dia < 27.43:
      result = 25.0
  elif dia < 32.0:
      result = 50.0

  return result, dia

def checkCenter(img, cir, thresh = [30,15]):
  # Check if center (x,y) is greater than bkgd threshold
  cir1 = cir.copy()
  radius = np.int32(6)
  # overwrite radius
  cir1[2] = radius
  metric, _ = getCircleColor(img, cir1, 'hsv')

  #yg = (thresh[1]/(thresh[0]+1)) * metric[0] + 10
  #val = metric[0]

  if metric[0] < 0.4*thresh[0]:
      return True, metric
  else:
      return False, metric


def getCircleColor(image, circ, flag = 'hsv'):
  # return mean of colors over circle from RGB input image
  if flag == 'hsv':
      color = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
  elif flag == 'lab':
      color = cv2.cvtColor(image, cv2.COLOR_BGR2LAB) 
  else:
      color = image
  # https://stackoverflow.com/questions/10469235/opencv-apply-mask-to-a-color-image
  circ = np.uint16(np.around(circ))
  mask = np.full((image.shape[0], image.shape[1]), 0, dtype=np.uint8)   
  cv2.circle(mask, (circ[0], circ[1]), circ[2], (255, 255, 255), -1)

  metric = cv2.mean(color, mask)

  return metric, mask


def getDimePennyDecision(img, circ):
  # use hsv s-v space to discern dime from pennies
  metric, _ = getCircleColor(img, circ, flag = 'hsv')
  h = metric[0]
  s = metric[1]

  penny = True
  #if (1.10 * h -s + 38 > 0):
  if (6.4 * h - s - 75 > 0):
      penny = False

  return penny

def  findRangeAllContours(contours):
   # find x-y range of ALL objects & ID card
   xmin = 9999
   ymin = 9999
   xmax = 0
   ymax = 0

   for c in contours:       
       extLeft = tuple(c[c[:, :, 0].argmin()][0])
       extRight = tuple(c[c[:, :, 0].argmax()][0])
       extTop = tuple(c[c[:, :, 1].argmin()][0])
       extBot = tuple(c[c[:, :, 1].argmax()][0])

       if extLeft[0] < xmin:
           xmin = extLeft[0]
       if extRight[0] > xmax:
           xmax = extRight[0]
       if extTop[1] < ymin:
           ymin = extTop[1]
       if extBot[1] > ymax:
           ymax = extBot[1]

   rect = [(xmin,ymin), (xmax,ymax)]

   return rect

def  getBkgdMetric(contour, flag = 'hsv'):
   # find color of background
   #
   rectRange = findRangeAllContours(contour)
   x1 = int(rectRange[0][0]/2)
   y1 = int(rectRange[0][1]/2)
   r = int(0.5 * np.sqrt(x1**2 + y1**2))
   circ = [x1, y1, min(r, 15)]
   metric_bkgd, mask_bkgd =  getCircleColor(blurred, circ, 'hsv')

   return metric_bkgd, rectRange

""" ============ main ======================================
"""
if __name__ == '__main__':

logging.basicConfig(filename='coins.log',filemode='w',level=logging.DEBUG)
logging.info("Program started")    

if VIDEO:
   cap = cv2.VideoCapture(camera)
   logging.info("video capture")
   if (cap.isOpened()== False):
      logging.info("Error opening video stream or file")
else:
   im = cv2.imread('..\images/ID_card1.jpg')
   im = cv2.imread('..\images/ID_card2.jpg')
   im = cv2.imread('..\images/Lucky_ID.jpg')
   im = cv2.imread('..\images/ID_card1.jpg')
   im = cv2.imread('..\images/new_blk.jpg')
   #im = cv2.imread('..\images/compare_blk.jpg')
   im = cv2.imread('..\images/test_final2.jpg')
   #im = cv2.imread('..\images/non-touch.jpg')
   #im = cv2.imread('..\images\ID-1.jpg')
   #im = cv2.imread('..\images/velvBkgd.jpg')
   logging.info("still image")


while(True):
# read image
# ========================================================

   if VIDEO:
      success, im = cap.read()
      if not success:
         logging.info('Failed to read video')
         sys.exit(1)

   # We will run Object detection at an fixed height image
   finalHeight = 640
   # resize image to height finalHeight
   scale = finalHeight / im.shape[0]
   image = cv2.resize(im, None, fx=scale, fy=scale)
   cv2.imshow("Original",image)


   """ Apply camera calibration here, using stored matrices
   # ======================================================
   # file used is calibrate.py
   # matrices stored are:
   """

   if CAL:
     with open('calibration.yaml') as f:
        loadeddict = yaml.load(f)
        K = loadeddict.get('camera_matrix')
        K = np.array(K)
        d = loadeddict.get('dist_coeff')    
        d = np.array(d) 

     # Read an example image and acquire its size
     h, w = image.shape[:2]
     # Generate new camera matrix from parameters
     newcameramatrix, roi = cv2.getOptimalNewCameraMatrix(K, d, (w,h), 0)
     # Generate look-up tables for remapping the camera image
     mapx, mapy = cv2.initUndistortRectifyMap(K, d, None, newcameramatrix, (w, h), 5)
     # Remap the original image to a new image
     newimg = cv2.remap(image, mapx, mapy, cv2.INTER_LINEAR)

     # Display old and new image
     if(0):
        cv2.imshow("Before map", image)
        cv2.imshow("After map", newimg)

     imageCorr = newimg

   else:
     imageCorr = image

   """ alter gamma
   """
   gamma = adjust_gamma(imageCorr, 2.2)

   """ processing pipeline:
   # ==========================================================
   #   calibrate camera & distortion
   #   gamma
   #   blur before gray
   #   gray blurred image
   #   edge gray image
   #
   """     
   output = gamma.copy()

   # process image
   blurred = cv2.GaussianBlur(gamma, (3,3), 0)
   cv2.imshow("Blurred", blurred) 

   gray = cv2.cvtColor(blurred, cv2.COLOR_BGR2GRAY)
   cv2.imshow("gray", gray)

   edged = cv2.Canny(gray, 50, 200, 10) # 50,200
   cv2.imshow("Canny", edged)

   """ find bounding boxes for coins and ID-1
   # ===========================================================
   # find all contours:
   # Pick out largest area as the ID-1 reference
   # use cv2.minAreaRect(cnt) on largest bounding box
   # standard ID-1 as reference (bank card or ID card)
   #
   # Note: contours NOT good for detecting touching coins
   # need segmentation for that.
   """
   (_,contours,_) = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

   ## debug --- draw contours
   #item1 = 0
   #for circ in contours:
      #cv2.drawContours(output, circ, -1, (0,0,255), 3)
      #item1 = item1+1
      #cv2.imshow('contours',output)
      #cv2.waitKey(2000)


   """ get background color
   """
   metric_bkgd, error = getBkgdMetric(contours, 'hsv')

   #print((metric_bkgd))

   if error[0][0]==0 or error[1][0]==image.shape[1]:
       print("ERROR: clipping image in x")
   if error[0][1]==0 or error[1][1]==image.shape[1]:
       print("ERROR clipping image in y")

   cmax = max(contours, key = cv2.contourArea)
   rectID = cv2.minAreaRect(cmax)  # find rotated rectangle

   pnts = cv2.boxPoints(rectID)
   box = np.int0(pnts)
   cv2.drawContours(output,[box],0,(255,0,0),2)
   logging.info("box ID1")
   logging.info(box)
   logging.info("")

   """ find coins and draw circle & bounding rectange using HughCircles
   # ==================================================================
   """
   HIGH = 175 # param1
   LOW = 45   # param2
   circles1 = 25  # set max upper limit for coins
   circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 2, 20, circles1, \
        param1=HIGH, param2=LOW, minRadius=15,maxRadius=40) #12,45

   # ensure circles exist
   if circles is not None:
     # convert the (x, y) coordinates and radius of the circles to integers
     circlesInt = np.round(circles[0, :]).astype("int")

   """ loop over the (x, y) coordinates and radius of the Hough circles and
       use float for precision 
   """
   logging.info("ref")
   L , W = getBoxDim(rectID)
   logging.info([L, W])
   logging.info("circles:")

   coins = 0
   item = 0
   amount = 0.0
   for circ in circlesInt:
      #logging.info([item, circles[item]])
      x = circles[0][item][0]
      y = circles[0][item][1]
      r = circles[0][item][2]

      # check range of x and y within imageCorr
      # --- do it here

      penny = getDimePennyDecision(blurred, circ)
      center, val2 = checkCenter(blurred, circ, thresh = metric_bkgd)

      #print([center, np.around(val2), np.around(metric_bkgd)])
      print(val2)

      value, dia = checkCoinDia(r, W, L, penny)        
      metric, mask_c =  getCircleColor(blurred, circ, 'hsv')


      logging.info([item, int(10*dia)/10, value, np.around(metric), penny, center, val2])      
      #print([item, int(10*dia)/10, value, np.around(metric), penny, center, np.around(val2)]) 

      if value < 0 or not center:
          color = (0,0,255)
          #cv2.putText(output, "{}".format('X'), (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.4, color, 1)
      else:
         coins = coins + 1
         amount = amount + value
         color = (0,255,0)
      # draw the circle in the output image, then print the circle #
      # corresponding to the center of the circle
      cv2.circle(output, (int(x), int(y)), int(r), color, 2)
      cv2.putText(output, "{}".format(int(10*dia)/10), (int(x) - 10, int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255), 1)    
      # cv2.putText(output, "{}".format(int(metric[ch])), (int(x) - 10, int(y)+15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 0, 0), 1) 
      cv2.putText(output, "{}".format(value), (int(x) - 10, int(y)+15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 0, 0), 1)
      item = item + 1

   logging.info("---")

   cv2.putText(output, "coin count: {}".format(coins), (30, 610), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
   cv2.putText(output, "$: {}".format(amount/100.0), (30, 630), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)

   cv2.imshow("output", output)  

   # analyze each coin for color ? if needed ...
   # ==========================================================



   # clean up and exit on waitKey
   # ==========================================================
   Wait = True
   if VIDEO==1 & (cv2.waitKey(10) & 0xFF == ord('q')):
     logging.info("exit waitKey")
     Wait = False

   if VIDEO == 0:
      while True:
          if cv2.waitKey(0) & 0xFF == ord('q'):
              logging.info("exit 'q' key")
              cv2.destroyAllWindows()
              Wait = False
              break
   else:
      cv2.waitKey(200)  # frame rate

   if not Wait:
       break

# save image to disc (use for project report)
file_path = ".\Results/"
cv2.imwrite(file_path + "original" + ".png", image) 
cv2.imwrite(file_path + "calibrated" + ".png", imageCorr) 
cv2.imwrite(file_path + "gamma" + ".png", gamma)
cv2.imwrite(file_path + "gray" + ".png", gray)
cv2.imwrite(file_path + "blur" + ".png", blurred)
cv2.imwrite(file_path + "edged" + ".png", edged)
cv2.imwrite(file_path + "results" + ".png", output)      

if VIDEO:
   cap.release()

cv2.destroyAllWindows()
Hussein
  • 49
  • 1
  • 1
  • 7

1 Answers1

6

cv2.imread() and a couple of other operations in cv2 silently return None when it fails. This is not a very nice behavior to have for a function obviouly, as you would expect cv2 to throw some exception.

The problem is that the code is not checking for those cases. So depending on the source of the im variable in your code (probably cv2.imread('..\images/test_final2.jpg') at line 206), the im variable is being assigned None because the reading failed for some reason (file not found, invalid file format, etc).

To solve this problem, just ensure that the returned value is different from None when you call a cv2 function:

if __name__ == '__main__':
    
    logging.basicConfig(filename='coins.log',filemode='w',level=logging.DEBUG)
    logging.info("Program started")    

    if VIDEO:
        cap = cv2.VideoCapture(camera)
        if (cap.isOpened()== False):
            logging.info("Error while opening camera.")
            sys.exit(0)
        _, image = cap.read()
        logging.info("video capture mode")

    else:
        image = cv2.imread("./image.png")
        if image == None:
            if not os.path.exists("./image.png"):
                logging.info("Still image not found.")
            else:
                logging.info("Error while opening still image.")
            sys.exit(0)
        logging.info("still image mode")
    
    final_height = 640
    scale = final_height / image.shape[0]

    if VIDEO:

        while(True):
            success, image = cap.read()
            if not success:
                logging.info('Failed to read video')
                sys.exit(0)
            image = cv2.resize(image, None, fx=scale, fy=scale)
            cv2.imshow("Original", image)

    else: # still image

        image = cv2.resize(image, None, fx=scale, fy=scale)
        cv2.imshow("Original", image)
Victor Deleau
  • 875
  • 5
  • 17
  • Thanks, do You have any suggestions how to solve this problem? – Hussein Feb 02 '20 at 18:28
  • Just ensure that the return value is different than `None`. Make sure that the local file exists and is in the right format for `cv2` to work. – Victor Deleau Feb 02 '20 at 19:19
  • Hi, thanks for your reply, when I put your code I get a syntax error after else File "detectCoins.py", line 207 else ^ SyntaxError: invalid syntax – Hussein Feb 02 '20 at 21:54
  • A semicolon is missing at `else:`. But this is not supposed to be a copy & paste solution, it's just to give you an hint about how to make this work ;) – Victor Deleau Feb 02 '20 at 22:00
  • yes i solved that problem for else but i'm still getting another error => python detectCoins.py File "detectCoins.py", line 231 """ Apply camera calibration here, using stored matrices ^ IndentationError: unindent does not match any outer indentation level You have mail in /var/mail/huseinhasan1 I know that it's not a copy paste solution, but I'm a beginner with python that's why I'm asking You for assist:) – Hussein Feb 02 '20 at 22:03
  • That's a line outside the code I provided you. Make sure that the code is correctly aligned. Python requires you to indent nested code using 4 spaces (a tab). Refer to [this](https://www.w3schools.in/python-tutorial/concept-of-indentation-in-python/) – Victor Deleau Feb 02 '20 at 22:11