Использование 2D штрих-кодов для выравнивания изображений в .NET/C#

Категория: Штрих-кодыОбработка форм документов.NET

15 мая 2020

Технология обработки форм и распознавания оптических меток (OMR) широко используется для классификации и распознавания электронных документов (счета, накладные, ...) по шаблонам форм документов. Также технология используется для распознавания оптических меток заполненных в формах тестов, анкет, бюллетеней.

VintaSoft Forms Processing .NET Plug-in может быть использован для распознавания форм документов и выравнивания изображений по шаблону. Эта статья показывает как использовать штрих-коды вместо линий для идентификации изображений и тем самым повысить точность и надежность распознавания.

Процесс выравнивания изображения состоит из идентификация изображения по шаблону и получения матрицы преобразования, которая определяет смещения, сжатие и поворот изображения относительно шаблона. Далее с помощью матрицы преобразования производится выравнивание изображения.

Идентификация изображения по шаблону, который использует ключевые зоны на базе штрих-кодов


Идентификация изображения производится с помощью класса Vintasoft.Imaging.FormsProcessing.TemplateMatching.TemplateMatchingCommand. Результатом успешной идентификации изображения является матрица преобразования, которая определяет смещения, сжатие и поворот изображения относительно шаблона.

Класс Vintasoft.Imaging.FormsProcessing.TemplateMatching.TemplateMatchingCommand создает отпечаток входного изображения и отпечаток шаблона изображения, а потом сравнивает созданные отпечатки чтобы определить, являются ли изображения похожими. По умолчанию, класс создает отпечаток изображения на основе набора прямых линий.

В этой статье мы изменим алгоритм создания отпечатка изображения и мы будем использовать 2-х мерные штрих-коды Aztec Rune для создания отпечатка изображения. Штрих-код Aztec Rune идеально подходит для нашей задачи, потому что:

Для идентификации изображения с помощью ключевых зон на базе штрих-кодов необходимо выполнить следующие действия:


Выравнивание изображения по шаблону, который использует ключевые зоны на базе штрих-кодов


Выравнивание изображения производится с помощью класса Vintasoft.Imaging.FormsProcessing.TemplateMatching.TemplateAligningCommand. Класс использует матрицу трансформации, которая получена на первом шаге при идентификации изображения.

Для выравнивания изображения с помощью ключевых зон на базе штрих-кодов необходимо выполнить следующие действия:


Пример выравнивания изображений по шаблону, который использует ключевые зоны на базе штрих-кодов


Вот изображение, которое мы будем использовать в качестве шаблона:


Три штрих-кода не лежащие на одной прямой (то есть три опорные точки) это минимальное количество штрих-кодов, которое необходимо для корректной работы алгоритма. Можно использовать больше штрих-кодов на случай, если некоторые штрих-коды могут быть испорчены или обрезаны. Штрих-коды могут располагаться в любом месте страницы, однако наилучшая точность определения искажения достигается тогда, когда штрих-коды будут находится в углах документа.

Для тестирования возьмем 3 изображения документа.

Вот повернутое на 90 градусов изображение документа, которое содержит ключевые зоны на базе штрихкода:
Повернутое на 90 градусов изображение документа с ключевыми зонами на базе штрих-кодов

Вот повернутое на 150 градусов и масштабированное изображение документа, которое содержит ключевые зоны на базе штрихкода:
Повернутое на 150 градусов и масштабированное изображение документа с ключевыми зонами на базе штрих-кодов

Вот повернутое на 180 градусов и масштабированное изображение документа, которое содержит ключевые зоны на базе штрихкода:
Повернутое на 180 градусов и масштабированное изображение документа с ключевыми зонами на базе штрих-кодов


Вот пример тестового C# консольного приложения, которое выполняет процесс идентификации и выравнивания изображений:
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)
{
    // создать распознаватель ключевой зоны на базе штрих-кода
    BarcodeKeyZoneRecognizerCommand barcodeRecognizer = new BarcodeKeyZoneRecognizerCommand();
    // установить настройки распознавателя штрих-кодов
    barcodeRecognizer.Settings.ScanBarcodeTypes = Vintasoft.Barcode.BarcodeType.Aztec;
    barcodeRecognizer.Settings.ScanDirection = Vintasoft.Barcode.ScanDirection.Horizontal | Vintasoft.Barcode.ScanDirection.Vertical;
    barcodeRecognizer.Settings.AutomaticRecognition = true;
    // указать, что изображение содержит 3 штрих-кода
    barcodeRecognizer.Settings.ExpectedBarcodes = 3;

    // создать генератор отпечатков, который использует ключевые зоны на базе штрих-кодов
    Vintasoft.Imaging.FormsProcessing.TemplateMatching.ImageImprintGeneratorCommand barcodeImprintGenerator = new Vintasoft.Imaging.FormsProcessing.TemplateMatching.ImageImprintGeneratorCommand(barcodeRecognizer);

    // создать команду для сравнения шаблонов
    Vintasoft.Imaging.FormsProcessing.TemplateMatching.TemplateMatchingCommand templateMatchingCommand = new Vintasoft.Imaging.FormsProcessing.TemplateMatching.TemplateMatchingCommand();
    // указать, что команда для сравнения шаблонов должна использовать генератор отпечатков, который использует ключевые зоны на базе штрих-кодов
    templateMatchingCommand.ImageImprintGenerator = barcodeImprintGenerator;

    // для каждого файла с изображением шаблона
    foreach (string template in templates)
    {
        // добавить файла с изображением шаблона в список шаблонов команды для сравнения шаблонов
        templateMatchingCommand.TemplateImages.Add(template);
    }

    // для каждого входного файла изображения
    foreach (string imageFilename in images)
    {
        // создать входное изображение
        using (Vintasoft.Imaging.VintasoftImage image = new Vintasoft.Imaging.VintasoftImage(imageFilename))
        {
            // найти шаблон для изображения
            templateMatchingCommand.ExecuteInPlace(image);
            // получить результат сравнения изображения с шаблоном
            Vintasoft.Imaging.FormsProcessing.TemplateMatching.ImageImprintCompareResult compareResult = templateMatchingCommand.Result.ImageCompareResult;

            // напечатать имя входного файла
            System.Console.WriteLine(imageFilename);
            // напечатать результат сравнения изображения и шаблона
            System.Console.WriteLine(string.Format(" IsReliable ={0}", compareResult.IsReliable));

            // если для изображения найден шаблон
            if (compareResult.IsReliable)
            {
                // напечатать информацию о уровне доверия к результату сравнения изображения и шаблона
                System.Console.WriteLine(string.Format(" Confidence ={0}%", compareResult.Confidence * 100));
                // напечатать информацию о матрице, которая определяет трансформацию изображения по отношению к шаблону
                System.Console.WriteLine(string.Format(" TransformMatrix ={0}", compareResult.TransformMatrix));

                // создать команду для выравнивания изображения по шаблону
                Vintasoft.Imaging.FormsProcessing.TemplateMatching.TemplateAligningCommand templateAligningCommand = new Vintasoft.Imaging.FormsProcessing.TemplateMatching.TemplateAligningCommand();
                // указать, что команда для выравнивания изображения должна использовать матрицу, которая определяет трансформацию изображения по отношению к шаблону
                templateAligningCommand.CompareResult = compareResult;
                // выполнить выравнивание изображения
                templateAligningCommand.ExecuteInPlace(image);
                // сохранить выровненное изображение в файл
                image.Save(string.Format("{0}_result.png", System.IO.Path.GetFileNameWithoutExtension(imageFilename)));
            }
            System.Console.WriteLine();
        }
    }
}

После выполнения приложения мы получим изображения документа, которые являются не повернутыми и имеют масштаб шаблона.

Вот результат распознавания форм для повернутого на 90 градусов изображения документа с ключевыми зонами на базе штрих-кодов:
Результат распознавания форм для повернутого на 90 градусов изображения документа с ключевыми зонами на базе штрих-кодов

Вот результат распознавания форм для повернутого на 150 градусов и масштабированного изображения документа с ключевыми зонами на базе штрих-кодов:
Результат распознавания форм для повернутого на 150 градусов и масштабированного изображения документа с ключевыми зонами на базе штрих-кодов

Вот результат распознавания форм для повернутого на 180 градусов и масштабированного изображения документа с ключевыми зонами на базе штрих-кодов:
Результат распознавания форм для повернутого на 180 градусов и масштабированного изображения документа с ключевыми зонами на базе штрих-кодов



Исходные коды, которые используются в данной статье


Вот C# код класса BarcodeKeyZone, который определяет ключевую зону в которой находится штрих-код:
/// <summary>
/// Представляет ключевую зону на базе штрих-кода.
/// </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

}


Вот C# код класса BarcodeKeyZoneRecognizerCommand, который определяет команду для поиска ключевых зон с штрих-кодами:
/// <summary>
/// Представляет команду для поиска ключевых зон с штрих-кодами на изображении.
/// </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

}