Poor scans, phone shots, faxes, and compressed screenshots often defeat OCR. The good news: a little preprocessing goes a long way. This guide gives you practical, runnable steps (aligned with the gist at the end) to clean up images before OCR and to tune the OCR engine for significantly better results.
Complete Example
Prerequisites
- .NET 8 (or .NET 6+) SDK
- NuGet:
Aspose.OCR
- (Optional)
Aspose.Total.lic
if you plan to exceed evaluation limits
Create a console app and add the package:
dotnet new console -n OCRImprovementExample -f net8.0
cd OCRImprovementExample
dotnet add package Aspose.OCR
Step 1 — Preprocess Low-Quality Images
Goal: reduce noise, normalize contrast/brightness, and (optionally) upscale or crop before OCR.
1.1 Load the image
using System;
using System.Drawing;
using System.IO;
// Step 1: Load the low-quality image
string imagePath = "low_quality_image.png";
if (!File.Exists(imagePath))
throw new FileNotFoundException(imagePath);
Bitmap image = (Bitmap)Image.FromFile(imagePath);
// Optional: quick sanity check
Console.WriteLine($"Loaded {imagePath} ({image.Width}x{image.Height}px)");
1.2 Remove noise (median filter)
Use a median filter to suppress salt-and-pepper noise and JPEG artifacts.
using Aspose.Ocr.ImageProcessing;
// Median filter: try odd sizes 3, 5 (larger = stronger, but may blur small text)
var filterOptions = new FilterOptions
{
MedianFilter = true,
MedianFilterSize = 3
};
When to change:
- If you still see speckles, raise
MedianFilterSize
to 5. - If tiny characters disappear, drop back to 3 or turn it off.
1.3 Normalize contrast/brightness
Make text stand out from background.
var contrastOptions = new ContrastOptions
{
// Positive values increase contrast/brightness; negatives decrease
ContrastAdjustment = 20, // try 10..30
BrightnessAdjustment = 10 // try -10..+15 based on exposure
};
Rules of thumb:
- Overexposed (washed out): decrease brightness (e.g., -10) and keep contrast moderate.
- Underexposed (too dark): increase brightness (e.g., +10) and contrast (e.g., +20).
1.4 Build processing pipeline & preprocess
var processingOptions = new ImageProcessingOptions();
processingOptions.Filters.Add(filterOptions);
processingOptions.Contrast = contrastOptions;
// (Optional) more options can be added here if your build exposes them
// e.g., processingOptions.Sharpen = new SharpenOptions { Strength = 1 };
using (var ocrEngine = new Aspose.Ocr.Api.OcrEngine())
{
Bitmap preprocessed = ocrEngine.PreprocessImage(image, processingOptions);
// Keep this for OCR below
image.Dispose();
image = preprocessed;
}
1.5 (Optional) Upscale tiny text
If text is very small (<10px height), upscale before OCR using high-quality resampling.
// 1.5 Optional: upscale 1.5x to help recognition of tiny text
Bitmap Upscale(Bitmap src, double scale)
{
int w = (int)(src.Width * scale);
int h = (int)(src.Height * scale);
var dest = new Bitmap(w, h);
using (var g = Graphics.FromImage(dest))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(src, 0, 0, w, h);
}
return dest;
}
// Example usage
// image = Upscale(image, 1.5);
1.6 (Optional) Crop a Region of Interest (ROI)
If you only need a portion (e.g., header, invoice totals), crop to reduce clutter and error.
// Crop a rectangle (x,y,width,height)
Rectangle roi = new Rectangle(0, 0, image.Width, Math.Min(400, image.Height)); // top band
Bitmap cropped = image.Clone(roi, image.PixelFormat);
image.Dispose();
image = cropped;
1.7 (Optional) Quick binarization (DIY)
If background colors are complex, convert to grayscale and threshold. (Use this only if your OCR build lacks a dedicated binarization option; it’s a simple fallback.)
// Simple grayscale + global threshold (0..255); try 170..200
Bitmap ToBinary(Bitmap src, byte threshold = 185)
{
var bw = new Bitmap(src.Width, src.Height);
for (int y = 0; y < src.Height; y++)
for (int x = 0; x < src.Width; x++)
{
var c = src.GetPixel(x, y);
byte gray = (byte)(0.299 * c.R + 0.587 * c.G + 0.114 * c.B);
byte v = gray >= threshold ? (byte)255 : (byte)0;
bw.SetPixel(x, y, Color.FromArgb(v, v, v));
}
return bw;
}
// Example usage
// var bin = ToBinary(image, 190);
// image.Dispose();
// image = bin;
Step 2 — Configure OCR Settings (Optional, if available in your build)
Some Aspose.OCR builds expose engine-level settings. If your package has them, set language and page layout hints to aid segmentation and recognition.
// Only if your build exposes these settings:
using Aspose.Ocr;
var settingsAvailable = false; // flip true if your API supports it
// Example (may vary by package version):
// ocrEngine.Settings.Language = RecognitionLanguages.English;
// ocrEngine.Settings.PageSegmentationMode = PageSegmentationMode.Auto;
When to set:
- Mixed languages: switch to appropriate language or multi-language mode.
- Dense text blocks:
PageSegmentationMode.Auto
or Document mode. - Forms/tables: prefer Document segmentation; crop to the region whenever possible.
Step 3 — Run OCR & Evaluate
This is the exact flow from the gist: preprocess → recognize → print.
using System;
using System.Drawing;
using Aspose.Ocr;
using Aspose.Ocr.ImageProcessing;
namespace OCRImprovementExample
{
class Program
{
static void Main(string[] args)
{
string imagePath = "low_quality_image.png";
Bitmap image = (Bitmap)Image.FromFile(imagePath);
// Preprocess (median + contrast/brightness)
var filterOptions = new FilterOptions { MedianFilter = true, MedianFilterSize = 3 };
var contrastOptions = new ContrastOptions { ContrastAdjustment = 20, BrightnessAdjustment = 10 };
var processingOptions = new ImageProcessingOptions();
processingOptions.Filters.Add(filterOptions);
processingOptions.Contrast = contrastOptions;
using (Aspose.Ocr.Api.OcrEngine ocrEngine = new Aspose.Ocr.Api.OcrEngine())
{
// Preprocess
Bitmap preprocessedImage = ocrEngine.PreprocessImage(image, processingOptions);
// OCR
string recognizedText = ocrEngine.RecognizeImage(preprocessedImage);
Console.WriteLine("Recognized Text:");
Console.WriteLine(recognizedText);
}
}
}
}
Exporting text: write to a file for inspection:
File.WriteAllText("recognized.txt", recognizedText);
Symptom → Fix (Cheat-Sheet with API options)
Symptom | What to try | How to set (code) |
---|---|---|
Speckle noise / JPEG artifacts | Median filter (3 → 5) | new FilterOptions { MedianFilter = true, MedianFilterSize = 3 } |
Too dark | Increase brightness (+5..+15) and contrast (+10..+25) | new ContrastOptions { BrightnessAdjustment = 10, ContrastAdjustment = 20 } |
Washed out | Decrease brightness (-5..-15), moderate contrast | BrightnessAdjustment = -10, ContrastAdjustment = 10..20 |
Very small text | Upscale ×1.25–×1.75, then OCR | image = Upscale(image, 1.5); |
Busy background / color noise | DIY binarization or crop ROI | var bin = ToBinary(image, 185); or image = image.Clone(roi, ...) |
Skewed scan | Deskew (if exposed) or re-scan straighter | (If your build exposes a Deskew option, enable it; else crop & rescan) |
Mixed languages | Set OCR language(s) explicitly | (If available) ocrEngine.Settings.Language = RecognitionLanguages.English; |
Tabular content | Crop to the table region before OCR | image.Clone(roi, image.PixelFormat) |
Not all builds expose the same engine settings. If a property isn’t available in your package, rely on the image preprocessing techniques above—they are API-stable and effective.
Best Practices
- Tweak in small steps. Change one parameter at a time (e.g.,
MedianFilterSize
3 → 5) and compare outputs. - Prefer ROI. Cropping to just the relevant area often beats any filter.
- Avoid over-processing. Too much blur/upscaling can destroy glyph shapes.
- Automate baselines. Keep a small golden set of tricky images and run them in CI to detect regressions.
- Save intermediates. Save preprocessed images to a
./debug/
folder during tuning.