Python OpenCV - Find black areas in a binary image
Asked Answered
S

5

14

There is any method/function in the python wrapper of Opencv that finds black areas in a binary image? (like regionprops in Matlab) Up to now I load my source image, transform it into a binary image via threshold and then invert it to highlight the black areas (that now are white).

I can't use third party libraries such as cvblobslob or cvblob

Seamstress answered 29/1, 2012 at 20:51 Comment(1)
+1 for The keyword "regionprops" which saved me hours of googlingRental
T
25

Basically, you use the findContours function, in combination with many other functions OpenCV provides for especially this purpose.

Useful functions used (surprise, surprise, they all appear on the Structural Analysis and Shape Descriptors page in the OpenCV Docs):

example code (I have all the properties from Matlab's regionprops except WeightedCentroid and EulerNumber - you could work out EulerNumber by using cv2.RETR_TREE in findContours and looking at the resulting hierarchy, and I'm sure WeightedCentroid wouldn't be that hard either.

# grab contours
cs,_ = cv2.findContours( BW.astype('uint8'), mode=cv2.RETR_LIST,
                             method=cv2.CHAIN_APPROX_SIMPLE )
# set up the 'FilledImage' bit of regionprops.
filledI = np.zeros(BW.shape[0:2]).astype('uint8')
# set up the 'ConvexImage' bit of regionprops.
convexI = np.zeros(BW.shape[0:2]).astype('uint8')

# for each contour c in cs:
# will demonstrate with cs[0] but you could use a loop.
i=0
c = cs[i]

# calculate some things useful later:
m = cv2.moments(c)

# ** regionprops ** 
Area          = m['m00']
Perimeter     = cv2.arcLength(c,True)
# bounding box: x,y,width,height
BoundingBox   = cv2.boundingRect(c)
# centroid    = m10/m00, m01/m00 (x,y)
Centroid      = ( m['m10']/m['m00'],m['m01']/m['m00'] )

# EquivDiameter: diameter of circle with same area as region
EquivDiameter = np.sqrt(4*Area/np.pi)
# Extent: ratio of area of region to area of bounding box
Extent        = Area/(BoundingBox[2]*BoundingBox[3])

# FilledImage: draw the region on in white
cv2.drawContours( filledI, cs, i, color=255, thickness=-1 )
# calculate indices of that region..
regionMask    = (filledI==255)
# FilledArea: number of pixels filled in FilledImage
FilledArea    = np.sum(regionMask)
# PixelIdxList : indices of region. 
# (np.array of xvals, np.array of yvals)
PixelIdxList  = regionMask.nonzero()

# CONVEX HULL stuff
# convex hull vertices
ConvexHull    = cv2.convexHull(c)
ConvexArea    = cv2.contourArea(ConvexHull)
# Solidity := Area/ConvexArea
Solidity      = Area/ConvexArea
# convexImage -- draw on convexI
cv2.drawContours( convexI, [ConvexHull], -1,
                  color=255, thickness=-1 )

# ELLIPSE - determine best-fitting ellipse.
centre,axes,angle = cv2.fitEllipse(c)
MAJ = np.argmax(axes) # this is MAJor axis, 1 or 0
MIN = 1-MAJ # 0 or 1, minor axis
# Note: axes length is 2*radius in that dimension
MajorAxisLength = axes[MAJ]
MinorAxisLength = axes[MIN]
Eccentricity    = np.sqrt(1-(axes[MIN]/axes[MAJ])**2)
Orientation     = angle
EllipseCentre   = centre # x,y

# ** if an image is supplied with the BW:
# Max/Min Intensity (only meaningful for a one-channel img..)
MaxIntensity  = np.max(img[regionMask])
MinIntensity  = np.min(img[regionMask])
# Mean Intensity
MeanIntensity = np.mean(img[regionMask],axis=0)
# pixel values
PixelValues   = img[regionMask]        
Taxonomy answered 30/1, 2012 at 5:14 Comment(1)
It's useful to wrap this up in a function and return some sort of structure (e.g. numpy recarray) with the information in it. While I'm at it I also draw a labelled image where region i has intensity i+1 (this is to avoid region 0 merging with the background).Taxonomy
G
2

After inverting binary image to turn black to white areas, apply cv.FindContours function. It will give you boundaries of the region you need.

Later you can use cv.BoundingRect to get minimum bounding rectangle around region. Once you got the rectangle vertices, you can find its center etc.

Or to find centroid of region, use cv.Moment function after finding contours. Then use cv.GetSpatialMoments in x and y direction. It is explained in opencv manual.

To find area, use cv.ContourArea function.

Greenway answered 30/1, 2012 at 3:4 Comment(0)
I
0

Transform it to binary image using threshold with the CV_THRESH_BINARY_INV flag, you get threshold + inversion in one step.

Implicatory answered 3/4, 2013 at 5:36 Comment(0)
B
0

If you can consider using another free library, you could use SciPy. It has a very convenient way of counting areas:

from scipy import ndimage

def count_labels(self, mask_image):
    """This function returns the count of labels in a mask image."""
    label_im, nb_labels = ndimage.label(mask_image)
    return nb_labels

If necessary you can use:

import cv2 as opencv

image = opencv.inRange(image, lower_threshold upper_threshold)

before to get a mask image, which contains only black and white, where white are the objects in the given range.

Brunella answered 10/1, 2016 at 2:36 Comment(0)
P
0

I know this is an old question, but for completeness I wanted to point out that cv2.moments() will not always work for small contours. In this case, you can use cv2.minEnclosingCircle() which will always return the center coordinates (and radius), even if you have only a single point. Slightly more resource-hungry though, I think...

Portie answered 24/3, 2021 at 11:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.