Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An Alternative to Barcodes

0.00/5 (No votes)
26 Apr 2008 1  
A method to create and read a number represented as a binary string of boxes printed on a page.

Introduction

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 student's record. This will provide significant time savings.

Naturally, bar codes were considered, and an example code was available on CodeProject. However, if the scan is not straight the bar codes will not be read correctly. There is a CodeProject 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 its 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 article on de-skewing an image did not work because its 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 straightforward 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here