条形码的替代方案






3.53/5 (9投票s)
一种将数字表示为页面上打印的方块的二进制字符串的方法。

引言
dnnScanFree 项目(一个用于创建免费且廉价的测试程序的开源项目)面临的挑战是创建一种指示参加考试的学生的 方法。预先在试卷上打印学生编号将允许评分程序对试卷进行评分,并自动更新学生的记录。这将节省大量时间。
自然而然地,考虑了条形码,并且 CodeProject 上提供了一个示例代码。但是,如果扫描不直,条形码将无法正确读取。 CodeProject 上有一篇关于图像去斜的文章;然而,它非常占用资源。
dnnScanFree 项目决定使用一种简单的方法:将数字转换为页面上打印的方块字符串。然后,将使用与用于对考试进行评分相同的技术读取这些方块。
程序
启动程序,从菜单中选择“BinaryCode”,然后选择“创建”。

弹出一个框,允许您输入一个七位数字。

该数字将出现在屏幕上,以及它的二进制表示形式和右上角的一个方块,该方块将用于在读取图像扫描时检测起始点。

选择“文件”,然后选择“打印”,将允许您打印图像并对其进行扫描

扫描图像后,可以通过使用“文件”,然后选择“打开”来打开图像进行读取。红色方框将绘制在每个识别区域周围(基于检测到的起始点)。一个弹出框将指示识别出的数字。

处理倾斜
图像扫描或打印完全笔直的情况很少见。以下显示了当图像向右倾斜时如何检测相同的图像

如果图像向左倾斜,则检测起始点的代码会意识到存在错误,因为它会计算一个 20 像素方块中的黑色像素。如果计数不匹配,它会假设左倾斜并在 X 轴上减去像素。

// 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()));
}
图像在倾斜时仍然可读。

修复倾斜
虽然方框可以在页面左侧倾斜时被读取,但倾斜在页面右侧变得更糟。 有必要实现一种对图像进行去斜的方法。 关于图像去斜的 CodeProject 文章不起作用,因为它的算法对图像感到困惑。 它也不适用于所有图像类型,并且速度显着降低。
然而,它确实提供了一个非常有用的RotateImage方法。 这非常有帮助,因为Bitmap通常只能使用枚举以 90 度增量旋转。 该项目需要更精细的旋转。
对图像进行去斜的过程很简单。 检测到FirstStartingPoint和SecondStartingPoint,并且这些值与Bitmap图像一起传递给StraightenImage方法,该方法将图像拉直。
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);
StraightenImage方法的代码是
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;
}
读取数字
创建方框是一个简单的过程。 在弹出框中输入一个数字,然后将其转换为 0 和 1 的字符串。
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));
}
ConvertToBinaryNumber方法调用GetBinaryNumber方法来创建二进制字符串。
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;
}
读取图像时,该过程会反过来进行。
// 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);
为什么不直接使用条形码?
条形码是一项伟大的技术,但是,它们被设计为使用特殊的条形码阅读器进行读取。 它们也被设计为清晰地打印,而不是从可能重新打印、传真和扫描的图像中读取。 虽然在这些条件下它们可能是可读的,但这是试图使一项技术在设计上不适用的条件下工作。
该项目提出了一种备选技术,该技术旨在在不利条件下读取。


