Preview a camera in DirectShow and capture a still image - in VB.net

我只是一个虾纸丫 提交于 2019-11-30 16:14:46

Here is what I finally used for my project - it gives you a preview window, and then on another form you can push a button to take the picture. I take the picture and display that stillshot image as another picturebox on the main form (a little larger). I added a cropbox to choose what portion of that picture box you would ultimately like to save, but am not including that in this answer for simplicities sake.

You initialize it with the call: cam = New Capture(my.Settings.VideoDeviceNum, FRAMERATE, my.Settings.CapturePictureWidth, my.Settings.CapturePictureHeight) and cam.Start()

The snapshot is take with this call:

Dim picError As Boolean=False

        cam.captureSaved=False
        cam.TakePicture
        Dim stTime As date = Now

        Do Until cam.captureSaved
            'do nothing - might want to have an automatic break if this takes too long
            If DateDiff(DateInterval.Second,stTime,Now) >15 then
                MsgBox("The camera is taking a long time to capture the picture. Please try again.")
                picError=True:Exit do
            End If

        Loop

        If not picError then
            cam.Capturedpic.RotateFlip (RotateFlipType.Rotate180FlipX)

            'scale the camera image and place it in the picture box
            CaptureBox.Image=scaleimage(cam.capturedpic,CaptureBox.Width,CaptureBox.Height)
        End If    

        SavePicture.Visible = True
        myStatus.Text = "Picture Taken."

The scaleimage function simply scales the snapshot image to the appropriate size for the box I have on the form. I only work with scaling the x, because I only allow a certain aspect ratio, so if you aren't going to lock in your aspect ratios for the camera, it will need to be adjusted:

Public Function ScaleImage(source As Bitmap, x As Integer, y As Integer) As Bitmap

        Dim scale As single = x / source.Width

        Dim myBmp As new Bitmap(cint(source.Width*scale), cint(source.height*scale),source.PixelFormat)

        Dim gr As Graphics = Graphics.FromImage(myBmp)

        gr.DrawImage(source, 0, 0, myBmp.Width + 1, myBmp.Height + 1)

        Return myBmp  

    End Function

The main camera class is below:

'****************************************************************************
'While the underlying libraries are covered by LGPL, this sample is released 
'as public domain.  It is distributed in the hope that it will be useful, but 
'WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
'or FITNESS FOR A PARTICULAR PURPOSE.  
'*****************************************************************************/

Imports System
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Imports System.Diagnostics

Imports DirectShowLib

Public Class Capture
    Implements ISampleGrabberCB
    Implements IDisposable

#Region "Member variables"

    ' <summary> graph builder interface. </summary>
    Private m_graphBuilder As IFilterGraph2 = Nothing
    Private m_mediaCtrl As IMediaControl = Nothing

    Private mediaEventEx As IMediaEventEx = Nothing
    Private videoWindow As IVideoWindow = Nothing
    Private UseHand As IntPtr = MainForm.PreviewBox.Handle
    Private Const WMGraphNotify As Integer = 13
    Private m_takePicture As Boolean = False
    Public mytest As String = "yes"
    Dim sampGrabber As ISampleGrabber = Nothing    

    Private bufferedSize As Integer = 0
    Private savedArray() As Byte
    Public capturedPic as bitmap
    Public captureSaved As Boolean
    Public unsupportedVideo As Boolean

    ' <summary> Set by async routine when it captures an image </summary>
    Public m_bRunning As Boolean = False

    ' <summary> Dimensions of the image, calculated once in constructor. </summary>
    Private m_videoWidth As Integer
    Private m_videoHeight As Integer
    Private m_stride As Integer

    Private m_bmdLogo As BitmapData = Nothing
    Private m_Bitmap As Bitmap = Nothing

#If Debug Then
    ' Allow you to "Connect to remote graph" from GraphEdit
    Private m_rot As DsROTEntry = Nothing
#End If

#End Region

#Region "API"

    Declare Sub CopyMemory Lib "Kernel32.dll" Alias "RtlMoveMemory" (ByVal Destination As IntPtr, ByVal Source As IntPtr, <MarshalAs(UnmanagedType.U4)> ByVal Length As Integer)

#End Region

    ' zero based device index, and some device parms, plus the file name to save to
    Public Sub New(ByVal iDeviceNum As Integer, ByVal iFrameRate As Integer, ByVal iWidth As Integer, ByVal iHeight As Integer)
        Dim capDevices As DsDevice()

        ' Get the collection of video devices
        capDevices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice)

        If (iDeviceNum + 1 > capDevices.Length) Then
            Throw New Exception("No video capture devices found at that index!")
        End If

        Dim dev As DsDevice = capDevices(iDeviceNum)

        Try
            ' Set up the capture graph
            SetupGraph(dev, iFrameRate, iWidth, iHeight)
        Catch
            Dispose()
            If unsupportedVideo then
                msgbox("This video resolution isn't supported by the camera - please choose a different resolution.")    
            Else
                Throw
            End If

        End Try
    End Sub
    ' <summary> release everything. </summary>
    Public Sub Dispose() Implements IDisposable.Dispose
        CloseInterfaces()
        If (Not m_Bitmap Is Nothing) Then
            m_Bitmap.UnlockBits(m_bmdLogo)
            m_Bitmap = Nothing
            m_bmdLogo = Nothing
        End If
    End Sub
    Protected Overloads Overrides Sub finalize()
        CloseInterfaces()
    End Sub

    ' <summary> capture the next image </summary>
    Public Sub Start()
        If (m_bRunning = False) Then
            Dim hr As Integer = m_mediaCtrl.Run()
            DsError.ThrowExceptionForHR(hr)

            m_bRunning = True
        End If
    End Sub
    ' Pause the capture graph.
    ' Running the graph takes up a lot of resources.  Pause it when it
    ' isn't needed.
    Public Sub Pause()
        If (m_bRunning) Then
            Dim hr As Integer = m_mediaCtrl.Pause()
            DsError.ThrowExceptionForHR(hr)

            m_bRunning = False
        End If
    End Sub

    'Added by jk
    Public Sub TakePicture()

        m_takePicture=True

    End Sub

    ' <summary> Specify the logo file to write onto each frame </summary>
    Public Sub SetLogo(ByVal fileName As String)
        SyncLock Me
            If (fileName.Length > 0) Then
                m_Bitmap = New Bitmap(fileName)

                Dim r As Rectangle = New Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height)
                m_bmdLogo = m_Bitmap.LockBits(r, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb)
            Else
                If Not m_Bitmap Is Nothing Then
                    m_Bitmap.UnlockBits(m_bmdLogo)
                    m_Bitmap = Nothing
                    m_bmdLogo = Nothing
                End If
            End If
        End SyncLock
    End Sub

    ' <summary> build the capture graph for grabber. </summary>
    Private Sub SetupGraph(ByVal dev As DsDevice, ByVal iFrameRate As Integer, ByVal iWidth As Integer, ByVal iHeight As Integer)

        Dim hr As Integer

        Dim baseGrabFlt As IBaseFilter = Nothing
        Dim capFilter As IBaseFilter = Nothing
        Dim muxFilter As IBaseFilter = Nothing
        Dim fileWriterFilter As IFileSinkFilter = Nothing
        Dim capGraph As ICaptureGraphBuilder2 = Nothing
        Dim sampGrabberSnap As ISampleGrabber = Nothing

        ' Get the graphbuilder object
        m_graphBuilder = DirectCast(New FilterGraph(), IFilterGraph2)
        m_mediaCtrl = DirectCast(m_graphBuilder, IMediaControl)

        'if taking a picture (a still snapshot), then remove the videowindow
        If not m_takePicture then 
            mediaEventEx = DirectCast(m_graphBuilder, IMediaEventEx)
            videoWindow = DirectCast(m_graphBuilder, IVideoWindow)
        Else
            mediaEventEx = Nothing
            videoWindow =  Nothing
        End If

#If Debug Then
        m_rot = New DsROTEntry(m_graphBuilder)
#End If

        Try


            ' Get the ICaptureGraphBuilder2
            capGraph = DirectCast(New CaptureGraphBuilder2(), ICaptureGraphBuilder2)

            ' Get the SampleGrabber interface
            sampGrabber = DirectCast(New SampleGrabber(), ISampleGrabber)
            sampGrabberSnap = DirectCast(New SampleGrabber(), ISampleGrabber)

            ' Start building the graph
            hr = capGraph.SetFiltergraph(DirectCast(m_graphBuilder, IGraphBuilder))
            DsError.ThrowExceptionForHR(hr)

            ' Add the video device
            hr = m_graphBuilder.AddSourceFilterForMoniker(dev.Mon, Nothing, dev.Name, capFilter)
            DsError.ThrowExceptionForHR(hr)

            baseGrabFlt = DirectCast(sampGrabber, IBaseFilter)
            ConfigureSampleGrabber(sampGrabber)

            ' Add the frame grabber to the graph
            hr = m_graphBuilder.AddFilter(baseGrabFlt, "Ds.NET Grabber")
            DsError.ThrowExceptionForHR(hr)

            ' If any of the default config items are set
            If (iFrameRate + iHeight + iWidth > 0) Then

                SetConfigParms(capGraph, capFilter, iFrameRate, iWidth, iHeight)
            End If

             hr = capGraph.RenderStream(PinCategory.Capture, MediaType.Video, capFilter, baseGrabFlt, muxFilter)
             DsError.ThrowExceptionForHR(hr)

            'if you set the m_takePicture it won't
            If Not m_takePicture then

                'Set the output of the preview
                hr = mediaEventEx.SetNotifyWindow(UseHand, WMGraphNotify, IntPtr.Zero)
                DsError.ThrowExceptionForHR(hr)

                'Set Owner to Display Video
                hr = videoWindow.put_Owner(UseHand)
                DsError.ThrowExceptionForHR(hr)

                'Set window location - this was necessary so that the video didn't move down and to the right when you pushed the start/stop button
                hr = videoWindow.SetWindowPosition(0, 0, my.Settings.previewwidth, my.Settings.previewHeight)
                DsError.ThrowExceptionForHR(hr)

                'Set Owner Video Style
                hr = videoWindow.put_WindowStyle(WindowStyle.Child)    
                DsError.ThrowExceptionForHR(hr)

            End If


            SaveSizeInfo(sampGrabber)

        Finally

            If (Not fileWriterFilter Is Nothing) Then
                Marshal.ReleaseComObject(fileWriterFilter)
                fileWriterFilter = Nothing
            End If
            If (Not muxFilter Is Nothing) Then
                Marshal.ReleaseComObject(muxFilter)
                muxFilter = Nothing
            End If
            If (Not capFilter Is Nothing) Then
                Marshal.ReleaseComObject(capFilter)
                capFilter = Nothing
            End If
            If (Not sampGrabber Is Nothing) Then
                Marshal.ReleaseComObject(sampGrabber)
                sampGrabber = Nothing
            End If
        End Try
    End Sub

    ' <summary> Read and store the properties </summary>
    Private Sub SaveSizeInfo(ByVal sampGrabber As ISampleGrabber)

        Dim hr As Integer

        ' Get the media type from the SampleGrabber
        Dim media As AMMediaType = New AMMediaType()
        hr = sampGrabber.GetConnectedMediaType(media)
        DsError.ThrowExceptionForHR(hr)

        If (Not (media.formatType.Equals(FormatType.VideoInfo)) AndAlso Not (media.formatPtr.Equals(IntPtr.Zero))) Then
            Throw New NotSupportedException("Unknown Grabber Media Format")
        End If

        ' Grab the size info
        Dim vInfoHeader As VideoInfoHeader = New VideoInfoHeader()
        Marshal.PtrToStructure(media.formatPtr, vInfoHeader)
        m_videoWidth = vInfoHeader.BmiHeader.Width
        m_videoHeight = vInfoHeader.BmiHeader.Height
        m_stride = m_videoWidth * (vInfoHeader.BmiHeader.BitCount / 8)

        DsUtils.FreeAMMediaType(media)
        media = Nothing
    End Sub
    ' <summary> Set the options on the sample grabber </summary>
    Private Sub ConfigureSampleGrabber(ByVal sampGrabber As ISampleGrabber)
        Dim hr As Integer
        Dim media As AMMediaType = New AMMediaType()

        media.majorType = MediaType.Video
        media.subType = MediaSubType.RGB24
        media.formatType = FormatType.VideoInfo
        hr = sampGrabber.SetMediaType(media)
        DsError.ThrowExceptionForHR(hr)

        DsUtils.FreeAMMediaType(media)
        media = Nothing

        ' Configure the samplegrabber callback
        hr = sampGrabber.SetOneShot(false)
        DsError.ThrowExceptionForHR(hr)

        If m_takePicture then
            hr = sampGrabber.SetCallback(Me, 0)
        Else
            hr = sampGrabber.SetCallback(Me, 0)
        End If
        DsError.ThrowExceptionForHR(hr)

        DsError.ThrowExceptionForHR(hr)

        'set the samplegrabber
        sampGrabber.SetBufferSamples(False)

    End Sub

    ' Set the Framerate, and video size
    Private Sub SetConfigParms(ByVal capGraph As ICaptureGraphBuilder2, ByVal capFilter As IBaseFilter, ByVal iFrameRate As Integer, ByVal iWidth As Integer, ByVal iHeight As Integer)
        Dim hr As Integer

        Dim o As Object = Nothing
        Dim media As AMMediaType = Nothing
        Dim videoStreamConfig As IAMStreamConfig
        Dim videoControl As IAMVideoControl = DirectCast(capFilter, IAMVideoControl)

        ' Find the stream config interface
        hr = capGraph.FindInterface(PinCategory.Capture, MediaType.Video, capFilter, GetType(IAMStreamConfig).GUID, o)

        videoStreamConfig = DirectCast(o, IAMStreamConfig)
        Try
            If (videoStreamConfig Is Nothing) Then
                Throw New Exception("Failed to get IAMStreamConfig")
            End If

            ' Get the existing format block
            hr = videoStreamConfig.GetFormat(media)
            DsError.ThrowExceptionForHR(hr)

            ' copy out the videoinfoheader
            Dim v As VideoInfoHeader = New VideoInfoHeader()
            Marshal.PtrToStructure(media.formatPtr, v)

            ' if overriding the framerate, set the frame rate
            If (iFrameRate > 0) Then
                v.AvgTimePerFrame = 10000000 / iFrameRate
            End If

            ' if overriding the width, set the width
            If (iWidth > 0) Then
                v.BmiHeader.Width = iWidth
            End If

            ' if overriding the Height, set the Height
            If (iHeight > 0) Then
                v.BmiHeader.Height = iHeight
            End If

            ' Copy the media structure back
            Marshal.StructureToPtr(v, media.formatPtr, False)

            ' Set the new format
            hr = videoStreamConfig.SetFormat(media)
            If hr<>0 then unsupportedVideo = True else unsupportedVideo=False
            DsError.ThrowExceptionForHR(hr)

            DsUtils.FreeAMMediaType(media)
            media = Nothing

            ' Fix upsidedown video
            If (Not videoControl Is Nothing) Then
                Dim pCapsFlags As VideoControlFlags

                Dim pPin As IPin = DsFindPin.ByCategory(capFilter, PinCategory.Capture, 0)
                hr = videoControl.GetCaps(pPin, pCapsFlags)
                DsError.ThrowExceptionForHR(hr)

                If ((pCapsFlags & VideoControlFlags.FlipVertical) > 0) Then
                    hr = videoControl.GetMode(pPin, pCapsFlags)
                    DsError.ThrowExceptionForHR(hr)

                    hr = videoControl.SetMode(pPin, 0)
                End If
            End If
        Finally
            Marshal.ReleaseComObject(videoStreamConfig)
        End Try
    End Sub

    ' <summary> Shut down capture </summary>
    Private Sub CloseInterfaces()
        Dim hr As Integer

        Try
            If (Not m_mediaCtrl Is Nothing) Then

                ' Stop the graph
                hr = m_mediaCtrl.Stop()
                m_mediaCtrl = Nothing
                m_bRunning = False

                'Release Window Handle, Reset back to Normal
                hr = videoWindow.put_Visible(OABool.False)
                DsError.ThrowExceptionForHR(hr)

                hr = videoWindow.put_Owner(IntPtr.Zero)
                DsError.ThrowExceptionForHR(hr)

                If mediaEventEx Is Nothing = False Then
                    hr = mediaEventEx.SetNotifyWindow(IntPtr.Zero, 0, IntPtr.Zero)
                    DsError.ThrowExceptionForHR(hr)
                End If

            End If
        Catch ex As Exception
            Debug.WriteLine(ex)
        End Try

#If Debug Then
        If (Not m_rot Is Nothing) Then
            m_rot.Dispose()
            m_rot = Nothing
        End If
#End If

        If (Not m_graphBuilder Is Nothing) Then
            Marshal.ReleaseComObject(m_graphBuilder)
            m_graphBuilder = Nothing
        End If
        GC.Collect()
    End Sub

    ' <summary> sample callback, Originally not used - call this with integer 0 on the setcallback method </summary>
    Function SampleCB(ByVal SampleTime As Double, ByVal pSample As IMediaSample) As Integer Implements ISampleGrabberCB.SampleCB
        myTest = "In SampleCB"

        Dim i As Integer=0

            'jk added this code 10-22-13
            if IsDBNull(pSample) =True then return -1
                dim myLen  As Integer = pSample.GetActualDataLength()
                dim pbuf As IntPtr
                if pSample.GetPointer(pbuf) = 0 AND mylen > 0 then
                    dim buf As byte()= new byte(myLen) {}
                    Marshal.Copy(pbuf, buf, 0, myLen)

                    'Alter the video - you could use this to adjust the brightness/red/green, etc.
                    'for i = myLen-1 to 0 step -1
                    '    buf(i) = (255 - buf(i))
                    'Next i

                    If m_takePicture then
                        Dim bm As new Bitmap(m_videoWidth,m_videoHeight,Imaging.PixelFormat.Format24bppRgb)
                        Dim g_RowSizeBytes As Integer
                        Dim g_PixBytes() As Byte

                        mytest = "Execution point #1"
                        Dim m_BitmapData As BitmapData = Nothing
                        Dim bounds As Rectangle = New Rectangle(0, 0, m_videoWidth, m_videoHeight)

                        mytest = "Execution point #2"
                        m_BitmapData = bm.LockBits(bounds, Imaging.ImageLockMode.ReadWrite, Imaging.PixelFormat.Format24bppRgb)

                        mytest = "Execution point #4"
                        g_RowSizeBytes = m_BitmapData.Stride

                        mytest = "Execution point #5"
                        ' Allocate room for the data.
                        Dim total_size As Integer = m_BitmapData.Stride * m_BitmapData.Height
                        ReDim g_PixBytes(total_size)

                        mytest = "Execution point #10"

                        'this writes the data to the Bitmap
                        Marshal.Copy(buf, 0, m_BitmapData.Scan0, mylen)
                        capturedPic=bm
                        mytest = "Execution point #15"

                        ' Release resources.
                        bm.UnlockBits(m_BitmapData)
                        g_PixBytes = Nothing
                        m_BitmapData = Nothing
                        bm=Nothing
                        buf=Nothing

                        m_takePicture=False
                        captureSaved = True
                        mytest = "Execution point #20"
                    End If
                End If


        Marshal.ReleaseComObject(pSample)
        Return 0

    End Function

' <summary> buffer callback, Not used - call this with integer 1 on the setcallback method </summary>
    Function BufferCB(ByVal SampleTime As Double, ByVal pBuffer As IntPtr, ByVal BufferLen As Integer) As Integer Implements ISampleGrabberCB.BufferCB

        SyncLock Me

            myTest = "In BufferCB"

        End SyncLock

        return 0
    End Function
End Class

I'm sure this could be improved upon, but it works well for my purposes for now, and I hope it helps someone.

To display video you will need something like the VideoWindow or BasicVideo2 filters which allows you to specify the windows control to draw the video on:

iVidWdw.put_Owner(mVidCtl.Handle). 

I have never gotten SampleGrabber to work, but BasicVideo, VMR9 and MediaDetector can all capture (one or 2 might need MediaSeeking). It sounds like you might need one filter to save to file (if you want that) and another to play/show, which is not uncommon. Also, you will almost certainly want to handle the getbitmap (snapshot) part in the C# Lib to convert the bmp data to a managed resource.

Frame capture depends very much on the filter being used. As I said, I have had little luck with SampleGrabber and the others all work differently. MOST return bitmap data NOT a bitmap and with some the image is upside down, so the data has to be transferred to a managed bitmap (MUCH faster in C#) and then rotated depending on the filter used.

If you got SampleGrabber to work (thats all it does it grab images - maybe clips, I forget), you should be able to save bitmaps/captures - I am not sure what the status there is based on the post. It sort of sounds like you have 3 issues: a) capturing the camera stream b) saving the stream to disk (?) and then c) Getting snapshots

One other detail about frame caps is that SOME filters require the graph to be paused in order to capture. That is probably suboptimal for a webcam stream, so it would rule out 1 or 2 (BasicVideo2 is at least one of them).

Here's a short example of working with images of a webcam video stream in C# and DirectShow using Sample Grabber:

http://www.infognition.com/blog/2013/working_with_raw_video_in_c_sharp_directshow.html

All you need is to get data in suitable format (RGB24 or RGB32, not YUY2) either by setting it as preferred format of sample grabber or converting the data as you receive it. Then saving it to a jpeg/bitmap file is trivial.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!