How to detect contours touching the border?

Question:

I’m coding a program that can detect inconsistencies in the powderbed of our 3D printers using Python and OpenCV. What I did until now is create a masked image using edge detection, and then perform edge detection again and exclude any contours touching the border. This works well, however I would like to detect contours touching the border as well. I’ve tried various methods but nothing seems to achieve the result I’m looking for. This article seems to have the same question as me and provides an answer but it’s coded in C++, which I do not fully understand.

As an example, here is an image with a contour touching the border: image.

And here is the image after performing edge detection: image. As you can see the border of the image is included in the contour I’m trying to detect. Note that the border is not always a square but can be any polygon shape.

Asked By: stan5079

||

Answers:

Here is one way to do that in Python/OpenCV.

  • Read the input
  • Convert to gray
  • Threshold
  • Get bounds of white pixels
  • Crop input to those bounds to remove the black border
  • Threshold the cropped image to isolate the brighter region
  • Apply morphology to clean it up
  • Get the contour and its bounding box
  • Test whether the bounding box touches any of the 4 sides of the cropped image
  • Draw the contour bounding box on the input image
  • Save the results

Input:

enter image description here

import cv2
import numpy as np

# load image as grayscale
img = cv2.imread('streak.png')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# threshold 
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]

# get bounds of white pixels
white = np.where(thresh==255)
xmin, ymin, xmax, ymax = np.min(white[1]), np.min(white[0]), np.max(white[1]), np.max(white[0])
print(xmin,xmax,ymin,ymax)

# crop the gray image at the bounds
crop = gray[ymin:ymax, xmin:xmax]
hh, ww = crop.shape

# do adaptive thresholding
thresh2 = cv2.adaptiveThreshold(crop, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 3, 1.1)

# apply morphology
kernel = np.ones((1,7), np.uint8)
morph = cv2.morphologyEx(thresh2, cv2.MORPH_CLOSE, kernel)
kernel = np.ones((5,5), np.uint8)
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)

# invert
morph = 255 - morph

# get contours (presumably just one) and its bounding box
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
for cntr in contours:
    x,y,w,h = cv2.boundingRect(cntr)

# draw bounding box on input
bbox = img.copy()
cv2.rectangle(bbox, (x+xmin, y+ymin), (x+xmin+w, y+ymin+h), (0,0,255), 1)

# test if contour touches sides of image
if x == 0 or y == 0 or x+w == ww or y+h == hh:
    print('region touches the sides')
else:
    print('region does not touch the sides')

# save resulting masked image
cv2.imwrite('streak_thresh.png', thresh)
cv2.imwrite('streak_crop.png', crop)
cv2.imwrite('streak_bbox.png', bbox)

# display result
cv2.imshow("thresh", thresh)
cv2.imshow("crop", crop)
cv2.imshow("thresh2", thresh2)
cv2.imshow("morph", morph)
cv2.imshow("bbox", bbox)
cv2.waitKey(0)
cv2.destroyAllWindows()

Thresholded image to find borders:

enter image description here

Cropped input:

enter image description here

Morphology cleaned second threshold:

enter image description here

Bounding box of region contour on the input:

enter image description here

Message printed:

region touches the sides

Answered By: fmw42

You can use this code to find whether the contour is touching border or not, and it is relatively much more faster:

import cv2
from skimage import measure
from skimage.segmentation import clear_border

img = cv2.imread('image.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_,im_bw = cv2.threshold(gray,127,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)

cntrs,_ = cv2.findContours(im_bw, 1, 2)
cntrs = sorted(cntrs, key=cv2.contourArea)

# you can also for loop here to find border touching for each contours
# For now, I am only doing it for single contour

cnt = cntr[-1]   #finding whether the last contour is touching the border or not

im_bw = cv2.fillPoly(im_bw, pts=[cnt], color=(1)) #converting to 0/1 pixel range
cleared = clear_border(im_bw)

if len(np.unique(cleared)) == 1:
  print("Contour is touching the border")
else:
  print("Contour is not touching the border")

# If its True, contour is touching the border else it is not touching
Answered By: Prakash Dahal
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.