Страницы

Поиск по вопросам

вторник, 23 апреля 2019 г.

Обнаружение зон при использовании порога

Имею изображение после использования порога: Вопрос в обнаружении белых зон: 1) Используя многоугольник (который рисует пользователь (хотелось бы еще узнать как рисовать на Mat)) и узнавать процент закрашенности внутри этой зоны 2) Автоматически (рисуя вокруг зон квадраты) Любой из двух вариантов подойдет.
Дополнение Для внесения ясности попытался сфотошопить то, чтобы я хотел получить.
Попробовав так, получил краш при использовании FindContours (баг EmguCV или моя ошибка?)
using (Mat hirarchy = new Mat()) { using (VectorOfPoint countours = new VectorOfPoint()) { CvInvoke.FindContours(_diffMat, countours, hirarchy, RetrType.Tree, ChainApproxMethod.ChainApproxSimple);
for (int i = 0; i < countours.Size; i++) { CvInvoke.DrawContours(_diffMat, countours, i, new MCvScalar(0,0,255)); } } }


Ответ

Лучше начать, наверное, сразу со второго пункта, так как могу предположить, что именно он является приоритетным.
Бинаризация (пороговая обработка) - это по сути процесс, при котором количество информации, имеющейся на изображении, сводится к минимуму. Этот факт является одновременно и плюсом и минусом, поскольку вместе с непредставляющей интереса информацией стирается и та, что могла бы существенно помочь в решении задачи.
Если обратиться к изображению с "фотошопом" и в частности к некоторым из многоугольников по центру, границы которых отмечены красными линиями, то не трудно заметить наличие с правой стороны от них белых "зон". Эти "зоны" имеют тот же размер и в целом никак не выделяются от точно таких же белых "зон", но уже входящих в области интереса (парковочные места).
Белые "зоны" внутри области интереса также могут иметь произвольный размер и часто их нельзя рассматривать как единое целое с другими, расположенными неподалёку, поскольку они имеют самостоятельные границы и относительно близкий размер с теми, что не входят в область интереса.
Ко всему прочему, "зоны" между двумя областями интереса могут находиться ближе друг другу (по расстоянию между центральными моментами контуров), нежели чем "зоны" одного и того же парковочного места.
Все вышеперечисленные исключения в полной мере представлены на изображении "фотошопа", что фактически сводит на нет возможность корректной кластеризации контуров "зон" в объекты "автомобиль".
При помощи бинаризации чрезвычайно редко удаётся получить приемлемый результат, если исходное изображение содержит естественную среду.
Однако, если допустить вмешательство человека в работу алгоритма, а именно подсказать последнему настоящие границы объекта путём ручного выделения прямоугольников парковочных мест, то вся задача сведётся к достаточно простой операции. Пример бинаризации изображения на C++ и C# (EmguCV).
Предположим, имеется исходник:

Загружаем его, переводим в оттенки серого и бинаризуем:
C++:
cv::Mat src_mat = cv::imread("img.jpg"); if(src_mat.empty()) return;
cv::Mat gry_mat; cv::cvtColor(src_mat, gry_mat, cv::COLOR_BGR2GRAY);
cv::Mat bin1_mat, bin2_mat; cv::threshold(gry_mat, bin1_mat, 230, 255, cv::THRESH_BINARY); cv::threshold(gry_mat, bin2_mat, 25, 255, cv::THRESH_BINARY_INV); cv::Mat bin_mat = bin1_mat + bin2_mat;
C#:
Mat srcMat = new Mat("img.jpg", LoadImageType.AnyColor); if(srcMat.IsEmpty) return;
Mat grayMat = new Mat(); Mat bin1Mat = new Mat(); Mat bin2Mat = new Mat(); Mat binMat = new Mat();
CvInvoke.CvtColor(srcMat, grayMat, ColorConversion.Bgr2Gray); CvInvoke.Threshold(grayMat, bin1Mat, 230, 255, ThresholdType.Binary); CvInvoke.Threshold(grayMat, bin2Mat, 25, 255, ThresholdType.BinaryInv); CvInvoke.Add(bin1Mat, bin2Mat, binMat);
Здесь выполнена попытка бинаризации по верхней и нижней границам. Коэффициенты подобраны вручную и могут отличаться по оптимальности для других изображений. Таким образом получается сохранить больше полезных деталей:

Далее, если мы начнём искать контуры и нарисуем их:
C++:
std::vector > contours; cv::findContours(bin_mat.clone(), contours , cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
cv::Mat ctr_mat = cv::Mat::zeros(bin_mat.size(), CV_8U); cv::drawContours(ctr_mat, contours, -1, cv::Scalar::all(255), 1);
C#:
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint(); Mat hierarchy;
CvInvoke.FindContours(binMat, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple);
Mat ctrMat = new Mat(binMat.Size, DepthType.Cv8U, 3); CvInvoke.DrawContours(ctrMat, contours, -1, new MCvScalar(255));
... то получим такую картину:

Контуров оказалось довольно много, и уже закрадывается сомнение в том, что удастся собрать контуры отдельных частей в единый контур автомобиля. Попробуем отфильтровать часть из них, например, по площади, заодно нарисовав их опоясывающие "квадратики" на исходном изображении:
C++:
std::vector > contours; cv::findContours(bin_mat.clone(), contours , cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
std::vector > contours2; for(auto itr = contours.begin(); itr != contours.end(); ++itr) { if(cv::contourArea(*itr) > 8) { contours2.push_back(*itr);
cv::Rect rc = cv::boundingRect(*itr); cv::rectangle(src_mat, rc, cv::Scalar(0,0,255)); } }
cv::Mat ctr_mat = cv::Mat::zeros(bin_mat.size(), CV_8U); cv::drawContours(ctr_mat, contours2, -1, cv::Scalar::all(255), 1);
C#:
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint(); VectorOfVectorOfPoint contours2 = new VectorOfVectorOfPoint(); Mat hierarchy;
CvInvoke.FindContours(binMat, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple);
for (int i = 0; i < contours.Size; ++i) { if (CvInvoke.ContourArea(contours[i]) > 8) { contours2.Push(contours[i]);
Rectangle rc = CvInvoke.BoundingRectangle(contours[i]); CvInvoke.Rectangle(srcMat, rc, new MCvScalar(0, 0, 255)); } }
Mat ctrMat = new Mat(binMat.Size, DepthType.Cv8U, 3); CvInvoke.DrawContours(ctrMat, contours2, -1, new MCvScalar(255));
Контуров стало значительно меньше, "шума" поубавилось, но мы тем самым продолжаем терять информацию и об объектах интереса (автомобилях), делая их в итоге всё более непохожими на исходные. Те, что расположены поближе к камере, ещё куда ни шло, но чем дальше, тем ситуация становится плачевнее:

В конце концов, может быть стоит нарисовать опоясывающие контуры прямоугольники, и тогда станет видно, за что возможно было бы зацепиться с тем, чтобы объединить разрозненные части каждого автомобиля в единое целое:

Но ракурс съёмки не позволяет усомниться: части различных объектов расположены на относительно тех же расстояниях, что части одного и того же объекта, приводя к невозможности корректной кластеризации. А поскольку при бинаризации у нас других факторов, за которые возможно было бы зацепиться, попросту нет, то и задача с автоматическим определением границ автомобилей в имеющихся условиях становится нерешаемой. Ну может быть получится один-два автомобиля, находящихся ближе всего к камере, но не более.
В таком случае не остаётся ничего более, как вручную определить границы парковочных мест. Эту задачу на OpenCV обычно не перекладывают, т.к. у него функционал работы с мышью (с тем, чтобы пользователь указал на изображении координаты точек многоугольников парковочных мест) в целом ограничен и реализуют при помощи фреймворков общего назначения.
Для получения матрицы изображения парковочного места из заранее определённых координат в кадре можно воспользоваться простой конструкцией:
cv::Mat parking_lot_mat = src_mat(cv::Rect(0,0,100,100)).clone();
Теперь parking_lot_mat будет содержать часть исходного изображения по координатам x, равное 0, и y, равное 0, и будет размером 100х100 пикселей.
Далее производим всю ту же работу по бинаризации, что делали над большим изображением. Под конец останется найти контуры и их площади при помощи метода: cv::contourArea(). Если сумма площадей контуров превышает некий порог, вычисленный эмпирически, то значит автомобиль на данном парковочном месте можно считать обнаруженным.

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

Отправить комментарий