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
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.
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.
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.
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; }
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);
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.