Foto einfügen von der Laptop Kamera



In 50 Zeilen F# Code, inklusive WinForm GUI, Spiegelung, Overlay, JPEG Speicherung und Clipboard.



Der Auslöser für diesen Blog war folgendes:
Ich wollte eine kleine Papier-Handskizze mal schnell in ein Dokument einfügen.

Meine Vorstellung davon:

  1. Zettel vor die Kamera halten
  2. Knopf drücken für das Foto
  3. Einfügen im Dokument via Ctrl+v

Realität in Windows 8.1

  • Also auf meinem Windows 8.1 die Camera-App (ist so eine Metro-App) starten.
  • Notiz vor die Kamera halten
  • Auf Fotografieren klicken.
  • Leeres Clipboard?
    • Da ist leider nichts im Clipboard. Hmmm..
  • Wo ist das Foto?
    • Zurück blättern, clicken bis ein Menu erscheint, da gibts ‘Open with’ und ‘Set as Lockscreen’.
    • Auch unter all den Edit Möglichkeiten, kann man das Foto nicht mit Ctrl+C abholen. Hmmm..
  • Wo sind die Fotos gespeichert?
    • Nach Googeln findet sich
    • C:\Users\DeinUser\Pictures\Camera Roll
    • Mit Drag&Drop ins Dokument damit. Hmmm..
  • Spiegelverkehrt?
    • Wenn ich eine Textnotiz vor die Kamera halte, erscheint diese Spiegelverkehrt. Hmmmm...
All die Hmmms.. verraten, ich finde es nicht praktisch! (Useability?)



Wieviele Zeilen F# Code braucht es dazu?

Kern Problem: Kamera Bild auslesen

Varianten:

  1. mit Windows Mitteln, z.B. WinRT (vorweg genommen, es macht kein Spass)
  2. Open Source Library ;-)

1. WinRT für Desktop! (nicht Windows Phone)

Die benötigte Windows.winmd findet man im Windows SDK:
  1. Installieren von Windows Software Development Kit (SDK) für Windows 8.1 http://msdn.microsoft.com/de-DE/windows/desktop/bg162891
  2. Referenzieren von Windows.winmd Winmd ist ein spezielles IL Format für Windows8 Metro Projekte.
    1: 
    #r "C:\ProgramFiles(x86)\WindowsKits\8.0\References\CommonConfiguration\Neutral\Windows.winmd"
  3. Um *.winmd Files zu nutzen muss das Projekt File noch angepasst werden. Innerhalb PropertyGroup ist TargetPlatformVersion zu wie folgt setzen:
    1: 
    <TargetPlatformVersion>8.0</TargetPlatformVersion>
  4. Die WinRT Unterstützung, d.h. das Referenzieren und Compilieren von .winmd Files, in Visual Studio 2012 ist für F# nicht voll unterstützt.
    1: 
    error FS0193: Could not load file or assembly 'file:///C:\Program Files (x86)\Windows Kits\8.0\References\CommonConfiguration\Neutral\Windows.winmd' or one of its dependencies. Operation is not supported. (Exception from HRESULT: 0x80131515)
    Man muss daher einen Teil in C# schreiben, um die WinRT auf dem Desktop zu nutzen. Hmmmm...
Ich suche nach einer reibungslosen Lösung. Also nach Open Source Camera Library suchen. AForge http://www.aforgenet.com/framework/ sieht vielversprechend aus!



2. AForge

  1. Via Nuget folgende ins Projekt holen:
    1: 
    2: 
    3: 
    AForge, 
    AForge.Video, 
    AForge.Video.DirectShow
  2. #r und open (siehe Code) und sehen ob meine Camera (im ASUS ZenBook) gefunden wird
    1: 
    FilterInfoCollection( FilterCategory.VideoInputDevice )
    wunderbar, da ist meine “USB2.0 HD UVC WebCam”
    1: 
    2: 
    3: 
    4: 
    5: 
    val it : FilterInfoCollection =
      seq
        [AForge.Video.DirectShow.FilterInfo
           {MonikerString = "@device:pnp:\\?\usb#vid_04f2&pid_b330&mi_00#7&1fa3e1a8&1&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global";
            Name = "USB2.0HDUVCWebCam";}]
  3. Das Bild kommt per NewFrame Event vom VideoCaptureDevice, also verpacken wir das in einer Funktion:
    1: 
    let onImage f = videoDevice.NewFrame.AddHandler(NewFrameEventHandler f)
  4. Die Bild Bearbeitung für die Anzeige soll wie folgt erfolgen.
    1: 
    let showImage _ e = e |> getImage |> mirror() |> overlay() |> show
    Das Datum-Zeit-Overlay dient nur der Anzeige.
  5. Beim Speichern den mirror() nochmals anwenden, falls dieser eingeschaltet war, damit es nicht spiegelverkehrt ist.
    1: 
    let save = getImage |> mirror() |> toClip |> toFile
  6. Um das Pipelining |> mit den GUI Einstellungen der mirrored und overlayed Checkboxen zu steuern, werden diese so definiert
    1: 
    2: 
    let mirror()  = ifThen (fun () -> mirrored.Checked)  (tee Image.mirror) 
    let overlay() = ifThen (fun () -> overlayed.Checked) (tee Overlay.draw)
    mit der Hilfsfunktion ifThen, die die Transformation mittels der Identity Funktion id abschalten kann. Auf tee wird später eingegangen.
    1: 
    let ifThen b f = if b() then f else id
  7. Um das Pipelining bei Seiten Effekten wie ClipBoard befüllen und File erzeugen im Fluss zu halten,
    1: 
    let save = getImage |> mirror() |> toClip |> toFile
    wird toClip mit Hilfe der tee Funktion definiert.
    1: 
    let tee f x = f x;x
    Damit lässt sich der Input wieder zurückgeben.
  8. So kleine Funktionen wie tee darf man nicht unterschätzen, denn deren Wiederverwendbarkeit ist sehr hoch! So auch beim Erstellen der Bedieneroberfläche, mit einem add das auf tee basiert, werden die Controls an die Form form gebunden. Was sich dann mit dem Rückwärts-Pipe <| ganz elegant liest let shot = add <| new Button
    1: 
    2: 
    3: 
    4: 
    5: 
    6: 
    let add x        = tee form.Controls.Add x
    
    let pic          = add <| new PictureBox(SizeMode=PictureBoxSizeMode.AutoSize)
    let shot         = add <| new Button  (Text="capture"  , Dock=DockStyle.Bottom)
    let mirrored     = add <| new CheckBox(Text="mirrored" , Dock=DockStyle.Bottom, Checked=true)
    let overlayed    = add <| new CheckBox(Text="overlayed", Dock=DockStyle.Bottom, Checked=false)
  9. Der Code ist absichtlich so formatiert, alle = untereinander, dass es auffällt, das er nur aus kurzen und lesbaren “Deklarationen” besteht. Ausführen kann man den Code direkt mit Alt+Enter im Visual-Studio.
     1: 
     2: 
     3: 
     4: 
     5: 
     6: 
     7: 
     8: 
     9: 
    10: 
    11: 
    12: 
    13: 
    14: 
    15: 
    16: 
    17: 
    18: 
    19: 
    20: 
    21: 
    22: 
    23: 
    24: 
    25: 
    26: 
    27: 
    28: 
    29: 
    30: 
    31: 
    32: 
    33: 
    34: 
    35: 
    36: 
    37: 
    38: 
    39: 
    40: 
    41: 
    42: 
    43: 
    44: 
    45: 
    46: 
    47: 
    48: 
    49: 
    50: 
    51: 
    // WebCam capture in 50 lines of F#
    #r "System.Windows.Forms.dll"
    #r @"..\packages\AForge.2.2.5\lib\AForge.dll"
    #r @"..\packages\AForge.Video.DirectShow.2.2.5\lib\AForge.Video.DirectShow.dll"
    #r @"..\packages\AForge.Video.2.2.5\lib\AForge.Video.dll"
    open System
    open System.Drawing
    open System.Windows.Forms
    open AForge
    open AForge.Video
    open AForge.Video.DirectShow
    [<AutoOpen>]
    module Composition   =
        let tee    f x   = f x;x        
        let ifThen b f   = if b() then f else id
    module Camera        =
        let myCamIndex   = 0
        let videoInput   = FilterInfoCollection(FilterCategory.VideoInputDevice).Item myCamIndex
        let videoDevice  = VideoCaptureDevice(videoInput.MonikerString)
        let start _      = videoDevice.Start()
        let stop _       = videoDevice.SignalToStop()
        let onImage f    = videoDevice.NewFrame.AddHandler(NewFrameEventHandler f)
    module Time          =      
        let now()        = DateTime.Now.ToString()
        let nowStamp()   = now().Replace('.','_').Replace(':','_').Replace(' ','_')
    module Overlay       =
        let x,y          = 2.0f, 2.0f
        let font,color   = new Font("Arial", 24.0f), Brushes.Green
        let draw image   = use g=Graphics.FromImage image in g.DrawString(Time.now(), font,color, x,y)
    module Image         =
        type I           = Image
        let toClip (i:I) = tee Clipboard.SetImage i
        let toFile (i:I) = i.Save <| @"C:\temp\cam1" + Time.nowStamp() + ".jpeg"
        let mirror (i:I) = i.RotateFlip RotateFlipType.Rotate180FlipY  
    module Gui           =
        type N           = NewFrameEventArgs
        let form         = new Form(TopMost=true, Visible=true, Width=660, Height=600, Text="WebCamcapturein50linesofF#")
        let add x        = tee form.Controls.Add x
        let pic          = add <| new PictureBox(SizeMode=PictureBoxSizeMode.AutoSize)
        let shot         = add <| new Button  (Text="capture"  , Dock=DockStyle.Bottom)
        let mirrored     = add <| new CheckBox(Text="mirrored" , Dock=DockStyle.Bottom, Checked=true)
        let overlayed    = add <| new CheckBox(Text="overlayed", Dock=DockStyle.Bottom, Checked=false)
        let show image   = pic.Image <- image
        let mirror()     = ifThen (fun () -> mirrored.Checked)  (tee Image.mirror)
        let overlay()    = ifThen (fun () -> overlayed.Checked) (tee Overlay.draw)
        let save         = tee shot.Click.Add (fun _ -> pic.Image |> mirror() |> Image.toClip |> Image.toFile)
        let getImage(e:N)= e.Frame.Clone() |> unbox
        let showImage _ e= e |> getImage |> mirror() |> overlay() |> show
    module App           =
        let run()        = Camera.onImage Gui.showImage;  Gui.form.Closing.Add Camera.stop;  Camera.start()
    App.run()
  10. Es macht was es soll an einen verschneiten Dezember Nachmittag. Es ist kein Production Code (mögliche Leaks und module privacy..)
  11. Weitere Ideen und Anregungen Stereo Image mit mehreren USB WebCams http://www.aforgenet.com/framework/samples/video.html Augmented Reality http://www.aforgenet.com/articles/glyph_recognition/ https://www.youtube.com/watch?v=3sEFk4rRcU4&feature=player_embedded
  12. Andere Publikationen zu WebCam Nutzung mit F#:
    1: 
    2: 
    3: 
    4: 
    5: 
    "UsingaWebcamwithDirectShowNETandF#" 
    DirectShow, DirectShowNET
    Luis Diego Fallas 
    Wednesday, February 24, 2010
    http://langexplr.blogspot.ch/2010/02/using-webcam-with-directshownet-and-f.html
    http://langexplr.blogspot.ch/2010/02/using-webcam-with-directshownet-and-f.html
    1: 
    2: 
    3: 
    4: 
    5: 
    "SilverlightF#(Fsharp+XAML)application.AccesstoWebCamusingonlyF#."
    Windows Phone Silverlight
    Vasily Kalugin 
    3/28/2014
    https://code.msdn.microsoft.com/windowsapps/WebCam-Silverlight-F-sharp-f4f50e85 
    https://code.msdn.microsoft.com/windowsapps/WebCam-Silverlight-F-sharp-f4f50e85
Keywords: Kamera, WebCam, AForge, .Net, dotNet, WinForm, F#, FSharp, Pipelining, id, tee, ifThen, DSL, Wiederverwendbarkeit, webcam fsharp, read camera, cam capture, cam recording, foto image captureing, Foto Aufnahme, integrierte Kamera, ASUS ZenBook camera, USB WebCams, PnP Devices
val not : value:bool -> bool Full name: Microsoft.FSharp.Core.Operators.not
Multiple items val seq : sequence:seq<‘T> -> seq<‘T> Full name: Microsoft.FSharp.Core.Operators.seq ——————– type seq<‘T> = System.Collections.Generic.IEnumerable<‘T> Full name: Microsoft.FSharp.Collections.seq<_>
module Checked from Microsoft.FSharp.Core.Operators
val id : x:’T -> ‘T Full name: Microsoft.FSharp.Core.Operators.id
namespace System
namespace System.Drawing
namespace System.Windows
namespace System.Windows.Forms
Multiple items type AutoOpenAttribute = inherit Attribute new : unit -> AutoOpenAttribute new : path:string -> AutoOpenAttribute member Path : string Full name: Microsoft.FSharp.Core.AutoOpenAttribute ——————– new : unit -> AutoOpenAttribute new : path:string -> AutoOpenAttribute
val unbox : value:obj -> ‘T Full name: Microsoft.FSharp.Core.Operators.unbox
namespace Windows