среда, 6 мая 2020 г.

Датчик цвета из usb камеры и Raspberry pi

Цифровая видеокамера может быть источником информации о внешнем мире для какого либо робота или электронного прибора подобно глазу для человека. Помимо камеры существуют и другие приборы для сбора информации о внешнем мире (например ЛИДАР) но именно камера является одним из самых дешёвых и при том информативных. Возможно это связано с тем что для человека наиболее важна информация получаемая от изображений и поэтому технологии изготовления видеокамер получили большое развитие. Если у нас например имеется ондноплатный компьютер Raspberry pi (или вообще какой либо другой компьютер) а также usb камера которую можно подключить к этому компьютеру то мы можем, на основе этих приборов, пытаться создавать какие либо устройства способные распознавать визуальные образы из внешнего мира. Задача распознавания образов компьютером является сложной и это препятствует автоматизации некоторых видов работ которые выполняют люди. Однако возможно что кто то сможет решить эту проблему в какой либо сложноавтоматизируемой области и возможно что это будет один из тех кто прочитает данную статью. Для начала мы попробуем сделать простейший датчик цвета на основе usb камеры и Raspberry pi. Сделав данный прибор мы освоим один из наиболее простых способов извлечения информации из цифровой цветной usb web камеры. Далее с извлеченной информацией можно будет эксперементировать по разному. Для удобной визуализации результата можно использовать например RGB светодиод с общими анодами и соединить его так как показано на рисунке:
Рисунок 1 - Схема подключения RGB светодиода к Raspberry pi 3

Если камера обнаружит преобладание красной составляющей то загорится красный свет, если зеленой то - зеленый, если синей то - синий. Теперь осталось разобраться с USB камерой и распознаванием цвета. Для Raspberry pi нужно будет написать какую то программу на каком то языке программирования. Т.к. Raspberry pi это просто компьютер с операционной системой то вариантов того какой язык программирования выбрать очень много. Если кто либо из читающих данную статью занимается web программированием то ему наверняка знакомы такие языки как PHP, javascript, python и т.д. Это хорошие языки, по ним есть много учебников они популярны и распространены, для них есть много библиотек и их можно использовать для программирования Raspberry pi. Однако эти языки интерпретируемые т.е. когда программы написанные на них выполняются то вместе с их выполнением происходит их трансляция на машинный код и это занимает некоторое время. Для обработки большого количества информации (а у нас именно такой случай т.к. современные камеры имеют высокое разрешение и могут снимать видео) лучше подходят компилируемые языки. Существуют также варианты работающие через виртуальную машину - это например java и C# когда сначала код программы преобразуется в байт код а потом этот байт код преобразуется в машинный. Но и в этом случае расходуется время на преобразование байт кода в машинный код. Языки C и C++ являются компилируемыми и они очень похожи на описанные выше языки своим синтаксисом (за исключением питона) поэтому эти языки очень хорошо подходят для наших целей т.к. многие их уже знают либо смогут освоить и программы написанные на них могут быть достаточно быстры для обработки изображений с камеры. К тому же вместе с операционной системой для Raspberri pi обычно прилагаются компиляторы для данных языков и их скорее всего не придется дополнительно устанавливать. Помимо компилятора C++ нам понадобятся некоторые другие утилиты поэтому включим Raspberry pi зайдем в терминал и впишем там команды (нажимая enter после каждой):
Теперь проверим есть ли на нашем Raspberry pi утилита fswebcam для того чтобы делать снимки с usb камеры. Впишем команду whereis webcam и нажмем enter
Если в консоли появятся какие то пути то данная утилита уже установлена, если нет то она не установлена и установить её можно командой sudo apt-get install fswebcam
Также нам понадобятся ещё 3 утилиты, это конвертер изображений imagemagic, компилятор языка C++ а также утилита wiringpi для управления пинами Raspberry pi (которая обычно также как и компилятор устанавливается также вместе с операционной системой). С этими тремя утилитами поступим ткже как и с первой. Сначала проверяем наличие утилиты потом устанавливаем её если её нет:
Теперь можно начинать писать программу. Для этого нужно открыть какой нибудь текстовый редактор. Можно например это сделать через графический интерфейс или в терминале открыть например vim или nano:
Подключим библиотеку ввода/вывода:
Далее надо будет подключить ещё пару библиотек:
Библиотека stdlib.h нужна для того чтобы можно было использовать функцию system а эта функция нужна для того чтобы можно было запускать какие либо внешние команды которые мы можем вписать в терминале или в bash скрипте. И ещё одна библиотека:
Эта библиотека нужна для чтения файла (в нашем случае фотографии с камеры). Также эту библиотеку можно использовать для вывода вместо iostream. Далее напишем функцию main:
Функция должна что то вернуть, вернем ноль:
Далее нужно объявить все необходимые структуры, массивы и переменные т.к. в языке С++ переменные можно использовать только после их объявления.
Первым объявляется указатель на переменную типа FILE. В языке C++ и C если при объявлении переменной ставиться звездочка то это указатель. Данный указатель нужен для того чтобы прочитать файл (в данном случае фотографию). Далее имеется массив символов для хранения строки (в языке C++ нет отдельного типа "строка" как в других языках напр. java, python, javascript и т.д. вместо строки тут используется массив символов). Размер его 12 по тому что для хранения одного пикселя (в формате который мы рассмотрим ниже) нужно 12 символов (не всегда их будет столько но здесь мы ставим максимальное количество чтобы они всегда помещались). Далее объявляется переменная x типа int для того чтобы считать количество строк. После чего переменная типа long для того чтобы хранить символ. Эта переменная не типа char по тому что в переменную типа char нельзя поместить символ конца файла. Далее есть три переменный для хранения трех составляющих цвета пикселя. И наконец ещё одна переменная - это ещё один вспомогательный счётчик. Теперь мы можем настроить пины Raspberry pi к которым подключен светодиод на выход:
После того как мы настроили пины мы делаем снимок с камеры, убираем баннер (который утилита fswebcam делает на фотографии по умолчанию) чтобы он не влиял на результат (он добавит красной составляющей если его не убрать) и сожмем всю фотографию до размеров одного пикселя:
Теперь мы имеем картинку в формате jpg размером 1х1 пиксель. Чтобы достать этот пиксель из картинки формата jpg придется изрядно потрудиться т.к. формат jpg очень сложный. Это не просто набор пикселей а сложный формат с длинной преамбулой и сжатием. Помимо jpg есть и другие форматы напр. gif или png но они тоже сложные и со сжатием. Формат bmp без сжатия и хранит изображение как набор из 3х (RGB) или 4х (RGBA (A-прозрачность)) составляющих для каждого пикселя. Однако этот формат тоже не прост и имеет большую и сложную преамбулу. Он занимает много места и он сложен и утилита fswebcam не выдает изображения в этом формате поэтому этот формат тоже не годиться. Но к счастью для нас существует ещё один замечательный простой формат изображений который идеально подойдет для наших целей. Это формат PPM !!! Утилита fswebcam не может выдать изображение в таком формате и поэтому ранее мы установили ещё одну утилиту ( imagemagic ) для конвертации изображений. Сконвертируем наше изображение в формат PPM
Видно что мы конвертируем в формат ppm и без сжатия. В данном случае это означает что мы получим картинку в виде набора символов а не байтов. И после данной команды мы даже сможем открыть эту картинку текстовым редактором и понять какого цвета наш пиксель! Также мы сможем понять с какой строки нужно начинать вынимать пиксели из данного файла. Мы получили сконвертированную картинку и теперь можем её открыть функцией fopen
После открытия данной картинки имеется цикл на каждой итерации которого из картинки вынимается один символ и если это символ конца строки то переменная x инкрементируется. Так происходит подсчет количества строк. Если мы уже глянули в файл который получается после конвертации изображения из формата JPG в формат PPM то мы увидели что на строке под номером 5 находиться наш пиксель и именно с этой строки мы можем записать в наш массив символов. Это не очень хороший способ получить нужную нам информацию т.к. по разным причинам (обновление утилиты и т.д.) номер строки может поменяться и тогда система сработает неправильно. К тому же в данном коде присутствует "магическое число", такие числа очень не любят программисты по тому что когда программист читает чужой код то он не понимает что означает это число и поэтому злиться. Мы могли бы придумать другой способ получения нужной нам информации или как минимум сделать удобочитаемую константу для номера нашей строки но в данном случае мы разбираем простую программу просто в целях образования. Чтобы было проще понять основную суть, код делается небольшим, без дополнительных удобных наворотов. В коде мы видим что перед помещением символа в массив символов происходит преобразование из типа long в тип char. Это сделать нужно обязательно т.к. в языке C++ нет автоматического преобразования. Если из файла прочитался символ конца файла то цикл завершается. После чего надо обязательно закрыть файл функцией fclose(). После того как мы получили строку с нашим пикселем мы можем вытащить из неё три составляющие и записать их в предназначенные для них переменные. Сделать это мы можем функцией sscanf
Их мы можем вывести на экран
Это на самом деле было не обязательно т.к. далее мы выведем результат на светодиод но это может быть полезно для наладки если что то будет работать не так как надо. И наконец мы можем выяснить какая составляющая преобладает и в соответствии с эти зажечь нужный свет светодиода. Для этого мы использует обычное всем известное ветвление if
Ну а дальше идет конец функции main и программы
Теперь наш код надо сохранить
и откомпилировать компилятором
Если компилятор не выдал ошибок то скорее всего всё получилось и программу можно запустить 
Программа выполнится один раз. Её можно зациклить сделав простой bash скрипт с циклом
Данные скрипты мы уже делали в предыдущих статьях данного блога. Это отдельная длинная тема но не очень сложная, не сложнее чем C++. На языке bash тоже можно писать сложные программы однако это интерпретируемый язык и по этой причине он не используется в нашем случае целиком. Конечно вставки в код на C++ кода на языке bash замедляют всю программу в целом но здесь мы рассмотрели самый простой вариант с которого можно начать, достаточно быстро получить результат и мотивацию (т.к. полученный без больших усилий результат обычно дает удовлетворение и мотивирует продолжать) чтобы не забросить данное занятие на начальном этапе. Посмотреть как работает данное устройство и увидеть данную статью в видеоформате можно на видео:
скопировать полный текст программы можно из данного текстового поля (оказывается текстовые поля отлично подходят для вставок кода в html страницу т.к. тексты в треугольных  скобочках в них не считаются тегами)
КАРТА БЛОГА (содержание)