André Alves de Lima

Talking about Software Development and more…

Acessando a webcam no .NET com a biblioteca AForge

Uma das possibilidades que temos à nossa disposição para fazermos o acesso à webcam no .NET é através da biblioteca AForge. Essa biblioteca é muito fácil e intuitiva, muito simples de utilizar. Uns tempos atrás eu escrevi um artigo onde eu mostrei como tirar fotos com a webcam no C#, onde eu utilizei tanto a biblioteca AForge quanto a biblioteca DirectShow.NET. Como esse é um dos artigos mais populares do meu site, eu resolvi gravar um vídeo expandindo essa ideia, focando somente na biblioteca AForge, que é a mais utilizada:

Instalação da biblioteca AForge pelo NuGet

No artigo que eu escrevi tempos atrás, eu só mostrei como podemos baixar a biblioteca manualmente no seu site. Hoje em dia a utilização do NuGet se tornou praticamente padrão nos projetos .NET, por isso, faz todo o sentido partirmos para essa estratégia.

Para adicionarmos a referência à biblioteca AForge pelo NuGet, temos que primeiramente procurar por “AForge“. Uma vez listadas as opções, instale o item “AForge.Video.DirectShow“:

Outra opção é utilizarmos o Package Manager Console, executando o comando “Install-Package AForge.Video.DirectShow“.

Namespace

Todas as classes relacionadas à webcam no AForge estão localizadas no namespace “AForge.Video.DirectShow“, dessa forma, para facilitar as coisas, eu sugiro que você adicione uma referência a esse namespace utilizando a cláusula “using AForge.Video.DirectShow” no topo do seu formulário.

Listando as webcams

A biblioteca AForge serve para trabalharmos com diversas coisas no .NET (como áudio e vídeo). O acesso à webcam é somente uma das possibilidades que temos à nossa disposição. Para listarmos as webcams, utilizamos a classe “FilterInfoCollection“, passando “FilterCategory.VideoInputDevice” como parâmetro no construtor.

Instanciando uma webcam

No AForge, a webcam pode ser manipulada através da classe “VideoCaptureDevice“. O construtor dessa classe espera um “moniker string“, que seria algo como um “id” do dispositivo de captura. Essa informação pode ser recuperada através dos objetos retornados pelo “FilterInfoCollection” mencionado logo acima.

O evento NewFrame

A classe “VideoCaptrureDevice” possui um evento chamado “NewFrame“. Estando a câmera ligada, esse evento será disparado cada vez que um novo frame for capturado pela webcam. Isso quer dizer que, caso o frame rate da câmera seja, por exemplo, 40fps, esse evento será disparado 40 vezes por segundo.

Nos argumentos desse evento, temos acesso ao frame capturado (que é uma imagem). Com o frame capturado, podemos cloná-lo para, por exemplo, exibirmos em um controle do tipo PictureBox.

Evitando consumo desnecessário de memória

Uma vez que estamos clonando as imagens retornadas pelo evento “NewFrame“, o consumo de memória pode ficar rapidamente muito alto. O .NET só descartará as imagens que não estão sendo mais utilizadas quando ele perceber que nós não precisamos mais delas. Isso pode demorar 10, 15, até 30 segundos dependendo da situação, o que pode potencialmente levar a um estouro de memória.

Para contornarmos esse problema, o ideal é chamarmos um “Dispose” na imagem atual do PictureBox antes de atualizarmos com a imagem nova. Isso é uma maneira de dizermos para o .NET que não precisamos mais daquela imagem velha, fazendo com que a sua memória seja recuperada quase que imediatamente.

Ligando e desligando a câmera

Para sabermos se a câmera deve ser ligada ou desligada, utilizamos a propriedade “IsRunning“, que retornará verdadeiro caso a câmera já esteja ligada ou falso caso contrário. A câmera pode ser ligada através do método “Start” ou desligada através do método “Stop“. É importante que nós lembremos de limpar a imagem do PictureBox quando desligarmos a câmera, caso contrário ela ficará com o último frame capturado antes do desligamento.

Capturando a imagem da webcam

Estando a câmera ligada, nós podemos salvar o frame atual através do método “Save” da imagem que está sendo exibida no PictureBox. Nós podemos utilizar um caminho fixo para a imagem ou, melhor ainda, nós podemos utilizar um “SaveFileDialog” para perguntar para o usuário onde é que ele quer salvar a imagem.

Um problema muito interessante que vamos encontrar ao utilizarmos a classe “SaveFileDialog” é que o PictureBox continuará sendo atualizado com a imagem da webcam enquanto o usuário escolhe o caminho onde ele quer salvar a imagem. O resultado disso é que a imagem que será salva não necessariamente será a mesma imagem de quando o usuário clicou no botão “Capturar“.

Esse problema pode ser resolvido se pausarmos a captura enquanto o usuário escolhe o caminho, restaurando a captura logo após o confirmação ou cancelamento do diálogo. Isso pode ser feito removendo o “hook” do evento “NewFrame” e depois adicionando novamente, tudo isso dentro de um bloco try-finally para evitarmos inconsistências caso uma exception seja lançada no meio do caminho.

Desligue a câmera antes de fechar o formulário!

Em algumas situações (não muito raras) a biblioteca AForge disparará uma exception caso o formulário (ou aplicativo) seja fechado com a câmera ligada. Para evitar esse problema, temos que fazer um “override” do método “OnFormClosing“, onde verificaremos se a câmera está ligada e, caso positivo, nós desligamos a câmera antes que o formulário seja efetivamente fechado.

O código completo

Aqui vai o código completo do exemplo demonstrado nesse vídeo:

        // C#
        private VideoCaptureDevice videoSource;

        public FormCaptura()
        {
            InitializeComponent();

            var videoSources = new FilterInfoCollection(FilterCategory.VideoInputDevice);
            if (videoSources != null && videoSources.Count > 0)
            {
                videoSource = new VideoCaptureDevice(videoSources[0].MonikerString);
                videoSource.NewFrame += VideoSource_NewFrame;
            }
        }

        private void VideoSource_NewFrame(object sender, AForge.Video.NewFrameEventArgs eventArgs)
        {
            if (pbWebcam.Image != null)
                pbWebcam.Image.Dispose();
            pbWebcam.Image = (Bitmap)eventArgs.Frame.Clone();
        }

        private void btLigarDesligar_Click(object sender, EventArgs e)
        {
            if (videoSource.IsRunning)
            {
                videoSource.Stop();
                pbWebcam.Image = null;
            }
            else
            {
                videoSource.Start();
            }
        }

        private void btCapturar_Click(object sender, EventArgs e)
        {
            if (pbWebcam.Image != null)
            {
                try
                {
                    videoSource.NewFrame -= VideoSource_NewFrame;

                    using (var dialog = new SaveFileDialog())
                    {
                        dialog.DefaultExt = "png";
                        dialog.AddExtension = true;

                        if (dialog.ShowDialog() == DialogResult.OK)
                        {
                            pbWebcam.Image.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Png);
                        }
                    }
                }
                finally
                {
                    videoSource.NewFrame += VideoSource_NewFrame;
                }
            }
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            if (videoSource.IsRunning)
            {
                videoSource.Stop();
            }
            base.OnFormClosing(e);
        }
    ' VB.NET
    Private VideoSource As VideoCaptureDevice

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim VideoSources = New FilterInfoCollection(FilterCategory.VideoInputDevice)
        If VideoSources IsNot Nothing AndAlso VideoSources.Count > 0 Then
            VideoSource = New VideoCaptureDevice(VideoSources(0).MonikerString)
            AddHandler VideoSource.NewFrame, AddressOf VideoSource_NewFrame
        End If
    End Sub

    Private Sub VideoSource_NewFrame(sender As Object, eventArgs As AForge.Video.NewFrameEventArgs)
        If PbWebcam.Image IsNot Nothing Then
            PbWebcam.Image.Dispose()
        End If
        PbWebcam.Image = DirectCast(eventArgs.Frame.Clone(), Bitmap)
    End Sub

    Private Sub BtLigarDesligar_Click(sender As Object, e As EventArgs) Handles BtLigarDesligar.Click
        If VideoSource.IsRunning Then
            VideoSource.Stop()
            PbWebcam.Image = Nothing
        Else
            VideoSource.Start()
        End If
    End Sub

    Private Sub BtCapturar_Click(sender As Object, e As EventArgs) Handles BtCapturar.Click
        If PbWebcam.Image IsNot Nothing Then
            Try
                RemoveHandler VideoSource.NewFrame, AddressOf VideoSource_NewFrame

                Using dialog = New SaveFileDialog()
                    dialog.DefaultExt = "png"
                    dialog.AddExtension = True

                    If dialog.ShowDialog() = DialogResult.OK Then
                        PbWebcam.Image.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Png)
                    End If
                End Using
            Finally
                AddHandler VideoSource.NewFrame, AddressOf VideoSource_NewFrame
            End Try
        End If
    End Sub

    Protected Overrides Sub OnFormClosing(e As FormClosingEventArgs)
        If VideoSource.IsRunning Then
            VideoSource.[Stop]()
        End If
        MyBase.OnFormClosing(e)
    End Sub

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Até a próxima!

André Lima

Photo by Peter Shanks used under Creative Commons
https://pixabay.com/en/startup-start-up-notebooks-creative-593327/

Song Rocket Power Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: By Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0/

Newsletter do André Lima

* indicates required



Powered by MailChimp

6 thoughts on “Acessando a webcam no .NET com a biblioteca AForge

  • Tony Rodrigues disse:

    Parabéns pelo vídeo, sou seu fã…

    Uma dúvida que não está relacionada ao tema do post mas me surgiu aq:
    Para o evento de fechamento do form eu vou no designer na janela Events e dou clique duplo em FormClosing que gera o seguinte código:
    private void nomeDoForm_FormClosing(object sender, FormClosingEventArgs e) {}

    vc usou:
    protected override void OnFormClosing(FormClosingEventArgs e) {}

    Qual a diferença entre os dois?

    • andrealveslima disse:

      Olá Tony!

      Basicamente eles fazem a mesma coisa.. O método OnFormClosing do Form faz as chamadas dos hooks que você tiver para o evento FormClosing.. Em artigos eu normalmente faço com o override porque aí o leitor pode simplesmente copiar e colar o código para dentro do formulário e tudo funcionará normalmente, enquanto que com o evento eu teria que instruir o leitor a criar o hook para o evento, sacou?

      Já no vídeo eu não sei ao certo o motivo de eu ter feito com o override.. Talvez foi porque eu já estava ali no editor, aí fica mais rápido fazer com o override.. Se eu fosse fazer com o hook do evento eu teria que primeiro ir para o design do formulário, criar o hook pro evento, etc etc..

      Mas, resumindo: basicamente não tem diferença.. O base.OnFormClosing vai disparar todos os hooks que tiverem sido feitos para o evento FormClosing..

      Abraço!
      André Lima

  • Du disse:

    Oi André, top o seu artigo, com certeza será utilizado em um trabalho que estou desenvolvendo. O video excelente também. Parabéns!

  • José Carlos R Leoner disse:

    Show. Estava precisando disso. Valeu.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *