Создаём нейронную сеть InceptionV3 для распознавания изображений

Привет, Хабр! Под катом пойдёт речь о реализации свёрточной нейронной сети архитектуры InceptionV3 с использованием фреймворка Keras. Статью я решил написать после ознакомления с туториалом «Построение мощных моделей классификации с использованием небольшого количества данных». С одобрения автора туториала я немного изменил содержание своей статьи. В отличие от предложенной автором нейронной сети VGG16, мы будем обучать гугловскую глубокую нейронную сеть Inception V3, которая уже предустановлена в Keras.
- Импортировать нейронную сеть Inception V3 из библиотеки Keras;
- Настраивать сеть: загружать веса, изменять верхнюю часть модели (fc-layers ), таким образом, приспосабливая модель под бинарную классификацию;
- Проводить тонкую настройку нижнего свёрточного слоя нейронной сети;
- Применять аугментацию данных при помощи ImageDataGenerator;
- Обучать сеть по частям для экономии ресурсов и времени;
- Оценивать работу модели.
1. Данные:
В нашем примере мы будем использовать изображения с соревнования по машинному обучению под названием «Dogs vs Cats» на kaggle.com. Данные будут доступны после регистрации. Набор включает в себя 25000 изображений: 12500 кошек и 12500 собак. Класс 1 соответствует собакам, класс 0- кошкам. После загрузки архивов, разместите по 1000 изображений каждого класса в директориях следующим образом:
Вам ничего не мешает пользоваться всем набором данных. Я, также как и автор оригинальной статьи, решил использовать ограниченную выборку, чтобы проверить эффективность работы сети с малым набором изображений.
У нас есть три проблемы:
- Ограниченный объём данных;
- Ограниченные системные ресурсы (у меня, к примеру, Intel Core i5-4440 3.10GHz, 8 Гб оперативной памяти, NVIDIA GeForce GTX 745);
- Ограниченное время: мы хотим обучать модель меньше суток.
При ограниченном объёме изображения высока вероятность переобучения. Для борьбы с этим нужно:
- Установить большой Dropout. В нашем случае он будет 0.5;
- Будем использовать аугментацию данных. Этот приём позволит нам увеличить количество изображений путём различных трансформаций (в нашем случае будут изменения масштаба, сдвиги, горизонтальное отражение).
- Для нашего эксперимента мы возьмём глубокую сеть.
- Изначально мы используем модель, обученную на большом количестве изображений из базы imagenet. Благо, что набор изображений включал кошек и собак;
- Будем обучать модель по частям:
- Сначала прогоним аугментированные изображения через нижнюю часть сети (только через Inception) и сохраним их в виде numpy массивов;
- Обучим с помощью полученных numpy массивов верхний полносвязный слой;
- Затем, мы объединим верхний и нижний слои модели и проведём тонкую настройку новой модели, но при этом мы заблокируем от обучения у Inception все слои, кроме последнего.
2. Создадим модель InceptionV3, загрузим в неё изображения, сохраним их:
В библиотеке Keras находится несколько подготовленных нейронных сетей.
include_top: включать или не включать верхнюю часть сети, которая представляет из полносвязный слой с 1000 выходов.
Нам он без надобности, поэтому ставим: include_top=False;
weights: загружать/не загружать обученные веса. Если None, веса инициализируются случайно. Если «imagenet», будут загружены веса, тренированные на данных ImageNet.
В нашей модели веса нужны, поэтому weights=«imagenet»;
input_tensor: данный аргумент удобен, если мы используем слой Input для нашей модели.
Мы его не будем трогать.
input_shape : в этом аргументе мы задаём размер нашего изображения. Его указывают, если отсоединяется верхний слой (include_top=False). Если мы загрузили модель с верхним слоем, сто размер изображения должен быть только (3, 299, 299).
Мы убрали верхний слой и хотим анализировать изображения меньшего размера (3, 150, 150). Поэтому кажем: input_shape=()
Создаём нашу модель:
Теперь сделаем аугментацию данных. Для этого в Keras предусмотрены так называемые ImageDataGenerator. Они будут брать изображения прямо из папок и проводить над ними все необходимые трансформации.
Картинки каждого класса должны быть в отдельных папках. Для того, чтобы нам не загружать оперативную память изображениями, мы их трансформируем прямо перед загрузкой в сеть. Для этого используем метод .flow_from_directory. Создадим отдельные генераторы для тренировочных и тестовых изображений:
Хочу выделить важный момент. Мы указали shuffle=False. То есть, изображения из разных классов не будут перемешиваться. Сначала будут поступать изображения из первой папки, а когда они все закончатся, из второй. Зачем это нужно, увидите позже.
Прогоним аугментированные изображения через обученную Inception и сохраним вывод в виде numpy массивов:
Процесс займёт некоторое время.
3. Создадим верхнюю часть модели, загрузим в неё данные, сохраним их:
В оригинальном посте автор использовал один слой сети с 256 нейронами, однако я буду использовать два слоя по 64 нейрона в каждом и Dropout слой со значением 0.5. Это изменение я был вынужден внести из-за того, когда я обучал готовую модель (которую мы сделаем в следующем шаге), мой компьютер зависал и перезагружался.
Загрузим массивы:
Обратите внимание, ранее мы указывали shuffle=False. А теперь мы легко укажем labels. Поскольку в каждом классе у нас 2000 изображений и все изображения поступали по очереди, у нас будет по 1000 нулей и 1000 единиц для тренировочной и для тестовой выборок.
Создадим модель FFN сети, скомпилируем её:
Загрузим в неё наши массивы:
Теперь мы загружаем данные не из папок, поэтому используем обычный метод fit.
Оценим точность модели:
Наша модель удовлетворительно справляется со своей задачей. Но принимает она только numpy массивы. Это нас не устраивает. Для того, чтобы получить полноценную модель, которая принимает на вход изображения, мы соединим две наши модели и обучим их ещё раз.
4. Создадим итоговую модель, загрузим в неё аугментированные данные, сохраним веса:
Загрузим в неё веса:
Скажу честно, я не заметил никакой разницы между эффективностью обучения модели с загрузкой весов или без неё. Но оставил этот раздел, потому что он описывает, как загрузить веса в определённые слои по имени (by_name=True).
Заблокируем с 1 по 205 слои Inception:
Скомпилируем модель:
Сделаем так, чтобы в процессе обучения сохранялись только веса с наибольшей точностью на тестовой выборке:
Создадим новые генераторы изображений для обучения полной модели. Мы будем трансформировать только учебную выборку. Тестовую трогать не будем.
pred_generator мы используем позже для демонстрации работы модели.
Загрузим изображения в модель:
У меня на каждую эпоху уходило по 210-220 секунд. 200 эпох обучения заняли около 12 часов.
5. Оценим точность модели
Вот нам и пригодился pred_generator. Обратите внимание, val_samples должны соответствовать величине batch_size в генераторе!
Точность 91,7%. Учитывая ограниченность выборки, возьму на себя смелость сказать, что это неплохая точность.
Проиллюстрируем работу модели
Просто смотреть на % правильных ответов и величину ошибки нам не интересно. Давайте посмотрим, сколько правильных и неправильных ответов дала модель для каждого класса:
pred_generator.next() удобная штука. Она загружает изображения в переменную и присваивает лейблы.
Количество изображений каждого класса будет различным при каждой генерации:
Сколько изображений каждого класса модель предсказала правильно?
pd.crosstab(labels,rounded_pred)
| Col_0 | 0.0 | 1.0 |
|---|---|---|
| Row_0 | ||
| 0.0 | 47 | 4 |
| 1.0 | 8 | 41 |
Для модели было загружено 100 случайных изображений: 51 изображение кошек и 49 собак. Из 51 кошки модель правильно распознала 47. Из 50 собак правильно были распознаны 41. Общая точность модели на этой узкой выборке составила 88%.
Посмотрим, какие фотографии были распознаны неправильно:
Синие числа-истинный класс изображений. Красные числа- предсказанные моделью (если красное число меньше 0.5, модель считает, что на фото кот, если больше 0.5, то собака). Чем больше число приближается к нулю, тем сеть больше уверена, что перед ней кот. Интересно, что много ошибок с изображениями собак содержат породы небольшого размера или щенков.
Посмотрим первые 20 изображений, которые модель предсказала правильно:
Видно, что модель прилично справляется с задачей распознавания изображений на сравнительно небольших выборках.
Я надеюсь, что пост был вам полезен. С удовольствием выслушаю ваши вопросы или пожелания.
Как написать программу распознавания изображений

- Импортировать нейронную сеть Inception V3 из библиотеки Keras;
- Настраивать сеть: загружать веса, изменять верхнюю часть модели (fc-layers ), таким образом, приспосабливая модель под бинарную классификацию;
- Проводить тонкую настройку нижнего свёрточного слоя нейронной сети;
- Применять аугментацию данных при помощи ImageDataGenerator;
- Обучать сеть по частям для экономии ресурсов и времени;
- Оценивать работу модели.
- Ограниченный объём данных;
- Ограниченные системные ресурсы (у меня, к примеру, Intel Core i5-4440 3.10GHz, 8 Гб оперативной памяти, NVIDIA GeForce GTX 745);
- Ограниченное время: мы хотим обучать модель меньше суток.
- Установить большой Dropout. В нашем случае он будет 0.5;
- Будем использовать аугментацию данных. Этот приём позволит нам увеличить количество изображений путём различных трансформаций (в нашем случае будут изменения масштаба, сдвиги, горизонтальное отражение).
- Для нашего эксперимента мы возьмём глубокую сеть.
- Изначально мы используем модель, обученную на большом количестве изображений из базы imagenet. Благо, что набор изображений включал кошек и собак;
- Будем обучать модель по частям:
- Сначала прогоним аугментированные изображения через нижнюю часть сети (только через Inception) и сохраним их в виде numpy массивов;
- Обучим с помощью полученных numpy массивов верхний полносвязный слой;
- Затем, мы объединим верхний и нижний слои модели и проведём тонкую настройку новой модели, но при этом мы заблокируем от обучения у Inception все слои, кроме последнего.
2. Создадим модель InceptionV3, загрузим в неё изображения, сохраним их:



Заблокируем с 1 по 205 слои Inception:
| Col_0 | 0.0 | 1.0 |
|---|---|---|
| Row_0 | ||
| 0.0 | 47 | 4 |
| 1.0 | 8 | 41 |

Синие числа-истинный класс изображений. Красные числа- предсказанные моделью (если красное число меньше 0.5, модель считает, что на фото кот, если больше 0.5, то собака). Чем больше число приближается к нулю, тем сеть больше уверена, что перед ней кот. Интересно, что много ошибок с изображениями собак содержат породы небольшого размера или щенков.

Видно, что модель прилично справляется с задачей распознавания изображений на сравнительно небольших выборках.
Completing the CAPTCHA proves you are a human and gives you temporary access to the web property.
What can I do to prevent this in the future?
If you are on a personal connection, like at home, you can run an anti-virus scan on your device to make sure it is not infected with malware.
If you are at an office or shared network, you can ask the network administrator to run a scan across the network looking for misconfigured or infected devices.
Another way to prevent getting this page in the future is to use Privacy Pass. You may need to download version 2.0 now from the Chrome Web Store.
Cloudflare Ray ID: 71a9e880bbd690a3 • Your IP : 82.102.23.104 • Performance & security by Cloudflare
Сканер документов на основе технологии машинного зрения
В последнее время, когда я работал с OpenCV, мне пришла в голову идея написать фреймворк для преобразования изображений. Такое приложение будет полезно каждый день и с ним будет просто работать. Потом как-то меня попросили оцифровать чек за топливо как основание для оформления претензии. Я делал такое и раньше, брал для этого одно из множества приложений, которые можно скачать с Playstore, но в этот раз у меня в голове поселился вопрос “почему я не делаю это своими силами?”. Ведь в целом, это всего лишь захват области документа с фотографии или изображения. Ну и ну!
Я сразу же увидел идею для нового проекта: сделать свой собственный сканер документов. И раз в OpenCV есть все эти функции по обработке изображений, то я решил взять его в качестве инструмента. Так я и приступил к созданию своего собственного сканера документов при помощи машинного зрения, OpenCV и Python.
Давайте перейдём к делу без долгих предисловий. Для начала я составил список основных этапов обработки. Вот они:
- “Прочитать” фотографию или изображение: если размеры изображения большие, вы можете выбрать уменьшить его в масштабе, чтобы обработать быстро или же изменить размер прямо в текущем окне.
- Идентифицировать границы: чтобы сделать это, нам может понадобиться конвертировать изображение в серое для уменьшения цветового шума. Чтобы убрать любой высокочастотный шум, мы слегка размоем изображение. Это поможет дальше определить контуры.
- Найти границы документа в изображении: это выявит нужную нам область в изображении. На этом этапе мы сможем увидеть контур своего документа.
- Идентифицировать и получить границы документа: в этой части больше всего программирования, а мы будем соотносить каждую пару координат с ближайшим углом и рассчитаем размеры документа.
- Применить перспективное преобразование: чтобы получить полную картинку сверху донизу, так сказать, обзор документа “с высоты птичьего полёта”, мы будем переводить полученную интересующую нас область в нужную перспективу.
- Финальные шаги: мы подготовим изображение к итоговому выводу для пользователя. Этого шага может и не быть, вам решать, как вы хотите видеть свой документ. А может вы выберете применить любое преобразование, как, например, черно-белое или увеличение контраста.
Давайте перейдём к практике по каждому пункту. Я добавил куски кода и снэпшоты изображений в ходе обработки, чтобы проиллюстрировать то, что происходит с документом в процессе. Программирую в этом случае на Python.
Шаг 1: чтение фотографии или изображения
Первый шаг. В нём мы импортируем необходимые библиотеки и определяем аргумент исходного изображения со ссылкой на место, где оно лежит, чтобы отсканировать его и обработать. После этого мы читаем изображение при помощи функции imread OpenCV и делаем бэкап-копию на случай, если оно понадобится позже.
Мы отображаем обработанное изображение при помощи команды imshow. Если вы не знакомы с ней, imshow открывает всплывающее окно, которое показывает превью изображения, а функция waitKey будет держать его в таком положении, пока вы не нажмёте ключевую клавишу (в примере это 0). Как только вы закроете превью, нажмите кнопку 0 на клавиатуре и открывшийся попап исчезнет, а ваша сессия Python станет активной.
Шаг 2: идентификация краёв
Мы конвертировали цветное изображение в серое, чтобы уменьшить любой цветовой шум. Чтобы освежить знания: у цветного изображения глубина 3 (по единице на каждый цвет из RGB), в то время как у серого цвета глубина 1. Затем мы применяем функцию blur, чтобы размыть изображение с фильтром (3,3). Размытие уменьшает любой высокочастотный шум и упрощает определение контуров.
Теперь применим алгоритм определения границ Canny, чтобы найти контуры на размытом изображении. Это один из самых популярных алгоритмов для определения широкого диапазона границ в изображениях. John F. Canny разработал его в 1986-м и этот метод еще называют оптимальным детектором.
Помните, что в коде выше я использовал настройки, которые подходят только для моего случая. Если эти значения не сработают на вашем изображении, то поиграйте с настройками и оставьте те из них, которые приводят к наилучшему результату в вашем примере.
Шаг 3: определение границ области на изображении
Теперь мы займёмся поиском контуров в нашем обрезанном изображении. Оставляем контур с максимальной областью и отбрасываем остальные варианты. Рассматривая изображение с границами, отображенными в предыдущем шаге, мы можем чётко увидеть, что границы документа очерчивают максимальную целостную область. Вместо множества итераций по каждому контуру и сохранению экстремумов, мы просто проверяем область и сохраняем ту, у которой максимальная площадь. Это экономит нам много ресурсов. Если мы заранее знаем количество углов в нашем документе, тогда мы можем использовать такой метод проверки и рассчитывать на хорошие результаты.
Тут мы использовали парочку функций CV2, таких как findContours — для получения всех контуров в обрезанном изображении, contourArea — для получения области, замкнутой контуром координат и arcLength. Мы сохранили координаты контуров, чтобы пользоваться ими дальше для обрезки необходимой нам области документа с изображения.
Шаг 4: идентификация и получение границ документа
Из всего процесса сканирования эта часть больше всего насыщена программированием. И теперь у нас есть координаты 4 углов документа, мы будем соотносить координаты с углами, а затем расположим их в определённом порядке.
Поясню основы: изображение состоит из пикселей разного значения (интенсивность цвета). Для цветных изображений у нас будет 3 пикселя значения для точки, а для серого изображения у нас будет один пиксель. Итак, размер изображения — это матрица с первым элементом высоты, вторым — ширины и третьим — глубины цвета, которая обычно равна 3 для цветного изображения и 1 для серого. Итак, мы можем сказать, что для серого изображения у нас будет только два измерения — высота и ширина.
Теперь, с этой базой, каждый из 4-х углов изображения может быть идентифицирован с его пиксельными координатами как точка на плоскости X-Y. Пиксели изображения начинаются сверху слева. Принимая, что h представляет высоту, а w — ширину, форма изображения будет определяться (h, w, d), где d — это глубина, на которую можно не обращать внимания в случае с серым изображением (h,w).
- Верхний левый угол изображения будет иметь координаты (0,0), значит, мы можем сказать, что высота и ширина будут наименьшими среди данных 4-х пар координат.
- Верхний правый угол изображения будет иметь минимальное значение высоты, но максимальное по ширине. В идеале это будет (0,w).
- Нижний левый угловой пиксель изображения будет находиться по диагонали напротив верхнего правого. Он должен быть (h,0), т.е. иметь максимальное значение высоты, но минимальное по ширине.
- Нижний правый угол изображения будет иметь максимальное значение по высоте и ширине одновременно.
Применяя описанные параметры 4 углов, мы можем сделать вывод, что верхний левый угол будет иметь наименьшую сумму, в то время как нижняя правая точка будет иметь максимальную сумму среди 4-х точек с координатами. Верхняя правая точка будет иметь минимальную разницу, в то время как нижний левый угол будет иметь наибольшую разницу среди 4 точек с координатами.
Применяя это рассуждение, мы можем определить, какие координаты к какому из углов ближе. Мы создали список и захватили контур, т.е. координаты ROI (Region of Interest — области интереса) для того, чтобы первая запись в списке была о верхней левой точке, вторая — о правой верхней, третья — о правой нижней и 4-я — о нижней слева. В принципе, направление по часовой стрелке и начинается с левого верхнего угла.
Мы рассчитали ширину и высоту изображения во многом так, как мы вычисляем расстояние между 2 точками на плоскости X-Y. Тут нужна базовая статистика. Для остального вы можете найти идеи решения в приведённом фрагменте кода и том, как он имплементирован.
Шаг 5: применить перспективное преобразование
Ну а теперь у нас есть размеры нашей ROI, и мы собираем набор итоговых точек, чтобы получить обзор с высоты птичьего-полёта, т.е. вид всего изображения сверху. Снова, давайте определим точки в том же порядке, что и раньше, т.е. верх-лево, верх-право, низ-право, низ-лево. Здесь важен порядок, и он должен быть совместим с порядком того, в каких координатах ROI сохранена наша область. Причина в том, что мы будем извлекать ROI из изображения и преобразовывать её по новым размерам.
Применив функцию getPerspectiveTransform, мы рассчитали матрицу, которая будет преобразовывать нашу ROI в итоговые размеры, т.е. целостное изображение нашего документа и ничего больше. Для этого нам нужны координаты ROI и итоговой матрицы. Далее мы извлекли площадь ROI из изображения и применили эту трансформацию на полученной области, чтобы получить вид всего нашего документа целиком. Тут мы применили функцию warpPerspective. Вывод: это и есть изображение всего документа.
Шаг 6: финальные шаги
Мы получили наш документ, фактически отсканированным и вырезанным из исходного изображения. И даже несмотря на то, что документ был сфотографирован под углом, мы смогли получить правильную перспективу, применив матрицу преобразования на выделенной области. Итоговый результат на самом деле выглядит хорошо.
И в конце мы можем подготовить изображение для отображения результата. Обратите внимание, цвет документа такой же, как на исходной картинке. В этом шаге мы применим некоторые действия по пост-обработке, чтобы улучшить внешний вид нашего отсканированного документа. Многие люди могут захотеть, чтобы их скан был черно-белым или предпочтут увеличить итоговый контраст в случае с документом. Мы можем также усилить цвет в финальном отсканированном изображении. В финале мы и делаем всё это.
Заключение
И наконец, мы закончили со сканированием документа и преобразованием для оптимизации его внешнего вида. Этот код можно поместить в веб-интерфейс с помощью flask. Flask — это один из часто используемых и лучших фреймворков для веб-приложений, написанных на Python. Мы можем дальше запускать наш код “на лету” или “по требованию” при помощи docker. Такая программа всего лишь основа для создания приложения. Например, этот код можно генерализировать и создать из него мобильное или веб-приложение. Я очень хочу вдохновить вас на создание своего собственного приложения сканирования вместо использования тех, что доступны в интернете.
Одна важная вещь, которую я хотел бы подчеркнуть ещё раз, что для разных параметров функций, я пользовался теми значениями, что подходили лучше всего в моём случае. Я пришёл к этим значениям после того, как испробовал различные комбинации. В случае, если вы будете переиспользовать мой подход и саму программу, вам следует проверить, какая комбинация работает лучше именно для вас. Более того, если мы планируем всё это действительно запускать, т.е. зарелизим приложение или сканер документов, тогда постарайтесь сфотографировать документ ровно (держите телефон параллельно поверхности) вместо того, чтобы снимать под углом. Так вы получите лучшие результаты.
Я надеюсь, вам понравилась моя статья, а знания пригодятся для вашей собственной разработки приложения по сканированию изображений.
Урок 1. Распознавание изображений и обнаружение объектов

Начинаю серию уроков (мини-курс) о распознавании изображений и обнаружении объектов.
В первой части краткое объяснение понятий распознавание изображений с использованием традиционных методов компьютерного зрения. Я называю методы, не основанные на глубоком обучении, традиционными методами компьютерного зрения, потому что они быстро заменяются методами, основанными на глубоком обучении. Тем не менее, традиционные подходы к компьютерному зрению используются по-прежнему во многих приложениях. Многие из этих алгоритмов также доступны в библиотеках компьютерного зрения, таких как OpenCV એ , и очень хорошо работают «из коробки».
Мини-курс, который я пишу, будет состоять из 8 уроков по приблизительно следующей тематике:
- Распознавание изображений с использованием традиционных методов компьютерного зрения
- Обнаружение объектов с использованием традиционных методов компьютерного зрения
- Как обучить и протестировать собственный детектор объектов OpenCV
- Распознавание изображений с использованием глубокого обучения
Краткая история распознавания изображений и обнаружения объектов
Наша история начинается в 2001 году; В этом году Пол Виола и Майкл Джонс изобрели эффективный алгоритм распознавания лиц. Их демонстрация, показывающая, что лица обнаруживаются в реальном времени на веб-камере, была самой ошеломляющей демонстрацией компьютерного зрения и его потенциала на то время. Скоро, алгоритм был реализован в OpenCV એ , и метод Виолы — Джонса એ стал синонимом обнаружение лиц.
Каждые несколько лет появляется новая идея, которая заставляет людей делать паузу и принимать к сведению. В области обнаружения объектов эта идея появилась в 2005 году в статье Навнит Далала и Билла Триггса. Их дескриптор функции, гистограмма направленных градиентов એ (HOG), значительно превзошел существующие алгоритмы обнаружения пешеходов.

Примерно каждые десять лет появляется новая идея, настолько эффективная и мощная, что вы отказываетесь от всего, что было до нее, и всем сердцем принимаете новое. Глубокое обучение એ — идея этого десятилетия. Алгоритмы глубокого обучения существуют уже давно, но они стали мейнстримом в компьютерном зрении благодаря его оглушительному успеху на конкурсе ImageNet Large Scale Visual Recognition Challenge (ILSVRC) 2012 года. В этом конкурсе алгоритм, основанный на глубоком обучении Алекса Крижевского, Ильи Суцкевер,и Джеффри Хинтон потрясли мир компьютерного зрения с поразительной точностью 85% — на 11% лучше, чем алгоритм, занявший второе место! В ILSVRC 2012 это была единственная запись, основанная на глубоком обучении. В 2013, все победившие работы были основаны на глубоком обучении, и в 2015 году несколько алгоритмов под общим названием Свёрточная нейронная сеть એ (CNN) превзошли уровень естественного распознавания человеком 95%.
При таком огромном успехе в распознавании изображений обнаружение объектов на основе глубокого обучения было неизбежным. Такие методы, как Faster R-CNN, производят челюсти. Отбрасывание результатов по нескольким классам объектов. Мы узнаем об этом в следующих публикациях, но пока имейте в виду, что если вы не изучили алгоритмы распознавания изображений и обнаружения объектов на основе глубокого обучения для своих приложений, вы можете упустить огромную возможность получить лучшие результаты.
Теперь готовы вернуться к основной цели этого поста — разобраться в распознавании изображений с помощью традиционных методов компьютерного зрения.
Распознавание изображений (также известное как классификация изображений)
Алгоритм распознавания изображений (также известный как классификатор изображений) принимает изображение (или фрагмент изображения) в качестве входных данных и выводит то, что содержит изображение. Другими словами, вывод — это метка класса (например, «кошка», «собака», «таблица» и т.д.). Как алгоритм распознавания изображений узнает содержимое изображения? Хорошо, вам нужно обучить алгоритм, чтобы узнать различия между разными классами. Если вы хотите найти кошек на изображениях, вам необходимо обучить алгоритм распознавания изображений с тысячами изображений кошек и тысячами изображений фона, которые не содержат кошек. Разумеется, подобный алгоритм может понимать только те объекты/классы, которые он знает.
Чтобы упростить задачу, в этом посте мы сосредоточимся только на двухклассовых (бинарных) классификаторах. Вы можете подумать, что это очень ограничивающее предположение, но имейте в виду, что многие популярные детекторы объектов (например, детектор лиц и детектор пешеходов) имеют под капотом бинарный классификатор. Например, внутри детектора лиц находится классификатор изображений, который сообщает, является ли участок изображения лицом или фоном.
Анатомия классификатора изображений

На следующей диаграмме показаны этапы работы традиционного классификатора изображений.
Интересно, что многие традиционные алгоритмы классификации изображений компьютерного зрения следуют этому конвейеру, в то время как алгоритмы, основанные на глубоком обучении, полностью обходят этап извлечения признаков. Давайте рассмотрим эти шаги более подробно.
Шаг 1: предварительная обработка
Часто входное изображение предварительно обрабатывается для нормализации эффектов контрастности и яркости. Очень распространенный этап предварительной обработки — вычесть среднее значение интенсивности изображения и разделить его на стандартное отклонение. Иногда гамма-коррекция дает немного лучшие результаты. При работе с цветными изображениями преобразование цветового пространства (например, цветовое пространство RGB в LAB) может помочь получить лучшие результаты.
Обратите внимание, что я не прописываю, какие шаги предварительной обработки являются хорошими. Причина в том, что никто заранее не знает, какой из этих шагов предварительной обработки даст хорошие результаты. Вы пробуете несколько разных, и некоторые из них могут дать немного лучшие результаты. Вот абзац из Далала и Триггса
Мы оценили несколько представлений входных пикселей, включая цветовые пространства в оттенках серого, RGB и LAB, опционально со степенным (гамма) выравниванием. Эти нормализации имеют лишь умеренное влияние на производительность, возможно, потому, что последующая нормализация дескриптора дает аналогичные результаты. Мы используем информацию о цвете, когда она доступна. Цветовые пространства RGB и LAB дают сравнимые результаты, но ограничение оттенками серого снижает производительность на 1,5% при 10–4 кадрах в секунду. Гамма-сжатие с квадратным корнем для каждого цветового канала улучшает производительность при низких значениях FPPW (на 1% при 10–4 кадрах в секунду), но логарифмическое сжатие слишком велико и ухудшает его на 2% при 10–4 кадрах в секунду.
Как вы видете, авторы не знали заранее, какую предварительную обработку использовать. Они делали разумные предположения и использовали метод проб и ошибок.
В рамках предварительной обработки входное изображение или фрагмент изображения также обрезаются и изменяются до фиксированного размера. Это важно, потому что следующий шаг, извлечение признаков, выполняется на изображении фиксированного размера.
Шаг 2: извлечение признаков
Входное изображение содержит слишком много дополнительной информации, которая не нужна для классификации. Следовательно, первым шагом в классификации изображений является упрощение изображения путем извлечения важной информации, содержащейся в изображении, и исключения остальной части. Например, если вы хотите найти на изображениях пуговицы рубашек и пальто, то заметите значительные различия в значениях пикселей RGB. Однако, запустив детектор краев изображения, можно упростить изображение. Вы все еще можете легко различить круглую форму кнопок на этих изображениях краев, и поэтому мы можем сделать вывод, что обнаружение краев сохраняет важную информацию, отбрасывая несущественную информацию. Этот шаг называется извлечением признаков. В традиционных подходах к компьютерному зрению разработка этих функций имеет решающее значение для производительности алгоритма. Оказывается, мы можем сделать намного лучше, чем простое обнаружение краев, и найти функции, которые намного надежнее. В нашем примере с пуговицами рубашки и пальто, хороший детектор функций будет не только фиксировать круглую форму кнопок, но и информацию о том, чем кнопки отличаются от других круглых объектов, таких как автомобильные шины.
Некоторыми хорошо известными функциями, используемыми в компьютерном зрении, являются функции типа Хаара, представленные Виолой и Джонсом, гистограмма направленных градиентов (HOG), Масштабно-инвариантная трансформация признаков એ Scale-Invariant Feature Transform (SIFT), ускорение надежного элемента Speeded Up Robust Feature (SURF) и т.д.
В качестве конкретного примера давайте посмотрим на извлечение признаков с помощью гистограммы ориентированных градиентов (HOG).
Histogram of Oriented Gradients (HOG) или гистограмма направленного градиента
Алгоритм извлечения признаков преобразует изображение фиксированного размера в вектор признаков фиксированного размера. В случае обнаружения пешеходов дескриптор объекта HOG вычисляется для фрагмента изображения размером 64 \times 128 и возвращает вектор размером 3780 . Обратите внимание, что исходный размер этого фрагмента изображения был 64 \times 128 \times 3 = 24,576 , который сокращен до 3780 дескриптором HOG.
HOG основан на идее, что внешний вид локального объекта может быть эффективно описан распределением (гистограммой) направлений краев (направленных градиентов). Шаги по вычислению дескриптора HOG для изображения размером 64 × 128 перечислены ниже.
Расчет градиента: вычисление градиентов x и y изображений и, исходя из исходного изображения. Это можно сделать, отфильтровав исходное изображение следующими ядрами. 
-
Используя изображения градиента и, можно вычислить величины g_x и g_y направления градиента, используя следующие уравнения:
Шаг 3: алгоритм классификации (подробнее)
В предыдущем разделе мы узнали, как преобразовать изображение в вектор признаков. В этом разделе мы узнаем, как алгоритм классификации принимает этот вектор признаков в качестве входных данных и выводит метку класса (например, кошка или фон).
Прежде чем алгоритм классификации сможет творить чудеса, нам нужно обучить его, показывая тысячи примеров кошек и фонов. Различные алгоритмы обучения работают по‑разному, но общий принцип заключается в том, что алгоритмы обучения рассматривают векторы признаков как точки в пространстве более высоких измерений, и попытайтесь найти плоскости/поверхности, которые разделяют пространство более высоких измерений таким образом, что все примеры, принадлежащие к тому же классу, находятся на одной стороне плоскости/поверхности.
Чтобы упростить задачу, давайте более подробно рассмотрим один алгоритм обучения, который называется Support Vector Machines (SVM) или метод опорных векторов એ . Как работает метод опорных векторов (SVM) для классификации изображений?
Метод опорных векторов — один из самых популярных алгоритмов контролируемой двоичной классификации. Хотя идеи, используемые в SVM, существуют с 1963 года, текущая версия была предложена в 1995 году Кортесом и Вапником.
На предыдущем шаге мы узнали, что дескриптор HOG изображения является вектором признаков длиной 3 780. Мы можем думать об этом векторе как о точке в 3 780-мерном пространстве. Визуализировать пространство большого измерения невозможно, поэтому немного упростим ситуацию и представим, что вектор признаков был двухмерным. 
В нашем упрощенном мире теперь у нас есть 2D-точки, представляющие два класса (например, кошки и фон). На изображении выше два класса представлены двумя разными типами точек. Все черные точки принадлежат одному классу, а белые точки — другому классу. Во время тренировки мы предоставляем алгоритм с множеством примеров из двух классов. Другими словами, мы сообщаем алгоритму координаты двумерных точек, а также то, какая точка — черная или белая.
Различные алгоритмы обучения выясняют, как по-разному разделить эти два класса. Линейная SVM пытается найти лучшую линию, разделяющую два класса. На рисунке выше H1, H2 и H3 — это три линии в этом двухмерном пространстве. H1 не разделяет два класса и поэтому не является хорошим классификатором. H2 и H3 разделяют два класса, но интуитивно кажется, что H3 — лучший классификатор, чем H2, потому что H3, кажется, разделяет два класса более четко. Почему? Потому что H2 находится слишком близко к некоторым черным и белым точкам. С другой стороны, H3 выбирается таким образом, чтобы он находился на максимальном расстоянии от членов двух классов.
Учитывая 2D-функции на рисунке выше, SVM найдет для вас линию H3. Если вы получите новый двумерный вектор признаков, соответствующий изображению, которого алгоритм никогда раньше не видел, то можете просто проверить, на какой стороне линии лежит точка, и присвоить ей соответствующую метку класса. Если ваши векторы признаков находятся в 3D, SVM найдет подходящую плоскость, которая максимально разделяет два класса. Как вы, возможно, догадались, если ваш вектор признаков находится в 3 780‑мерном пространстве, SVM найдет соответствующую гиперплоскость.
Оптимизация SVM
Пока все хорошо, но я знаю, что у вас есть один важный вопрос, на который нет ответа. Что, если объекты, принадлежащие двум классам, нельзя разделить с помощью гиперплоскости? В таких случаях, SVM по-прежнему находит лучшую гиперплоскость, решая задачу оптимизации, которая пытается увеличить расстояние гиперплоскости от двух классов, одновременно пытаясь обеспечить правильную классификацию многих обучающих примеров. Этот компромисс контролируется параметром C. Когда значение C мало, выбирается гиперплоскость с большим запасом за счет большего числа ошибочных классификаций. И наоборот, когда C велико, выбирается гиперплоскость меньшего поля, которая пытается правильно классифицировать гораздо больше примеров.
Теперь вы можете быть сбиты с толку, какое значение выбрать для C. Выберите то значение, которое лучше всего работает на тестовом наборе, которого не было в обучающей выборке.