Код, который вы указали, не самый лучший, но давайте посмотрим, что произойдет.
Я взял это начальное изображение знака:
![Original Image](https://i.stack.imgur.com/zsVj8.jpg)
Затем вызывается pre_process
, который в основном запускает детектор контуров Canny, а также некоторые приемы, которые должны привести к лучшему обнаружению контуров.Я не буду смотреть на них, но это то, что он возвращает:
![pre_process result](https://i.stack.imgur.com/mVFkj.jpg)
Не самое большое.Может быть, какая-то настройка параметров поможет.
Но теперь вызывается CtC_features
, что является предметом вопроса.Роль CtC_features
заключается в получении некоторых функций для алгоритмов машинного обучения.Это сводится к нахождению числового описания изображения, которое поможет алгоритму ML обнаружить знак.Такое описание может быть чем угодно.Подумайте о том, как это описал бы кто-то, кто никогда не видел знак СТОП и не знает, как читатьОни сказали бы что-то вроде: «Красная плоская пластина с 8 сторонами и белым материалом посередине».Основываясь на этом описании, кто-то может сказать, что это знак СТОП.Мы хотим сделать то же самое, но поскольку компьютеры являются компьютерами, мы ищем числовые функции.А с их помощью можно научить некоторому алгоритму «узнавать», какие функции имеет каждый знак.
Итак, давайте посмотрим, какие функции CtC_features
получает от контуров.
Первоеон звонит findContours
.Эта функция берет двоичное изображение и возвращает массивы точек, представляющих контуры изображения.По сути, он берет ребра и помещает их в массивы точек.С подключением, поэтому мы в основном знаем, какие точки связаны.Если мы используем код здесь для визуализации, мы можем видеть, что происходит:
![Contours](https://i.stack.imgur.com/R17VN.jpg)
Итак, массив contours
представляет собой std::vector<std::vector<cv::Point>>
иу вас есть в каждом подмассиве непрерывный контур, здесь нарисованный другим цветом.
Далее мы вычисляем количество точек (краевых пикселей) и делаем среднее по их координатам, чтобы найти центр тяжестикрай изображения.Центроид - это заполненный круг:
![centroid visualization](https://i.stack.imgur.com/o3ccs.jpg)
Затем мы перебираем все точки и создаем вектор std::pair<double, double>
, записывая для каждой точкирасстояние от центроида и угол.Функция угла определяется в нижней части файла как
double angle(Point2f a, Point2f b) {
return atan((a.y - b.y) / (a.x - b.x));
}
. Она в основном вычисляет угол линии от a
до b
относительно оси x, используя функцию арктангенса.Я позволю вам посмотреть видео по арктангенсу, но это то, что он дает вам угол из соотношения.В радианах (круг равен 2 пи радианам, полукруг - пи радиан).Проблема в том, что функция является периодической, с периодом PI.Это означает, что на круге (круг всех точек на одинаковом расстоянии вокруг центроида) есть 2 угла, которые дают одинаковое значение.Итак, мы вычисляем отношение (отношение, которое известно как тангенс угла), применяем обратную функцию (арктангенс) и получаем угол (соответствующий точке).Но что, если это другой момент?Итак, мы знаем, что другая точка точно со смещением градусов PI (это диаметрально противоположно), поэтому мы добавляем PI, если обнаружим, что это другая точка.
Рисунок ниже также помогает понять, почему есть 2точки:
![enter image description here](https://i.stack.imgur.com/rHMVo.jpg)
Тангенс угла подсвечивается вертикальным расстоянием ,.Но угол на другой стороне диагональной линии, которая пересекает окружность слева внизу, также имеет ту же касательную.Функция atan
дает касательные только для углов с левой стороны от центра.Обратите внимание, что нет двух направлений с одинаковой касательной.
Проверка проверяет, находится ли точка справа от центроида.Это сделано для того, чтобы иметь возможность добавить полукруга (радианы PI или 180 градусов), чтобы скорректировать результат atan
.
Теперь мы знаем расстояние (простая формула) и нашли (и скорректировали) угол. Вставляем эту пару в вектор feature_v
и сортируем ее. Функция сортировки, которая называется так, сортирует по первому элементу пары, поэтому мы сортируем по углу, а затем по расстоянию.
Переменная interval
:
int degree = 10;
double interval = double((double(degree) / double(360)) * 2 * 3.14159); //5 degrees interval
- это просто значение degree
, переведенное из градусов в радианы. Нам нужны радианы, так как углы были вычислены в радианах, а градусы более удобны для пользователя. Да, комментарий неправильный, интервал 10 градусов, а не 5.
Переменная ang
, определенная ниже, равна -PI / 2 (четверть круга):
double ang = - 1.57079;
Теперь он делит точки вокруг центроида на бункеры, основываясь на угле. Каждая корзина имеет ширину 10 градусов. Это делается путем перебора точек, отсортированных по углу, все накапливаются, пока мы не перейдем к следующему бину. Нас интересует только наибольшее расстояние точки в каждой ячейке. Начальная точка должна быть достаточно маленькой, чтобы захватить все направления (точки).
Чтобы понять, почему он начинается с -PI / 2, нам нужно вернуться к диаграмме тригонометрической функции выше. Что произойдет, если угол пойдет так:
![negative tangent](https://i.stack.imgur.com/MaDs3.jpg)
Посмотрите, как выделенный вертикальный сегмент идет "вниз" по оси y. Это означает, что его длина (и косвенно касательная) здесь отрицательна. Кроме того, угол считается отрицательным (в противном случае было бы 2 угла на одной стороне центра с одинаковой касательной). Теперь нас интересует диапазон углов, который у нас есть. Это все углы на правой стороне центроида, начиная с нижней точки в -PI / 2 и заканчивая верхней в PI / 2. Диапазон Пи радиан, или 180 градусов. Это также написано в документации atan :
Если ошибок не возникает, возвращается арктангенс арг (arctan (arg)) в диапазоне [-PI / 2, + PI / 2] радиан.
Итак, мы просто разбиваем все возможные направления (360 градусов) на сегменты по 10 градусов и берем расстояние до самой дальней точки в каждом контейнере. Поскольку круг имеет 360 градусов, мы получим 360/10 = 36 корзин. Затем они нормализуются так, что наибольшее значение равно 1. Это немного помогает в алгоритме машинного обучения.
Как мы можем узнать, принадлежит ли выбранная нами точка знаку? Мы не Большинство компьютерных зрений делают некоторые предположения относительно изображения, чтобы упростить проблему. Идея алгоритма заключается в определении формы знака путем записи расстояния от центра до краев. Это делает предположение, что центроид находится примерно в середине знака. В зависимости от используемого алгоритма ML и обучающих данных могут быть получены различные уровни надежности.
Кроме того, предполагается, что (некоторые из) ребер могут быть надежно идентифицированы. Посмотрите, как на моем изображении алгоритм не смог обнаружить верхний левый край?
Хорошая новость в том, что это не обязательно должно быть идеально. Алгоритмы ML знают, как обрабатывать эту вариацию (до некоторой степени) при условии, что они соответствующим образом обучены. Это не должно быть идеально, но это должно быть достаточно хорошо. Чтобы ответить на вопрос, что значит достаточно хорошо, каковы действительные ограничения алгоритма, необходимо провести дополнительное тестирование и некоторое понимание используемого алгоритма ML. Но это также и то, почему ML так популярен в видении: он может справляться с множеством вариаций довольно хорошо.
В конце мы получаем массив из 36 чисел, по одному на каждую из 36 корзин по 10 градусов, представляющих максимальное расстояние до точки в корзине. Я предполагаю, что это потому, что разработчик алгоритма хотел получить способ запечатлеть форму знака, рассматривая расстояния от центра в различных направлениях. Это предполагает, что края не обнаруживаются на заднем плане, а знак выглядит примерно так:
![enter image description here](https://i.stack.imgur.com/hlxx2.png)
Максимальное расстояние используется для выбора границы, а не те или иные символы на знаке.
Здесь он не используется напрямую, но возможно связанное чтение - это преобразование Хафа , которое использует аналогичную специализацию для обнаружения прямых линий на изображении.