Имею изображение после использования порога:
Вопрос в обнаружении белых зон:
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(). Если сумма площадей контуров превышает некий порог, вычисленный эмпирически, то значит автомобиль на данном парковочном месте можно считать обнаруженным.