pythonopencvconvex-hullconvexity-defects

Python's cv2 convex defects distances are too large?


UPDATE: it seems like the ratio between the distances I calculate myself and the distances returned by cv2 is exactly 256. This is not surprising, since looking at their code (line 394 here) shows the distances in pixels get multiplied by 256. I just don't understand why, I guess.


I am using cv2.convexityDefects to find, well, the convex defects of some shape. I wish to compute the depths (distances to the convex hull, as nicely explained here) of the defects.

However, the distances I get are way too large to make sense. I additionally tried to compute the distances manually, using the start and end point output arguments of cv2.convexityDefects (see code below), and get a more reasonable result.

The distances calculated by cv2.convexityDefects are,

>> d
array([21315, 26578, 19093, 56472, 35230, 20476, 26825], dtype=int32)

which make no sense at all in the context of the image, which ~500 pixels wide. What am I doing wrong?


More info:

This is an image (ehhr I mean work of art) I created for this test,

test image for convex defects

Here is a the code:

from numpy.linalg import norm

# Load an image
img = cv2.imread('buff.png')

# Threshold
ret, img = cv2.threshold(img,127,255,cv2.THRESH_BINARY)

# Detect edges
edges = cv2.Canny(img, 1, 2)

# Find contours
cnts, h = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

# Find convex hull
hull = cv2.convexHull(cnts[0], returnPoints = False)

# Find defects
defects = cv2.convexityDefects(cnts[0], np.sort(np.squeeze(hull)))
# Reshape the output
s = np.reshape(defects,(-1,4))[:,0] # start point idx
e = np.reshape(defects,(-1,4))[:,1] # end point idx
f = np.reshape(defects,(-1,4))[:,2] # defect idx
d = np.reshape(defects,(-1,4))[:,3] # distances as calculated by cv2.convexityDefects

# Draw contours in red, convex hull in blue
img = cv2.drawContours(img, cnts[0], -1, (255,0,0), 2)
img = cv2.polylines(img, [cnts[0][np.squeeze(hull)]], True, (0,0,255), 3)

# Calculate distances manually and put in a list d2
d2 = list()
for i in range(len(f)):
    # Draw the defects in blue, start points in pink:
    img = cv2.circle(img, tuple(cnts[0][f[i]][0]), 5,(0, 0, 255), -1)
    img = cv2.circle(img, tuple(cnts[0][s[i]][0]), 5,(255, 0, 255), -1)

    # Manually calculate the distances as the distances between the defects (blue points)
    # and the line defined between each pair of start and end points (pink here, for demonstration)
    d2.append(norm(np.cross(cnts[0][s[i]][0]-cnts[0][e[i]][0], cnts[0][e[i]][0]-cnts[0][f[i]][0]))/norm(cnts[0][s[i]][0]-cnts[0][e[i]][0]))
    
pp.imshow(img)
pp.show()

Here is the output of the above code, when applied on the test image:

Convex pts as calculated by the code


Solution

  • It seems like the ratio between the distances I calculate myself and the distances returned by cv2 is exactly 256. This is not surprising, since looking at their code (convhull.cpp line 394 here) shows the distances in pixels get multiplied by 256. I just don't understand why, I guess.