none
Auf versteckten Videostream einer webcam bildweise zugreifen? RRS feed

  • Frage

  • Hallo,

    ich benutze eine WM_CAP_DRIVER Class (unten angefügt). Das Video wird gestartet und während es läuft lese ich bildweise Informationen aus.

    Das klappt auch sehr gut, aber NUR wenn ich das Video gleichzeitig sehe! Weil ich wenig Platz habe, soll das Video in einer Tabpage laufen während ich in einer anderen Tabpage auf den Stream bildweise zugreifen möchte (Positionierung eines Roboters). Das klappt nicht, erst wieder wenn ich kurz den Tabpage mit dem Video öffne, dann bekomme ich das mir zuletzt angezeigte Videobild in der anderen Tabpage.

    Wie kann ich dieses Problem lösen? Bisher starte und beende ich den Stream jedes mal, wenn ich ein Standbild sehen möchte (dafür muss ich den Stream aber nicht sehen), aber als gute Lösung sehe ich das nicht an!

    Gruß, Sebastian

    Public Class Win32Api
    
    #Region "Api Functions"
    
      Private Declare Auto Function capCreateCaptureWindow Lib "avicap32.dll" ( _
        ByVal lpszWindowName As String, _
        ByVal dwStyle As Integer, _
        ByVal x As Integer, _
        ByVal y As Integer, _
        ByVal nWidth As Integer, _
        ByVal nHeight As Integer, _
        ByVal hWnd As IntPtr, _
        ByVal nID As Integer) _
        As IntPtr
    
      Private Declare Auto Function SendMessage Lib "user32.dll" ( _
        ByVal hwnd As IntPtr, _
        ByVal uMsg As Integer, _
        ByVal wParam As Integer, _
        ByVal lParam As Integer) _
        As Integer
    
    #End Region
    
    #Region "Constants"
    
      Private Const WM_USER As Int32 = &H400
      Private Const WS_CHILD As Integer = &H40000000
      Private Const WS_VISIBLE As Integer = &H10000000
      Private Const WM_CAP_START As Integer = WM_USER
      Private Const WM_CAP_DRIVER_CONNECT As Integer = (WM_CAP_START + 10)
      Private Const WM_CAP_SET_PREVIEWRATE As Integer = (WM_CAP_START + 52)
      Private Const WM_CAP_SET_OVERLAY As Integer = (WM_CAP_START + 51)
      Private Const WM_CAP_SET_PREVIEW As Integer = (WM_CAP_START + 50)
      Private Const WM_CAP_DRIVER_DISCONNECT As Integer = (WM_CAP_START + 11)
      Private Const WM_CAP_EDIT_COPY As Integer = (WM_CAP_START + 30)
    
    #End Region
    
    #Region "Private"
    
      Private _hwnd As IntPtr
      Private _width As Integer
      Private _height As Integer
    
    #End Region
    
    #Region "Camera Id"
    
      Private Const CameraId As Integer = 0
    
    #End Region
    
    #Region "Frames"
    
      Private Const Frames As Integer = 10
    
    #End Region
    
    #Region "Positions"
    
      Private x As Integer = 0
      Private y As Integer = 0
    
    #End Region
    
    #Region "Public"
    
      Public MyHandle As IntPtr
    
    #End Region
    
    #Region "Constructor"
    
      Public Sub New(ByVal hWnd As IntPtr, ByVal Width As Integer, ByVal Height As Integer)
        If Not hWnd.Equals(IntPtr.Zero) Then
          Me._hwnd = hWnd
          If (Width >= 320) And (Height >= 240) Then
            Me._width = Width : Me._height = Height
          Else
            Return
          End If
        Else
          Return
        End If
      End Sub
    
    #End Region
    
    #Region "Functions"
    
      Public Function GetCaptureHandle() As IntPtr
        Dim [Handle] As IntPtr = Win32Api.capCreateCaptureWindow("CaptureWindow", _
          Win32Api.WS_CHILD + Win32Api.WS_VISIBLE, _
          x, y, _
          Me._width, Me._height, _
          Me._hwnd, _
          Win32Api.CameraId)
    
        SendMessage([Handle], Win32Api.WM_CAP_DRIVER_CONNECT, Win32Api.CameraId, 0)
    
        SendMessage([Handle], Win32Api.WM_CAP_SET_PREVIEWRATE, Win32Api.Frames, 0)
        SendMessage([Handle], Win32Api.WM_CAP_SET_OVERLAY, 1, 0)
        SendMessage([Handle], Win32Api.WM_CAP_SET_PREVIEW, 1, 0)
        If Not [Handle].Equals(IntPtr.Zero) Then
          Return [Handle]
        Else
          Return IntPtr.Zero
        End If
      End Function
    
      Public ReadOnly Property GetImage() As Drawing.Image
        Get
          Return Me.SetCurrentImageToClipBoard()
        End Get
      End Property
    
      Private Function SetCurrentImageToClipBoard() As Drawing.Image
        Try
          My.Computer.Clipboard.Clear()
          SendMessage(Me.SetHandle(), Win32Api.WM_CAP_EDIT_COPY, 0, 0)
          Dim img As Image = My.Computer.Clipboard.GetImage
          If img IsNot Nothing Then
            Return img
          Else
            Return Nothing
          End If
        Catch
          Return Nothing
        End Try
      End Function
    
      Public Property SetHandle() As IntPtr
        Get
          Return MyHandle
        End Get
        Set(ByVal value As IntPtr)
          MyHandle = value
        End Set
      End Property
    
      Public Sub DisposeConnection()
        Dim result As Integer = SendMessage(Me.SetHandle(), Win32Api.WM_CAP_DRIVER_DISCONNECT, Win32Api.CameraId, 0)
        Debug.WriteLine("Disconnected: " & result.ToString())
      End Sub
    
    #End Region
    
    End Class
    
    


    [VB express 2010]
    Mittwoch, 6. April 2011 09:22

Antworten

  • Hallo Sebastian,

    ich hatte Zeit mich etwas intensiver mit dem Thema zu beschäftigen. Das iCam Beispiel sollte man dabei nach Möglichkeit komplett vergessen oder als Beispiel hernehmen, wie man es nicht machen sollte. Als AnyCPU/64 Bit ausgeführt ist es nicht lauffähig, was abenteuerlichen API Deklarationen und Umwandlungen geschuldet ist. Zudem es das Extrahieren eines Frames nur über die BitBlt/Screencopy Methode realisiert und damit auch nur dann funktioniert, wenn das Capture Window sichtbar ist, wie Du schon richtig vermutet hast.

    Zur Lösung Deines Problems: Du kannst einzelne Bilder von der WebCam explizit über die Message WM_CAP_GRAB_FRAME_NOSTOP anfordern. Diese legt eine Momentaufnahme im Framebuffer des Capturewindow ab, auch wenn dieses gerade nicht angezeigt wird. Dieses Image kann dann beispielsweise in die Zwischenablage kopiert werden:

     

     Private Function SetCurrentImageToClipBoard() As Drawing.Image
     Try
      My.Computer.Clipboard.Clear()
    
      SendMessage(Me.SetHandle(), Win32Api.WM_CAP_GRAB_FRAME_NOSTOP, 0, 0)
      SendMessage(Me.SetHandle(), Win32Api.WM_CAP_EDIT_COPY, 0, 0)
    
      Dim img As Image = My.Computer.Clipboard.GetImage
    
      Return img
    
     Catch
      Return Nothing
     End Try
     End Function
    

     

    Deklaration für die Konstante:

     

     Private Const WM_CAP_GRAB_FRAME_NOSTOP As Integer = (WM_CAP_START + 61)
    

     

    Die Callback Variante verhält sich hier im Übrigen genauso. Das heißt, der Callback wird nur aufgerufen, solange das Capturewindow sichtbar ist oder wenn man explizit ein Frame anfordert. Die Lösung hätte dennoch den Charme, dass man auf den Umweg über die Zwischenablage verzichten kann. Bei Interesse kannst Du ja Bescheid geben.

     


    Thorsten Dörfler
    Microsoft MVP Visual Basic
    vb-faq.de
    Sonntag, 10. April 2011 14:23

Alle Antworten

  • Hallo Sebastian,

    ungeprüft, könnte die capVideoStreamCallback Function eine Lösung sein:

    capVideoStreamCallback
    http://msdn.microsoft.com/en-us/library/dd797759.aspx

    Problems getting capVideoStreamCallback and WM_CAP_SET_CALLBACK_VIDEOSTREAM to work
    http://stackoverflow.com/questions/3876945/problems-getting-capvideostreamcallback-and-wm-cap-set-callback-videostream-to-wo

    Ferner bietet folgende Klasse Methoden einzelne Frames aus dem Stream zu fischen:

    iCam - A WebCam Class in Visual Basic
    http://www.codeguru.com/vb/gen/vb_misc/gamesandfun/article.php/c13951


    Thorsten Dörfler
    Microsoft MVP Visual Basic
    vb-faq.de
    Mittwoch, 6. April 2011 21:18
  • Hallo Thorsten,

    danke für die links, die sahen auf den ersten Blick wirklich sehr gut aus! Der erste link bringt mich leider nicht weiter, da ich davon dann doch zu wenig Ahnung und Zeit habe... müsste ja meine Klasse umschreiben.

    Das Projekt des zweiten links ist leider unvollständig, das probiere ich lieber nicht. Mit dem dritten link (iCam) hatte ich schon einmal geliebäugelt, aber das "fischen" aus einem stream bezieht sich dabei auf das herauskopieren aus der picturebox in welche der stream gesendet wird. Sieht man die picturebox mit dem stream nicht, ist das bild der picturebox leer. (das ist meine Vermutung, weil es sich mit meinem Beispiel genauso verhält).

    Wenn ich am Wochenende Zeit habe, werde ich es trotzdem mal schnell testen!

    Gruß, Sebastian


    [VB express 2010]
    Donnerstag, 7. April 2011 15:24
  • Hallo Sebastian,

    ich hatte Zeit mich etwas intensiver mit dem Thema zu beschäftigen. Das iCam Beispiel sollte man dabei nach Möglichkeit komplett vergessen oder als Beispiel hernehmen, wie man es nicht machen sollte. Als AnyCPU/64 Bit ausgeführt ist es nicht lauffähig, was abenteuerlichen API Deklarationen und Umwandlungen geschuldet ist. Zudem es das Extrahieren eines Frames nur über die BitBlt/Screencopy Methode realisiert und damit auch nur dann funktioniert, wenn das Capture Window sichtbar ist, wie Du schon richtig vermutet hast.

    Zur Lösung Deines Problems: Du kannst einzelne Bilder von der WebCam explizit über die Message WM_CAP_GRAB_FRAME_NOSTOP anfordern. Diese legt eine Momentaufnahme im Framebuffer des Capturewindow ab, auch wenn dieses gerade nicht angezeigt wird. Dieses Image kann dann beispielsweise in die Zwischenablage kopiert werden:

     

     Private Function SetCurrentImageToClipBoard() As Drawing.Image
     Try
      My.Computer.Clipboard.Clear()
    
      SendMessage(Me.SetHandle(), Win32Api.WM_CAP_GRAB_FRAME_NOSTOP, 0, 0)
      SendMessage(Me.SetHandle(), Win32Api.WM_CAP_EDIT_COPY, 0, 0)
    
      Dim img As Image = My.Computer.Clipboard.GetImage
    
      Return img
    
     Catch
      Return Nothing
     End Try
     End Function
    

     

    Deklaration für die Konstante:

     

     Private Const WM_CAP_GRAB_FRAME_NOSTOP As Integer = (WM_CAP_START + 61)
    

     

    Die Callback Variante verhält sich hier im Übrigen genauso. Das heißt, der Callback wird nur aufgerufen, solange das Capturewindow sichtbar ist oder wenn man explizit ein Frame anfordert. Die Lösung hätte dennoch den Charme, dass man auf den Umweg über die Zwischenablage verzichten kann. Bei Interesse kannst Du ja Bescheid geben.

     


    Thorsten Dörfler
    Microsoft MVP Visual Basic
    vb-faq.de
    Sonntag, 10. April 2011 14:23
  • Jaaa, voll gut, vielen vielen Dank! Funktioniert einwandfrei :-)

    Jetzt ist allerdings ein neues Problem aufgetaucht, denn die Kamera regelt die Helligkeit nicht mehr selbst! Ich habe mal zwei Bilder angehängt, in beiden liegt meine Schwelle im Histogramm bei 100. Das Bild, welches ich durch deine Methode aufgenommen habe, ist deutlich dunkler und mein Objekt wird nicht mehr fehlerfrei erkannt.

    Bilder: https://profiles.google.com/115345283700031324241/photos

    Von dem An-und Ausschalten der Kamera bin ich übrigens weg und habe das Video immer als Hintergrund meiner Benutzeroberfläche laufen lassen, das hellere Bild von beiden ist damit aufgenommen.

    Könnte das doch an der Callback Funktion liegen? Beim Start der USB-Kamera in anderen Programmen braucht sie etwa 1-2 Sekunden um die Helligkeit zu regeln. Das würde etwa 15-30 Bilder entsprechen. Effektiv nehme ich aber bisher nur 5-6 Bilder nach dem Start auf. Könnte es sein, dass ich erstmal 30 Bilder abholen muss, bevor sie "betriebsbereit" ist? Würde sich das über Callback-Funktion lösen lassen?

    Gruß, Sebastian


    [VB express 2010]
    Montag, 11. April 2011 06:57
  • Hallo,

    wenn ich einen Snapshot direkt nach dem Aktivieren das Cam abhole, ist dieser auch deutlicher dunkler, als ein späterer. Warte ich eine Weile (ca. 10 sek.), während das aktuelle Cam Bild ausgeblendet ist, habe ich aber ein ganz normal ausgeleuchtetes Bild. Ggf. kannst Du hier etwas an den allgemeinen Optionen der Cam drehen. Für Vergleichsaufnahmen wäre vielleicht auch eine konstante Ausleuchtung, sowie fix justierte Helligkeit/Empfindlichkeit von Vorteil. Das hilft jedoch nur, wenn Du diese Faktoren beeinflussen kannst bzw. sie in der Praxis später beeinflussbar sind.


    Thorsten Dörfler
    Microsoft MVP Visual Basic
    vb-faq.de
    Montag, 11. April 2011 16:40
  • Hallo Thorsten,

    ja du hast Recht, nach einer Weile sind die Bilder heller. Ich mache es jetzt so, dass ich zuerst 20 Bilder über eine Sekunde abhole, in der Zeit verfährt der Motor eh noch. Merkt also kein Mensch :-)

    Danach kann ich mit deiner Methode einzelne Bilder abholen die auch gut ausgeleuchtet sind. Manuell kann man bei der Kamera nichts einstellen....

    Vielen Dank für deine Hilfe, der Thread kann damit als gelöst betrachtet werden!

     

    Gruß, Sebastian


    [VB express 2010]
    Montag, 11. April 2011 17:19
  • Hallo,

    es ist leider ein weiteres Problem aufgetreten. In meinem Fall habe ich zwei USB-Kameras angeschlossen. Leider nimmt mein Programm beim Starten der Kamera immer nur eine an. Und zwar die, die ich beim letzten mal über das automatisch (von Windows) aufpoppende Fenster ausgewählt habe. (Foto hier: https://picasaweb.google.com/115345283700031324241/Projekt?authkey=Gv1sRgCJOHtoOyk_7nIg#)

    Diese Fenster erhalte ich nur, wenn ich es durch eine doppelte Eingabe von

    [Handle] = w32.GetCaptureHandle
    [Handle] = w32.GetCaptureHandle
    

    provoziere. In dem Fall startet dann das Video in der Picturebox, das Fenster poppt auf, ich wähle eine Kamera aus und falls es nicht die bereits ausgewählte Kamera ist, kann ich das Video auch nicht mehr beenden. Durch Neustart und Weglassen des zweiten [Handle] = w32.GetCaptureHandle nimmt er dann automatisch für alle Zukunft die Kamera, die ich ausgewählt habe. Weil ich aber zwischen zwei Kameras hin- und herwechsel geht das so nicht.

    Ich habe schon versucht das [Handle] auf Null zu setzen, aber folgende Schritte haben nichts gebracht:

          [Handle] = [Handle].Equals(IntPtr.Zero)
          w32.SetHandle = [Handle].Equals(IntPtr.Zero)
          w32.MyHandle = IntPtr.Zero
    

    Weiterhin habe ich versucht die CameraID in der Klasse manuell zu verändern. Standart ist 0, eine Änderung bis 10 bringt keinerlei Erfolg.

    Für jede Hilfe bin ich sehr dankbar!

    Gruß, Sebastian

     


    [VB express 2010]
    Dienstag, 12. April 2011 12:01