OpenCV, как использовать массивы точек для сглаживания и контуров выборки?

У меня есть проблема, чтобы получить главу о контурах сглаживания и выборки в OpenCV (С++ API). Допустим, у меня есть последовательность точек, полученных из cv::findContours (например, применительно к этому изображению:

enter image description here

В конечном счете, я хочу

  • Сглаживание последовательности точек с использованием разных ядер.
  • Чтобы изменить размер последовательности, используя различные типы интерполяций.

После сглаживания я надеюсь получить результат, например:

enter image description here

Я также рассмотрел рисунок моего контура в cv::Mat, фильтрацию Мата (используя размытие или морфологические операции) и повторное обнаружение контуров, но медленно и субоптимально. Поэтому, в идеале, я мог бы выполнять эту работу, используя исключительно точечную последовательность.

Я прочитал несколько сообщений об этом и наивно думал, что могу просто преобразовать std::vector (of cv::Point) в cv::Mat, а затем функции OpenCV, такие как blur/resize, будут выполнять эту работу для меня... но они этого не сделали.

Вот что я пробовал:

int main( int argc, char** argv ){

    cv::Mat conv,ori;
    ori=cv::imread(argv[1]);
    ori.copyTo(conv);
    cv::cvtColor(ori,ori,CV_BGR2GRAY);

    std::vector<std::vector<cv::Point> > contours;
    std::vector<cv::Vec4i > hierarchy;

    cv::findContours(ori, contours,hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);

    for(int k=0;k<100;k += 2){
        cv::Mat smoothCont;

        smoothCont = cv::Mat(contours[0]);
        std::cout<<smoothCont.rows<<"\t"<<smoothCont.cols<<std::endl;
        /* Try smoothing: no modification of the array*/
//        cv::GaussianBlur(smoothCont, smoothCont, cv::Size(k+1,1),k);
        /* Try sampling: "Assertion failed (func != 0) in resize"*/
//        cv::resize(smoothCont,smoothCont,cv::Size(0,0),1,1);
        std::vector<std::vector<cv::Point> > v(1);
        smoothCont.copyTo(v[0]);
        cv::drawContours(conv,v,0,cv::Scalar(255,0,0),2,CV_AA);
        std::cout<<k<<std::endl;
        cv::imshow("conv", conv);
        cv::waitKey();
    }
    return 1;
}

Может ли кто-нибудь объяснить, как это сделать?

Кроме того, поскольку я, вероятно, работаю с гораздо меньшими контурами, мне было интересно, как этот подход будет иметь дело с эффектом границы (например, при сглаживании, поскольку контуры являются круговыми, последние элементы последовательности должны использоваться для вычисления новое значение первых элементов...)

Большое спасибо за ваши советы,

Edit:

Я также пробовал cv::approxPolyDP(), но, как вы можете видеть, он имеет тенденцию сохранять экстремальные точки (которые я хочу удалить):

Эпсилон = 0

enter image description here

Эпсилон = 6

enter image description here

Эпсилон = 12

enter image description here

Эпсилон = 24

enter image description here

Изменить 2: Как предположил Бен, кажется, что cv::GaussianBlur() не поддерживается, но cv::blur() есть. Это выглядит намного ближе к моим ожиданиям. Вот мои результаты:

к = 13

enter image description here

к = 53

enter image description here

к = 103

enter image description here

Чтобы обойти эффект границы, я сделал:

    cv::copyMakeBorder(smoothCont,smoothCont, (k-1)/2,(k-1)/2 ,0, 0, cv::BORDER_WRAP);
    cv::blur(smoothCont, result, cv::Size(1,k),cv::Point(-1,-1));
    result.rowRange(cv::Range((k-1)/2,1+result.rows-(k-1)/2)).copyTo(v[0]);

Я все еще ищу решения для интерполяции/выборки моего контура.

Ответ 1

Ваше гауссовское размытие не работает, потому что вы размыты в направлении столбца, но есть только один столбец. Использование GaussianBlur() приводит к ошибке "функция не реализована" в OpenCV при попытке скопировать вектор обратно в cv::Mat (вероятно, почему у вас этот странный resize() в вашем коде), но все работает отлично, используя cv::blur(), не нужно resize(). Попробуйте размер (0,41), например. Использование cv::BORDER_WRAP для проблемы с границей тоже не работает, но здесь - это еще один поток тех, кто нашел обходное решение для этого.

О... еще одна вещь: вы сказали, что ваши контуры, вероятно, будут намного меньше. Сглаживание контура таким образом сократит его. Крайний случай k = size_of_contour, что приводит к одной точке. Поэтому не выбирайте свой k слишком большой.

Ответ 2

Другая возможность заключается в использовании алгоритма openFrameworks:

https://github.com/openframeworks/openFrameworks/blob/master/libs/openFrameworks/graphics/ofPolyline.cpp#L416-459

Он пересекает контур и, по существу, применяет фильтр нижних частот, используя точки вокруг него. Должен делать именно то, что вы хотите с низкими накладными расходами (и нет никаких оснований делать большой фильтр на изображении, по существу, только контур).

Ответ 3

Из раздела 2.1 OpenCV doc Основные структуры:

template<typename T>
explicit Mat::Mat(const vector<T>& vec, bool copyData=false)

Вероятно, вы хотите установить 2-й параметр в true в:

smoothCont = cv::Mat(contours[0]);

и повторите попытку (этот способ cv::GaussianBlur должен иметь возможность изменять данные).

Ответ 4

Как насчет approxPolyDP()?

Он использует этот алгоритм для "сглаживания" контура (в основном gettig избавляется от большинства точек контура и оставляет те, которые представляют собой хороший аппроксимация вашего контура)

Ответ 5

Я знаю, что это было написано давно, но вы пробовали большую эрозию, за которой следовало большое расширение (открытие), а затем найти трафик? Это похоже на простое и быстрое решение, но я думаю, что он может работать, по крайней мере, до некоторой степени.

Ответ 6

В основном резкие изменения контура соответствуют высокочастотному контенту. Простым способом сглаживания контура было бы найти коэффициенты Фурье, предполагая, что координаты образуют комплексную плоскость х + iy, а затем путем исключения высокочастотных коэффициентов.

Ответ 7

Мое занятие... много лет спустя...!

Возможно два простых способа сделать это:

  • цикл несколько раз с расширением, размытием, эрозией. И найдите контуры на обновленной форме. Я нашел 6-7 раз дает хорошие результаты.
  • создать ограничительную рамку контура и нарисовать эллипс внутри ограниченного прямоугольника.

Добавление визуальных результатов ниже:

введите описание изображения здесь