Utilisation de codes-barres 2D pour l'alignement d'images en .NET/C#

Catégorie du blog: Code-barresTraitement des formulaires.NET

15.05.2020

La technologie de traitement de formulaires et de reconnaissance de marques optiques (OMR) est largement utilisée pour la classification, le routage et la reconnaissance, à l'aide de modèles, de formulaires de documents électroniques tels que les factures, les lettres de transport, etc.; la reconnaissance des marques optiques apposées dans un formulaire de test, un questionnaire, un bulletin de vote, etc.

VintaSoft Forms Processing .NET Plug-in permet la reconnaissance de formulaires et l'alignement d'images à l'aide de modèles. Cet article explique comment utiliser des codes-barres plutôt que des lignes pour l'identification d'images, améliorant ainsi la précision et la fiabilité de la reconnaissance.

Le processus d'alignement d'images consiste à identifier l'image à l'aide d'un modèle et à obtenir la matrice de transformation, qui définit le décalage, la compression et la rotation de l'image par rapport au modèle. Ensuite, la matrice de transformation est utilisée pour aligner l'image.

Identifier une image par modèle, utilisant des zones clés basées sur des codes-barres.


L'identification de l'image est réalisée à l'aide de la classe TemplateMatchingCommand. En cas d'identification réussie, le résultat est une matrice de transformations qui définit les décalages, la compression et la rotation de l'image par rapport au modèle.

La classe TemplateMatchingCommand crée une empreinte de l'image d'entrée et de l'image modèle, compare les empreintes et détermine si les images sont similaires. Par défaut, la classe génère une empreinte d'image basée sur des lignes droites.

Dans cet article, nous allons modifier l'algorithme de génération d'une empreinte d'image et utiliser des codes-barres 2D compacts de type rune aztèque. La rune aztèque est parfaitement adaptée à notre tâche, car:

Pour l'identification d'images à l'aide de zones clés basées sur des codes-barres, nous devons procéder comme suit:


Alignez l'image à l'aide d'un modèle utilisant des zones clés basées sur des codes-barres.


L'alignement de l'image est effectué à l'aide de la classe TemplateAligningCommand. Cette classe utilise la matrice des transformations obtenues lors de l'identification de l'image.

Pour aligner une image à l'aide de zones clés basées sur des codes-barres, procédez comme suit:


Exemple d'alignement d'image


Voici une image qui nous servira de modèle:
Document template image with key zones based on barcodes

Elle contient 3 codes-barres non colinéaires (soit 3 points de référence), le nombre minimal requis pour le bon fonctionnement de l’algorithme. Il est possible d’utiliser davantage de codes-barres s’ils sont endommagés ou tronqués.
Les codes-barres peuvent être placés n’importe où sur la page, mais la meilleure précision d’identification des distorsions est obtenue lorsqu’ils sont situés sur les bords du document.

Utilisons 3 images de documents de test.

Voici une image de document pivotée à 90 degrés avec des zones clés basées sur les codes-barres:
90-degree rotated document image with key zones based on barcodes

Voici une image de document pivotée à 150 degrés et mise à l'échelle avec des zones clés basées sur les codes-barres:
150-degree rotated and scaled document image with key zones based on barcodes

Voici une image de document pivotée à 180 degrés et mise à l'échelle avec des zones clés basées sur les codes-barres:
180-degree rotated and scaled document image with key zones based on barcodes


Voici un exemple d'application console C# qui effectue le processus de comparaison et d'alignement d'images:
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();
        }
    }
}

Après l'exécution de l'application, nous obtiendrons des images de documents non pivotées et non mises à l'échelle.

Voici le résultat de la reconnaissance de formulaire pour une image de document pivotée à 90 degrés avec des zones clés basées sur les codes-barres:
The form recognition result for 90-degrees rotated document image with keyzones based on barcodes

Voici le résultat de la reconnaissance de formulaire pour une image de document pivotée et mise à l'échelle à 150 degrés avec des zones clés basées sur les codes-barres:
The form recognition result for 150-degree rotated and scaled document image with keyzones based on barcodes

Voici le résultat de la reconnaissance de formulaire pour une image de document pivotée et mise à l'échelle à 180 degrés avec des zones clés basées sur les codes-barres:
The form recognition result for 180-degree rotated and scaled document image with keyzones based on barcodes



Codes sources utilisés dans cet article


Voici le code C# de la classe BarcodeKeyZone, qui détermine la zone clé contenant un code-barres:
/// <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

}


Voici le code C# de la classe BarcodeKeyZoneRecognizerCommand, qui définit la commande de recherche des zones clés contenant un code-barres:
/// <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

}