Как получить расширенный или сжатый контур в OpenCV?

Можно ли получить расширенную или сокращенную версию контура?

Например, на изображении ниже я использовал cv::findContour() и cv::drawContour для бинарного изображения, чтобы получить контуры:

Имгур

Я хотел бы нарисовать еще один контур, который имеет заданное расстояние в пикселях от исходного контура, например:

Имгур

Имгур

За исключением эрозии, что, как мне кажется, может быть плохой идеей, поскольку с помощью эрозии трудно контролировать расстояние между пикселями, я понятия не имею, как решить эту проблему. Могу ли я узнать, какое должно быть правильное направление?


person Komgcn    schedule 27.03.2019    source источник
comment
Вы можете использовать cv::dilate() и cv::erode(), а затем снова обнаружить контуры.   -  person zeFrenchy    schedule 27.03.2019
comment
Насколько велики исходные контуры? Насколько точно полученный контур должен отражать форму исходного контура? Эти два вопроса определят, насколько сложным будет решение. Я согласен, что cv::dilate или cv::erode будут выполнять работу в определенной степени (или в деталях). Сверхтонкое решение будет включать в себя что-то вроде нахождения центра масс, проецирования x, y координат исходного контура в правильном направлении и определения новых x, y координат для результирующего контура, таким образом, я предполагаю, что будет много интерполяции и экстраполяции.   -  person HansHirse    schedule 27.03.2019


Ответы (2)


Использование cv::erode с небольшим ядром и несколькими итерациями может быть достаточным для ваших нужд, даже если это не точно.

Код С++:

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

Демо:

# 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 итераций cv.MORPH_RECT с ядром 3x3:

MORPH_RECT

10 итераций cv.MORPH_CROSS с ядром 3x3:

MORPH_CROSS

Вы можете изменить смещение, отрегулировав количество итераций.

Гораздо более точным подходом было бы использование cv::distanceTransform для поиска всех пикселей, которые находятся примерно в 10 пикселях от контура:

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

Вы получите два контура с каждой стороны исходного контура. Используйте findContours с RETR_EXTERNAL, чтобы восстановить только внешний контур. Чтобы также восстановить внутренний контур, используйте RETR_LIST

person Stefan Dragnev    schedule 27.03.2019

Я думаю, что решение может быть проще, без расширения и новых контуров.

  1. Для каждого центра масс поиска контура: cv::moments(contours[i]) -> cv::Point2f mc(mu.m10 / mu.m00), mu.m01 / mu.m00));

  2. Для каждой точки контура: сделать сдвиг центра масс -> умножить на коэффициент K -> сдвинуть назад: pt_new = (k * (pt - mc) + mc);

Но коэффициент k должен быть индивидуальным для каждой точки. Чуть позже посчитаю...

person Nuzhny    schedule 27.03.2019