AI Trainingsdaten generieren mit Blazor

Auf meiner Reise durch das AI Universum stellte sich die Frage, welches Problem lösen wir. Da finden sich Taxi Fare und House Prices als Regression Beispiele. Oder Hund und Auto Erkennung in Bildern in der Kategorie Vision. 

Also konstruieren wir uns ein Problem. Ein Whiteboard in das der Benutzer per Ink/Pen Figuren zeichnet, die AI erkennt was es ist und an die gleiche Stelle eine korrekt unverwackelte Skizze erstellt, Gibt es schon.

Lösung soll mit Blazor sein. Dazu brauch ich eine Trainings Web App. Sogar Server basiert, damit ich keinen Aufwand mit speichern habe. Recht unspektakulär nehme ich dazu SVG

   1:  <div id="Whiteboard" @ref="Whiteboard" 
   2:      @onmousemove="MoveMouse" @onmousedown="DownMouse" @onmouseup="UpMouse"
   3:       @ontouchmove="MoveTouch" @ontouchstart="DownTouch" @ontouchend="UpTouch" draggable="false"
   4:       style="border:2px solid black;display:inline-block;
max-height:80vh;max-width:100vw;overflow:hidden;touch-action: none;"
>
   5:      <svg style="width:800px; height:800px;">
   6:          @foreach (var (line, i) in Lines.WithIndex())
   7:          {
   8:              <LineComponent X1=@line.prevX Y1=@line.prevY 
X2=@line.currX Y2=@line.currY Stroke=@line.color StrokeWidth=@line.lineWidth @key="i" />
   9:          }
  10:      </svg>
  11:  </div>

 

Die Idee ist eine Liste von Linien zu einer Figur zu verbinden. Für einen Kreis nicht ganz so optimal, aber was solls.

   1:      protected List<Line> Lines = new List<Line>();
   2:      protected bool flag = false;
   3:      protected double prevX = 0;
   4:      protected double currX = 0;
   5:      protected double prevY = 0;
   6:      protected double currY = 0;
   7:      protected int count = 0;
   8:      protected int status = 0;
   9:      protected string color = "#000000";
  10:      protected float lineWidth = 2;
  11:      ElementReference Whiteboard;
  12:      BoundingClientRect DIVOffset;
  13:      int counter;
  14:      protected override async Task OnAfterRenderAsync(bool firstRender)
  15:      {
  16:          if (firstRender)
  17:          {
  18:              DIVOffset = await JSRuntime.InvokeAsync<BoundingClientRect>(
"eval", "document.getElementById('Whiteboard').getBoundingClientRect()");
  19:          }
  20:   
  21:      }

Der Trick beliebigen JavaScript Code per Eval auszuführen ist nicht fein aber Effektiv um die Position des Boards zu erhalten. Dann führe ich zwei Hilfsklassen ein

   1:  public class Line
   2:  {
   3:      public Line() { }
   4:      public Line(double prevX, double prevY, double currX, double currY, string color, float lineWidth)
   5:      {
   6:          this.prevX = prevX;
   7:          this.prevY = prevY;
   8:          this.currX = currX;
   9:          this.currY = currY;
  10:          this.color = color;
  11:          this.lineWidth = lineWidth;
  12:      }
  13:      public double prevX { get; set; }
  14:      public double currX { get; set; }
  15:      public double prevY { get; set; }
  16:      public double currY { get; set; }
  17:      public string color { get; set; }
  18:      public float lineWidth { get; set; }
  19:   
  20:  }
  21:   
  22:  public class BoundingClientRect
  23:  {
  24:      public double X { get; set; }
  25:      public double Y { get; set; }
  26:      public double Width { get; set; }
  27:      public double Height { get; set; }
  28:      public double Top { get; set; }
  29:      public double Right { get; set; }
  30:      public double Bottom { get; set; }
  31:      public double Left { get; set; }
  32:  }

Den folgenden C# Blazor Code erläutere ich nicht im Detail.

   1:   private async Task MoveMouse(MouseEventArgs e)
   2:   {
   3:       await Move(e.ClientX, e.ClientY);
   4:   }
   5:   private async Task MoveTouch(TouchEventArgs e)
   6:   {
   7:       await Move(e.Touches[0].ClientX, e.Touches[0].ClientY);
   8:   }
   9:   private async Task Move(double clientX, double clientY)
  10:   {
  11:       if (flag)
  12:       {
  13:           prevX = currX;
  14:           prevY = currY;
  15:           currX = clientX - DIVOffset.X;
  16:           currY = clientY - DIVOffset.Y;
  17:           var addedLine = new Line(prevX, prevY, currX, currY, color, lineWidth);
  18:           Lines.Add(addedLine);
  19:           status++;
  20:           count++;
  21:           this.StateHasChanged();
  22:       }
  23:   }
  24:   private async Task DownMouse(MouseEventArgs e)
  25:   {
  26:       await Down(e.ClientX, e.ClientY);
  27:   }
  28:   private async Task DownTouch(TouchEventArgs e)
  29:   {
  30:       await Down(e.Touches[0].ClientX, e.Touches[0].ClientY);
  31:   }

Schon etwas spannender ist der Code wenn der Stift den Bildschirm verlässt. Die Figur ist dann fertig gezeichnet. Allerdings als SVG. Wir brauchen eine Library

NuGet\Install-Package Svg.Skia 
 

Aus dem Line Array wird ein SVG String zusammengesetzt. Danach (Zeile 23) ein SK Bitmap generiert. Ab Zeile 25 wird das SVG in das Bitmap Objekt gezeichnet und am Ende als png (Zeile 23) auf der Festplatt des Webservers abgelegt.

   1:  private void UpTouch(TouchEventArgs e)
   2:  {
   3:   flag = false;
   4:   using (var svg = new SKSvg())
   5:     {
   6:         var sr = new StringBuilder();
   7:         sr.Append("<svg style=\"width: 800px; height: 800px; \">\n");
   8:         foreach (var line in Lines)
   9:          {
  10:          sr.Append($@"<line x1=""{line.prevX.ToString(
CultureInfo.InvariantCulture)}"
" y1 =""{line.prevY.ToString(
CultureInfo.InvariantCulture)}"
"
  11:             x2 =""{line.currX.ToString(CultureInfo.InvariantCulture)}""
y2 ="
"{line.currY.ToString(CultureInfo.InvariantCulture)}"" stroke=""black""
  12:  stroke-width =""{line.lineWidth.ToString(CultureInfo.InvariantCulture)}"" />");
  13:              }
  14:              sr.Append("</svg>");
  15:      }
  16:     var cs = SKColorSpace.CreateSrgb();
  17:     var farbe = SKColor.Parse("#ffffff");
  18:     svg.FromSvg(sr.ToString());
  19:     if (svg.Picture != null)
  20:         {
  21:         var width = (int)svg.Picture.CullRect.Width;
  22:         var height = (int)svg.Picture.CullRect.Height;
  23:         using (var bitmap = new SKBitmap(width, height))
  24:           {
  25:           using (var canvas = new SKCanvas(bitmap))
  26:             {
  27:             canvas.Clear(farbe);
  28:             canvas.DrawPicture(svg.Picture);
  29:             canvas.Flush();
  30:             }
  31:   
  32:         using (var stream = File.OpenWrite($@"c:\temp\viereck{counter}.png"))
  33:            {
  34:             bitmap.Encode(stream, SKEncodedImageFormat.Png, 100);
  35:            }
  36:   
  37:          }
  38:          Lines.Clear();
  39:          counter++;
  40:      }
  41:  }
  42:          
 

Für mein Training habe ich jeweils 50 mal Dreieck, Kreis und Viereck gezeichnet. Den Code geändert und das Blazor Programm neu gestartet.

Im letzten Schritt kann man die Grafiken für zwei verschiedene Model Builder Szenarien aufbereiten, Bild Klassifizierung und Bild Erkennung.

Für die Bilderkennung werden drei Unterordner angelegt, die den Labels entsprechen und die PNG jeweils in den passenden Ordner verschoben,

daten1

Deutlich mehr Aufwand wird benötigt um Objekte in Bildern zu erkennen. Dazu müssen die Labels definiert werden und die Objekte in Position und Größte markiert werden. Auch hier gibt es eine Reihe von Formaten. Microsoft setzt auf VoTT und bietet dafür einen Editor.

Ich habe hier nur jeweils ein Objekt pro Png um das ganze schneller zu erledigen. In der Praxis werden die Objekte auch mehrfach in Bildern vorhanden sein. Ein Hund, Auto oder Person.

vott1

Am Ende werden die Markierungen exportiert. In ein JSON, CSV, Tensorflow oder Azure Custom Vision Service Format.  Um nur einen Auszug zu nennen.

Mit diesen Daten können wir dann ein Training starten. Das aber in einem anderen Blog Beitrag.

 
 
 
Kommentare sind geschlossen