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:
- Zettel vor die Kamera halten
- Knopf drücken für das Foto
- 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..
- Da ist leider nichts im Clipboard.
- 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...
- Wenn ich eine Textnotiz vor die Kamera halte, erscheint diese Spiegelverkehrt.
All die Hmmms..
verraten, ich finde es nicht praktisch! (Useability?)
Wieviele Zeilen F# Code braucht es dazu?
Kern Problem: Kamera Bild auslesen
Varianten:
- mit Windows Mitteln, z.B. WinRT (vorweg genommen, es macht kein Spass)
- Open Source Library 😉
1. WinRT für Desktop! (nicht Windows Phone)
Die benötigte Windows.winmd
findet man im Windows SDK:
- Installieren von Windows Software Development Kit (SDK) für Windows 8.1
- 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"
- 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>
- 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
- Via Nuget folgende ins Projekt holen:
1: 2: 3:
AForge, AForge.Video, AForge.Video.DirectShow
#r
undopen
(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";}]
- Das Bild kommt per NewFrame Event vom VideoCaptureDevice, also verpacken wir das in einer Funktion:
1:
let onImage f = videoDevice.NewFrame.AddHandler(NewFrameEventHandler f)
- 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.
- Beim Speichern den
mirror()
nochmals anwenden, falls dieser eingeschaltet war, damit es nicht spiegelverkehrt ist.
1:
let save = getImage |> mirror() |> toClip |> toFile
- Um das Pipelining
|>
mit den GUI Einstellungen dermirrored
undoverlayed
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 Funktionid
abschalten kann. Auftee
wird später eingegangen.1:
let ifThen b f = if b() then f else id
- 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 dertee
Funktion definiert.1:
let tee f x = f x;x
Damit lässt sich der Input wieder zurückgeben.
- 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 auftee
basiert, werden die Controls an die Formform
gebunden. Was sich dann mit dem Rückwärts-Pipe<|
ganz elegant liestlet 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)
- 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 mitAlt+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()
- Es macht was es soll an einen verschneiten Dezember Nachmittag. Es ist kein Production Code (mögliche Leaks und module privacy..)
- 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
- 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
Full name: Microsoft.FSharp.Core.Operators.not
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<_>
from Microsoft.FSharp.Core.Operators
Full name: Microsoft.FSharp.Core.Operators.id
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
Full name: Microsoft.FSharp.Core.Operators.unbox