Перейти к содержимому
Compvision.ru
idrua

Нахождение контуров прямоугольников

Recommended Posts

А кто мне подскажет, как найти контуры прямоугольников в такой сложной ситуации? Выделил красным желаемый результат разделения.

Вероятно, можно нейросетями (не пробовал, но уверен, что получится), но хотелось бы классическими методами OpenCV.ce4119cc6e4b396f4a4a379e18fbc268-full.pn

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

На рисунке слипшиеся объекты. Классическим методом для разделения являются НС. Ещё можно через мат. морфологию оператор открытия - open .

Ещё можно искать углы и восстанавливать четвёртый по трём другим.  Через детектор особых точек(ищем углы). А далее школьная классика перебор точек через 3-и цикла и поиск прямоугольных треугольников.  

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

На правах идеи, подглядел где-то у Смородова:

1. Находим точку С0 внутри самого большого скопления белых объектов (контурами или вертикальными и горизонтальными гистограммами). Это будет где-то внутри белого большого прямоугольника.

2. Считаем, что в точке С0 у нас расположен центр прямоугольника с размерами 3х3 пикселя.

3. В цикле увеличиваем размер прямоугольника следующими 4-мя преобразованиями: сдвиг границы прямоугольника влево, вправо, вверх и вниз на 1 пиксель (можно и больше сначала). Получаем 4 новых прямоугольника, у каждого из которых смотрим отношение белых пикселей к общей площади k1. Если отношение больше, скажем, k = 0.95, то получаем новый прямоугольник с центром C1 и размерами на 1 пиксель больше. Из 4-х прямоугольников выбираем тот, у которого отношение наибольшее.

4. Повторяем пункт 3 пока не достигнем границ картинки, либо пока отношение белой площади к обще не станет ниже, чем k.

5. Если исходный прямоугольник удовлетворяет нашим условиям (размер его больше и меньше неким эвристикам), то он вычислен верно. Закрашиваем его полностью чёрным цветом.

6. Повторяем пункты 1-5, пока у нас всё изображение не станет чёрным, либо пока не закончатся прямоугольники достаточно большого размера.

 

Эта процедура может быть достаточно медленной, но она поддаётся ускорению:

- в пункте 3 можно брать шаг побольше, чем 1 и снижать его, если значение ki будет меньше k;

- для посчёта сумм внутри прямоугольников можно предварительно вычислить интегральное изображение и брать суммы по нему.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
21 час назад, Nuzhny сказал:

На правах идеи, подглядел где-то у Смородова:

3. В цикле увеличиваем размер прямоугольника следующими 4-мя преобразованиями: сдвиг границы прямоугольника влево, вправо, вверх и вниз на 1 пиксель (можно и больше сначала). Получаем 4 новых прямоугольника, у каждого из которых смотрим отношение белых пикселей к общей площади k1. Если отношение больше, скажем, k = 0.95, то получаем новый прямоугольник с центром C1 и размерами на 1 пиксель больше. Из 4-х прямоугольников выбираем тот, у которого отношение наибольшее.

 

С пунктом 3 как-то не очень понятно.
Было 3х3, увеличили, стало 5х5. Или увеличиваем по одной стороне, а не все сразу?
Имеем 5х5, разбили на 4 по 3х3. Предположим, все белого цвета, отношение одинаковое. Оставляем старый центр?
Еще увеличили и теперь общий размер 6х6. Разбивать с другим размером 4х4?

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Этот пункт можно выполнять по-разному.

Первый вариант, который я описывал: Например, у нас есть первоначальный прямоугольник Rect(15, 20, 3, 3).

Делаем из него 4: граница влево Rect(14, 20, 4, 3), граница вправо Rect(15, 20, 4, 3), граница вверх Rect(15, 19, 3, 4), граница вниз Rect(15, 20, 3, 4).

Если у одного из них соотношение выше всех, то его и оставляем. Если одинаковое у всех, то можно оставить и все расширения: Rect(14, 19, 5, 5). Или оставлять только самые лучшие расширения.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Ну как, получилось? У меня появилось немного времени, я реализовал свою идею (код корявый, конечно - сорри):

///
/// \brief PosRefinement
/// \param bin
/// \param cutRect
/// \param kThreshold
/// \param minSize
/// \param vw
/// \return 
///
bool PosRefinement(
    cv::Mat bin,
    cv::Rect& cutRect,
    double kThreshold,
    cv::Size minSize,
    cv::VideoWriter& vw
    )
{
    cv::namedWindow("mc", cv::WINDOW_NORMAL | cv::WINDOW_KEEPRATIO);
    cv::Mat clImg;
    cv::cvtColor(bin, clImg, cv::COLOR_GRAY2BGR);
    cv::waitKey(1);

    const double areaThreshold = 100;
    const int radius = 5;
    const int maxIters = 100;

    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(bin, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE, cv::Point());

    size_t bestCont = contours.size();
    double maxArea = 0;
    for (size_t i = 0; i < contours.size(); i++)
    {
        double area = cv::contourArea(contours[i]);
        if (area > maxArea)
        {
            maxArea = area;
            bestCont = i;
        }
    }
    std::cout << "maxArea = " << maxArea << std::endl;
    if (maxArea < areaThreshold)
    {
        return false;
    }

    cv::Moments m = cv::moments(contours[bestCont]);
    cv::Point mc(cvRound(m.m10 / m.m00), cvRound(m.m01 / m.m00));

    cv::Rect currRect(mc.x - radius / 2, mc.y - radius / 2, radius, radius);

    cv::rectangle(clImg, currRect, cv::Scalar(0, 0, 255), 1);
    cv::imshow("mc", clImg);
    cv::waitKey(1);

    auto Clamp = [](int v, int hi) -> bool
    {
        if (v < 0)
        {
            v = 0;
            return true;
        }
        else if (hi && v > hi - 1)
        {
            v = hi - 1;
            return true;
        }
        return false;
    };
    auto RectClamp = [&](cv::Rect& r, int w, int h) -> bool
    {
        return Clamp(r.x, w) || Clamp(r.x + r.width, w) || Clamp(r.y, h) || Clamp(r.y + r.height, h);
    };

    int stepL = radius / 2;
    int stepR = radius / 2;
    int stepT = radius / 2;
    int stepB = radius / 2;

    double k = 0;

    struct State
    {
        double k = 0;
        int stepL = 0;
        int stepR = 0;
        int stepT = 0;
        int stepB = 0;
        cv::Rect currRect;

        State() = default;
        State(double k_, int stepL_, int stepR_, int stepT_, int stepB_, cv::Rect currRect_)
            :
              k(k_),
              stepL(stepL_),
              stepR(stepR_),
              stepT(stepT_),
              stepB(stepB_),
              currRect(currRect_)
        {
        }
        bool operator==(const State& st) const
        {
            return (st.k == k) && (st.stepL == stepL) && (st.stepR == stepR) && (st.stepT == stepT) && (st.stepB == stepB) && (st.currRect == currRect);
        }
    };
    const size_t statesCount = 2;
    State prevStates[statesCount];
    size_t stateInd = 0;

    for (int it = 0; it < maxIters; ++it)
    {
        std::cout << "it " << it << ": " << currRect << std::endl;

        cv::Rect rleft(currRect.x - stepL, currRect.y, currRect.width + stepL, currRect.height);
        cv::Rect rright(currRect.x, currRect.y, currRect.width + stepR, currRect.height);
        cv::Rect rtop(currRect.x, currRect.y - stepT, currRect.width, currRect.height + stepT);
        cv::Rect rbottom(currRect.x, currRect.y, currRect.width, currRect.height + stepB);

        double kleft = 0;
        double kright = 0;
        double ktop = 0;
        double kbottom = 0;

        if (!RectClamp(rleft, bin.cols, bin.rows))
        {
            cv::Rect rstep(currRect.x - stepL, currRect.y, stepL, currRect.height);
            if (cv::sum(bin(rstep))[0] / (255.0 * rstep.area()) > kThreshold / 2)
            {
                kleft = cv::sum(bin(rleft))[0] / (255.0 * rleft.area());
            }
        }
        if (!RectClamp(rright, bin.cols, bin.rows))
        {
            cv::Rect rstep(currRect.x + currRect.width, currRect.y, stepR, currRect.height);
            if (cv::sum(bin(rstep))[0] / (255.0 * rstep.area()) > kThreshold / 2)
            {
                kright = cv::sum(bin(rright))[0] / (255.0 * rright.area());
            }
        }
        if (!RectClamp(rtop, bin.cols, bin.rows))
        {
            cv::Rect rstep(currRect.x, currRect.y - stepT, currRect.width, stepT);
            if (cv::sum(bin(rstep))[0] / (255.0 * rstep.area()) > kThreshold / 2)
            {
                ktop = cv::sum(bin(rtop))[0] / (255.0 * rtop.area());
            }
        }
        if (!RectClamp(rbottom, bin.cols, bin.rows))
        {
            cv::Rect rstep(currRect.x, currRect.y + currRect.height, currRect.width, stepB);
            if (cv::sum(bin(rstep))[0] / (255.0 * rstep.area()) > kThreshold / 2)
            {
                kbottom = cv::sum(bin(rbottom))[0] / (255.0 * rbottom.area());
            }
        }

        std::cout << "kleft = " << kleft << ", kright = " << kright << ", ktop = " << ktop << ", kbottom = " << kbottom << std::endl;

        bool wasEnlargeX = false;
        if (kleft > kThreshold)
        {
            currRect.x -= stepL;
            currRect.width += stepL;
            wasEnlargeX = true;

            if (kleft > k)
            {
                ++stepL;
            }
        }
        else
        {
            if (stepL > 1)
            {
                --stepL;
            }
            currRect.x += 1;
            currRect.width -= 1;
        }
        if (kright > kThreshold)
        {
            currRect.width += stepR;
            wasEnlargeX = true;

            if (kright > k)
            {
                ++stepR;
            }
        }
        else
        {
            if (stepR > 1)
            {
                --stepR;
            }
            currRect.width -= 1;
        }

        bool wasEnlargeY = false;
        if (ktop > kThreshold)
        {
            currRect.y -= stepT;
            currRect.height += stepT;
            wasEnlargeY = true;

            if (ktop > k)
            {
                ++stepT;
            }
        }
        else
        {
            if (stepT > 1)
            {
                --stepT;
            }
            currRect.y += 1;
            currRect.height -= 1;
        }
        if (kbottom > kThreshold)
        {
            currRect.height += stepB;
            wasEnlargeY = true;

            if (kbottom > k)
            {
                ++stepB;
            }
        }
        else
        {
            if (stepB > 1)
            {
                --stepB;
            }
            currRect.height -= 1;
        }

        k = cv::sum(bin(currRect))[0] / (255.0 * currRect.area());
        std::cout << "k = " << k << ", stepL = " << stepL << ", stepR = " << stepR << ", stepT = " << stepT << ", stepB = " << stepB << std::endl;

        State currState(k, stepL, stepR, stepT, stepB, currRect);

        bool repState = false;
        for (size_t i = 0; i < statesCount; ++i)
        {
            if (prevStates[i] == currState)
            {
                repState = true;
                break;
            }
        }
        if (repState)
        {
            break;
        }
        else
        {
            prevStates[stateInd] = currState;
            stateInd = (stateInd + 1 < statesCount) ? (stateInd + 1) : 0;
        }

        if (k < kThreshold && (stepL + stepR + stepT + stepB == 4) && !wasEnlargeX && !wasEnlargeY)
        {
            break;
        }

        cv::cvtColor(bin, clImg, cv::COLOR_GRAY2BGR);
        cv::rectangle(clImg, currRect, cv::Scalar(0, 0, 255), 1);
        cv::imshow("mc", clImg);
        cv::waitKey(40);
        if (vw.isOpened())
        {
            vw << clImg;
        }
    }

    cutRect = currRect;
    std::cout << cutRect << ", " << minSize << std::endl;
    return (cutRect.width >= minSize.width) && (cutRect.height >= minSize.height);
}
      
///
/// \brief main
/// \param argc
/// \param argv
/// \return
///
int main(int argc, char** argv)
{
    cv::Mat img = cv::imread("/home/snuzhny/Downloads/ce4119cc6e4b396f4a4a379e18fbc268-full.png", cv::IMREAD_GRAYSCALE);

    cv::Rect objRect(0, 0, 630, 300);
    cv::Size minSize(objRect.width / 10, objRect.height / 10);

    cv::Mat clImg;
    cv::cvtColor(cv::Mat(img, objRect), clImg, cv::COLOR_GRAY2BGR);

    cv::Mat bin;
    cv::threshold(cv::Mat(img, objRect), bin, 128, 255, cv::THRESH_BINARY);

    cv::namedWindow("clImg", cv::WINDOW_NORMAL | cv::WINDOW_KEEPRATIO);
    cv::imshow("clImg", clImg);

    cv::VideoWriter vw("res_rects.avi", cv::VideoWriter::fourcc('P', 'I', 'M', '1'), 25, clImg.size(), true);

    for (;;)
    {
        cv::Rect cutRect;
        if (!PosRefinement(bin, cutRect, 0.9f, minSize, vw))
        {
            break;
        }
        cv::rectangle(bin, cutRect, cv::Scalar(0, 0, 0), cv::FILLED);

        objRect.x += cutRect.x;
        objRect.y += cutRect.y;
        objRect.width = cutRect.width;
        objRect.height = cutRect.height;

        cv::rectangle(clImg, cutRect, cv::Scalar(0, 0, 255), 1);

        cv::imshow("clImg", clImg);
        cv::waitKey(1);
    }

    cv::imshow("clImg", clImg);
    cv::waitKey(0);

    cv::imwrite("res_rects.png", clImg);
    return 0;
}

 

res_rects.png

res_rects.avi

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Ого! Спасибо, гляну. Я на C# пишу, но думаю разберусь, OpenCV примерно везде одинаковый.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте учётную запись или войдите для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать учётную запись

Зарегистрируйтесь для создания учётной записи. Это просто!

Зарегистрировать учётную запись

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас


  • Сейчас на странице   0 пользователей

    Нет пользователей, просматривающих эту страницу

×