Verwendung von 2D-Barcodes zur Bildausrichtung in .NET/C#

Blogkategorie: BarcodeFormularverarbeitung.NET

15.05.2020

Die Technologie zur Formularverarbeitung und optischen Markierungserkennung (OMR) wird häufig zur Klassifizierung, Weiterleitung und Erkennung von Formularen elektronischer Dokumente wie Rechnungen, Frachtbriefen usw. anhand von Vorlagen verwendet; ebenso zur Erkennung optischer Markierungen in Testformularen, Fragebögen, Stimmzetteln usw.

Das VintaSoft Forms Processing .NET Plug-in kann sowohl zur Formularerkennung als auch zur Bildausrichtung anhand von Vorlagen verwendet werden. Dieser Artikel beschreibt, wie Barcodes anstelle von Linien zur Bildidentifizierung verwendet werden können, um die Genauigkeit und Zuverlässigkeit der Erkennung zu verbessern.

Der Bildausrichtungsprozess besteht aus der Bildidentifizierung anhand einer Vorlage und der Ermittlung der Transformationsmatrix. Diese definiert den Versatz, die Stauchung und die Drehung des Bildes relativ zur Vorlage. Anschließend wird die Transformationsmatrix zur Bildausrichtung verwendet.

Die Bildidentifizierung erfolgt anhand einer Vorlage, die auf Barcodes basierende Schlüsselzonen verwendet.


Die Bildidentifizierung erfolgt mithilfe der Klasse TemplateMatchingCommand. Das Ergebnis einer erfolgreichen Identifizierung ist eine Transformationsmatrix, die die Verschiebungen, die Stauchung und die Drehung des Bildes relativ zur Vorlage definiert.

Die Klasse TemplateMatchingCommand erzeugt einen Abdruck des Eingabebildes und des Vorlagenbildes, Vergleicht Bildabdrücke und ermittelt deren Ähnlichkeit. Standardmäßig generiert die Klasse einen Bildabdruck basierend auf geraden Linien.

In diesem Artikel ändern wir den Algorithmus zur Erzeugung eines Bildabdrucks und verwenden kompakte 2D-Barcodes vom Typ Aztec Rune. Aztec Rune eignet sich perfekt für unsere Aufgabe, denn:

Für die Bildidentifizierung mithilfe von Schlüsselzonen basierend auf Barcodes müssen wir die folgenden Schritte durchführen:


Bildausrichtung anhand einer Vorlage mit Schlüsselzonen basierend auf Barcodes.


Die Bildausrichtung erfolgt mithilfe der Klasse TemplateAligningCommand. Die Klasse verwendet die ermittelte Transformationsmatrix. Während der Bildidentifizierung.

Für die Bildausrichtung mithilfe von Schlüsselzonen basierend auf Barcodes sind folgende Schritte erforderlich:


Beispiel für die Bildausrichtung


Hier ist ein Bild, das wir als Vorlage verwenden:
Document template image with key zones based on barcodes

Es enthält drei nicht kollineare Barcodes (d. h. drei Referenzpunkte). Dies ist die minimale Anzahl an Referenzpunkten, die für die korrekte Funktion des Algorithmus erforderlich ist. Es können mehr Barcodes verwendet werden, falls diese beschädigt oder abgeschnitten sein sollten.
Die Barcodes können an beliebiger Stelle auf der Seite platziert werden, die höchste Genauigkeit bei der Erkennung von Verzerrungen wird jedoch erreicht, wenn sich die Barcodes an den Dokumenträndern befinden.

Wir verwenden drei Testdokumentbilder.

Hier ist ein um 90 Grad gedrehtes Dokumentbild mit Schlüsselbereichen basierend auf den Barcodes:
90-degree rotated document image with key zones based on barcodes

Hier ist ein um 150 Grad gedrehtes und skaliertes Dokumentbild mit Schlüsselbereichen basierend auf den Barcodes:
150-degree rotated and scaled document image with key zones based on barcodes

Hier ist ein um 180 Grad gedrehtes und skaliertes Dokumentbild mit Schlüsselbereichen basierend auf den Barcodes:
180-degree rotated and scaled document image with key zones based on barcodes


Hier ist ein Beispiel für eine C#-Konsolenanwendung, die den Bildvergleich und die Ausrichtung durchführt:
public static void Test()
{
    AlignImages(
        new string[] { "BarcodeTemplateMatching_template1.png" },
        new string[] {
    "BarcodeTemplateMatching_1_rotate90.png",
    "BarcodeTemplateMatching_1_scale-rotate150.png",
    "BarcodeTemplateMatching_1_scale-rotate180.png"});
}

public static void AlignImages(string[] templates, string[] images)
{
    // create barcode key zone recognizer
    BarcodeKeyZoneRecognizerCommand barcodeRecognizer = new BarcodeKeyZoneRecognizerCommand();
    // set barcode reader settings
    barcodeRecognizer.Settings.ScanBarcodeTypes = Vintasoft.Barcode.BarcodeType.Aztec;
    barcodeRecognizer.Settings.ScanDirection = Vintasoft.Barcode.ScanDirection.Horizontal | Vintasoft.Barcode.ScanDirection.Vertical;
    barcodeRecognizer.Settings.AutomaticRecognition = true;
    // specify that image contains 3 barcodes
    barcodeRecognizer.Settings.ExpectedBarcodes = 3;

    // create an imprint generator, which is based on barcode key zones
    Vintasoft.Imaging.FormsProcessing.TemplateMatching.ImageImprintGeneratorCommand barcodeImprintGenerator =
        new Vintasoft.Imaging.FormsProcessing.TemplateMatching.ImageImprintGeneratorCommand(barcodeRecognizer);

    // create template matching command
    Vintasoft.Imaging.FormsProcessing.TemplateMatching.TemplateMatchingCommand templateMatchingCommand =
        new Vintasoft.Imaging.FormsProcessing.TemplateMatching.TemplateMatchingCommand();
    // specify that the template matching command must use imprint generator, which is based on barcode key zones
    templateMatchingCommand.ImageImprintGenerator = barcodeImprintGenerator;

    // for each template image file
    foreach (string template in templates)
    {
        // add template image file to the templates of template matching command
        templateMatchingCommand.TemplateImages.Add(template);
    }

    // for each input image file
    foreach (string imageFilename in images)
    {
        // create input image
        using (Vintasoft.Imaging.VintasoftImage image = new Vintasoft.Imaging.VintasoftImage(imageFilename))
        {
            // find a template for the image
            templateMatchingCommand.ExecuteInPlace(image);
            // get the image compare result
            Vintasoft.Imaging.FormsProcessing.TemplateMatching.ImageImprintCompareResult compareResult = templateMatchingCommand.Result.ImageCompareResult;

            // print image file name
            System.Console.WriteLine(imageFilename);
            // print the image comparison result
            System.Console.WriteLine(string.Format(" IsReliable ={0}", compareResult.IsReliable));

            // if template for image is found
            if (compareResult.IsReliable)
            {
                // print the image comparison confidence
                System.Console.WriteLine(string.Format(" Confidence ={0}%", compareResult.Confidence * 100));
                // print information about matrix, which defines image transformation relative to the template image
                System.Console.WriteLine(string.Format(" TransformMatrix ={0}", compareResult.TransformMatrix));

                // create the template aligning command
                Vintasoft.Imaging.FormsProcessing.TemplateMatching.TemplateAligningCommand templateAligningCommand =
                    new Vintasoft.Imaging.FormsProcessing.TemplateMatching.TemplateAligningCommand();
                // specify that template aligning command must use matrix, which defines image transformation relative to the template image
                templateAligningCommand.CompareResult = compareResult;
                // apply the aligning command to the image
                templateAligningCommand.ExecuteInPlace(image);
                // save aligned image to a file
                image.Save(string.Format("{0}_result.png", System.IO.Path.GetFileNameWithoutExtension(imageFilename)));
            }
            System.Console.WriteLine();
        }
    }
}

Nach der Ausführung der Anwendung erhalten wir Dokumentbilder, die weder gedreht noch skaliert sind.

Hier ist das Ergebnis der Formularerkennung für ein um 90 Grad gedrehtes Dokumentenbild mit Schlüsselzonen basierend auf Barcodes:
The form recognition result for 90-degrees rotated document image with keyzones based on barcodes

Hier ist das Ergebnis der Formularerkennung für ein um 150 Grad gedrehtes und skaliertes Dokumentenbild mit Schlüsselzonen basierend auf Barcodes:
The form recognition result for 150-degree rotated and scaled document image with keyzones based on barcodes

Hier ist das Ergebnis der Formularerkennung für ein um 180 Grad gedrehtes und skaliertes Dokumentenbild mit Schlüsselzonen basierend auf Barcodes:
The form recognition result for 180-degree rotated and scaled document image with keyzones based on barcodes



Quellcodes, die in diesem Artikel verwendet werden


Hier ist der C#-Code der Klasse BarcodeKeyZone, die die Schlüsselzone bestimmt, die einen Barcode enthält:
/// <summary>
/// Represents key zone based on recognized barcode.
/// </summary>
public class BarcodeKeyZone : Vintasoft.Imaging.FormsProcessing.TemplateMatching.KeyZone
{

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="BarcodeKeyZone"/> class.
    /// </summary>
    /// <param name="barcodeInfo">The barcode information.</param>
    public BarcodeKeyZone(Vintasoft.Barcode.IBarcodeInfo barcodeInfo)
        : base()
    {
        _barcodeInfo = barcodeInfo;
        if (barcodeInfo is Vintasoft.Barcode.BarcodeInfo.AztecInfo)
        {
            // get the barcode center from barcode info
            _location = ((Vintasoft.Barcode.BarcodeInfo.AztecInfo)barcodeInfo).BulleyeCenter;
        }
        else
        {
            // calculate the barcode center

            _location = System.Drawing.PointF.Empty;
            System.Drawing.Point[] points = barcodeInfo.Region.GetPoints();
            for (int i = 0; i < points.Length; i++)
            {
                _location.X += points[i].X;
                _location.Y += points[i].Y;
            }
            _location.X /= points.Length;
            _location.Y /= points.Length;
        }
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="BarcodeKeyZone"/> class.
    /// </summary>
    /// <param name="barcodeInfo">The barcode information.</param>
    /// <param name="location">The location.</param>
    private BarcodeKeyZone(Vintasoft.Barcode.IBarcodeInfo barcodeInfo, System.Drawing.PointF location)
        : base()
    {
        _barcodeInfo = barcodeInfo;
        _location = location;
    }

    #endregion



    #region Properties

    System.Drawing.PointF _location;
    /// <summary>
    /// Gets the location of the key zone on the image.
    /// </summary>
    public override System.Drawing.PointF Location
    {
        get
        {
            return _location;
        }
    }

    Vintasoft.Barcode.IBarcodeInfo _barcodeInfo;
    /// <summary>
    /// Gets the barcode information.
    /// </summary>
    public Vintasoft.Barcode.IBarcodeInfo BarcodeInfo
    {
        get
        {
            return _barcodeInfo;
        }
    }

    #endregion



    #region Methods

    /// <summary>
    /// Applies a transformation to the key zone.
    /// </summary>
    /// <param name="m">The <see cref="Vintasoft.Imaging.AffineMatrix" />
    /// that specifies the transformation to apply.</param>
    public override void Transform(Vintasoft.Imaging.AffineMatrix m)
    {
        if (m == null)
            return;
        _location = Vintasoft.Imaging.PointFAffineTransform.TransformPoint(m, _location);
    }

    /// <summary>
    /// Returns the similarity of specified <see cref="Vintasoft.Imaging.FormsProcessing.TemplateMatching.KeyZone" /> and the current key zone.
    /// </summary>
    /// <param name="zone">The <see cref="Vintasoft.Imaging.FormsProcessing.TemplateMatching.KeyZone" /> to compare with.</param>
    /// <returns>
    /// The similarity of specified keyzone and the current key zone in range from 0 to 1.<br />
    /// 0 means that zones are absolutely NOT similar;
    /// 1 means that zones are perfectly similar.
    /// </returns>
    public override double CalculateSimilarity(Vintasoft.Imaging.FormsProcessing.TemplateMatching.KeyZone zone)
    {
        BarcodeKeyZone barcodeZone = zone as BarcodeKeyZone;
        if (barcodeZone == null)
            return 0;

        if (barcodeZone.BarcodeInfo.Value == BarcodeInfo.Value &&
            barcodeZone.BarcodeInfo.BarcodeType == BarcodeInfo.BarcodeType)
            return 1;

        return 0;
    }

    /// <summary>
    /// Creates a new object that is a copy of the current instance.
    /// </summary>
    /// <returns>A new object that is a copy of this instance.</returns>
    public override object Clone()
    {
        return new BarcodeKeyZone(BarcodeInfo, Location);
    }

    #endregion

}


Hier ist der C#-Code der Klasse BarcodeKeyZoneRecognizerCommand, die den Befehl zum Suchen nach Schlüsselzonen definiert, die einen Barcode enthalten:
/// <summary>
/// Recognizes key zones, based on barcodes, on an image.
/// </summary>
public class BarcodeKeyZoneRecognizerCommand : Vintasoft.Imaging.FormsProcessing.TemplateMatching.KeyZoneRecognizerCommand
{

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="BarcodeKeyZoneRecognizerCommand"/> class.
    /// </summary>
    public BarcodeKeyZoneRecognizerCommand()
        : base()
    {
    }

    #endregion



    #region Properties

    static System.Collections.ObjectModel.ReadOnlyCollection<Vintasoft.Imaging.PixelFormat> _supportedNativePixelFormats;
    /// <summary>
    /// Gets a list of supported pixel formats for this processing command.
    /// </summary>
    public override System.Collections.ObjectModel.ReadOnlyCollection<Vintasoft.Imaging.PixelFormat> SupportedNativePixelFormats
    {
        get
        {
            if (_supportedNativePixelFormats == null)
            {
                System.Collections.Generic.List<Vintasoft.Imaging.PixelFormat> supportedPixelFormats = new System.Collections.Generic.List<Vintasoft.Imaging.PixelFormat>();
                supportedPixelFormats.Add(Vintasoft.Imaging.PixelFormat.BlackWhite);
                supportedPixelFormats.Add(Vintasoft.Imaging.PixelFormat.Bgr24);
                supportedPixelFormats.Add(Vintasoft.Imaging.PixelFormat.Bgr32);
                supportedPixelFormats.Add(Vintasoft.Imaging.PixelFormat.Indexed1);
                supportedPixelFormats.Add(Vintasoft.Imaging.PixelFormat.Indexed8);
                supportedPixelFormats.Add(Vintasoft.Imaging.PixelFormat.Gray8);
                _supportedNativePixelFormats = supportedPixelFormats.AsReadOnly();
            }
            return _supportedNativePixelFormats;
        }
    }

    Vintasoft.Barcode.ReaderSettings _settings = new Vintasoft.Barcode.ReaderSettings();
    /// <summary>
    /// Gets or sets the barcode reader settings.
    /// </summary>
    /// <value>
    /// The reader settings.
    /// </value>
    public Vintasoft.Barcode.ReaderSettings Settings
    {
        get
        {
            return _settings;
        }
        set
        {
            if (value == null)
                throw new System.ArgumentNullException();
            _settings = value;
        }
    }

    #endregion



    #region Methods

    /// <summary>
    /// Creates a new <see cref="BarcodeKeyZoneRecognizerCommand"/> that is a copy of the current
    /// instance.
    /// </summary>
    /// <returns>A new <see cref="BarcodeKeyZoneRecognizerCommand"/> that is a copy of this
    /// instance.</returns>
    public override object Clone()
    {
        BarcodeKeyZoneRecognizerCommand recognizer = new BarcodeKeyZoneRecognizerCommand();
        recognizer.IsNested = IsNested;
        recognizer.RegionOfInterest = RegionOfInterest;
        recognizer.Settings = Settings.Clone();
        return recognizer;
    }


    /// <summary>
    /// Recognizes key zones in the specified rectangle of the specified image.
    /// </summary>
    /// <param name="image">The image where key zones must be searched.</param>
    /// <param name="rect">The region of interest on image.</param>
    /// <returns>An array of recognized key zones.</returns>
    protected override Vintasoft.Imaging.FormsProcessing.TemplateMatching.KeyZone[] Recognize(Vintasoft.Imaging.VintasoftImage image, System.Drawing.Rectangle rect)
    {
        Vintasoft.Barcode.BarcodeReader reader = new Vintasoft.Barcode.BarcodeReader();
        reader.Settings = Settings.Clone();
        reader.Settings.ScanRectangle = rect;
        using (System.Drawing.Bitmap bitmap = image.GetAsBitmap())
        {
            Vintasoft.Barcode.IBarcodeInfo[] infos = reader.ReadBarcodes(bitmap);
            Vintasoft.Imaging.FormsProcessing.TemplateMatching.KeyZone[] result = new Vintasoft.Imaging.FormsProcessing.TemplateMatching.KeyZone[infos.Length];
            for (int i = 0; i < infos.Length; i++)
                result[i] = new BarcodeKeyZone(infos[i]);
            return result;
        }
    }

    #endregion

}