Jump to content
Compvision.ru
idrua

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

Recommended Posts

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

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

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites

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

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;

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

Share this post


Link to post
Share on other sites
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?

 

Share this post


Link to post
Share on other sites

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

Первый вариант, который я описывал: Например, у нас есть первоначальный прямоугольник 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). Или оставлять только самые лучшие расширения.

Share this post


Link to post
Share on other sites

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

///
/// \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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Recently Browsing   0 members

    No registered users viewing this page.

×