C#. Берем web-камеру под контроль
«Раскрыть тайны. Показать все, что скрыто. Узнать только самое главное и интересное. Все это в программе….». Не подумай, это не реклама одной из известных телевизионных передач. Примерно так у меня ассоциируется тема сегодняшней статьи. Современный комп/бук тяжело представить без наличия WEB-камеры. Видео чаты, скайп, youtube – изрядно стимулируют на использование подобных девайсов. А что? Заснял прикольный ролик с участием себя и Иринки из пятой квартиры и стал порно…, сорри, звездой. Идей применения – масса! Сегодня я хочу на реальных примерах показать, как заюзать самую стандартную web-камеру по максимуму. Итак, как сказал Гагарин: «Поехали!».
На что еще сгодится камера?
WEB-камера – это девайс не только для видеоконференций и баловства. Немного творческого подхода и на ум невольно приходит несколько идей:
1. Видеоглазок. Я даже не знаю, можно ли отнести эту идею к разряду нестандартных. Про такой способ использования WEB-камеры я услышал еще в далеком 2002 году. Суть заключается в возможности применения камеры вместо дверного глазка. Монтируешь камеру к двери и раз и навсегда забываешь, что нужно отрывать задницу от компьютера, когда в твою дверь кто-то настырно звонит. Открыл приложение, посмотрел лик негодяя и забил на подъем из мягкого и нагретого кресла. Скажу по секрету, такой способ я пробовал еще во времена своего недолгого студенчества. Реально удобно.
2. Детектор движения. И вот, обычная web-камера легким движением руки превращается в бр… полноценный детектор движения. Идея просто как две копейки. Ставим камеру в нужное место (ничего пошлого и ругательного я не имел в виду) и при помощи хитрой тулзы делаем постоянное сравнение кадров. Если изображение начинает меняться, значит, враги проявляют активность и пора начинать бить тревогу. Один из моих интернет-знакомых заюзал такой трюк для охраны гаража. Он просто взял захудалый комп, обеспечил ему выход в инет (благо сегодня организовать беспроводной доступ не проблема) и развесил по периметру камеры. Все камеры постоянно делают снимки и отправляют на комп, а там уже небольшая программа производит сравнение. При обнаружения сильных различий, комп спешил отрапортовать хозяину по email и sms. Неплохая идея, правда?
3. Камера видеонаблюдения. Эта идея также проста, но пользы от нее не меньше, чем от предыдущей. WEB-камера вполне сгодиться для организации системы видеонаблюдения. Профессиональных фишек (типичных для таких задач) из нее выжать вряд ли удастся, но организовать постоянную запись видео реально. Качество, конечно же, будет, не ахти, но для домашних целей хватит. Плохое качество видео несет не только минусы, но и плюсы. Один из них – сэкономленное на винте место. Кстати, если ты думаешь, что видеонаблюдение – это лишь игра, то глубоко заблуждаешься. Купив камеру покруче, реально без проблем организовать систему видеонаблюдения в своем офисе. Если ты начинающий предприниматель и у тебя каждая копейка на счету, то считай, что несколько убитых енотов сэкономлено. Видеонаблюдение, созданное своими руками, обойдется значительно дешевле.
4. Продвинутый логон в систему. В старых фантастических фильмах часто показывали как герои выполняют вход систему после того, как компьютер произведет сканирование их образа. Тогда это казалось чем-то нереальным и выглядело потрясно, а сегодня такой эффект реально замутить у себя дома, потратив при этом 20-30$ (цена дешевой web-камеры). А может даже не придется платить. Со многими буками уже идет весь необходимый софт. Установил, настроил и приготовься удивлять друзей.
5. Управление компьютером. Особые умельцы успешно юзают web-камеру для управления своим компом. Выглядит это примерно так. Настраивается специальный софт и производится базовое «обучение». После этого комп будет выполнять твои команды как дрессированный пес. Махнул ты рукой, а он сразу отдал тебе честь и запустил FireFox или еще чего. Идею ты понял, а если нет, то сбегай на youtube и посмотри соответствующие ролики (см. ссылки во врезке).
Первые досадные огорчения
Я был очень удивлен и растроен, когда узнал, что в великом и могучем .NET Framework напрочь отсутствует возможность взаимодействия с WEB-камерами (точнее простого взаимодействия). В четвертой версии ситуация вроде улучшилась (для SilverLight проектов точно появились соответствующие классы), но протестировать я их не успел. Пример для данной статьи я начал писать еще до официального выхода VS2010 и 4-го дот нета.
Уже было, отчаявшись, я полез в любимый гугл и подобно поисковой ищейки стал вбивать один запрос за другим. Вначале я исследовал лишь рунет. Результаты были бедноватыми. Все что удалось найти - ссылки на MSDN и технологию DirectDraw. Я даже попробовал набросать простенький примерчик, но из-за отсутствия опыта в работе с DirectDraw по-лоховски облажался. У меня получилось собрать совсем простенькое приложение, но я так и не смог выловить в нем все глюки. Закодить на раз два три приложение на основе данной технологии - дело не простое!
Еще больше отчаявшись, я принялся сечить ресурсы наших западных товарищей. У них всегда есть чем поживиться. Проштудиров несколько десятков ссылок мне удалось нарыть много вкусностей. Среди них были всевозможные примеры и небольшие статейки (американцы не любят много писать). Мне даже удалось найти рабочий пример на основе DirectDraw, но когда я увидел код, то ужаснулся. Разобраться в нем новичку будет проблематично. Поэтому я решил с ним незаморачиваться, а попытаться найти способ проще. Не успев попрощаться с примером на DirectDraw, мне на глаза попался еще один. Автор примера закодил целую библиотеку для работы с web-камерами и другими устройствами видео захвата, используя технологию VFW (Video For Windows).
Жаль, что проект автора (я про библиотеку) был максимально кастрирован. Все что она позволяла библиотека сделать - вывести изображение с web-камеры. Ни захвата отдельных кадров, ни записи видео и других полезных нам фич не было.
Тут мое шестое чувство взвыло и всячески начало пытаться намекнуть, что этот проект именно то, что я искал. Не тратя драгоценное время на размышления, я решил залезть и покопаться в сорцах американского коллеги.
Не успел я беглым взглядом пробежаться по коду, как увидел имена знакомых win-сообщений и не менее знакомых названий WinAPI функций. Когда-то давным-давно мне приходилось писать приложение для работы с web-камерой на Delphi. Тогда-то я и столкнулся с этими функциями впервые.
Соответствующих модулей для Delphi не было, поэтому пришлось мне портировать C++ код в царство дельфяндии. Эх, намучился же я тогда. Прошу прощения, что-то я немного отвлекся. Посмотрев сорцы, я решил написать свою версию библиотеки и снабдить ее нужным функционалом.

Вполне возможно, что в одном компе/ноуте может быть несколько web-камер. За примером далеко ходить не надо. Мне по работе часто приходится организовывать простенькие видеоконференции. Обычно в них участвуют два человека. Каждого из участников снимает отдельная камера. Сами камеры подключены к моему компу. Когда я начинаю съемку, то выбираю в программе для работы с видеокамерами нужную в настоящий момент камеру. Раз уж мы решили взять камеру под контроль, то обязаны разобраться, как получать список установленных устройств видео захвата и произвести выбор того, с которым будем работать в настоящий момент. Для решения этой нехитрой задачи в WindowsAPI предусмотрена функция capGetDriverDescription(). Она принимает аж пять параметров:
1. wDriverIndex - индекс драйвера видео захвата. Значение индекса может варьироваться от 0 до 9.
2. lpszName - указатель на буфер, содержащий соответствующее имя драйвера
3. cbName - размер (в байтах) буфера lpszName
4. lpszVer - указатель на буфер, содержащий описание определенного драйвера.
5. cbVer - размер буфера (в байтах), в котором хранится описание драйвера.
В случае успешного выполнения, функция вернет TRUE и FALSE в случае ошибки. Описание функции у нас есть, теперь посмотрим, как определить ее в 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
3. y - координата Y
4. nWidth - ширина окна
5. nHeight - высота окна
6. hWnd - handle родительского окна
7. 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# ничего не знает о существовании этих констант. Тебе опять придется определять их самостоятельно. Список всех необходимых констант с комментариями я привел в листинге 1.
Листинг1. Необходимые константы
//Пользовательское сообщение 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;
Дальнейшее описание класса для работы с web-камерой я опущу. Каркас я рассмотрел, а со всем остальным разберешься в хорошо прокомментированном исходнике. Единственное, что я не хотел, бы оставлять за кадром – пример использования библиотеки.
Всего в библиотеке я реализовал (точнее дописал) пару методов: GetAllDevices (уже рассматривали), GetDevice (получение драйвера устройства виде озахвата по индексу), ShowWindow (отображение изображения с web-камеры), 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) у меня выполняется при получении очередного кадра с web-камеры. Получив кадр, я выполняю банальное сравнение (метод ProcessFrame): если значение переменной motionlevel больше motionLevelAlarm (0.015f), то значит надо бить тревогу! Движение обнаружено. На одном из скришотов хорошо видна работа демонстрация детектора движений. В момент, когда кадры начинают различаться.
Готовность №4
Помнишь, в начале статьи я говорил, что Web-камеру можно запросто приспособить для распознавания лиц и создания продвинутого способа лог-она в систему? Если, переварив весь этот материал, думаешь, что это сложно, то ты ошибаешься! В конце марта на сайте http://codeplex.com (хостинг для OpenSource проектов от MS) появился пример (а затем и ссылка на статью), демонстрирующий реализацию программы для распознавания лиц с использованием WEB-камеры. Сам пример базируется на использовании новых возможностей .NET и SilverLight. Разобрать этот пример в рамках журнальной статьи нереально, т.к. автор исходника действительно постарался и все сделал максимально шикарно. Тут тебе и алгоритмы для работы с изображениями (фильтр размытия, уменьшения шума, попиксельное сравнение, растяжка и т.д.) и демонстрация новинок SilverLight и много чего еще. Одним словом must use! Ссылку на проект и статью ищи во врезке.
Конец фильма
Приведенные в статье примеры послужат тебе хорошей отправной точкой. На их основе легко сварганить как профессиональную утилиту для работы с web-камерой, поднимая на ее продаже несколько сотен баксов или написать хитрого и злобного трояна-шпиона.
Вспомни статью про бэкап Skype-бесед. В ней я говорил, что времена клавиатурных шпионов уже прошли. Сейчас особенно актуальны аудио и видеоданные. Эту статью можно рассматривать как своеобразное предложение. Как перехватить аудио ты уже должен знать, а теперь еще узнал, как работать с web-камерой. Если учесть, что сегодня web-камера обязательный атрибут любого ноутбука, то нетрудно представить, сколько "интересного" видео ты сможешь заснять, подсунув жертве «полезную программку» Учти, я тебе этого не говорил
Удачи в программировании, будут вопросы - пиши.
Полезные ссылки
http://video.aol.com/video-detail/webcam-mouse-control/2225590454 - Видео, демонстрирующее управление компом при помощи WEB-камеры.
http://www.youtube.com/watch?v=L476V10Ozi0&feature=related – Управляем компом при помощи ручки-фонарика и WEB-камеры. Очень интересная идея, автор которого демонстрирует несколько способов применения WEB-камеры в качестве инструмента управления компьютером.
http://blogs.msdn.com/rucoding4fun/archive/2010/04/02/facelight-silverlight-4.aspx - Русская версия статьи "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.
Written by: Антонов Игорь aka Spider_NET
- Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии
- 17679 просмотров



Комментарии
22 комментария(ев)Дата: Втр, 02/11/2010 - 04:00
немогу найти исходников к этому проекту.. как минимум застрял на описании класса Device . А Так статься ооочень хорошая.. спасибо
Дата: Втр, 02/11/2010 - 04:09
Говорили что исходники потерялись.
Дата: Втр, 02/11/2010 - 21:40
Да... к сожалению, Spider_NET куда-то положил сорцы и не может их найти. Вот у меня такого никогда не бывает. На винте всегда порядок. Можно найти всё, что нужно
Дата: СР, 02/03/2011 - 11:35
так и не нашлись исходники?
Дата: ЧТ, 03/03/2011 - 11:21
2 CepbIu
Не нашлись. Судя по всему, утеряны безвозвратно...
Дата: СР, 16/03/2011 - 21:01
Классная статья!Но без описания класса Device она мало полезна(((((((((((((
Дата: ПТ, 22/04/2011 - 04:57
я все таки смог написать прогу которая ведет запись видео (avi) с вебкамеры. делается это примерно так: 1)с помощью библиотеки DirectShowLib-2005.dll подключается камера и раз в 200мс с нее получается изображение 2) паралельно пишется wav звук с микрофона с помощью подключения встроенных библиотек винды 3) через минуту происходит запуск потока который уже собирает отснятые картинки и звук в видео с помощью ]]>http://www.codeproject.com/KB/audio-video/avifilewrapper.aspx]]>
если картинки собирать сразу в видео то все тормозит.
3.1) после запуска потока естессно идет сбор картинок для следующего видео
4) получаем видео длительностью в минуту со звуком, объемом около 25МБ (зависит от качества сохраненных фоток)
Имхо это самый простой для любителя-программиста на c# способ. я долго гуглил и по-моему такое решение вполне сойдет.
кому интересны исходники - обращайтесь на koroteeww[цобако]ya[точко]ru с темой "исходники вебкамеры"
Дата: ПТ, 22/04/2011 - 15:05
To wildboar
А ты бы взял и наваял статейку на тему твоего исходника для VR
Дата: Пнд, 05/09/2011 - 12:46
Статья хорошая, но бесполезная ибо исходников автор пожалел. Кастрированные классы Device и DeviceManager я нашел на немецком форуме ]]>mycsharp.de]]>
Дата: Пнд, 05/09/2011 - 14:03
Я не пожалел. Сорцы куда-то сгинули при переезде на другой комп.