Is the centroid of a contour always its geometrical centre? (OpenCV, Python) - python

I am working with OpenCV+Python and I want to find the geometrical centre of the following contour:
The OpenCV documentation suggests the following to find the centroid of a contour:
import numpy as np
import cv2 as cv
img = cv.imread('star.jpg',0)
ret,thresh = cv.threshold(img,127,255,0)
im2,contours,hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
M = cv.moments(cnt)
print( M )
#Centroid
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
If I am right, according to this formula the centroid is calculated as the mean (or the average?) of all the points of the contour.
However, if for example fewer points are detected at the upper part of the contour than at the lower part of the contour then the centroid will be a bit higher than the actual geometrical centre of the (fully detected) contour.
Am I right?
If so, then is it better to calculate the average of the extreme points of the contour to find the geometrical centre of the contour and in this way to not depend at all on if the points of the contour are uniformly detected?

Am I right?
No. The OpenCV function moments() uses Green's theorem as mentioned in the OpenCV moments() docs. Green's theorem is indeed a correct way to find the center of mass of a shape. Green's theorem specifically relates integrals about some shape to integrals about the shape's border. As such, it doesn't at all matter how many points define the border or not.
I say center of mass specifically, because you can pass in a single-channel image array into moments() as well to compute the moments and not just a point array. However for an image array, if the array is just binary, then the center of mass is the centroid. And with an array of points (from your contours), there is no array to tell the pixel values inside, so the result is similarly still the centroid.

Related

Open CV Contours - Splitting concave polygon into multiple convex ones

I have the below image in a numpy array
I want to
separate the blocks into individual contours or any coordinate representation.
I then want to transform any concave polygons into multiple convex polygons.
Like this
So far I've managed to isolate each block into contours with opencv... but is there an easy way to split the L shape objects into two or more square blocks. The new contours of each shape can overlap if needed.
It may also be the case that I have an Image like this which does not have such straight lines.
I have used cv2.approxPolyDP to draw the shape, but again they are concave and I need them splitting.
Any help appreciated.
One way I can think of is, for each contour, find it convex hull first.See this link
Now find the defect points between contour and its convex hull. See this link
Now using the data of defects distance, find the point with maximum distance. This point will be the points where the 2 objects are joined in L shape. Now from this point, draw a perpendicular line to the contour tangent at that point, and again find contours. The resultant contours will be the 2 contours for the L shape.
Note: In this approach, it is possible that some part of one object comes in other while dividing them at the boundary.
Ok so thanks Rahul for your answer.
I ended up finding a package that helped me trangulate the polygons which solved my issue.
download with :
pip install sect
Then :
from sect.triangulation import constrained_delaunay_triangles
Take the contours generated by openCV - this generates them as below.
Then "smooth" the colours so there are less of them. I've used this
epsilon = 0.005 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
then run it through sect
constrained_delaunay_triangles([tuple(x) for x in approx.squeeze()])
The output splits the polygons into triangles removing ALL concave polygons totally.

Python: Return position and size of arbitrary/teeth shapes in image using OpenCV

I'm very new to the image processing and object detection. I'd like to extract/identify the position and dimensions of teeth in the following image:
Here's what I've tried so far using OpenCV:
import cv2
import numpy as np
planets = cv2.imread('model.png', 0)
canny = cv2.Canny(planets, 70, 150)
circles = cv2.HoughCircles(canny,cv2.HOUGH_GRADIENT,1,40, param1=10,param2=16,minRadius=10,maxRadius=80)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(planets,(i[0],i[1]),i[2],(255,0,0),2)
# draw the center of the circle
cv2.circle(planets,(i[0],i[1]),2,(255,0,0),3)
cv2.imshow("HoughCirlces", planets)
cv2.waitKey()
cv2.destroyAllWindows()
This is what I get after applying canny filter:
This is the final result:
I don't know where to go from here. I'd like to get all of the teeth identified. How can I do that?
I'd really appreciate any help..
Note that the teeth-structure is more-or-less a parabola (upside-down). If you could somehow guess the parabolic shape that defines the centroids of those blobs (teeth), then your problem could be simplified to a reasonable extent. I have shown a red line that passes through the centers of the teeth.
I would suggest you to approach it as follows:
Binarize your image (background=0, else 1). You could use sklearn.preprocessing.binarize.
Calculate the centroid of all the non-zero pixels. This is the central blue circle in the image. Call this structure_centroid. See this: How to center the nonzero values within 2D numpy array?.
Make polar slices of the entire image, centered at the location of the structure_centroid. I have shown a cartoon image of such polar slices (triangular semi-transparent). Cover complete 360 degrees. See this: polarTransform library.
Determine the position of the centroid of the non-zero pixels for each of these polar slices. See these:
find the distance between a point and a curve python.
Find the minimum distance from a point to a curve.
The array containing these centroids gives you the locus (path) of the average location of the teeth. Call this centroid_path.
Run an elimination/selection algorithm on the circles you were able to detect, that are closest to the centroid_path. Use a threshold distance to drop the outliers.
This should give you a good approximation of the teeth with the circles.
I hope this helps.

How to find distance between two contours

I'm working on a project to find defective parts on a metal ring.I successfully find defective parts on the surface but I cannot detect the protrusions on the inner surface of the metal ring.
I thought to determine the error using the distance between the inner and outer surface, but I don't know how I can calculate the distance between the two contour.
sucsess, frame = capture.read()
kernel = np.ones((1,1),np.uint8)
blur = cv2.bilateralFilter(frame,6,140,160)
threshold = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,21,16)
closing = cv2.morphologyEx(threshold,cv2.MORPH_CLOSE,kernel)
erosion = cv2.erode(closing,kernel,iterations =0)
contours, hierarchy = cv2.findContours(erosion,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
area = cv2.contourArea(cnt)
if area > 72000 and area < 80000:
cv2.drawContours(frame,cnt,-1,(0,0,0),3)
for cnt2 in contours:
area2 = cv2.contourArea(cnt2)
if area2 > 30 and area2 < 200:
cv2.drawContours(frame,cnt2,-1,(0,0,0),3)
cv2.imshow("frame",frame)
cv2.imshow("Erosion",erosion)
cv2.waitKey(0)
This is my code. First image is the object I am looking, second image is the output of the erosion.
enter image description here
enter image description here
My main problem is I am not able to detect any protrusion inside inner radius.
Any suggestion and help are welcome.
I thought to determine the error using the distance between the inner and outer surface, but I don't know how I can calculate the distance between the two contour.
One method is to take both contours and calculate the centroid, giving you a nominal centre point. Then from this point, cast rays out through 360 degrees, and compute the points of intersection with the inner and outer contours. (Closest point by Euclidean distance for example.) Then you have two corresponding radii on both the inner and outer surface, so you can subtract inner from outer to get the ring thickness. Compute this all the way around, using an angle increment proportional to the accuracy you need. The standard deviation of the thickness all the way around is a measure of protrusions (lower numbers are better!).
My main problem is I am not able to detect any protrusion inside inner radius.
If you are only interested in the inner radius, another way is to take the extracted contour from the inner surface, and again compute the centroid to find a nominal reference point. Take the average distance from this centre to each point on the contour, and that gives you the ideal fitted circle. Compute the distance from this ideal circle to each closest point on the actual contour and that gives you a measure of the perturbations.

Smooth values from skimage.measure.marching_cubes

I'm using skimage.measure.marching_cubes to extract a surface, defined as faces and vertices. marching_cubes also outputs values for each face.
How do I "smooth" these values (the actual smoothing could be a low-pass filter, median filter etc)? I thought that one way to achieve this would be to project, or to represent this surface in 2D, and then apply standard filters, but I can't think of how to do this from a list of faces and vertices.
The reason for this "smoothing" is because the values are not informative at the scale of a single face of the surface, but over larger areas of the surface represented by many faces.
Thanks in advance!
I eventually found a way to do this, based on MATLAB code from this paper:
Welf et al. "Quantitative Multiscale Cell Imaging in Controlled 3D Microenvironments" in Developmental Cell, 2016, Vol 36, Issue 4, p462-475
def median_filter_surface(faces, verts, measure, radius, p_norm=2):
from scipy import spatial
import numpy as np
# INPUT:
# faces: triangular surface faces - defined by 3 vertices
# verts: the above vertices, defined by x,y,z coordinates
# measure: the value related to each face that needs to be filtered
# radius: the radius for median filtering (larger = more filtering)
# p_norm: distance metric for the radius, default 2 (euclidian)
# OUTPUT:
# measure_med_filt: the "measure" after filtering
num_faces = len(faces)
face_centres = np.zeros((num_faces, 3))
# get face centre positions in 3D space (from vert coordinates)
for face in range(0, num_faces):
face_centres[face, :] = np.mean(verts[faces[face, :], :], 0)
# return all other points within a radius
tree = spatial.KDTree(face_centres)
faces_in_radius = tree.query_ball_point(face_centres, radius, p_norm)
measure_med_filt = np.zeros(len(faces))
for face in range(0, len(faces)):
measure_med_filt[face] = np.median(measure[faces_in_radius[face]])
return measure_med_filt

time series for binary shapes

I have been working around extracting the time series from shapes based on distances to center of mass clockwise starting from angle 0 to 360.
My Implementation that arranges contour points based on their angle to the [1,0], vector might be good for some shapes but is not useful for shapes that has much articulation. Consider the following code:
im = Image.open(os.path.join(path,filename))
im = im.filter(ifilter.MedianFilter)
contim = im.filter(ifilter.CONTOUR)
contim = contim[1:-1,1:-1] # this is because borders are extracted here as contours
contpts = np.where(contim ==0)
contpts = np.vstack((contpts[0],contpts[1])) # Just need to arrange these points clockwise with respect to the center of mass of the shape
Can anyone direct me to how I can extract that feature from any shape where I can start from a point and keep going along the contour to extract all the distances to the center of mass of the shape.
For more information about the feature, please view this paper: "LB_Keogh Supports Exact Indexing of Shapes under Rotation Invariance with Arbitrary Representations and Distance Measures"
If I understood, there's a geometrical figure in a discretized plane, represented as a matrix. If the entry is 1, you're inside the figure. If it's 0, you're outside. He wants to determine de distance between the edge of the figure and the center of the figure for all points in the edge. He parametrized it with a polar coordinate system. The center of the figure is the origin and now he wants to get the distance to the border as a function of the angle. This is what he calls his "time series".
Is this correct?
If yes, couldn't you just:
1. determine the center of mass,
2. reposition the origin to match the center of mass.
3. start angle at 0
4. r = 0
5. for each angle in [0,1,...,360]
1. If you're in inside the figure, increase r until you reach the border.
2. If you're outside the figure, decrease r until you reach the border.
3. When you reach the border, d(angle) = r
It the figure have a more or less continuous border, this will follow the contour.
Would this work?

Categories