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)

SymptomWhat to tryHow to set (code)
Speckle noise / JPEG artifactsMedian filter (3 → 5)new FilterOptions { MedianFilter = true, MedianFilterSize = 3 }
Too darkIncrease brightness (+5..+15) and contrast (+10..+25)new ContrastOptions { BrightnessAdjustment = 10, ContrastAdjustment = 20 }
Washed outDecrease brightness (-5..-15), moderate contrastBrightnessAdjustment = -10, ContrastAdjustment = 10..20
Very small textUpscale ×1.25–×1.75, then OCRimage = Upscale(image, 1.5);
Busy background / color noiseDIY binarization or crop ROIvar bin = ToBinary(image, 185); or image = image.Clone(roi, ...)
Skewed scanDeskew (if exposed) or re-scan straighter(If your build exposes a Deskew option, enable it; else crop & rescan)
Mixed languagesSet OCR language(s) explicitly(If available) ocrEngine.Settings.Language = RecognitionLanguages.English;
Tabular contentCrop to the table region before OCRimage.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.

More in this category