Height of contour at different positions

Hello,
i have a picture where i can easily find contours in. And i am also able to put a minAreaRect around it and display its width and height.
Now im wondering whether i can also calculate the height of the contour at different positions. Maybe i can even calculate height (y-direction) for every pixel in x-direction and display the minimum and maximum height?
So far i havent found a solution, this topic has good ideas but i cant come to a solution: opencv - Contour width measurement along its entire lendth - Stack Overflow
My contours are even easier i guess, its always oval shaped.

example: this is what i do at the moment without problems
grafik

example: this is what i would like to have

So far i havent found a way to solve the problem. I looked into DistanceTransform but i dont know how to use it so i can calculate height at different positions. The contours are ALWAYS oval shaped and have similar ratio. The x length is always greater than y length and its horizontally aligned.

If your ovals are practically ellipses, and you don’t need absolute accuracy, you can calculate it instead of measuring.

Just think of the oval as squashed circle. Now calculate the measure on that opened-up circle (simple trigonometry) and then squash the measure down by the squashing ratio.

Actually they are not ellipses! Contours found are similar but more like a rectangle with half circles left and right and the horizontal lines of rectangle aren’t 100 percent straight but can go up and down.

ditch contours. definitely not a useful data structure for this task.

work with connected components. for each pixel column (or whichever you desire), find the first and last pixel in the contour. you can do that with some numpy functions.

if the shapes are FILLED, you can just do a columnwise sum, hence counting, which is equal to the “thickness” in that column.

if the shapes are NOT filled, I’d recommend getting them filled, or else you’ll have some more work getting the thickness (or numpy will).

present actual data, not just drawn ellipses. then we can discuss that.

Okay, heres my object after processing my image. It is filled. All objects look like this. At the moment i use only cv.minAreaRect to see width and height.

I’ll have a look into connected components tomorrow!

OK, i looked into connected components and wrote a script that instead of using cv.findCountours() uses cv.connectedComponentsWithStats().
How can i now find the height of each (or every n-th) pixel column? I havent worked with numpy a lot.

say you consider one component, labeled with the number label. stats contain the bounding box (xywh, also area)

take a slice so you don’t have to work on the entire picture.

in that slice, find pixels given your label. you’ll have a boolean numpy array. True values as 1, which is convenient for summing. OpenCV masks contain 0 or 255 (any non-zero but usually 255), i.e. work slightly differently.

columns lie along axis 0.

mask = (labels[y:y+h, x:x+w] == label)
column_sums = mask.sum(axis=0)

Yesterday i managed to implement your idea and got good results i guess. For the mask i just took output.copy()[y:y+h, x:x+h] (output is a black numpy array with the relevant components added) because i didnt know what to use for labels and label in your example.

Now i have a numpy array with as many elements as the width of the objects in pixel, which is good. But i dont know what the results show. In output the objects are white and background black, as my sent picture. What confuses me, is that the area of one component is 20000, but the elements in array summed up is way more than this. Each element (except far left and far right) is approx 12000. And the height of bounding box for the objects is approx 110, i expected similar values for the column_sums.
I can post code and output of code when im home after work if this is interesting, i didnt want to overload this post.
Where do i think wrong? So far it seems like im on a good way. I hope thats true :smiley:

see above. you really must inspect the values of variables/elements of arrays.

result from CC with stats; one specific label (number) for example.

Sorry, i dont get it…
I dont know what to use for labels and label. I tried every return of CCwithstats but i cant get results. With my try, i get results but work with OpenCv mask i think…
I mean the results i get make sense. They get bigger at beginning and smaller at end, as the height of the picture i sent. Ill post my code and return of one object, maybe you can help me to understand. Many lines are from internet, i just found out about CCwithstats when u told me. I understand what it does, but dont understand every single parameter. I think this is my problem.
Code:

# image
img4k = cv.imread(path_object)
img = cv.resize(img4k, dsize=(1920, 1080)) # img = img4k
imggray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imggray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
# CC
analysis = cv.connectedComponentsWithStats(thresh, 4, cv.CV_32S)
(totalLabels, label_ids, values, centroid) = analysis
# Initialize a new image to store all the output components
output = np.zeros(imggray.shape, dtype="uint8")

# Loop through each component
for i in range(1, totalLabels):
    # Area of the component
    area = values[i, cv.CC_STAT_AREA]

    if (area > 25000):
        print('area: ', area)
        componentMask = (label_ids == i).astype("uint8") * 255

        output = cv.bitwise_or(output, componentMask)
        # extract coordinate points
        x = values[i, cv.CC_STAT_LEFT]
        y = values[i, cv.CC_STAT_TOP]
        w = values[i, cv.CC_STAT_WIDTH]
        h = values[i, cv.CC_STAT_HEIGHT]
        # bounding box
        pt1 = (x, y)
        pt2 = (x+ w, y+ h)
        (X, Y) = centroid[i]
        cv.rectangle(img,pt1,pt2, (255, 0, 255), 1)
        cv.putText(img, "w={},h={}".format(w, h), (int(x),int(y)-int(h/1.6)), cv.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 1)

        mask = output.copy()[y:y+h, x:x+w]
        column_sums = mask.sum(axis=0)
        print('column_sums:', column_sums)
        cv.imshow("mask", mask)
        cv.waitKey(0)

cv.imshow("Image", img)
cv.imshow("Filtered Components", output)
cv.waitKey(0)

result:


2nd component look very similar but even more elements

@digerid Did you later figure out the solution to this? I am looking for a similar solution where i can slice the contour in say 5 sections then extract the distance at each of these points.

Actually I didn’t…
I had some good results actually putting my code into ChatGPT and it optimising it but as it was just a nice to have instead of must have I stopped working on it due to the lack of time!

If you find something out, I’d love to hear though! Couldn’t find a good answer online

you were so close… your screenshot even shows that you got sensible values.

Yeah I know, thanks to you…
I was desperate having all info I needed and still not really finishing this task… Code works well enough for what it should do but this last change would have make it perfect!
Can’t have it all I guess :slight_smile: