Фотографируем через WIA на .NET

    imageЗадача поста — освежить информацию о работе с WIA в .NET, лежащую на просторах сети, т.к. большинство примеров безбожно устарели (они не работают), поделиться опытом с интересующимися и спросить совета у бывалых. Я думаю, совместно обсудить некоторые моменты, будет полезно для коллег, занимающихся подобными задачами. Постараюсь разжевать все моменты.

    Мотив был таков: в программе, где хранится информация о людях есть их фотографии, но фотографии туда попадают сложным путем. Берем бумажный оригинал фото (их приносят люди вместе с документами), сканируем в файл, открываем в простом редакторе, кадрируем под 3х4см, сохраняем, в программе открываем анкету человека и там уже добавляем ему фото из готового файла.

    Сейчас в программе несколько тысяч человек и стоит задача всех их сфотографировать красиво, потому что они приносят фотографии просто ужасного качества и с разным процентом заполнения лица. Дело за малым: повесить экран, выставить свет, стул, штатив, камера, USB… и наша программа.

    У меня были большие планы по реализации этой функции:
    • приходит человек, причесывается, садится напротив камеры
    • оператор наводит камеру, поправляет человеку волосы, одежду… ну чтобы красиво
    • садиться за программу и нажимает кнопку «Сделать все автоматом»
    • все.


    Тем временем программа должна:
    • подключиться к камере через WIA
    • заставить камеру сделать фотографию
    • получить из камеры свежий снимок
    • повернуть его в соответствии с данными о повороте
    • найти на фотографии лицо, определив прямоугольник, в который оно вписано
    • высчитать процент масштабирования так, чтобы прямоугольник лица занимал 80% кадра 3х4см при разрешении 300dpi
    • смасштабировать фото и наложить его на белый фон
    • сохранить фото в анкету человека


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

    WIA

    Windows Image Acquisition (WIA) — это простой способ работы с большинством моделей устройств обработки изображений (фотокамера, сканер, видеокамера и другие). Для успешной работы, нужно, чтобы устройство, как минимум, отобразилось в «Сканерах и камерах» Панели управления Windows. Чтобы это произошло — нужен подходящий драйвер. Какие-то устройства работают из коробки, а другим нужно устанавливать WIA-драйвер вручную, как в случае с фотокамерами Canon EOS.
    Самая полезная статья MSDN, которая действительно помогает разораться с проблемами WIA — это статья с примерами. Я случайно наткнулся только на одну, must have.

    Простейший код

    Для простоты понимания, я приведу здесь код на VB.NET 2008 и C# 2008, и разберу их по полочкам. Сразу обращу внимание, что в нем опущены все субъективные методики, а также проверки на ошибки, чтобы максимально упростить пример. Продумывать логику приложения каждый должен сам, т.к. задачи у всех разные, и бывает очень сложно разобраться в чьем-то коде, особенно, когда тебе из него нужно лишь несколько строк. Коллеги могут раскрыть код на предпочтительном языке и обращаться к нему по ходу текста. Шаги полностью аналогичны.

    Здесь исходный код формы Form1 с единственным элементом PictureBox1, растянутым на всю форму. И самое «сложное» здесь — добавить в параметрах проекта ссылку на COM-объект WIA. Перед запуском убедитесь, что ваше устройство включено.

    Код VB.NET
    Imports WIA
    
    Public Class Form1
    
        Dim Device1 As WIA.Device
        Dim CommonDialog1 As New WIA.CommonDialogClass
        Dim DeviceManager1 As New WIA.DeviceManager
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            ' 1. Помогаем пользователю выбрать устройство
            Device1 = CommonDialog1.ShowSelectDevice(WiaDeviceType.CameraDeviceType)
            ' 2. Делаем снимок
            Device1.ExecuteCommand(WIA.CommandID.wiaCommandTakePicture)
    
            ' 4. Подключаемся к устройству для получения фото
            Dim Device1a As WIA.Device = DeviceManager1.DeviceInfos(Device1.DeviceID).Connect
            ' 3. Запоминаем какой объект нам нужен
            Dim newItem As WIA.Item = Device1a.Items(Device1a.Items.Count)
            ' 5. Читаем файл из устройства
            Dim newImage As WIA.ImageFile = CommonDialog1.ShowTransfer(newItem, WIA.FormatID.wiaFormatJPEG)
            ' 6. Преобразуем полученные данные в вектор 
            Dim newVector As WIA.Vector = newImage.FileData
    
            ' 7. Забираем из вектора байтовый массив, содержащий изображение
            Dim bytBLOBData() As Byte = newVector.BinaryData
            ' 8. Преобразуем массив в поток
            Dim stmBLOBData As New IO.MemoryStream(bytBLOBData)
            ' 9. Преобразуем поток в изображение и присваиваем его элементу PictureBox
            PictureBox1.Image = Image.FromStream(stmBLOBData)
            ' 10. Режим масштабирования Zoom помогает увидеть весь кадр (в целях отладки)
            PictureBox1.SizeMode = PictureBoxSizeMode.Zoom
        End Sub
    
    End Class
    


    Код C# 2008
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using WIA;
    using System.IO;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
    
                WIA.DeviceManager DeviceManager1 = new DeviceManagerClass();
                WIA.CommonDialogClass CommonDialog1 = new CommonDialogClass();
                
                // 1. Помогаем пользователю выбрать устройство
                WIA.Device Device1 = CommonDialog1.ShowSelectDevice(WiaDeviceType.CameraDeviceType, true, false);
    
                // 2. Делаем снимок
                Device1.ExecuteCommand(WIA.CommandID.wiaCommandTakePicture);
    
                // 3. Снова подключаемся к устройству для получения фото
                WIA.Device Device1a = null;
                foreach (DeviceInfo dev_item in DeviceManager1.DeviceInfos)
                {
                    // Перечисляем все устройства
                    if (dev_item.DeviceID == Device1.DeviceID)
                    {
                        // и подключаемся к тому, чей DeviceID совпадает с ранее выбранным
                        Device1a = dev_item.Connect();
                        break;
                    }
                }
                
                // 4. Находим какой объект нам нужен
                WIA.Item newItem = Device1a.Items[Device1a.Items.Count];
    
                // 5. Читаем файл из устройства
                WIA.ImageFile newImage = (ImageFile)CommonDialog1.ShowTransfer(newItem, WIA.FormatID.wiaFormatJPEG, false);
    
                // 6. Преобразуем полученные данные в вектор
                WIA.Vector newVector = newImage.FileData;
    
                // 7. Забираем из вектора байтовый массив, содержащий изображение
                Byte[] bytBLOBData = (Byte[])newVector.get_BinaryData();
    
                // 8. Преобразуем массив в поток
                MemoryStream stmBLOBData = new MemoryStream(bytBLOBData);
            
                // 9. Преобразуем поток в изображение и присваиваем его элементу PictureBox
                pictureBox1.Image = Image.FromStream(stmBLOBData);
                
                // 10. Режим масштабирования Zoom помогает увидеть весь кадр (в целях отладки)
                pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
    
            }
        }
    }
    



    После 7-го шага Вы вольны делать с изображением что угодно: сохранить в файл, сохранить в базу данных, вывести на форму, преобразовать, применить фильтры и т.д. Главное, что оно у вас есть.
    Я, чтобы убедиться что все работает, отображал его на форме.

    Вроде все просто

    Как видите — код краток и насыщен полезными действиями. Но грабли и подводные камни — вечные спутники программистов по разные стороны Microsoft. Там думали, что все сделали просто и очевидно, а мы, как всегда, не поняли.

    Шаг 1

            ' 1. Помогаем пользователю выбрать устройство
            Device1 = CommonDialog1.ShowSelectDevice(WiaDeviceType.CameraDeviceType)
    

    Надо выбрать устройство. Есть два пути: выбрать самому или дать выбрать пользователю. Цель у обоих способов одна — получить ID устройства — DeviceID. Дальше можно работать с WIA, используя везде этот DeviceID как ссылку на нужное устройство.



    В примере открывается стандартное окно WIA для выбора устройства. Их может быть несколько.
    Если нам не нужно, чтобы пользователь решал чем он хочет пользоваться, тогда можно отфильтровать доступные устройства, используя параметр WiaDeviceType. В примере я ищу фотокамеру — CameraDeviceType. Но берегитесь, некоторые картридеры прикидываются фотокамерами.
    Здесь можно поступить так: пройтись циклом по всем устройствам и проверить, поддерживают ли они функцию wiaCommandTakePicture (сделать фотку). Соответственно, если таковое небыло найдено, но есть хотябы одно подходящее, то тут пользователю не отвертеться — придется ему решать, чем фотографировать. Как это сделать, хорошо описано в одном из примеров статьи MSDN (см.выше).

    Камень №раз

    Может статься, что ни одно устройство не поддерживает wiaCommandTakePicture. Это вводит в замешательство, особенно, если у вас Canon и стоят все программы с их диска, включая программу для Remote Shooting (удаленной съемки по USB). В ней он фотографирует, а через WIA не хочет. Здесь можно либо бодаться с Canon и клянчить у них SDK, которую они мало кому дают, а затем писать свой модуль, либо заставлять пользователя делать снимок вручную, а дальше все делать автоматом. Но в целом игра стоит свечь. Имейте в виду, что у Canon много подразделений, и SDK они распространяют каждое в своем регионе. Для России все грустно, но можно попробовать обратиться в Европейское отделение. Примерно так они мне ответили по почте.

    Шаг 2

            ' 2. Делаем снимок
            Device1.ExecuteCommand(WIA.CommandID.wiaCommandTakePicture)
    

    Ок, допустим нам повезло и можно фотографировать. Ур… а-э-э… What a f*ck?
    Никаких настроек. Можно только сделать фото. Камера (читай — объектив) выезжает, щелкает в режиме полной автоматики (или что там на переключателе выставлено) и уезжает обратно. Ну, и на том спасибо.
    Честно говоря, я не углублялся в проблематику предварительной настройки камеры удаленно, но потуги у населения замечены при помощи Гугла. Возможно кто-то смог найти решение.
    Камера делает кадр и сохраняет его в свою память или карту. Теперь его надо оттуда достать!

    Шаг 3

            ' 3. Подключаемся к устройству для получения фото
            Dim Device1a As WIA.Device = DeviceManager1.DeviceInfos(Device1.DeviceID).Connect
    

    Еще раз подключаемся к камере, но уже в другом режиме. На C# этот шаг чуть длиннее. Здесь мы подключаемся к ней, как вместилищу файлов. Будьте бдительны! Структура файлов запоминается на момент подключения! Об этом дальше...

    Шаг 4

            ' 4. Находим какой объект нам нужен
            Dim newItem As WIA.Item = Device1.Items(Device1.Items.Count)
    

    Пока писал статью, похоже, понял причину проблемы с Камнем №два. Спасибо Харб!
    На этом шаге, мы запоминаем ID последнего файла, хранящегося в камере, т.к. объект Device1.Items хранит информацию обо всех файлах и папках, хранящихся в памяти камеры. Да да, и папок тоже — будьте внимательны. Через этот объект можно получить любую информацию, например имя файла. Это все есть в примерах MSDN (см.выше). Здесь нам помогает простой счетчик объектов. Логично, но не всегда верно, что наша новая фотография — сделана последней. Так что проверку на ошибки здесь тоже нужно вставить.

    Шаг 5

            ' 5. Читаем файл из устройства
            Dim newImage As WIA.ImageFile = CommonDialog1.ShowTransfer(newItem, WIA.FormatID.wiaFormatJPEG)
    

    И хватаем файл, указатель на который запомнили.
    Здесь процесс загрузки фотографии будет показан стандартным WIA-интерфейсом с прогрессбаром.
    Одним из параметров функции является формат передаваемого файла. Мне вот нужен wiaFormatJPEG. Это чтобы фотоаппарат вдруг не отдал мне TIFF. (шутка)

    Камень №два

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

    Шаг 6

            ' 6. Преобразуем полученные данные в вектор 
            Dim newVector As WIA.Vector = newImage.FileData
    

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

    Шаги 7,8,9

            ' 7. Забираем из вектора байтовый массив, содержащий изображение
            Dim bytBLOBData() As Byte = newVector.BinaryData
            ' 8. Преобразуем массив в поток
            Dim stmBLOBData As New IO.MemoryStream(bytBLOBData)
            ' 9. Преобразуем поток в изображение и присваиваем его элементу PictureBox
            PictureBox1.Image = Image.FromStream(stmBLOBData)
    

    Этот BinaryData требует парочки преобразований, прежде чем он станет полноценной картинкой. Байты в поток, поток в изображение. А дальше можно делать с полученным объектом что угодно (согласно комментариям в листинге).
    К слову: указанным методом изображения преобразуются для хранения в СУБД (только преобразование идет в обратную сторону).

    Шаг 10

            PictureBox1.SizeMode = PictureBoxSizeMode.Zoom
    

    Каждому свой путь, ну а я для наглядности, присвоил картинку свойству Image элемента PictureBox1. Чтобы посмотреть на себя тепленького.

    Теперь осталось только обеспечить обработку ошибок, т.к. WIA — очень капризная и ситуаций будет много (больше всего их будет при разработке под x64-систему, потому что во время отладки в случае ошибки, код не выдаст исключения, а просто не будет выполнен начиная с места возникновения ошибки). Камера, например, может уснуть, пока ей долго не будут пользоваться. Многое нужно предусмотреть.

    Очень часто применяется эта технология для работы типа «бюро пропусков» с фотографиями.

    Теперь о вопросах для обсуждения:
    1. У кого-нибудь есть SDK для Canon? Можете ли поделиться необходимыми DLL-ками для некоммерческого использования?
    2. Распознать прямоугольник, в который вписано лицо на фотографии, можно буквально за 10 строк, используя OpenCV и ее обертку для .NET. Чем я и займусь в ближайшем будущем.


    Спасибо за внимание!
    UPD: Добавлен пример на C#. Убрал путаницу с шагами.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 21

      +1
      По поводу распознавания developers.face.com/download/
        +1
        Направление верное, только способ не локальный — получается, что нужно быть онлайн и передавать фотографии на api.face.com.
        В любом случае спасибо.
        +4
        Rак-то как-то нетрадиционно встретить код на VB.Net — думал, что на нем кроме американцев никто не пишет
          0
          Программисты, вообще народ нетрадиционный. Но перевести этот код на C# или еще куда — дело 5 минут. Как я и говорил, понять проще, когда на пальцах. Но если сообщество желает, то я добавлю пример на C#.
            0
            Да. Пожалуйста добавьте. Читать VB не удобно, или не привычно…
              +1
              Вечером сделаю. Благодарю за терпение.
                0
                Пожалуйста.
                0
                Присоединяюсь к просьбе о коде на C#.
              0
              Пожалуйста.
              0
              Попутный вопрос — как купить камеру и точно узнать, что она видится как WIA девайс, а не картридер?
                0
                Не просто. На сайте производителя посмотрите на драйвера, которые можно скачать. Canon, например, может указывать на то что это драйвер WIA. Если нет, то, как и для остальных, загляните в инструкцию — есть ли в ней упоминание поддержки WIA.
                Ну а если совсем не повезло, то у Вас есть 2 недели на возврат без объяснения причин.

                Надеюсь я ничего не перепутал? Точно 2 недели?
                  0
                  Или Вы имели в виду, что как это определить уже после покупки?
                  Подключить, установить родные драйвера, и зайти в «Сканеры и камеры». Если устройство там есть, тогда Вам повезло.
                  Я упоминал эту примету в статье.
                    0
                    Не повезло — запаковать, отвезти обратно в магазин.

                    Сейчас, на сколько курил тему, в фотокиосках используют уже снятую с производства линейку Powershot A*, которая ковыряется в подобном протоколе. Перейти на что-то актульное и продающееся — это значит только зеркалки и закрытое SDK.
                      0
                      Вполне можно перейти и не на Canon.
                  –1
                  Готов код на C#, господа!
                    0
                    Хорошо, есть у вас на заметке фотоаппарат до 5к со вспышкой, который подойдет для хорошего качества снимком на документы? Интересует только автоматический интерфейс (открытое SDK или описанный вами WIA). Пробовали в этом ключе Microsoft HD Livecam, но по WinAPI выдает всего 640х480, а матрица всё-равно шумит.
                      0
                      Увы, нет. Я бы посоветовал изучить эти предложения . А именно их драйверы. Но лучше будет написать каждому производителю письмо с вопросом про выбранные камеры, которые поддерживают фотографирование (Remote Shooting) через WIA.
                      Точнее сейчас не скажу. Вот мой PowerShot G7 прекрасно работает.

                      А вот со вспышкой я бы не горячился. Когда снимаешь человека вблизи, мыльницей со встроенной вспышкой, то получается плоское лицо, цвета былой простыни. В этом случае, вспышку лучше не использовать, а использовать внешние источники света, достаточно яркие, чтобы матрица не шумела, и вспышка не срабатывала. Но только не лампы дневного света — они мерцают и дают голубоватый оттенок.

                      Ну а если использовать веб-камеру, то можно легко уложиться в 5к, и купить Full HD. Попробуйте посмотреть в эту сторону.
                        0
                        Хаха, написать производителю.
                        Я связался с техподдержкой Nikon, с тем чтобы узнать поддерживает ли D800 работу с WIA.
                        Ответов было два:
                        Первый — что вы имеете в виду под WIA? (кинул им ссылку на википедию)
                        Второй — WIA — технология компании Microsoft, вот у нее и спрашивайте.
                        0
                        Подскажите как работать с WIA под Windows XP
                          0
                          работайте через twain

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

                        Самое читаемое