Title:       AlternativeToBarcodes
Author:      Michael Washington 
Email:       webmaster@adefwebserver.com
Member ID:   12345
Language:    C# 2.0 etc
Platform:    Windows, .NET 3.5 
Technology:  ASP.NET, GDI+
Level:       Beginner, Intermediate, Advanced
Description: A method to create and read a number represented as a binary string of boxes printed on a page 
Section      
SubSection   
License:     BSD

An alternative to barcodes

The dnnScanFree project (an Open Source project to enable the creation of free and inexpensive testing programs) was faced with the challenge of creating a method to indicate the student taking the test. Pre-printing the test forms with the student number will allow the grading program to grade the test and automatically update the students record. This will provide a significant time savings.

Naturally bar codes were considered and example code is available on Codeproject.com. However, if the scan is not straight the bar codes will not be read correctly. There is a Codeproject.com article on de-skewing an image, however it is resource intensive.

The dnnScanFree project decided to use a simple method, a number would be converted to a string of boxes printed on the page. The boxes would then be read using the same technology that is being used to grade the tests.

The Program

Start the program and select BinaryCode from the menu then Create.

A popup box will allow you to enter a seven digit number.

  

The number will appear on the screen along with it's binary representation and a block in the upper right-hand corner that will be used to detect the starting point when the scan of the image is read.

Selecting File then Print will allow you to print out the image and scan it

After scanning the image, it can be read by opening the image using File then Open. Red boxes will be drawn around each recognition zone (based on the detected starting point). A popup box will indicate the number that is recognized.

Dealing With Skew

It is rare for an image to scan or print completely straight. The following shows how the same image is detected when the image is skewed to the right.

If the image is skewed to the left the code that detects the starting point realizes there is an error because it counts the black pixels in a 20 pixel square. If the count does not match it assumes a left skew and subtracts pixels on the X axis.

// Count the pixels in the starting point to see if the scan will be readable
int intPixels = CountBlackPixelsInRectangle(m_Bitmap, StartingPoint.X, StartingPoint.Y, 20, 20);
if (intPixels < 120)
{
 DrawARedRectangle(m_Bitmap, StartingPoint);
 StartingPoint.X = StartingPoint.X - 25;
 MessageBox.Show(String.Format("Scan is skewed. Only {0} pixels found at the starting point. StartingPoint will be reset to {1}", 
  intPixels.ToString(), StartingPoint.X.ToString()));
}

The image is still readable with the skew.

Fixing The Skew

While the boxes can be read with a skew on the left hand side of the page, the skew gets worse on the right-hand side of the page. It was necessary to implement a method to de-skew the image. The Codeproject.com article on de-skewing an image did not work because it's algorithm was confused by the image. It also does not work with all image types and is significantly slower.

It did however provide a very useful RotateImage method. This was very helpful because a Bitmap normally can only be rotated in 90 degree increments using an enumeration. This project required a more granular rotation.

The process to de-skew the image is simple. FirstStartingPoint and SecondStartingPoint are detected and those values along with the Bitmap image are passed to the StraightenImage method which straightens the image.

Point FirstStartingPoint = FindStartingPoint1(m_Bitmap, BoxSize);
if (FirstStartingPoint.IsEmpty)
{ MessageBox.Show("Scan is unreadable");
return; }
Point SecondStartingPoint = FindStartingPoint2(m_Bitmap, FirstStartingPoint, 2158, BoxSize);
if (SecondStartingPoint.IsEmpty)
{ MessageBox.Show("Scan is unreadable");
return; }
// Straighten Image
m_Bitmap = StraightenImage(m_Bitmap, FirstStartingPoint, SecondStartingPoint, BoxSize);

The code for the StraightenImage method is:

        private Bitmap StraightenImage(Bitmap b, Point FirstStartingPoint, Point SecondStartingPoint, int BoxSize)
        {
            int intCompletelyBlackSquare = (BoxSize * 85);
            int intTmpPixels = CountBlackPixelsInRectangle(b, SecondStartingPoint, BoxSize, BoxSize);

            // Possibly rotate the image
            if (intTmpPixels < (intCompletelyBlackSquare - (BoxSize * 2)))
            {
                // The difference between a completely black square and the number of pixels found
                int intPixelsOff = intCompletelyBlackSquare - intTmpPixels;

                // Make a reading from a higher point to determine if the scan is skewed to the left or the right
                Point tmpStartingPoint = new Point(SecondStartingPoint.X, SecondStartingPoint.Y + 10);
                // Count the pixels in the new point
                int intTmpPixels2 = CountBlackPixelsInRectangle(b, tmpStartingPoint, BoxSize, BoxSize);

                // Determine if the scan is skewed to the left or the right
                float fOfsetPercentage;
                int intPixelSkew = intTmpPixels - intTmpPixels2;
                if (intPixelSkew > 0)
                {
                    // intPixelSkew is a negative number so the image is skewed to the left
                    fOfsetPercentage = ((float)intPixelsOff / (float)intCompletelyBlackSquare);
                }
                else
                {
                    // intPixelSkew is a negative number so the image is skewed to the right
                    fOfsetPercentage = ((float)intPixelsOff / (float)intCompletelyBlackSquare) * (float)-1;
                }

                // Rotate the scan based on the number of pixels and the direction ofthe skew
                b = RotateImage(b, fOfsetPercentage);

            }
            return b;
        }

Reading the numbers

Creating the boxes is a straight-forward process. A number is entered into the popup box and converted to a string of  0's and 1's.

private void createToolStripMenuItem_Click(object sender, EventArgs e)
{
 //Create Blank Image
 m_Bitmap = new Bitmap(800, 600);

 //Create starting marker
 StartingPoint = new Point(10, 30);
 DrawARectangle(m_Bitmap, StartingPoint, true);
 string strNumber = "5234567";

 strNumber = Microsoft.VisualBasic.Interaction.InputBox("Enter a seven digit number", "Enter Number", strNumber, 100, 100);
 string strBinaryNumber = ConvertToBinaryNumber(strNumber);
 int intWidth = 50;

 for (int i = 0; i <= 27; i++)
 {
  intWidth = intWidth + 10;
  StartingPoint = new Point(intWidth, 50);
  DrawARectangle(m_Bitmap, StartingPoint, (strBinaryNumber.Substring(i, 1) == "1"));
 }
 StartingPoint = new Point(50, 100);
 DrawText(m_Bitmap, StartingPoint, String.Format("Number: {0}", strNumber));
}

The GetBinaryNumber method is called by the ConvertToBinaryNumber method to create the binary string.

private string GetBinaryNumber(string p)
{
 string strBinaryNumber = "";
 switch (p)
 {
  case "0":
    strBinaryNumber = "0000";
    break;
  case "1":
    strBinaryNumber = "0001";
    break;
  case "2":
    strBinaryNumber = "0010";
    break;
  case "3":
    strBinaryNumber = "0011";
    break;
  case "4":
    strBinaryNumber = "0100";
    break;
  case "5":
    strBinaryNumber = "0101";
    break;
  case "6":
    strBinaryNumber = "0110";
    break;
  case "7":
    strBinaryNumber = "0111";
    break;
  case "8":
    strBinaryNumber = "1000";
    break;
  case "9":
    strBinaryNumber = "1001";
    break;
  }
 return strBinaryNumber;
}

When the image is read the process is reversed.

// Use StartingPoint to read each block and count the pixels and determine if the block is marked or not
string[] Answers = ReadBlocks(StartingPoint);
// Read the string of 0's and 1's and convert to decimal
string strNumber = ConvertAnswersToBinary(Answers);
MessageBox.Show(strNumber);

Why not just use bar codes?

Barcodes are a great technology however they were designed to be read with a special bar code reader. They were also designed to be printed clearly not read from possibly reprinted, faxed and scanned images. While they may be readable under these conditions, it is an attempt to make a technology work under conditions it was not designed for.

This project presents an alternative technology that is designed to be read under adverse conditions.