Creating A Credit Card OCR Application Part 2

Credit Card OCR II

With the second portion of the credit card ocr application, I wanted to focus on sprucing up the GUI and making the whole application more presentable. Most of my past developer experience has been related to back end and deep learning work, so I wanted to get better at front end and making applications more appealing to the eye. The contents of this repo can be found here.

Since the original application was coded in c#, the logical choice for my front end code is Xaml. Although it is very different to the other, more popular front end languages like JavaScript or CSS, coupling it with c# was my best option.

My early versions of the GUI were very minimal, with only the default textures in the xaml toolbox being used.

My first step in cleaning up my GUI was splitting up some of my functions and navigation into multiple pages. I separated two main functions into their own separate pages, the capture method relating to relaying a live camera feed to the GUI and capture an image when needed, and the output page where the detected numbers would go.

Below are the whole Xaml code for both the camera page and the output page.

<Page x:Class="Credit_Card_OCR.Pages.CameraPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:Credit_Card_OCR.Pages"
      mc:Ignorable="d" 
      d:DesignHeight="400" d:DesignWidth="700"
      Title="CameraPage">

    <Grid Background="DarkTurquoise">
        <Grid.RowDefinitions>
            <RowDefinition Height ="1*"/>
            <RowDefinition Height ="50"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>

        <Image x:Name="webcamOutput" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0" Stretch="Fill"/>
        <Button x:Name="btnCapture" Style="{StaticResource MetroButtonGreen}" Content="Start Camera Stream" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1" Click="btnCapture_Click"/>

    </Grid>
</Page>

<Page x:Class="Credit_Card_OCR.Pages.ImageListOutputPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:Credit_Card_OCR.Pages"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="ImageListOutputPage">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height ="1*"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>

        <Image x:Name="imgOutput" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1" Stretch="Fill"/>
        <ListBox x:Name="lstOutput" VerticalContentAlignment="Stretch" Grid.Column="1" Grid.Row="6" HorizontalContentAlignment="Stretch"/>
        
    </Grid>
</Page>

Most of the code above is just simple boilerplate that Microsoft provides when creating a page, the items on the page that I created are marked with the tags < and />. The only component of the Xaml code above that is not implemented in Microsoft’s default Xaml code with the button style “metrogreen”, which is defined in the App.xaml.

<Application x:Class="Credit_Card_OCR.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Credit_Card_OCR"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <Style x:Key="MetroButtonAllert" TargetType="{x:Type Button}" >
            <Setter Property="Background" Value="OrangeRed" />
            <Setter Property="Foreground" Value="White" />
            <Setter Property="HorizontalContentAlignment" Value="Center" />
            <Setter Property="VerticalContentAlignment" Value="Center" />
            <Setter Property="Padding" Value="10 5" />
            <Setter Property="FontFamily" Value="Tahoma" />
            <Setter Property="FontSize" Value="14" />
            <Setter Property="BorderThickness" Value="2" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Grid>
                            <Border
                                x:Name="Border"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"/>
                            <ContentPresenter
                                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                Margin="{TemplateBinding Padding}"
                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                RecognizesAccessKey="True"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsPressed" Value="True">
                                <Setter Property="OpacityMask" Value="#AA888888"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="#cf2a0e"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Foreground" Value="#ADADAD" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="MetroButtonGreen" BasedOn="{StaticResource MetroButtonAllert}" TargetType="{x:Type Button}">
            <Setter Property="Background" Value="OliveDrab"/>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="#368a55"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Application.Resources>
</Application>

The point of the code above is to create a style, a distinct set of parameters and events that can be assigned to different Xaml features to make it look better, similar to JavaScript and CSS. Here I have to styles, one for a green button and one for a red button, both of witch are used more in the next step in our Xaml code.

To control the navigation between these pages, we use multiple buttons on a stack panel, which allows a static control panel to exist while the rest of the GUI changes whenever needed.

<Window x:Class="Credit_Card_OCR.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Credit_Card_OCR"
        mc:Ignorable="d"
        Title="Credit Card Detection" Height="450" Width="800" Background="DarkTurquoise">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height ="1*"/>
            <RowDefinition Height ="50"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Grid.ColumnSpan="2">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height ="50"/>
                    <RowDefinition Height ="50"/>
                    <RowDefinition Height ="50"/>
                    <RowDefinition Height ="50"/>
                    <RowDefinition Height ="50"/>
                    <RowDefinition Height ="110"/>
                    <RowDefinition Height ="50"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition Width="1*"/>
                </Grid.ColumnDefinitions>

                <Button x:Name="btnOCR" Grid.Column="0" Style="{StaticResource MetroButtonGreen}" Grid.Row="1" Click="btnOCR_Click">
                    <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Visibility="Visible" Grid.Column="0">
                        <Image Source="Resources\ocrIcon.png" HorizontalAlignment="Center" Stretch="Uniform" Height="30" Width="50"/>
                        <TextBlock HorizontalAlignment="Center" FontSize="10"><Run Text="Capture Image"/></TextBlock>
                    </StackPanel>
                </Button>

                <Button x:Name="btnStart" Grid.Column="0" Style="{StaticResource MetroButtonGreen}" HorizontalAlignment="Stretch" Grid.Row="2" VerticalAlignment="Stretch" Click="btnStart_Click">
                    <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Visibility="Visible" Grid.Column="0">
                        <Image Source="Resources\startIcon.png" HorizontalAlignment="Center" Stretch="Uniform" Height="30" Width="50"/>
                        <TextBlock HorizontalAlignment="Center" FontSize="10"><Run Text="Start Camera"/></TextBlock>
                    </StackPanel>
                </Button>
                
                <Button x:Name="btnOpen" Grid.Column="0" Style="{StaticResource MetroButtonGreen}" HorizontalAlignment="Stretch" Grid.Row="3" VerticalAlignment="Stretch" Click="btnOpen_Click">
                    <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Visibility="Visible" Grid.Column="0">
                        <Image Source="Resources\folderIcon.png" HorizontalAlignment="Center" Stretch="Uniform" Height="30" Width="50"/>
                        <TextBlock HorizontalAlignment="Center" FontSize="10"><Run Text="Open Image"/></TextBlock>
                    </StackPanel>
                </Button>
                
                <Button x:Name="btnCapture" Grid.Column="0" Style="{StaticResource MetroButtonGreen}" HorizontalAlignment="Stretch" Grid.Row="4" VerticalAlignment="Stretch" Click="btnCapture_Click" IsEnabled="False">
                    <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Visibility="Visible" Grid.Column="0">
                        <Image Source="Resources\cameraIcon.png" HorizontalAlignment="Center" Stretch="Uniform" Height="30" Width="50"/>
                        <TextBlock HorizontalAlignment="Center" FontSize="10"><Run Text="Capture"/></TextBlock>
                    </StackPanel>
                </Button>
                
                <Button x:Name="btnClear" Grid.Column="0" Style="{StaticResource MetroButtonAllert}" HorizontalAlignment="Stretch" Grid.Row="6" VerticalAlignment="Stretch" Click="btnClear_Click">
                    <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Visibility="Visible" Grid.Column="0">
                        <Image Source="Resources\clearIcon.png" HorizontalAlignment="Center" Stretch="Uniform" Height="30" Width="50"/>
                        <TextBlock HorizontalAlignment="Center" FontSize="10"><Run Text="Clear"/></TextBlock>
                    </StackPanel>
                </Button>
            </Grid>
        </StackPanel>

        <Frame x:Name="frame" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" NavigationUIVisibility="Hidden"/>

    </Grid>
</Window>

For my stack panel, I have five buttons, one for running ocr with the color green, one for starting the camera with the color green, one for opening an image with the color green, one for capturing an image with the color green, and one for clearing all necessary objects with the color red.

The images on the buttons are from Icon8, which I choose them as they were simple and accomplished what the button was trying to do.

The OCR button is only used when an image is present, which launches the ocr detection method, of which we created back in the part one of this post. The start camera button initializes the camera page and creates a camera stream, which is created in the cs of the page.

using Emgu.CV;
using Emgu.CV.Structure;
using System;
using System.Drawing;
using System.Windows;
using System.Windows.Controls;

namespace Credit_Card_OCR.Pages
{
    /// <summary>
    /// Interaction logic for CameraPage.xaml
    /// </summary>
    public partial class CameraPage : Page
    {
        //The video capture stream that holds the video from the camera
        public VideoCapture stream = new VideoCapture(0);

        public CameraPage()
        {
            InitializeComponent();
        }

        public Bitmap GenerateTakenImage()
        {
            //Get the taken picture from the video stream
            Image<Bgr, byte> inputImg = CameraConfig.TakePicture(stream);

            //Convert the image to a bitmap
            Bitmap bit = inputImg.ToBitmap();

            return bit;
        }

        private void Capture_ImageGrabbed1(object sender, EventArgs e)
        {
            //Get the frame
            Bitmap frame = CameraConfig.GetFrame(stream);

            //Check if the frame taken is null
            //if it is, that means that no camera is connected,
            //so output that to the user and end the subscription
            if (frame == null)
            {
                MessageBox.Show("There is no camera connected");
                stream.ImageGrabbed -= Capture_ImageGrabbed1;
            }
            else
            {
                try
                {
                    //Pass each frame from the video capture to the output image
                    this.Dispatcher.Invoke(() =>
                    {
                        webcamOutput.Source = ImageUtils.ImageSourceFromBitmap(frame);
                    });
                }
                catch (System.Threading.Tasks.TaskCanceledException)
                {
                    //End the thread if its exited early
                    this.Dispatcher.InvokeShutdown();
                }
            }
        }

        private void btnCapture_Click(object sender, RoutedEventArgs e)
        {
            //Start grabbing frames from the webcam input
            stream.ImageGrabbed += Capture_ImageGrabbed1;
            stream.Start();
        }
    }
}

When the capture button is clicked on the capture page and start a subscription to constantly stream frames to the camera page. When the capture button on the MAINWINDOW is clicked, it grabs the latest frame being streamed and outputs it as a single still image ready for the OCR method. I put the streaming of the camera inside its own method, as if it was coupled inside the main thread, lag and loss of frames causes issues. The entire code for the main window can be found below.

using Credit_Card_OCR.Pages;
using Emgu.CV;
using Emgu.CV.CvEnum;
using System.Collections.Generic;
using System.Drawing;
using System.Windows;
using System.Windows.Forms;

namespace Credit_Card_OCR
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        ImageListOutputPage outputPage = new ImageListOutputPage();

        public MainWindow()
        {
            InitializeComponent();

            frame.Content = outputPage;
        }

        //Mat object to hold image that is taken
        public static Mat img = new Mat();

        //Bool variables to help with the flow of the algorithm
        bool isStream = false;
        bool isImageLoaded = false;

        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            //Set bool variable to use camera image
            isStream = true;

            //Create a new object for the page
            CameraPage camera = new CameraPage();

            btnCapture.IsEnabled = true;
            btnOCR.IsEnabled = false;

            //Navigate to the new page
            frame.NavigationService.Navigate(camera);
        }

        private void btnOCR_Click(object sender, RoutedEventArgs e)
        {
            if (isImageLoaded == true)
            {
                //If a video stream is currently happening, do the following, else do the last
                if (isStream == true)
                {
                    //Align the video capture image
                    img = AutoAlignment.Align(img);

                    Bitmap bit = img.ToBitmap();

                    //Pass the bitmap image to be displayed
                    outputPage.imgOutput.Source = ImageUtils.ImageSourceFromBitmap(bit);
                }
                else
                {
                    //Read in the desired image
                    img = OCR.ReadInImage();

                    //Convert the image to a bitmap, then to an image source
                    Bitmap bit = img.ToBitmap();
                    outputPage.imgOutput.Source = ImageUtils.ImageSourceFromBitmap(bit);
                }

                //Detect the text from the image
                string detectedText = OCR.RecognizeText(img);

                //Create a new list for the output
                List<string> output = new List<string>();

                //Add the detected string to the list
                output.Add(detectedText);

                //Display the list
                outputPage.lstOutput.ItemsSource = output;
            }
            else
            {
                System.Windows.MessageBox.Show("Please take or import an image");
            }
        }

        private void btnOpen_Click(object sender, RoutedEventArgs e)
        {
            //Create a string to hold the file location
            string fileLocation = string.Empty;

            //Create an openfiledialog object to select a photo
            OpenFileDialog dialog = new OpenFileDialog
            {
                InitialDirectory = @"C:\",
                Title = "Browse Images",

                CheckFileExists = true,
                CheckPathExists = true,

                FilterIndex = 2,
                RestoreDirectory = true,

                ReadOnlyChecked = true,
                ShowReadOnly = true
            };

            //If a photo is selected, do the following
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                //Get the file location of the photo selected
                fileLocation = dialog.FileName;

                //If the length is greater than one, then use it to read in the image
                if (fileLocation.Length > 1)
                {
                    //Read in the image
                    img = CvInvoke.Imread(fileLocation, ImreadModes.AnyColor);
                }
            }

            //If the file location is greater than one, pass the loaded in image to be displayed
            if (fileLocation.Length > 1)
            {
                //Convert the image to a bitmap, then to an image source
                Bitmap bit = img.ToBitmap();
                outputPage.imgOutput.Source = ImageUtils.ImageSourceFromBitmap(bit);

                //Declare the bool variable that an image has been loaded
                isImageLoaded = true;
            }
        }

        private void btnCapture_Click(object sender, RoutedEventArgs e)
        {
            CameraPage cameraPage = new CameraPage();

            Bitmap taken = cameraPage.GenerateTakenImage();

            if (taken != null)
            {
                outputPage.imgOutput.Source = ImageUtils.ImageSourceFromBitmap(taken);
            }

            //Declare that image is used
            isImageLoaded = true;

            //Enable the detect button
            btnOCR.IsEnabled = true;

            //Clear the frame
            frame.Content = outputPage;
        }

        private void btnClear_Click(object sender, RoutedEventArgs e)
        {
            //Clear the image
            outputPage.imgOutput.Source = null;

            //Clear the frame
            frame.Content = outputPage;

            //Empty the list box
            if (outputPage.lstOutput.Items.Count > 0)
            {
                outputPage.lstOutput.ItemsSource = null;
            }

            //Set both bool variables to false
            isImageLoaded = false;
            isStream = false;

            //Disable capture button
            btnCapture.IsEnabled = false;
            btnOCR.IsEnabled = true;
        }
    }
}

Although this is pretty boiler plate code, and I must admit the front end code does look a little sloppy, but Its a step in a better direction then what the GUI was earlier. I am still working to add more features to the GUI, and I hope to change the color scheme and other parts of the GUI.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: