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