idrua 8 Жалоба Опубликовано August 24, 2018 А кто мне подскажет, как найти контуры прямоугольников в такой сложной ситуации? Выделил красным желаемый результат разделения. Вероятно, можно нейросетями (не пробовал, но уверен, что получится), но хотелось бы классическими методами OpenCV. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Pavia00 32 Жалоба Опубликовано August 27, 2018 На рисунке слипшиеся объекты. Классическим методом для разделения являются НС. Ещё можно через мат. морфологию оператор открытия - open . Ещё можно искать углы и восстанавливать четвёртый по трём другим. Через детектор особых точек(ищем углы). А далее школьная классика перебор точек через 3-и цикла и поиск прямоугольных треугольников. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Nuzhny 243 Жалоба Опубликовано August 27, 2018 На правах идеи, подглядел где-то у Смородова: 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; - для посчёта сумм внутри прямоугольников можно предварительно вычислить интегральное изображение и брать суммы по нему. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
idrua 8 Жалоба Опубликовано August 28, 2018 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? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Nuzhny 243 Жалоба Опубликовано August 28, 2018 Этот пункт можно выполнять по-разному. Первый вариант, который я описывал: Например, у нас есть первоначальный прямоугольник 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). Или оставлять только самые лучшие расширения. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Nuzhny 243 Жалоба Опубликовано September 7, 2018 Ну как, получилось? У меня появилось немного времени, я реализовал свою идею (код корявый, конечно - сорри): /// /// \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.avi 1 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
idrua 8 Жалоба Опубликовано September 7, 2018 Ого! Спасибо, гляну. Я на C# пишу, но думаю разберусь, OpenCV примерно везде одинаковый. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах