Find countours on a dark background
Question:
Suppose, I have a dark image on a white background. For example, the image below:
With the code below, I can easily extract its countors
import imutils
import cv2
image = cv2.imread("image.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image = imutils.resize(image, width = 64)
thresh = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 7)
# initialize the outline image, find the outermost
# contours then draw
# it
outline = np.zeros(image.shape, dtype = "uint8")
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[0]
x=cv2.drawContours(outline, [cnts], -1, 255, -1)
This would give me the image below
However, let us try a more difficult image, like below
whenever I run such a code using the image above, I find the following error:
Traceback (most recent call last):
File "segment_img.py", line 16, in <module>
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[0]
IndexError: list index out of range
It obviously does not find any countors. However, I am using adaptive threshold which is supposed to work.
What can I change in my code so I could make both images having their corresponding contours?
Answers:
Instead of using cv2.adaptiveThreshold
we may use cv2.threshold
with cv2.THRESH_OTSU
argument (and cv2.THRESH_BINARY_INV
):
thresh = cv2.threshold(image, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)[1]
cv2.THRESH_OTSU
uses Otsu’s method for automatic image thresholding.
Code sample:
import imutils
import cv2
import numpy as np
image = cv2.imread("image.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image = imutils.resize(image, width = 64)
#thresh = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 7)
thresh = cv2.threshold(image, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)[1]
# initialize the outline image, find the outermost
# contours then draw
# it
outline = np.zeros(image.shape, dtype="uint8")
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key=cv2.contourArea, reverse = True)[0]
x = cv2.drawContours(outline, [cnts], -1, 255, -1)
It’s not a magic that works in all cases, and it’s also possible to select parameters for cv2.adaptiveThreshold
that works for the specific case.
Otsu’s method usually works where there are two main distinct gray levels (with some deviation from the mean of each distinct level).
The Otsu’s threshold did more or less what I need.
def prepare_image(image_name):
image = cv2.imread(image_name)
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image=cv2.resize(image, (28,28), interpolation = cv2.INTER_AREA)
ret2,thresh = v2.threshold(image,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# initialize the outline image, find the outermost
# contours, then draw
# it
outline = np.zeros(image.shape, dtype = "uint8")
cnts = cv2.findContours(thresh.copy(),
cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[0]
cv2.drawContours(outline, [cnts], -1, 255, -1)
x=np.zeros(outline.shape)
x[outline==0]=255
return x
The outputs of the two images are more or less like these:
Not excactly perfect. I am open to other suggestions.
Suppose, I have a dark image on a white background. For example, the image below:
With the code below, I can easily extract its countors
import imutils
import cv2
image = cv2.imread("image.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image = imutils.resize(image, width = 64)
thresh = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 7)
# initialize the outline image, find the outermost
# contours then draw
# it
outline = np.zeros(image.shape, dtype = "uint8")
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[0]
x=cv2.drawContours(outline, [cnts], -1, 255, -1)
This would give me the image below
However, let us try a more difficult image, like below
whenever I run such a code using the image above, I find the following error:
Traceback (most recent call last):
File "segment_img.py", line 16, in <module>
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[0]
IndexError: list index out of range
It obviously does not find any countors. However, I am using adaptive threshold which is supposed to work.
What can I change in my code so I could make both images having their corresponding contours?
Instead of using cv2.adaptiveThreshold
we may use cv2.threshold
with cv2.THRESH_OTSU
argument (and cv2.THRESH_BINARY_INV
):
thresh = cv2.threshold(image, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)[1]
cv2.THRESH_OTSU
uses Otsu’s method for automatic image thresholding.
Code sample:
import imutils
import cv2
import numpy as np
image = cv2.imread("image.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image = imutils.resize(image, width = 64)
#thresh = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 7)
thresh = cv2.threshold(image, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)[1]
# initialize the outline image, find the outermost
# contours then draw
# it
outline = np.zeros(image.shape, dtype="uint8")
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key=cv2.contourArea, reverse = True)[0]
x = cv2.drawContours(outline, [cnts], -1, 255, -1)
It’s not a magic that works in all cases, and it’s also possible to select parameters for cv2.adaptiveThreshold
that works for the specific case.
Otsu’s method usually works where there are two main distinct gray levels (with some deviation from the mean of each distinct level).
The Otsu’s threshold did more or less what I need.
def prepare_image(image_name):
image = cv2.imread(image_name)
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image=cv2.resize(image, (28,28), interpolation = cv2.INTER_AREA)
ret2,thresh = v2.threshold(image,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# initialize the outline image, find the outermost
# contours, then draw
# it
outline = np.zeros(image.shape, dtype = "uint8")
cnts = cv2.findContours(thresh.copy(),
cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[0]
cv2.drawContours(outline, [cnts], -1, 255, -1)
x=np.zeros(outline.shape)
x[outline==0]=255
return x
The outputs of the two images are more or less like these:
Not excactly perfect. I am open to other suggestions.