65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (16投票s)

2013年12月24日

CPOL

6分钟阅读

viewsIcon

50070

downloadIcon

94

这是“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());
 

http://img33.imageshack.us/img33/3936/omr3a.jpg

从相机/扫描仪图像中检测纸张

检测纸张的过程涉及检测纸张的角。在打印文档中,角用特定的二进制图像标记。我们检测它们,从而检测到一张纸。

  1. 所以,首先,我们需要使用正确的对比度、填充、阈值和反转滤镜来平整图片。作为起点,没有对比度、亮度或填充校正的原始图像会被反转。给定一个阈值,图像然后转换为二进制。这个图像被称为“平整图像”,通过使用“OMR.OpticalReader.flatten”方法获得。
  2. 一旦图像被平整,斑点检测就开始了。在第一阶段,从最小斑点大小开始检测所有大小和种类的斑点(这确保我们移除噪点颗粒斑点)。
  3. 首先检测左边缘,然后检测纸张的右边缘。
  4. 在第一个滤镜中从图片中检测到的数百个斑点中,通过检查它们的大小与相机/扫描仪图像的大小比率,过滤掉大小错误的斑点。
  5. 在第二个滤镜中,过滤掉放置在图像错误一侧的斑点。
  6. 在第三个滤镜中,过滤掉宽高比严重错误的斑点(确保我们检测并拒绝由纸张上的弯曲/线条产生的斑点)。
  7. 作为斑点的最后一个滤镜,所有斑点都与镜像的角图像进行比较(镜像是因为我们在第一步中反转了图像)。
  8. 再次验证过滤后的斑点,它们数量正好是四个,并且放置在纸张的右侧。此外,左右边缘的长度差异不大。
  9. 验证后的斑点代表图像坐标系中纸张角的真实位置。
  10. 图像可以通过这些点从未平整的图像中裁剪出来,并进行包裹,从而生成一个完美的矩形图像,称为 OMR 纸张。
  11. 如果以上所有滤镜只产生 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,以便结果完全电子化编译。

© . All rights reserved.