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

SergOishe

Пользователь
4 Июн 2018
292
192
296
Начинаем реализацию: первые досадные огорчения
Я был очень удивлен и расстроен, когда узнал, что в великом и могучем .NET

Framework напрочь отсутствует возможность простого взаимодействия с веб-камерами.

В четвертой версии ситуация улучшилась (для SilverLight-проектов точно появились

соответствующие классы), но протестировать я их не успел, поскольку пример для

данной статьи я начал писать еще до официального выхода VS2010 и 4-го .NET’a.



Практически отчаявшись, я плотно засел в гугле. Результаты поиска по рунету

меня не вдохновили – все, что я нашел – это ссылки на MSDN и технологию

DirectDraw. Я даже попробовал набросать простенький примерчик, но из-за

отсутствия опыта работы с DirectDraw меня постиг облом. У меня получилось

собрать совсем простенькое приложение, но я так и не смог выловить в нем все

глюки.

Еще больше отчаявшись, я принялся шерстить ресурсы наших западных товарищей.

Проштудировав несколько десятков ссылок, я смог нарыть много вкусностей. Среди

них были всевозможные примеры и небольшие статейки (американцы не любят много

писать). Мне даже удалось найти рабочий пример на основе DirectDraw, но, когда я

увидел код – ужаснулся. Разобраться в нем было тяжело. Поэтому я решил с ним не

заморачиваться, а попытаться найти способ попроще. Не успел я распрощаться с

примером на DirectDraw, как на глаза мне попался еще один. Автор примера закодил

целую библиотеку для работы с веб-камерами и другими устройствами видеозахвата,

используя технологию VFW (Video For Windows).

Жаль, что проект автора (я про библиотеку) был максимально кастрирован. Все,

что позволяла сделать библиотека – вывести изображение с веб-камеры. Ни захвата

отдельных кадров, ни записи видео и других полезных нам фич не было.

И тем не менее, мое подсознание решительно сказало мне, что этот проект и

есть то, что я искал. Не успел я беглым взглядом пробежаться по его коду, как

увидел имена знакомых win-сообщений и не менее знакомых названий WinAPI функций.

Когда-то давным-давно мне приходилось писать приложение для работы с веб-камерой

на Delphi. Тогда я и столкнулся с этими функциями впервые.

Посмотрев сорцы, я решил написать свою версию библиотеки и снабдить ее нужным

функционалом.





Взвод, готовность №1
Вполне возможно, что в одном компе/ноуте может быть несколько веб-камер. За

примером далеко ходить не надо. Мне по работе часто приходится организовывать

простенькие видеоконференции. Обычно в них участвуют два человека. Каждого из

участников снимает отдельная камера. Сами камеры подключены к моему компу. Когда

я начинаю съемку, то выбираю в программе для работы с видеокамерами нужную в

настоящий момент камеру. Раз уж мы решили взять камеру под контроль, то обязаны

разобраться, как получать список установленных устройств видеозахвата и выбрать

то, с которым будем работать в настоящий момент.

Для решения этой нехитрой задачи в WindowsAPI предусмотрена функция

capGetDriverDescription(). Она принимает пять параметров:

  1. wDriverIndex – индекс драйвера видеозахвата. Значение индекса может
  2. варьироваться от 0 до 9;
  3. lpszName – указатель на буфер, содержащий соответствующее имя драйвера;
  4. cbName – размер (в байтах) буфера lpszName;
  5. lpszVer – указатель на буфер, содержащий описание определенного
  6. драйвера;
  7. cbVer – размер буфера (в байтах), в котором хранится описание драйвера.
В случае успешного выполнения, функция вернет TRUE. Описание функции у нас

есть, теперь посмотрим, как определить ее в C#. Делается это так:

[DllImport("avicap32.dll")]

protected static extern bool capGetDriverDescriptionA (short wDriverIndex, [MarshalAs(UnmanagedType.VBByRefStr)]

ref String lpszName, int cbName, [MarshalAs(UnmanagedType.VBByRefStr)] ref

String lpszVer, int cbVer);

Обрати внимание, что перед тем, как указать имя подключаемой функции, в

обязательном порядке требуется написать имя DLL, в которой она определена. В

нашем случае это avicap32.dll.

Так, функция импортирована, теперь можно написать класс, в котором она будет

использоваться. Весь код класса для получения списка устройств я приводить не

стану, покажу лишь код ключевого метода:

public static Device[] GetAllCapturesDevices()

{

String dName = "".PadRight(100);

String dVersion = "".PadRight(100);

for (short i = 0; i < 10; i++)

{

if (capGetDriverDescriptionA(i,

ref dName, 100, ref dVersion,

100))

{

Device d = new Device(i);

d.Name = dName.Trim();

d.Version = dVersion.Trim();

devices.Add(d);

}

}

return (Device[])devices.ToArray

(typeof(Device));

}

Код выглядит проще некуда. Самое интересное место в нем – цикл, в котором

происходит вызов упомянутой выше функции capGetDriverDescription. Из MSDN мы

знаем, что индекс (первый параметр функции capGetDriverDescription()) может

варьироваться от 0 до 9, поэтому мы целенаправленно запускаем цикл в этом

диапазоне. Результатом выполнения метода будет массив классов Device (этот класс

я определил самостоятельно, смотри соответствующие исходники).

С получением списка устройств разобрались, теперь позаботимся об отображении

видеопотока с камеры. Тут нам сослужит хорошую службу функция

capCreateCaptureWindow(), предназначенная для создания окна захвата.

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

происходить путем банальной отправки сообщений окну захвата. Да, именно так,

придется воспользоваться до боли знакомой windows-программисту (и приколисту)

функцией SendMessage().

Теперь присмотримся внимательнее к функции capCreateCaptureWindow(). Ей

требуется передать шесть аргументов:

  1. lpszWindowName – нуль-терминальная строка, содержащая имя окна захвата;
  2. dwStyle – стиль окна;
  3. x – координата X;
  4. y – координата Y;
  5. nWidth – ширина окна;
  6. nHeight – высота окна;
  7. hWnd – handle родительского окна;
  8. nID – идентификатор окна.
Результатом выполнения функции будет handle созданного окна или NULL в случае

ошибки. Поскольку эта функция также относится к WinAPI, то ее опять-таки нужно

импортировать. Код импортирования приводить не буду, поскольку он практически

идентичен тому, что я писал для функции capGetDriverDescription(). Лучше сразу

взглянем на процесс инициализации камеры:

deviceHandle = capCreateCaptureWindowA (ref deviceIndex, WS_VISIBLE |

WS_CHILD, 0, 0, windowWidth, windowHeight, handle, 0);

if (SendMessage(deviceHandle, WM_CAP_DRIVER_CONNECT, this.index, 0) > 0)

{

SendMessage(deviceHandle, WM_CAP_SET_SCALE, -1, 0);

SendMessage(deviceHandle, WM_CAP_SET_PREVIEWRATE, 0x42, 0);

SendMessage(deviceHandle, WM_CAP_SET_PREVIEW, -1, 0);

SetWindowPos(deviceHandle, 1, 0, 0, windowWidth, windowHeight, 6);

}

В этом коде сразу после создания окна производится попытка отправки сообщения

WM_CAP_DRIVER_CONNECT. Отличный от нуля результат выполнения функции расскажет

нам о ее успешности.

Теперь представим, что сегодня боги на нашей стороне и произведем

незамедлительную отправку нескольких сообщений: WM_CAP_SET_SCALE,

WM_CAP_SET_PREVIEWRATE, WM_CAP_SET_PREVIEW. Увы, как и в случае с функциями, C#

ничего не знает о существовании этих констант. Тебе опять придется определять их

самостоятельно. Список всех необходимых констант с комментариями я привел ниже.

//Пользовательское сообщение

private const int WM_CAP = 0x400;

//Соединение с драйвером устройства видеозахвата

private const int WM_CAP_DRIVER_CONNECT = 0x40a;

//Разрыв связи с драйвером видеозахвата

private const int WM_CAP_DRIVER_DISCONNECT = 0x40b;

//Копирование кадра в буффер обмена

private const int WM_CAP_EDIT_COPY = 0x41e;

//Включение/отключение режима предпосмотра

private const int WM_CAP_SET_PREVIEW = 0x432;

//Включение/отключение режима оверлей

private const int WM_CAP_SET_OVERLAY = 0x433;

//Скорость previewrate

private const int WM_CAP_SET_PREVIEWRATE = 0x434;

//Включение/отключение масштабирования

private const int WM_CAP_SET_SCALE = 0x435;

private const int WS_CHILD = 0x40000000;

private const int WS_VISIBLE = 0x10000000;

//Установка callback-функции для preview

private const int WM_CAP_SET_CALLBACK_FRAME = 0x405;

//Получение одиночного фрейма с драйвера видеозахвата

private const int WM_CAP_GRAB_FRAME = 0x43c;

//Сохранение кадра с камеры в файл

private const int WM_CAP_SAVEDIB = 0x419;

Дальнейшее описание класса для работы с веб-камерой я опущу. Каркас я

рассмотрел, а со всем остальным ты легко разберешься путем раскуривания моего

хорошо прокомментированного исходника. Единственное, что я не хотел бы оставлять

за кадром – это пример использования библиотеки.

Всего в библиотеке я реализовал (точнее, дописал) пару методов: GetAllDevices

(уже рассматривали), GetDevice (получение драйвера устройства видеозахвата по

индексу), ShowWindow (отображение изображения с веб-камеры), GetFrame (захват

отдельного кадра в графический файл) и GetCapture (захват видеопотока).

В качестве демонстрации работоспособности изготовленной либы я набросал

небольшое приложение. На форме я расположил один компонент ComboBox

(используется для хранения списка имеющихся устройств видеозахвата) и несколько

кнопок – "Обновить", "Пуск", "Остановить" и "Скриншот". Ах да, еще на моей форме

пестреет компонент Image. Его я применяю для отображения видео с камеры.




Разбор полетов начнем с кнопки "Обновить". По ее нажатию я получаю список

всех установленных устройств видеозахвата. Начинка этого обработчика события:

Device[] devices = DeviceManager.GetAllDevices();

foreach (Device d in devices)

{

cmbDevices.Items.Add(d);

}

Правда, все просто? Разработанная нами библиотека берет на себя все черную

работу и нам остается лишь наслаждаться объектно-ориентированным

программированием. Еще проще выглядит код для включения отображения видеопотока

с камеры:

Device selectedDevice = DeviceManager.GetDevice(cmbDevices.SelectedIndex);

selectedDevice.ShowWindow(this.picCapture);

Опять же, все проще пареной репы. Ну и теперь взглянем на код кнопки "Скриншот":

Device selectedDevice = DeviceManager.GetDevice(cmbDevices.SelectedIndex);

selectedDevice.FrameGrabber();

Я не стал уделять особого внимания методу FrameGrabber(). В моем исходнике

вызов метода приводит к сохранению текущего кадра прямо в корень системного

диска. Разумеется, это не очень корректно, поэтому перед боевым применением

программы не забудь внести все необходимые поправки.





Готовность № 3
Теперь настало время поговорить о том, как соорудить простенькую, но надежную

систему видеонаблюдения. Обычно такие системы базируются на двух алгоритмах:

различие двух фреймов и простое моделирование фона. Их реализация (код)

достаточно объемна, поэтому в самый последний момент я решил пойти по более

простому пути. Под легким путем подразумевается использование мощного, но пока

малоизвестного фреймворка для .NET – AForge.NET.

AForge.NET в первую очередь предназначен для разработчиков и исследователей.

С его помощью, девелоперы могут существенно облегчить свой труд при разработке

проектов для следующих областей: нейросети, работа с изображениями (наложение

фильтров, редактирование изображений, попиксельная фильтрация, изменение

размера, поворот изображения), генетика, робототехника, взаимодействие с видео

устройствами и т.д. С фреймворком поставляется хорошая документация. В ней

описаны абсолютно все возможности продукта. Не поленись хорошенько с ней

ознакомиться. Особенно мне хочется отметить качество кода этого продукта. Все

написано цивильно и копаться в коде – одно удовольствие.

Теперь вернемся к нашей непосредственной задаче. Скажу честно, средствами

фреймворка она решается как дважды два. "Тогда зачем ты мне парил мозг WinAPI

функциями?" – недовольно спросишь ты. А за тем, чтобы ты не был ни в чем

ограничен. Сам ведь знаешь, что проекты бывают разные. Где-то удобнее применить

махину .NET, а где-то проще обойтись старым добрым WinAPI.

Вернемся к нашей задачке. Для реализации детектора движений нам придется

воспользоваться классом MotionDetector из вышеупомянутого фреймворка. Класс

отлично оперирует объектами типа Bitmap и позволяет быстренько вычислить процент

расхождения между двумя изображениями. В виде кода это будет выглядеть примерно

так:

MotionDetector detector = new MotionDetector(

new TwoFramesDifferenceDetector( ),

new MotionAreaHighlighting( ) );

//Обработка очередного кадра

if ( detector != null )

{

float motionLevel = detector.ProcessFrame( image );

if ( motionLevel > motionAlarmLevel )

{

flash = (int) ( 2 * ( 1000 / alarmTimer.Interval ) );

}

if ( detector.MotionProcessingAlgorithm is BlobCountingObjectsProcessing )

{

BlobCountingObjectsProcessing countingDetector = (BlobCountingObjectsProcessing)

detector.MotionProcessingAlgorithm;

objectsCountLabel.Text = "Objects: " + countingDetector.ObjectsCount.ToString(

);

}

else

{

objectsCountLabel.Text = "";

}

}

}




Вышеприведенный код (не считая инициализацию класса MotionDetector) у меня

выполняется при получении очередного кадра с веб-камеры. Получив кадр, я

выполняю банальное сравнение (метод ProcessFrame): если значение переменной

motionlevel больше motionLevelAlarm (0.015f), то значит, надо бить тревогу!

Движение обнаружено. На одном из скришотов хорошо видна работа демонстрация

детектора движений.





Готовность №4
Веб-камеру можно запросто приспособить для распознавания лиц и создания

продвинутого способа лог-она в систему? Если переварив весь этот материал, ты

думаешь, что это сложно, то ты ошибаешься! В конце марта на сайте

http://codeplex.com (хостинг

для OpenSource проектов от MS) появился пример (а затем и ссылка на статью),

демонстрирующий реализацию программы для распознавания лиц с использованием

веб-камеры. Сам пример основан на использовании новых возможностей .NET и

SilverLight. Разобрать этот пример в рамках журнальной статьи нереально, так как

автор исходника постарался и сделал все максимально шикарно. Тут тебе и

алгоритмы для работы с изображениями (фильтр размытия, уменьшения шума,

попиксельное сравнение, растяжка и т.д.) и демонстрация новинок SilverLight и

много чего еще. Одним словом, must use! Ссылку на проект и статью ищи ниже.





Конец фильма
Приведенные в статье примеры послужат тебе хорошей отправной точкой. На их

основе легко сварганить как профессиональную утилиту для работы с веб-камерой, и

поднимать на ее продаже несколько сотен баксов в квартал или написать хитрого и

злобного трояна-шпиона.

Вспомни статью про бэкап

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

Сейчас особенно актуальны аудио и видеоданные. Если учесть, что сегодня

веб-камера – обязательный атрибут любого ноутбука, то нетрудно представить,

сколько интересного видео ты сможешь заснять, подсунув жертве "полезную

программку"… Однако я тебе этого не говорил :). Удачи в программировании, а

будут вопросы – пиши.





WWW
http://blogs.msdn.com/

– Русская версия статьи "Silverlight 4 real-time Face Detection"

(распознавание лиц в реальном времени при помощи SilverLight).



http://facelight.codeplex.com/ – здесь хостится проект "Facelight",

позволяющий распознавать лица в реальном времени. Если ты собрался закодить

серьезную софтину для определения лиц или логона в систему, то посмотреть на

этот проект просто обязан.



http://www.aforgenet.com/framework/ – тут ты

найдешь AForge .NET – отличный и простой в использовании фреймворк для работы с

видео, изображениями и т.д.

http://vr-online.ru

– все исходники примеров, а также кучу дополнительной информации ты

можешь слить с сайта проекта VR-Online.