
Optical Mark Recognition (OMR) is an automated process of capturing and analyzing data marked on a special type of document form. This special type of document could be marked/ filled by people on survey forms, test sheets, and other paper documents. In this article, we will learn how to develop a GUI-based OMR Sheet Reader application using C#. Our solution will take the scanned OMR sheet image as input from a local disk, then recognize the marks and finally export the marked registration number and shaded answers in CSV format. After following the mentioned steps, we will have our C# Optical Mark Recognition (OMR) Software in .NET. So let’s begin.
The article shall cover the following topics:
- Features of C# Optical Mark Recognition (OMR) Software
- C# OMR .NET API and UI Control
- Steps to Develop C# OMR Software
- C# Optical Mark Recognition (OMR) Software Demo
- Download OMR Software Source Code
Features of C# Optical Mark Recognition (OMR) Software
Our Optical Mark Recognition (OMR) Software will have the following features:
- Interactively adjust recognition parameters and watch their effect in real-time. We can adjust the following:
- Recognition threshold
- Zoom
- Show/hide bubbles
- Select and load the scanned image in the following formats:
- Recognize the optical marks on the image.
- Export results in CSV and save them to your local disk.
C# OMR .NET API and UI Control
Aspose.OMR for .NET API allows designing, creating, and recognizing answer sheets, tests, MCQ papers, quizzes, feedback forms, surveys, and ballots. Moreover, it provides a graphical user interface control that can be added to .NET UI applications. We will integrate Aspose.OMR for .NET UI control in the .NET UI application for developing an OMR scanner/reader application. Please either download the DLL of the API or install it using NuGet.
PM> Install-Package Aspose.OMR
Steps to Develop C# OMR Software
We can develop a GUI-based OMR scanner/reader application by following the steps given below:
- Firstly, create a new project and select the WPF App (.NET Framework) project template.

Create a new project and select the project template.
- Next, in Configure your new project dialog, enter the Project name, choose the Location, and set other parameters.

Configure your WPF App Project
- Then, open NuGet Package Manager and install Aspose.OMR for .NET package.

Install Aspose.OMR for .NET
- Next, add a new file DialogHelper.cs to the project.

Add DialogHelper.cs
- Add the following code to the newly created DialogHelper.cs.
internal class DialogHelper | |
{ | |
/// <summary> | |
/// The filter string for the dialog that opens template images. | |
/// </summary> | |
private static readonly string ImageFilesFilterPrompt = "Image files |*.jpg; *.jpeg; *.png; *.gif; *.tif; *.tiff;"; | |
/// <summary> | |
/// The filter string for the dialog that saves recognition results | |
/// </summary> | |
private static readonly string DataExportFilesFilterPrompt = "Comma-Separated Values (*.csv)" + " | *.csv"; | |
/// <summary> | |
/// Shows Open Image file dialog. | |
/// </summary> | |
/// <returns>Path to selected file, or <c>null</c> if no file was selected.</returns> | |
public static string ShowOpenImageDialog(string suggestedDir = null) | |
{ | |
OpenFileDialog dialog = new OpenFileDialog(); | |
return ShowDialog(dialog, ImageFilesFilterPrompt, suggestedDir); | |
} | |
/// <summary> | |
/// Shows Save Recognition Results file dialog. | |
/// </summary> | |
/// <returns>Path to selected file, or <c>null</c> if no file was selected.</returns> | |
public static string ShowSaveDataDialog(string suggestedName) | |
{ | |
SaveFileDialog dialog = new SaveFileDialog(); | |
return ShowDialog(dialog, DataExportFilesFilterPrompt, suggestedName); | |
} | |
/// <summary> | |
/// Displays given dialog and returns its result as a <c>string</c>. | |
/// </summary> | |
/// <param name="dialog">The dialog to show.</param> | |
/// <param name="filter">File type filter string.</param> | |
/// <param name="suggestedDir">Suggested dialog initial directory</param> | |
/// <param name="suggestedName">Suggested file name</param> | |
/// <returns>Path to selected file, or <c>null</c> if no file was selected.</returns> | |
private static string ShowDialog(FileDialog dialog, string filter, string suggestedDir = null, string suggestedName = null) | |
{ | |
string fileName = null; | |
dialog.Filter = filter; | |
dialog.RestoreDirectory = true; | |
if (suggestedName != null) | |
{ | |
dialog.FileName = suggestedName; | |
} | |
if (suggestedDir != null) | |
{ | |
dialog.InitialDirectory = suggestedDir; | |
} | |
bool? result = dialog.ShowDialog(); | |
if (result == true) | |
{ | |
fileName = dialog.FileName; | |
} | |
return fileName; | |
} | |
} |
- Next, update MainWindow.xaml file with the following XAML content.
<Window x:Class="OMR_APP.MainWindow" | |
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | |
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |
xmlns:local="clr-namespace:OMR_APP" | |
mc:Ignorable="d" | |
Title="Aspose OMR Demo" Height="880" Width="1100"> | |
<Grid Background="WhiteSmoke"> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="40"></RowDefinition> | |
<RowDefinition Height="*"></RowDefinition> | |
</Grid.RowDefinitions> | |
<ToolBar Grid.Row="0" Background="LightGray"> | |
<TextBox Name="txtTemplatePath" Margin="5" Width="400" Height="30" Background="White" | |
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"> | |
</TextBox> | |
<Button Margin="5" Width="100" Height="30" Background="White" | |
Content="Get control" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" | |
Click="GetButtonClicked"/> | |
<Separator/> | |
<Button Margin="5" Width="100" Height="30" Background="White" | |
Content="Select Image" Click="SelectImageClicked"/> | |
<Button Margin="5" Width="100" Height="30" Background="White" | |
Content="Recognize Image" Click="RecognizeImageClicked"/> | |
<Button Margin="5" Width="100" Height="30" Background="White" | |
Content="Export Results" Click="ExportResultsClicked"/> | |
</ToolBar> | |
<ContentControl Grid.Row="1" x:Name="CustomContentControl" | |
HorizontalAlignment="Center" VerticalAlignment="Center"/> | |
</Grid> | |
</Window> |
- After that, replace the following content in MainWindow.xaml.cs file.
/// <summary> | |
/// Template for testing | |
/// </summary> | |
private static readonly string TemplateFilePath = @"C:\Files\OMR\Sheet.omr"; | |
/// <summary> | |
/// Path to the license Aspose.OMR.NET.lic file | |
/// </summary> | |
private static readonly string LicensePath = @""; | |
private CorrectionControl control; | |
public MainWindow() | |
{ | |
InitializeComponent(); | |
// Set and show template file path | |
txtTemplatePath.Text = TemplateFilePath; | |
// Set license, provide License file Path and uncomment to test full results | |
//License lic = new License(); | |
//lic.SetLicense(LicensePath); | |
} | |
public string UserImagePath { get; set; } | |
public string DataFolderPath { get; set; } | |
/// <summary> | |
/// Loads and displays CorrectionControl | |
/// </summary> | |
private void GetButtonClicked(object sender, RoutedEventArgs e) | |
{ | |
string path = txtTemplatePath.Text; | |
try | |
{ | |
OmrEngine engine = new OmrEngine(); | |
TemplateProcessor processor = engine.GetTemplateProcessor(path); | |
control = engine.GetCorrectionControl(processor); | |
CustomContentControl.Content = control; | |
control.Initialize(); | |
} | |
catch (Exception ex) | |
{ | |
MessageBox.Show(ex.Message,"Exception"); | |
} | |
} | |
/// <summary> | |
/// Select and display image | |
/// </summary> | |
private void SelectImageClicked(object sender, RoutedEventArgs e) | |
{ | |
if (control == null) | |
{ | |
return; | |
} | |
string imagePath = DialogHelper.ShowOpenImageDialog(this.DataFolderPath); | |
if (string.IsNullOrEmpty(imagePath)) | |
{ | |
return; | |
} | |
this.UserImagePath = imagePath; | |
control.LoadAndDisplayImage(imagePath); | |
} | |
/// <summary> | |
/// Recognize loaded image | |
/// </summary> | |
private void RecognizeImageClicked(object sender, RoutedEventArgs e) | |
{ | |
if (control == null) | |
{ | |
return; | |
} | |
control.RecognizeImage(); | |
} | |
/// <summary> | |
/// Export results to CSV | |
/// </summary> | |
private void ExportResultsClicked(object sender, RoutedEventArgs e) | |
{ | |
if (control == null) | |
{ | |
return; | |
} | |
string imageName = Path.GetFileNameWithoutExtension(this.UserImagePath); | |
string path = DialogHelper.ShowSaveDataDialog(imageName); | |
if (string.IsNullOrEmpty(path)) | |
{ | |
return; | |
} | |
control.ExportResults(path); | |
MessageBox.Show("The exported resultant CSV file can be found here : " + path, "Operation Successful"); | |
} |
- Finally, run the application.
C# Optical Mark Recognition (OMR) Software Demo
The following is the demonstration of the OMR Scanner/Reader application we have just created.

OMR Software Demo
Download C# .NET OMR Software Source Code
You can download the complete source code of the C# OMR Scanner application from GitHub.
Get a Free License
You can get a free temporary license to try the library without evaluation limitations.
Conclusion
In this article, we have learned how to
- integrate Aspose.OMR for .NET UI control in the .NET application;
- develop OMR sheet reader application in C#.
Besides, you can learn more about Aspose.OMR for .NET API using the documentation. In case of any ambiguity, please feel free to contact us on our forum.