Title: ScanFree Author: Michael Washington Email: webmaster@adefwebserver.com Member ID: 12345 Language: C++, C# 2.0 etc Platform: Windows, .NET 3.5 Technology: ASP.NET, GDI+ Level: Beginner, Intermediate, Advanced Description: A Section SubSection License: BSD
A group of developer's started the dnnScanFree project in hopes of creating Open Source software that would allow others to create free and inexpensive testing programs. The idea is that school children can be helped if there are reliable inexpensive methods to measure their progress. The group of developers were alarmed when they discovered that free programs such as this did not exist.
This project is in the early stages yet enough has been completed so far to be useful to other developers. The program will grade a test from a scanned sheet of paper.
Christian Graus wrote a great series of CodePlex articles on image processing. In his articles he explained how to process images quickly and reliably. There is no point covering what he so expertly covered so it is recommended that you read his original articles to understand the basic concepts of image manipulation. He covers important points such as why pointers are used rather than GetPixel and SetPixel.
First a method is used to turn each pixel into black or white. Next, the following method is used to determine a starting point. This is simply looking for the first two black pixels that are found together.
private Point LocateStartingPoint(Bitmap b) { Point tmpStartingPoint = new Point(0, 0); bool boolBreakOut = false;
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, b.PixelFormat);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
int nOffset = stride - (b.Width) * 3;
int nWidth = (b.Width) * 3;
for (int y = 0; y < (b.Height); ++y)
{
for (int x = 0; x < nWidth; ++x)
{ if (
((byte)p[0] == (byte)0)
& (x > 10 & y > 10)
& (x < b.Width + 10 & y < b.Height + 10)
) {
// See if the next one is black also
++p; if ((byte)p[0] == (byte)0)
{ tmpStartingPoint = new Point((x / 3), y);
boolBreakOut = true;
break;
}
// It wasn't black so move back
--p;
}
++p;
}
if (boolBreakOut)
{
break;
} p += nOffset;
}
}
b.UnlockBits(bmData);
return tmpStartingPoint;
}
Once the starting point is found it is a simple calculation to find the answer blocks and loop through them. "FindAnswerBlocks" is the method used to loop through the answer blocks and each answer in each block. "CountBlackPixelsInRectangle" is a simple method that counts the number of black pixels in each square. If a square has more than 1000 black pixels then it is considered to be marked as an answer.
private string FindAnswerBlocks()
{
string strSelectedAnswers = "";
// Find answer blocks
int intX = 499;
int intY = 209;
for (int i = 1; i <= 8; i++)
{
Point AnswerBlock = new Point(StartingPoint.X + intX, StartingPoint.Y + intY);
DrawARectangle(m_Bitmap, AnswerBlock, 480, 65, Color.Red);
// Find answers in answer block
int intX2 = StartingPoint.X + intX;
for (int ii = 1; ii <= 5; ii++)
{
Point Answer = new Point(intX2, AnswerBlock.Y);
DrawARectangle(m_Bitmap, Answer, 90, 62, Color.Blue);
int intCount = CountBlackPixelsInRectangle(m_Bitmap, Answer.X, Answer.Y, 90, 62);
if (intCount > 1000)
{
strSelectedAnswers = strSelectedAnswers + Environment.NewLine + String.Format("Answer Block: {0} | Answer: {1} ", i, ii);
}
intX2 = intX2 + 97;
}
intY = intY + 68;
}
return strSelectedAnswers;
}
There is still a lot more work that needs to be done. The boxes are currently too big and each answer should be compared rathern than using a set number of expected pixels. However the code should provide a starting point for other developer's