0

I'm trying to follow movement of a part using red dots. I tried with white dots and thresholding before, but there is too much reflection from the smartphone I'm using. The plan is to recognize a dot as a contour, find the center and fill the array with the coordinates of all contour centers for further calculation.

The code is posted bellow, it recognizes the correct number of dots, but I get the division by zero error. Does anyone know what I'm doing wrong?

Image:https://i.stack.imgur.com/mEUdJ.jpg

import cv2
import numpy as np
from matplotlib import pyplot as plt
import imutils

#load image
img = cv2.imread('dot4_red.jpg')

#apply median blur, 15 means it's smoothing image 15x15 pixels
blur = cv2.medianBlur(img,15)

#convert to hsv
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)

#color definition
red_lower = np.array([0,0,240])
red_upper = np.array([10,10,255])

#red color mask (sort of thresholding, actually segmentation)
mask = cv2.inRange(hsv, red_lower, red_upper)

#copy image for, .findContours distorts the source image
mask_copy = mask.copy()

#find contours
cnts = cv2.findContours(mask_copy,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

#extract contours from the list??
cnts = imutils.grab_contours(cnts)

#count number of conoturs of specific size
s1 = 500
s2 = 10000
xcnts = []
for cnt in cnts:
    if s1<cv2.contourArea(cnt)<s2:
        xcnts.append(cnt)

n = len(xcnts)

#pre-allocate array for extraction of centers of contours
s = (n,2)
array = np.zeros(s)

#fill array of center coordinates 
for i in range(0,n):
    cnt = cnts[i]
    moment = cv2.moments(cnt)
    c_x = int(moment["m10"]/moment["m00"])
    c_y = int(moment["m01"]/moment["m00"])

    array[i,:] = [c_x, c_y]

#display image
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.imshow('image', mask)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()

#print results
print ('number of dots, should be 4:',n)
print ('array of dot center coordinates:',array)
user2882635
  • 133
  • 2
  • 19

2 Answers2

1

moments00 (area) can be 0 for some shapes according to cv documentation. This is probably what is happening here:

Note Since the contour moments are computed using Green formula, you may get seemingly odd results for contours with self-intersections, e.g. a zero area (m00) for butterfly-shaped contours.

From: https://docs.opencv.org/3.4/d8/d23/classcv_1_1Moments.html#a8b1b4917d1123abc3a3c16b007a7319b

You need to ensure the area (m00) is not 0 before using it for division.

Jean-Marc Volle
  • 3,113
  • 1
  • 16
  • 20
  • 1
    Checking your input image, this is most probably not the reason but checking that area is >0 before using it for division is still important for the robustness of your solution – Jean-Marc Volle May 13 '20 at 10:09
  • Ok. So if I understand correctly, I need to implement and IF sentence in the second for loop in my code that is used to calculate center coords and fill up the array? – user2882635 May 13 '20 at 12:43
  • I tried doing that but I get the: "IndexError: index527 is out of bounds for axis 0 with size 4" error, which I assume is because the size of the matrix is not the same as the data that is being filled with. – user2882635 May 13 '20 at 12:49
1

The problem was the wrong color range. Because of this, there were holes in the mask of the circles. Due to division by zero. M00. You can choose the correct color range or pre-fill the holes in the mask. Or use this code:

import cv2
import numpy as np
#load image
img = cv2.imread('ZayrMep.jpg')

#apply median blur, 15 means it's smoothing image 15x15 pixels
blur = cv2.medianBlur(img,15)

#convert to hsv
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)

#color definition
red_lower = np.array([160,210,230])
red_upper = np.array([180,255,255])

#red color mask (sort of thresholding, actually segmentation)
mask = cv2.inRange(hsv, red_lower, red_upper)

connectivity = 4  
# Perform the operation
output = cv2.connectedComponentsWithStats(mask, connectivity, cv2.CV_32S)
# Get the results

num_labels = output[0]-1

centroids = output[3][1:]
#print results
print ('number of dots, should be 4:',num_labels )
print ('array of dot center coordinates:',centroids)
Alex Alex
  • 1,893
  • 1
  • 6
  • 12
  • How does this answer the question? Please describe what the problem in OP’s code is and how your code fixes it. – Cris Luengo May 13 '20 at 14:04
  • The problem was the wrong color range. Because of this, there were holes in the mask of the circles. Due to division by zero. M00. You can choose the correct color range or pre-fill the holes in the mask. – Alex Alex May 13 '20 at 14:12
  • 1
    Alex, if you write that into your answer, you’ll have a really good answer! – Cris Luengo May 13 '20 at 14:27
  • Alex, thanks for the comment about the colors, now the result is much better. But your code returns the error output = cv2.connectedComponentsWithStats(mask, connectivity, cv2.CV_32S) AttributeError: 'module' object has no attribute 'connectedComponentsWithStats' – user2882635 May 14 '20 at 10:43
  • It seems that the cv2.connectedComponentsWithStats only appears in OpenCV3 and I'm using the 2.4 version because I had a lot of issues with Python+OpenCV and I switched to Python2.7. – user2882635 May 14 '20 at 11:16
  • Also another question: what are the cells that you mentioned? – user2882635 May 14 '20 at 11:22
  • Sorry, this does not apply to the question. Copy and paste failed. – Alex Alex May 14 '20 at 17:43