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

#c #opencv #contour #opencv-contour #opencv-drawcontour

#c #opencv #контур

Вопрос:

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

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

Imgur

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

Imgur

Imgur

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

Комментарии:

1. Вы можете использовать cv::dilate () и cv::erode (), а затем снова определить контуры.

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

Ответ №1:

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

Код на C :

 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 с ядром 3×3:

MORPH_RECT

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

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

Ответ №2:

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

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

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

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