How to get an expanded or contracted contour in OpenCV?
Asked Answered
D

2

6

Is it possible to get the expanded or contracted version of a contour?

For example in the below image, I have used cv::findContour() and cv::drawContour on a binary image to get the contours:

Imgur

I would like to draw another contour which has a customed pixel distance from the original contour, like these:

Imgur

Imgur

Except for eroding, which I think it might not be a good idea as it seems hard to control the pixel distance using eroding, I have no idea on how to solve this problem. May I know what should be the correct direction?

Donetsk answered 27/3, 2019 at 9:54 Comment(2)
You can use cv::dilate() and cv::erode() then detect the contours again.Lovellalovelock
How large are the initial contours? How exact has the resulting contour to reflect the shape of original contour? These two issues will determine, how complex a solution will be. I would agree, that cv::dilate or cv::erode will do the job to a certain degree (or detail). The super-fine solution will incorporate something like finding the center of mass, projecting the x, y coordinates of the original contour into the right direction, and determining new x, y coordinates for the resuling contour, thus a lot of interpolating and extrapolating, I assume.Incontrovertible
G
7

Using cv::erode with a small kernel and multiple iterations may be enough for your needs, even if it's not exact.

C++ code:

cv::Mat img = ...;
int iterations = 10;
cv::erode(img, img,
   cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3)),
   cv::Point(-1,-1),
   iterations);

Demo:

# img is the image containing the original black contour
for form in [cv.MORPH_RECT, cv.MORPH_CROSS]:
    eroded = cv.erode(img, cv.getStructuringElement(form, (3,3)), iterations=10)
    contours, hierarchy = cv.findContours(~eroded, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
    vis = cv.cvtColor(img, cv.COLOR_GRAY2BGR)
    cv.drawContours(vis, contours, 0, (0,0,255))
    cv.drawContours(vis, contours, 1, (255,0,0))
    show_image(vis)

10 iterations with cv.MORPH_RECT with a 3x3 kernel:

MORPH_RECT

10 iterations with cv.MORPH_CROSS with a 3x3 kernel:

MORPH_CROSS

You can change the offset by adjusting the number of iterations.

A much more accurate approach would be to use cv::distanceTransform to find all pixels that lie approximately 10px away from the contour:

dist = cv.distanceTransform(img, cv.DIST_L2, cv.DIST_MASK_PRECISE)
ring = cv.inRange(dist, 9.5, 10.5) # take all pixels at distance between 9.5px and 10.5px
show_image(ring)
contours, hierarchy = cv.findContours(ring, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)

vis = cv.cvtColor(img, cv.COLOR_GRAY2BGR)
cv.drawContours(vis, contours, 0, (0,0,255))
cv.drawContours(vis, contours, 2, (255,0,0))
show_image(vis)

distanceTransform bw distanceTransform

You'll get two contours on each side of the original contour. Use findContours with RETR_EXTERNAL to recover only the outer contour. To also recover the inner contour, use RETR_LIST

Gregggreggory answered 27/3, 2019 at 12:54 Comment(0)
S
3

I think the solution can be easier, without dilataion and new contours.

  1. For each contour search mass center: cv::moments(contours[i]) -> cv::Point2f mc(mu.m10 / mu.m00), mu.m01 / mu.m00));

  2. For each point point of contour: make shift for mass center -> multiply by coefficient K -> shift backward: pt_new = (k * (pt - mc) + mc);

But coefficient k must be individual for each point. I will calculate it a little later...

Spurtle answered 27/3, 2019 at 15:0 Comment(1)
But this will shift every point away from the mass center, whereas I would expect it moves away perpendicular from the outer line. For convex bodies this will likely not matter much, the trouble will probably arise when there are insets. For example a hole in the contour that is getting "fixed".Mcnair

© 2022 - 2024 — McMap. All rights reserved.