В этом уроке я собираюсь показать решения некоторых проблем начального уровня компьютерного зрения с использованием библиотеки OpenCV для Python. Задачи здесь были предложены моим профессором для инженерного класса цифровой обработки изображений.

Библиотека OpenCV — самая известная библиотека компьютерного зрения с открытым исходным кодом (http://www.opencv.org/), доступная для многих языков программирования. Благодаря огромному количеству функций мы можем выполнять модификации цифровых изображений, такие как геометрические преобразования, фильтрация, калибровка камеры, извлечение признаков, обнаружение объектов и т. д. Используемая установка и настройка здесь не показаны.

Манипуляции с пикселями

Для первой задачи типа «привет, мир» мы будем получать доступ к изображению и манипулировать его пикселями.

Сначала импортируйте библиотеку.

Чтобы загрузить изображение, мы используем функцию imread, передавая в качестве параметров имя файла, который нужно прочитать, и то, как мы хотим, чтобы он читался: 0 — черно-белый, 1 — цветной, -1 — Без изменений. Для этой задачи мы будем читать только как черно-белые, так и цветные. Чтобы отобразить изображение аналогично чтению, мы вызываем функцию, передавая в качестве параметра имя отображаемого окна и его содержимое, обычно это объект изображения, который мы создаем, вызывая imread.

Итак, давайте прочитаем черно-белое изображение, получим доступ к его пикселям, кое-что изменим и покажем результат. Изображения загружаются как матрица (каждый элемент — один пиксель), если в шкале серого матрица имеет два измерения, причем каждый элемент представляет интенсивность серого. Если цветной, это набор из трех матриц, представляющих цветовые каналы RGB.

Важное примечание: в OpenCV координаты матриц разные. Горизонтальная координата — это Y, а вертикальная — X. Кроме того, в левом верхнем углу находится начало координат (0, 0). Таким образом, координаты растут от верхнего левого угла к правому и нижнему направлениям. Чтобы упростить понимание, при циклическом переборе координат с использованием переменных с именами X и Y мы будем переходить на нотацию OpenCV только при доступе к позиции. Пример далее в этом уроке.

После чтения мы можем выполнить простой доступ к матрице и изменить цвет пикселя по своему усмотрению. После изменения некоторых, чтобы сделать прямоугольник черным, мы показываем результат.

Здесь необходима очень полезная команда, чтобы мы могли выполнять несколько операций с изображениями, используя один и тот же код. waitKey ожидает, пока пользователь нажмет клавишу, чтобы продолжить работу с кодом (поэтому мы спокойно можем увидеть показанный результат). Параметр 0 ожидает нажатия любой клавиши для продолжения кода. После этой обработки мы снова считываем изображение Ленны, но на этот раз раскрашиваем и делаем красный прямоугольник точно так же, как черный, который мы делали раньше.

Доступ к пикселям для цветных изображений осуществляется по-разному, массив, который мы передаем пикселю, представляет цветовые каналы в порядке BGR (да, по какой-то причине он перевернут). На выходе у нас есть максимальный ярко-красный прямоугольник, созданный в том же месте.

Наконец, чтобы уничтожить окна, созданные для показа изображений, мы вызываем функцию destroyAllWindows, поэтому после остановки программы ничего не отображается.

Теперь давайте создадим прямоугольник с негативом черно-белого изображения. Положение прямоугольника задается пользователем, и отрицательный эффект создается путем вычитания пикселя из максимально возможного значения для пикселя (здесь 8 бит: 255).

Запустив следующие координаты 100, 200, 30 и 150, мы получим

Изучая дальнейшие манипуляции с пикселями, давайте изменим порядок четырех инвертированных квадрантов изображения. Квадрант представляет 1/4 изображения в квадратном формате, как если бы изображение было разделено пополам по вертикали и горизонтали.

Для этой задачи мы будем использовать библиотеку numpy, чтобы упростить работу с матрицей. Функция numpy split разбивает матрицу в заданной позиции и заданном направлении. После разделения изображения мы должны соединить части в перевернутом порядке.

Заполнение областей

В компьютерном зрении одной очень обычной задачей является подсчет объектов в обнаруженной сцене. Для восприятия объекта необходимо обнаружить совокупность пикселей, принадлежащих каждому объекту. Чтобы лучше работать с этим, мы собираемся использовать бинарное изображение (только пиксели в оттенках серого 0 и 255), что означает 0 — черный фон и 1 — пиксель объекта.

Здесь мы предполагаем, что каждая совокупность белых пикселей представляет собой отдельный объект. Итак, возможная вещь - это маркировка. Обычно алгоритм маркировки имеет на входе бинарное изображение и возвращает многомасштабное серое изображение. Для этой цели мы будем использовать встроенный алгоритм заполнения областей, который называется floodfill. Этот алгоритм, по сути, ищет, используя исходный пиксель в качестве эталона, соседей с тем же цветом. Если мы задаем цвет в качестве параметра, функция делает все пиксели, найденные при поиске, имеющими этот цвет.

floodFill из opencv запрашивает изображение, маску (не используется = None), начальный пиксель и цвет для окраски начального числа и аналогичных соседей. Имея это в руках, нам нужно только попиксельно проверять его цвет, если он белый, мы меняем его на какой-то серый цвет, а затем увеличиваем серый цвет, чтобы следующий объект имел другой оттенок серого. По ходу этого процесса мы можем подсчитать, сколько раз применялся floodFill, и предположить, что это количество объектов, которые у нас есть в сцене.

В результате мы не можем видеть некоторые объекты, потому что используемая шкала серого была той же самой переменной, которая использовалась для подсчета количества объектов, что делало ее очень темно-серой.

Поскольку мы используем оттенки серого для подсчета количества объектов, мы ограничены значением 255 объектов в сцене для 8-битной обработки. Вы можете придумать разные способы решения этой проблемы, я бы предложил создать еще одну переменную, которая отслеживает, когда количество элементов достигает 256 (начиная с 0). Когда это произойдет, эта переменная будет увеличена, а nelem обнулится, чтобы начать заново. К концу маркировки у нас будут объекты того же серого цвета, но переменная, которая отслеживает вместе с nelem, может вычислить, сколько еще объектов будет обнаружено после ограничения.

Теперь, снова увидев исходное бинарное изображение, мы можем понять, что есть два разных типа объектов: с отверстием и без него. Как мы могли считать их отдельными?

Во-первых, мы не знаем, есть ли дырка у тех, что обрезаны краем, поэтому мы их (каким-то образом) игнорируем. Имея все объекты в сцене, один из способов решить эту проблему — применить floodFill к фону изображения, чтобы сделать его белым 255 (в случае, если в сцене было более 255 объектов, нам пришлось бы отредактировать код, чтобы защитите максимальный белый от использования), так что теперь у нас есть объекты в оттенках серого, фон белый, а отверстия черные (старый фон), так как заливка фона белым цветом не попадет в отверстия.

Имея это в руках, мы решили проблему: имея черные дыры, мы просто ищем черные пиксели по всему изображению, и для каждого найденного черного пикселя мы увеличиваем количество дыр, а затем заливаем его, чтобы мы не ошиблись с суммирование. В этом примере мы делаем это с тем же цветом фона, чтобы создать ощущение дыры.

Вот и все, в этой первой части я попытался показать некоторые основы для понимания того, как составлено цифровое изображение и как мы можем получить доступ к его пикселям и провести некоторый анализ. Я надеюсь, что это было достаточно ясно :)

Спасибо за чтение, чтобы прочитать больше перейдите:

2. Введение в OpenCV с Python, часть II

3. Улучшение экспозиции освещения с помощью преобразования Фурье с OpenCV

4. Давайте поиграем с границами изображения в OpenCV

5. Квантование цвета с помощью Kmeans в OpenCV

Все коды можно найти в моем публичном репозитории на GitHub.