C# - 光学标记识别 (OMR) 引擎 1.0






4.65/5 (16投票s)
这是“C# - 光学标记识别 (OMR) 引擎 1.0”的替代方案
引言
OMR (维基) 是不打算由人类阅读的答题纸。这个项目消除了购买 OMR 阅读机甚至用于计算机的照片扫描仪的需要。任何 200 万像素以上带自动对焦的手机摄像头都可以完成这项工作。
背景
我搜索了谷歌,想找到一个好的 OMR 引擎,但一无所获,所以我决定自己制作一个。通常在学校和大学里,他们使用专门的机器来读取 OMR 答题纸。在我的案例中,我需要消除购买 OMR 阅读机甚至扫描仪的需要!用 200 万像素手机摄像头(需要自动对焦)拍摄的照片可以通过这个引擎读取。在最初阶段,我创建了自己的可以读取的纸张类型。图像处理部分使用了 AForge.Net 的图像处理器(库已包含在下载中)。
[编辑/补充] 它能与其他 OMR 答题纸一起使用吗?
看起来很多人在反馈中反复问了同一个问题。
“我们能用这个引擎读取其他 OMR 答题纸吗?”
答案是,“不能”。
为什么?因为在 ExtractPaperFromFlattened() 方法中使用的纸张图像提取算法。它通过查找纸张上的四个交叉圆形符号来识别扫描图像中的纸张。这些符号定义了纸张的边界,从而估算了纸张的裁剪、缩放和倾斜信息。
所以,没有符号,就无法检测到纸张。
[编辑/补充] 在 VB 中作为引用项目添加到 ASP.NET 网站(也适用于 C#)
为了在 ASP.NET 下使用此项目,只需将源包中的项目 OMR 作为 现有项目 添加到您的 ASP.NET Visual Studio 解决方案中。
然后,使用 添加引用 -> 项目 从您的网站项目创建引用。在解决方案编译期间,一个 OMR.DLL 将出现在 Bin 目录中。
用法,在 aspx 代码隐藏文件(使用 VB.NET)中
'Import OMR project namespace
Imports OMR
Imports OMR.XML
'Loading of left and right symbol images
Dim compImgRight As System.Drawing.Image = System.Drawing.Image.FromFile(Server.MapPath(".") & "/rc.jpg")
Dim compImgLeft As System.Drawing.Image = System.Drawing.Image.FromFile(Server.MapPath(".") & "/lc.jpg")
'URL of Specification XML Sheet
Dim strXML As String = Server.MapPath(".") & "/sheets.xml"
'Creating the OpticalReader object
Dim reader As OpticalReader = New OpticalReader
Dim answerSheetbm As Bitmap = Nothing
If fileUpload1.HasFile Then 'Using FileUpload Control
'Save to Temp Location
fileUpload1.SaveAs(Server.MapPath(".") & "/answersheet.png")
answerSheetbm = New Bitmap(System.Drawing.Image.FromFile(Server.MapPath(".") & "/answersheet.png"))
'Resizing bitmap to expected proportion
ImageUtilities.ResizeImage(answerSheetbm, 210, 210 * answerSheetbm.Height / answerSheetbm.Width)
'Extract relevant bitmap based on left/right symbols
answerSheetbm = reader.ExtractOMRSheet(answerSheetbm , strXML , OMREnums.OMRSheet.A550, compImgLeft, compImgRight)
End If
要读取 注册号,只需调用:
Dim strRegNum As String = reader.getRegNumOfSheet(answerSheetbm, OMREnums.OMRSheet.A550, strXML, False).ToString() '000-999
要将每个 OMR 答题行 读取为字符串,只需使用 OpticalReader
对象中的 reader.rateSlice()
和 reader.sliceOmarkBlock()
方法:
Dim blocks As System.Drawing.Rectangle() = New System.Drawing.Rectangle() { _
OMRSheetReader.GetSheetPropertyLocation(strXML, OMREnums.OMRSheet.A550, OMREnums.OMRProperty.tensBlock1), _
OMRSheetReader.GetSheetPropertyLocation(strXML, OMREnums.OMRSheet.A550, OMREnums.OMRProperty.tensBlock2), _
OMRSheetReader.GetSheetPropertyLocation(strXML, OMREnums.OMRSheet.A550, OMREnums.OMRProperty.tensBlock3), _
OMRSheetReader.GetSheetPropertyLocation(strXML, OMREnums.OMRSheet.A550, OMREnums.OMRProperty.tensBlock4) }
For Each rec As System.Drawing.Rectangle In blocks
Dim blk As Bitmap() = reader.SliceOMarkBlock(answerSheetbm, rec, 10)
For Each line As Bitmap In blk
Dim num As Integer = reader.rateSliceMax(line, 5) '0,1,2,3,4,5
Next
Next
注意:如果您不想在调试期间出现弹出窗口,可以修改 OMR 项目,将 Messagebox.show()
方法替换为 Throw new Exception()
。
原始的 reader.getRegNumOfSheet()
也包含一个 bug,如果第二行和第三行没有涂黑,注册号 '9' 将被读取为 '900'。
我稍后将上传此项目的编辑版本。很棒的项目 umar.techBOY!
Using the Code
添加所有引用(AForge 和 OMR)后,您可以使用最简单的方法重载从相机/扫描仪图像中提取 OMR 包裹的纸张。
原始图像必须包含受支持的纸张格式的清晰视图(可打印的 PDF 在下载中)。例如:

Bitmap unf = new Bitmap(panel1.BackgroundImage);
OpticalReader reader = new OpticalReader();
panel1.BackgroundImage = (System.Drawing.Image)reader.ExtractOMRSheet(unf,
"sheets.xml" , OMREnums.OMRSheet.A550);
这将提取纸张如下:
一旦纸张被提取,您可以使用以下方法进行处理:
OpticalReader rr = new OpticalReader(); MessageBox.Show(rr.getRegNumOfSheet(panel1.BackgroundImage, OMREnums.OMRSheet.A550, "sheets.xml",false).ToString());
从相机/扫描仪图像中检测纸张
检测纸张的过程涉及检测纸张的角。在打印文档中,角用特定的二进制图像标记。我们检测它们,从而检测到一张纸。
- 所以,首先,我们需要使用正确的对比度、填充、阈值和反转滤镜来平整图片。作为起点,没有对比度、亮度或填充校正的原始图像会被反转。给定一个阈值,图像然后转换为二进制。这个图像被称为“平整图像”,通过使用“
OMR.OpticalReader.flatten
”方法获得。 - 一旦图像被平整,斑点检测就开始了。在第一阶段,从最小斑点大小开始检测所有大小和种类的斑点(这确保我们移除噪点颗粒斑点)。
- 首先检测左边缘,然后检测纸张的右边缘。
- 在第一个滤镜中从图片中检测到的数百个斑点中,通过检查它们的大小与相机/扫描仪图像的大小比率,过滤掉大小错误的斑点。
- 在第二个滤镜中,过滤掉放置在图像错误一侧的斑点。
- 在第三个滤镜中,过滤掉宽高比严重错误的斑点(确保我们检测并拒绝由纸张上的弯曲/线条产生的斑点)。
- 作为斑点的最后一个滤镜,所有斑点都与镜像的角图像进行比较(镜像是因为我们在第一步中反转了图像)。
- 再次验证过滤后的斑点,它们数量正好是四个,并且放置在纸张的右侧。此外,左右边缘的长度差异不大。
- 验证后的斑点代表图像坐标系中纸张角的真实位置。
- 图像可以通过这些点从未平整的图像中裁剪出来,并进行包裹,从而生成一个完美的矩形图像,称为 OMR 纸张。
- 如果以上所有滤镜只产生 4 个角斑点,则继续该过程,否则对相同的函数使用相同的参数进行递归调用,但调整对比度校正,这可能会产生更好的结果值。
请参阅“OMR.OpticalReader.ExtractOMRSheet
”方法中逐行注释的代码。
读取提取的纸张
主要的图像处理在于图像提取部分。现在下一阶段是读取 OMR 纸张。
通常 OMR 答题纸对一个问题有多个选项。所有相同问题的选项都并排打印在纸上,形成一个“块”。所有块的位置、大小和给定选项的数量足以将其保存到 XML 文件中。位置根据坐标系记录,通常遵循 .NET 中的约定,即左上角为 O(0,0) (x,y),+x 轴向右,+y 轴向下。
要读取纸张中的特定块(纸张指的是第一部分中提取的纸张),可以调用 OMR.OpticalReader.getScoreOfSheet
。此方法重复执行上述过程,以读取纸张上打印的所有 4 个 BigAnswerBlocks 中的所有行。
从给定选项中读取选定的选项
当从文档中切片出多选块时,是时候读取选定的选项了。为了读取,该块被分成与其中存在的选项一样多的相等部分。然后,图像根据块的平均颜色转换为二进制。这就是我们将白纸转换为纯白色,并将超过一半墨水填充的像素转换为纯黑色的方式。
在块的每个细分上记录黑像素计数。
最暗的块与其他块进行比较,如果两个细分之间存在显著差异,则较暗的块被记录为“已标记”。根据标记选项的数量,可以决定选择了哪个选项。
注意
也请查看其他方法。这些方法可以在一个方法调用中读取一张纸上的所有选项,并为给定的两种纸张创建 XML 规范表。
相机图像纸张识别器的核心在于以下方法。
关注点
现在,接下来要做的是制作一个应用程序,可以处理一个文件夹,其中包含一个班级(50 名或更多学生)的测试卷答题纸。或者,连接到 PC 的扫描仪地址,然后一个接一个地处理图像。根据纸张上写的注册号,程序应该创建一个 XLS 文件和一个 PDF,以便结果完全电子化编译。