Home Course Index << Prev Next >> PDF Version of this Page

Course CVCis: Computer Vision with C#
Chapter C5: The Complete Code of the Fourier Recognition Project


Copyright © by V. Miszalok, last update: 2011-09-13


Main Menu after start of VC# 2010 Express: File -> New Project... -> Visual Studio installed templates: Windows Forms Application
Name: fourier_recogn1 -> Location: C:\temp -> Create directory for solution
: switch off -> OK
Delete two superfluous files: Form1.Designer.cs and Program.cs.
Replace the preprogrammed code of Form1.cs by:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
using System.Text;
using System.IO;

public class Form1 : Form
{ [STAThread] static void Main() {  Application.Run( new Form1() ); }
  const Int32 xSize = 16;
  const Int32 ySize = 16;
  const Int32 no_of_fourier_values = 8;
  Byte[,] i0       = new Byte[ySize  ,xSize  ];
  Brush[] brush    = new Brush[10];
  Brush blackbrush = SystemBrushes.ControlText;
  Brush redbrush   = new SolidBrush( Color.Red );
  Brush bluebrush  = new SolidBrush( Color.Blue );
  Pen redpen1       = new Pen( Color.Red, 1 );
  Pen redpen5       = new Pen( Color.Red, 5 );
  Pen whitepen;
  Font arial10     = new Font( "Arial", 10 );
  Font arial20     = new Font( "Arial", 20 );

  Int32 i, j, x, y, dx, dy;
  Byte threshold = 1;
  Button[] button = new Button[ySize];
  TextBox textbox;
  Point p0, p1;
  struct crackcode { public Point start; public String cracks;  }
  struct prototype { public char name; public float sum_of_deviations; }
  crackcode cc;
  char recogn_result;
  Graphics g;
  ArrayList pointArray = new ArrayList();
  float[] fr, fi;

  public Form1()
  { BackColor  = Color.White;
    Text       = "Simple Numeral Recognition";
    SetStyle( ControlStyles.ResizeRedraw, true );
    Width  = 800;
    Height = 600;
    for ( i=0; i < 10; i++ )
      brush[i] = new SolidBrush(Color.FromArgb( i*25, i*25, i*25 ) );
    for ( y=0; y < ySize; y++ )
    { button[y] = new Button();
      Controls.Add( button[y] );
      button[y].BackColor = Color.Gray;
      button[y].Text = "nothing";
      button[y].Name = y.ToString();
      button[y].Click += new EventHandler( do_it );
    }
    button[0].Name = button[0].Text = "Recognize";
    button[1].Name = button[1].Text = "Teach As";
    button[2].Name = button[2].Text = "Clear";
    button[3].Name =                  "Threshold";
    button[4].Name = button[4].Text = "Noise";
    button[3].Text = String.Format( "Threshold={0:#}", threshold );
    textbox = new TextBox(); Controls.Add( textbox );
    cc.cracks = "";
  }

  protected override void OnPaint( PaintEventArgs e )
  { g = e.Graphics;
    String s;
    Rectangle r = ClientRectangle;
    dx = r.Width / ( xSize+2 );
    dy = ( r.Height - 2 * FontHeight ) / ySize;
    for ( y=0; y < ySize; y++ )
    { button[y].Top = y*dy+1;
      button[y].Left = xSize*dx+1;
      if ( y != 1) button[y].Width = r.Width - button[y].Left - 2;
      else         button[y].Width = r.Width - button[y].Left - 25;
      button[y].Height = dy-2;
    }
    textbox.Top   = button[1].Top + ( button[1].Height - textbox.Height ) / 2;
    textbox.Left  = button[1].Left + button[1].Width + 1;
    textbox.Width = button[0].Width - button[1].Width - 2;
    textbox.TextAlign = HorizontalAlignment.Center;
    for ( y=0; y < ySize; y++ )
      for ( x=0; x < xSize; x++ )
        g.FillRectangle(brush[i0[y,x]], x*dx, y*dy, dx, dy );
    for ( x=0; x < xSize; x++ ) g.DrawLine( redpen1, x*dx, 0, x*dx, ySize*dy );
    for ( y=0; y < ySize; y++ ) g.DrawLine( redpen1, 0, y*dy, xSize*dx, y*dy );
    if ( cc.cracks.Length == 0 )
    { s = "Draw a digit or a letter -> Recognize -> Teach As -> Clear -> Next";
      g.DrawString( s, arial10, redbrush, (xSize*dx)/4, 0 );
      return;
    }
    x = cc.start.X;
    y = cc.start.Y;
    for ( i = 0; i < cc.cracks.Length; i++ )
      switch ( cc.cracks[i] )
      { case 'e': g.DrawLine( redpen5, x*dx, y*dy, (x+1)*dx, y*dy ); x++; break;
        case 's': g.DrawLine( redpen5, x*dx, y*dy, x*dx, (y+1)*dy ); y++; break;
        case 'w': g.DrawLine( redpen5, x*dx, y*dy, (x-1)*dx, y*dy ); x--; break;
        case 'n': g.DrawLine( redpen5, x*dx, y*dy, x*dx, (y-1)*dy ); y--; break;
      }
    g.FillRectangle( bluebrush, x*dx-5, y*dy-5, 11, 11 );
    s = "This is a '" + recogn_result + "' .";
    g.DrawString( s, arial20, redbrush, 0, button[ySize-1].Top + button[ySize-1].Height + 2 );
  }

  protected override void OnMouseDown( MouseEventArgs e )
  { p0.X = e.X;
    p0.Y = e.Y;
    pointArray.Add(p0);
    whitepen = new Pen( Color.White, dx < dy ? dx : dy );
        whitepen.StartCap = whitepen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
  }

  protected override void OnMouseMove( MouseEventArgs e )
  { if ( e.Button == MouseButtons.None ) return;
    p1.X = e.X;
    p1.Y = e.Y;
    //Insert an additional point if the distance is too far
    if ( Math.Abs( p1.X - p0.X ) >= dx || Math.Abs( p1.Y - p0.Y ) >= dy )
      pointArray.Add( new Point((p1.X + p0.X)/2, (p1.Y + p0.Y)/2) );
    pointArray.Add( p1 );
    g = this.CreateGraphics();
    g.DrawLine( whitepen, p0, p1 );
    p0 = p1;
  }

  protected void do_it( object sender, System.EventArgs e )
  { switch( ((Button)sender).Name )
    { case "Threshold"://*************************************
        if ( ++threshold > 9 ) threshold = 1;
        button[3].Text = "Threshold=" + threshold.ToString();
        cc.cracks =""; recogn_result = '?'; break;
      case "Noise"://****************************************
        Random random = new Random();
        for ( y=0; y < ySize; y++ )
          for ( x=0; x < xSize; x++ )
          { Int32 noise =  random.Next() % 3 - 1;//gives -1 or 0 or +1
            noise += i0[y,x];//add former gray value
            if (noise < 0)      i0[y,x] = 0;
            else if (noise > 9) i0[y,x] = 9;
            else                i0[y,x] = (Byte)noise;
          }
        cc.cracks =""; recogn_result = '?'; break;
      case "Clear"://*******************************************
        for ( y=0; y < ySize; y++ )
          for ( x=0; x < xSize; x++ ) i0[y,x] = 0;
        threshold = 1; button[3].Text = "Threshold=1";
        cc.cracks =""; pointArray.Clear(); recogn_result = '?'; break;
      case "Recognize"://***********************************
        //Digitize*****************************************
        for ( i = 0; i < pointArray.Count; i++ )
        {  Point vertex = (Point)pointArray[i];
           x = vertex.X / dx;
           y = vertex.Y / dy;
           try { if ( i0[y,x] < 9 ) i0[y,x]++; } catch{};
        }
        //Crack Code 8*****************************************
        //Search for a vertical start crack
        Byte leftpix;
        for ( y=0; y < ySize; y++ )
          for ( x=0; x < xSize; x++ )
          { if ( x > 0 ) leftpix = i0[y,x-1]; else leftpix = 0;
            if ( leftpix < threshold && i0[y,x] >= threshold )
            { cc.start.X = x; cc.start.Y = y; goto start_crack_found; }
          }
        return; //nothing to start with
        start_crack_found: y++;
        ArrayList phi1 = new ArrayList(); phi1.Add( -90 );
        System.Text.StringBuilder cracks = new System.Text.StringBuilder();
        cracks.Append( 's' );
        Char last_crack = 's';
        do
        { switch ( last_crack )
          { case 'e': if ( x == xSize )                            { phi1.Add(-90); goto n; }
                      if ( y <  ySize && i0[y  ,x  ] >= threshold) { phi1.Add(+90); goto s; }
                      if (               i0[y-1,x  ] >= threshold) { phi1.Add(  0); goto e; }
                                                                   { phi1.Add(-90); goto n; }

            case 's': if ( y == ySize )                            { phi1.Add(-90); goto e; }
                      if ( x >  0     && i0[y  ,x-1] >= threshold) { phi1.Add(+90); goto w; }
                      if (               i0[y  ,x  ] >= threshold) { phi1.Add(  0); goto s; }
                                                                   { phi1.Add(-90); goto e; }

            case 'w': if ( x == 0 )                                { phi1.Add(-90); goto s; }
                      if ( y >  0     && i0[y-1,x-1] >= threshold) { phi1.Add(+90); goto n; }
                      if (               i0[y  ,x-1] >= threshold) { phi1.Add(  0); goto w; }
                                                                   { phi1.Add(-90); goto s; }

            case 'n': if ( y == 0 )                                { phi1.Add(-90); goto w; }
                      if ( x <  xSize && i0[y-1,x  ] >= threshold) { phi1.Add(+90); goto e; }
                      if (               i0[y-1,x-1] >= threshold) { phi1.Add(  0); goto n; }
                                                                   { phi1.Add(-90); goto w; }
          }
          e: last_crack = 'e'; cracks.Append( 'e' ); x++; continue;
          s: last_crack = 's'; cracks.Append( 's' ); y++; continue;
          w: last_crack = 'w'; cracks.Append( 'w' ); x--; continue;
          n: last_crack = 'n'; cracks.Append( 'n' ); y--; continue;
        } while ( x != cc.start.X || y != cc.start.Y ); //end of do
        cc.cracks = cracks.ToString();
        Int32 NN, N = cc.cracks.Length;
        //Bad property of function phi1: All phi1[i] sum up to -360 degrees
        float[] phi2 = new float[N];
        float circular_angle_per_crack = 360.0f / N;
        for ( i=0; i < N; i++ )//Construction of a phi2 with a sum of 0
        { phi2[i] = (Int32)phi1[i] + circular_angle_per_crack;
          phi2[i] /= 10.0f;//not important, just to keep the fourier-values low
        }
        phi1.Clear();
        //Fourier Transform************************************
        fr   = new float[N/2+1];
        fi   = new float[N/2+1];
        FourierTransform( phi2, fr, fi );
        //Compare the Fourier values with the prototypes stored by former teachings
        StreamReader instream;
        try   { instream = new StreamReader( "FourierRecognitionPrototypes.txt" ); }
        catch { recogn_result = '?'; break; } //there are no prototypes yet
        ArrayList comparisons = new ArrayList();
        prototype proto       = new prototype();
        if ( no_of_fourier_values < fr.Length ) NN = no_of_fourier_values; else NN = fr.Length;
        string a_line;
        while ( ( a_line = instream.ReadLine()) != null )//new line
        { String[] s = a_line.Split( ' ', '/' );//split line into an array of substrings
          if ( s.Length < NN*2 + 1 ) continue;//not enough values in this line
          proto.name = (s[0])[0];//first character of the first substring
          proto.sum_of_deviations = 0;
          for ( i=0, j=1; i < NN; i++, j+=2 )
          { float fr2 = Convert.ToSingle( s[j] );
            float fi2 = Convert.ToSingle( s[j+1] );
            float dr = fr[i] - fr2;
            float di = fi[i] - fi2;
            proto.sum_of_deviations += dr*dr + di*di;
          }
          comparisons.Add(proto);
        }
        instream.Close();
        float minimum = float.MaxValue;
        for ( i=0; i < comparisons.Count; i++ )
        { proto = (prototype)comparisons[i];
          if (              proto.sum_of_deviations < minimum )
          { minimum       = proto.sum_of_deviations;
            recogn_result = proto.name;
          }
        }
        comparisons.Clear();
        break;
      case "Teach As"://***********************************
        Char c; String sfr, sfi;
        try { c = textbox.Text[0]; } catch { break; }
        if ( Char.IsWhiteSpace( c ) || Char.IsControl( c ) ) break;
        MessageBox.Show( "The current shape will be stored as prototype of a " + textbox.Text,
                         "Teaching a Shape", MessageBoxButtons.OKCancel );
        recogn_result = textbox.Text[0]; // OnPaint(..) will write it in red: "This is a .."
        StringBuilder newline = new StringBuilder();
        newline.Append( textbox.Text + ": " );
        if ( no_of_fourier_values < fr.Length ) NN = no_of_fourier_values; else NN = fr.Length;
        for ( i=0; i < NN; i++ )
        { sfr = String.Format( "{0:F2}", fr[i] ); if ( sfr.Length < 5 ) sfr = "+" + sfr;
          sfi = String.Format( "{0:F2}", fi[i] ); if ( sfi.Length < 5 ) sfi = "+" + sfi;
          newline.Append( sfr + "/" + sfi + " " );
        }
        StreamWriter outstream = new StreamWriter( "FourierRecognitionPrototypes.txt", true );
        outstream.WriteLine( newline );
        outstream.Close();
        textbox.Text = "";
        break;
    } // end of switch, end of all cases
    Invalidate();
  } // end of protected void do_it( object sender, System.EventArgs e )

  private void FourierTransform( float[] a, float[] fr, float[] fi )
  { int Na = a.Length, Nf = fr.Length;
    float sum_r, sum_i; fr[0] = fi[0] = 0;
    for ( i=0; i < Na; i++ ) fr[0] += a[i]; fr[0] /= Na;//fr[0] is the average (always 0 here).
    double dpi_div_N, x1_dpi_div_N, x2_x1_dpi_div_N;
    dpi_div_N = 2 * Math.PI / Na;
    for ( int x1 = 1; x1 < Nf; x1++ )
    { sum_r = sum_i = 0;
      x1_dpi_div_N = (double)x1 * dpi_div_N;
      for ( int x2 = 0; x2 < Na; x2++ )
      { x2_x1_dpi_div_N = (double)x2 * x1_dpi_div_N;
        sum_r += a[x2] * (float)Math.Cos( -x2_x1_dpi_div_N );
        sum_i += a[x2] * (float)Math.Sin( -x2_x1_dpi_div_N );
      }
      fr[x1] = 2 * sum_r / (float)Na;
      fi[x1] = 2 * sum_i / (float)Na;
    }
    fr[Nf-1] /= 2; fi[Nf-1] /= 2;
  } // end of FourierTransform
}