I'm trying to detect and fine-locate some objects in images from contours. The contours that I get often include some noise (maybe form the background, I don't know). The objects should look similar to rectangles or squares like:
I get very good results with shape matching (cv::matchShapes
) to detect contours with those objects in them, with and without noise, but I have problems with the fine-location in case of noise.
Noise looks like:
or for example.
My idea was to find convexity defects and if they become too strong, somehow crop away the part that leads to concavity. Detecting the defects is ok, typically I get two defects per "unwanted structure", but I'm stuck on how to decide what and where I should remove points from the contours.
Here are some contours, their masks (so you can extract the contours easily) and the convex hull including thresholded convexity defects:
Could I just walk through the contour and locally decide whether a "left turn" is performed by the contour (if walking clockwise) and if so, remove contour points until the next left turn is taken? Maybe starting at a convexity defect?
I'm looking for algorithms or code, programming language should not be important, algorithm is more important.
Here is a Python implementation following Miki's code.
import numpy as np
import cv2
def ed2(lhs, rhs):
return(lhs[0] - rhs[0])*(lhs[0] - rhs[0]) + (lhs[1] - rhs[1])*(lhs[1] - rhs[1])
def remove_from_contour(contour, defectsIdx, tmp):
minDist = sys.maxsize
startIdx, endIdx = 0, 0
for i in range(0,len(defectsIdx)):
for j in range(i+1, len(defectsIdx)):
dist = ed2(contour[defectsIdx[i]][0], contour[defectsIdx[j]][0])
if minDist > dist:
minDist = dist
startIdx = defectsIdx[i]
endIdx = defectsIdx[j]
if startIdx <= endIdx:
inside = contour[startIdx:endIdx]
len1 = 0 if inside.size == 0 else cv2.arcLength(inside, False)
outside1 = contour[0:startIdx]
outside2 = contour[endIdx:len(contour)]
len2 = (0 if outside1.size == 0 else cv2.arcLength(outside1, False)) + (0 if outside2.size == 0 else cv2.arcLength(outside2, False))
if len2 < len1:
startIdx,endIdx = endIdx,startIdx
else:
inside = contour[endIdx:startIdx]
len1 = 0 if inside.size == 0 else cv2.arcLength(inside, False)
outside1 = contour[0:endIdx]
outside2 = contour[startIdx:len(contour)]
len2 = (0 if outside1.size == 0 else cv2.arcLength(outside1, False)) + (0 if outside2.size == 0 else cv2.arcLength(outside2, False))
if len1 < len2:
startIdx,endIdx = endIdx,startIdx
if startIdx <= endIdx:
out = np.concatenate((contour[0:startIdx], contour[endIdx:len(contour)]), axis=0)
else:
out = contour[endIdx:startIdx]
return out
def remove_defects(mask, debug=False):
tmp = mask.copy()
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
# get contour
contours, _ = cv2.findContours(
mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
assert len(contours) > 0, "No contours found"
contour = sorted(contours, key=cv2.contourArea)[-1] #largest contour
if debug:
init = cv2.drawContours(tmp.copy(), [contour], 0, (255, 0, 255), 1, cv2.LINE_AA)
figure, ax = plt.subplots(1)
ax.imshow(init)
ax.set_title("Initital Contour")
hull = cv2.convexHull(contour, returnPoints=False)
defects = cv2.convexityDefects(contour, hull)
while True:
defectsIdx = []
for i in range(defects.shape[0]):
s, e, f, d = defects[i, 0]
start = tuple(contour[s][0])
end = tuple(contour[e][0])
far = tuple(contour[f][0])
depth = d / 256
if depth > 2:
defectsIdx.append(f)
if len(defectsIdx) < 2:
break
contour = remove_from_contour(contour, defectsIdx, tmp)
hull = cv2.convexHull(contour, returnPoints=False)
defects = cv2.convexityDefects(contour, hull)
if debug:
rslt = cv2.drawContours(tmp.copy(), [contour], 0, (0, 255, 255), 1)
figure, ax = plt.subplots(1)
ax.imshow(rslt)
ax.set_title("Corrected Contour")
mask = cv2.imread("a.png")
remove_defects(mask, True)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With