.NET/C#에서 이미지 정렬을 위한 2D 바코드 사용

블로그 카테고리:바코드폼 처리.NET

2020/05/15

양식 처리 및 광학 마크 인식(OMR) 기술은 송장, 운송장 등과 같은 전자 문서 양식의 분류, 라우팅 및 템플릿을 이용한 인식에 널리 사용됩니다. 또한 시험, 설문지, 투표 용지 등의 양식에 기입된 광학 마크를 인식하는 데에도 사용됩니다.

VintaSoft Forms Processing .NET Plug-in은 양식 인식과 템플릿을 이용한 이미지 정렬에 모두 사용할 수 있습니다. 이 문서에서는 이미지 식별에 선 대신 바코드를 사용하여 인식 정확도와 신뢰성을 향상시키는 방법을 설명합니다.

이미지 정렬 과정은 템플릿을 이용한 이미지 식별과 변환 행렬 생성으로 구성됩니다. 변환 행렬은 템플릿에 대한 이미지의 오프셋, 압축 및 회전을 정의합니다. 다음으로, 이 변환 행렬을 사용하여 이미지를 정렬합니다.

바코드를 기반으로 하는 키 영역을 사용하는 템플릿으로 이미지를 식별합니다.


이미지 식별은 TemplateMatchingCommand 클래스를 사용하여 수행됩니다. 식별이 성공적으로 완료되면 템플릿에 대한 이미지의 오프셋, 압축 및 회전을 정의하는 변환 행렬이 생성됩니다.

TemplateMatchingCommand 클래스는 입력 이미지와 템플릿 이미지의 특징을 생성하고, 이 특징들을 비교하여 이미지가 유사한지 판단합니다. 기본적으로 이 클래스는 직선을 기준으로 이미지 특징을 생성합니다.

이 문서에서는 이미지 각인을 생성하는 알고리즘을 변경하고, 이미지 각인 생성을 위해 컴팩트한 2D 바코드인 Aztec Rune을 사용합니다. Aztec Rune은 다음과 같은 이유로 우리의 작업에 완벽하게 적합합니다.

바코드를 기반으로 하는 키 영역을 사용하여 이미지를 식별하려면 다음 단계를 수행해야 합니다.


바코드를 기반으로 하는 키 영역을 사용하는 템플릿으로 이미지를 정렬합니다.


이미지 정렬은 TemplateAligningCommand 클래스를 사용하여 수행됩니다. 이 클래스는 이미지 식별 중에 얻은 변환 행렬을 사용합니다.

바코드를 기반으로 하는 키 영역을 사용하여 이미지를 정렬하려면 다음 단계를 수행해야 합니다.


이미지 정렬 예시


다음은 템플릿 이미지로 사용할 이미지입니다.
Document template image with key zones based on barcodes

이 이미지에는 일직선상에 있지 않은 3개의 바코드(즉, 3개의 기준점)가 포함되어 있으며, 이는 알고리즘의 정확한 작동에 필요한 최소 기준점 수입니다. 바코드가 손상되거나 잘릴 수 있는 경우를 대비하여 더 많은 바코드를 사용할 수도 있습니다.
바코드는 페이지의 어느 위치에든 배치할 수 있지만, 문서 가장자리에 배치할 때 왜곡 식별 정확도가 가장 높습니다.

3개의 테스트 문서 이미지를 사용해 보겠습니다.

바코드를 기반으로 주요 영역이 표시된 90도 회전된 문서 이미지입니다.
90-degree rotated document image with key zones based on barcodes

바코드를 기반으로 주요 영역이 표시된 150도 회전 및 크기 조정된 문서 이미지입니다.
150-degree rotated and scaled document image with key zones based on barcodes

바코드를 기반으로 주요 영역이 표시된 180도 회전 및 크기 조정된 문서 이미지입니다.
180-degree rotated and scaled document image with key zones based on barcodes


이미지 비교 및 ​​정렬 프로세스를 수행하는 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)
{
    // 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();
        }
    }
}

애플리케이션 실행 후 회전 및 크기 조정되지 않은 문서 이미지를 얻게 됩니다.

바코드를 기반으로 키존을 적용한 90도 회전 문서 이미지의 폼 인식 결과입니다.
The form recognition result for 90-degrees rotated document image with keyzones based on barcodes

바코드를 기반으로 키존을 적용한 150도 회전 및 확대/축소된 문서 이미지의 폼 인식 결과입니다.
The form recognition result for 150-degree rotated and scaled document image with keyzones based on barcodes

바코드를 기반으로 키존을 적용한 180도 회전 및 확대/축소된 문서 이미지의 폼 인식 결과입니다.
The form recognition result for 180-degree rotated and scaled document image with keyzones based on barcodes



이 문서에서 사용된 소스 코드입니다.


바코드를 포함하는 키존을 판별하는 BarcodeKeyZone 클래스의 C# 코드입니다.
/// <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

}


바코드를 포함하는 키존을 검색하는 명령을 정의하는 BarcodeKeyZoneRecognizerCommand 클래스의 C# 코드입니다.
/// <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

}