Barcode Recognition (Spire.Barcode)

deceptikon 1 Tallied Votes 2K Views Share

I love barcodes. In my line of work (image processing and document imaging), barcodes play an integral part in high quality automation. As such, I've developed code from the lowest level of encoding and decoding barcodes up to using a number of libraries for working with barcodes.

The biggest problem with libraries is cost versus functionality. Most of the commercial libraries tend to be expensive and also separate their licenses between 1D and 2D barcodes such that obtaining full functionality is even more expensive. Free barcode libraries rarely support 2D barcodes, which is a deal breaking 9 times out of 10 for me, and when they do the symbology support is weak at best. This is understandable because 2D barcodes typically involve terribly complex encoding and decoding logic, but it's still frustrating to drop thousands of dollars for a commercial library that often includes awkward licensing limitations.

In my endless search for the best library, I found Spire.Barcode. This is a freely available encoding and decoding library that supports both 1D and 2D barcodes. Further, the recognition quality is surprisingly good. That said, there is one huge con:

  • Barcode recognition returns an array of strings.

Why is this a con? Well, there are two questions which are common in barcode recognition that are not directly supported:

  1. What barcode symbology was used to detected the value?
  2. Where is the zone from which the barcode was detected?

As far as I can tell, #2 is impossible with Spire.Barcode as it is, but it's also the less common of the two questions. #1 is possible, after a fashion, which the following code snippet will handle. I wrote a barcode wrapper around Spire.Barcode that extends the functionality (with a primary focus on my WPF general imaging library) and answers question #1 in the process.

The trick, if you want to call it a trick, is to scan for each symbology separately, and store the results along with the symbology in a flattened collection. For convenience when there are multiple pages, the page index is also stored. The only downside to this approach is that it's slower. Barcode recognition in general is tricky to make fast, so adding inefficiencies to the process may be prohibitive. Spire.Barcode is not the fastest library I've used (that accolade goes to Vintasoft and Atalasoft), but it's not the slowest either.

Miscellaneous Stuff

The code snippet is self-contained barring the reference to Spire.Barcode, a supported barcode type enumeration, and a support method that converts BitmapSource to Bitmap. Even though Spire.Barcode uses a flags enumeration for its barcode types, I found that combining types with the bitwise OR operator does not work as well as I'd prefer. When combining all of the types, I received a "not supported" exception, which is silly. Further, the flags are set up in such a way that you might accidentally read unexpected barcodes (matching Codabar when looking for 3 of 9 or 128, for example). As such, I pared down the supported types to common ones I work with using another enumeration. All it does is limit the Spire enumeration:

[Flags]
public enum SupportedBarcodeType : long
{
    Code39Extended = BarCodeType.Code39Extended,
    Code93Extended = BarCodeType.Code93Extended,
    Code128 = BarCodeType.Code128,
    DataMatrix = BarCodeType.DataMatrix,
    QRCode = BarCodeType.QRCode,
    Pdf417 = BarCodeType.Pdf417,
}

In the interests of transparency, Imaging.GetBitmap is defined as follows:

/// <summary>
/// Loads a Bitmap from a BitmapSource.
/// </summary>
public static Bitmap GetBitmap(BitmapSource image)
{
    using (var stream = new MemoryStream())
    {
        var encoder = new BmpBitmapEncoder();

        encoder.Frames.Add(BitmapFrame.Create(image));
        encoder.Save(stream);

        // Bitmap objects created from a stream expect the stream
        // to stay open for the lifetime of the bitmap. We don't
        // want to manage the stream too, so copy the bitmap to
        // eliminate that dependency.
        using (var bitmap = new Bitmap(stream))
        {
            return new Bitmap(bitmap);
        }
    }
}

Enjoy!

using Spire.Barcode;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Media.Imaging;

namespace JRD.Imaging
{
    /// <summary>
    /// Represents an extracted barcode from an image.
    /// </summary>
    public class Barcode
    {
        #region Properties
        /// <summary>
        /// Gets the page index of the detected barcode.
        /// </summary>
        public int Frame { get; private set; }

        /// <summary>
        /// Gets the type of the detected barcode.
        /// </summary>
        public SupportedBarcodeType Type { get; private set; }

        /// <summary>
        /// Gets the value of the selected barcode.
        /// </summary>
        public string Value { get; private set; }
        #endregion

        #region Static Scan Interface
        /// <summary>
        /// Extracts barcodes from the provided image.
        /// </summary>
        /// <param name="image">The source image.</param>
        /// <param name="types">A list of barcode types to look for. All types will be checked if not provided.</param>
        /// <param name="zone">A search zone for barcodes. The whole page will be searched if not provided.</param>
        /// <returns>A collection of selected barcodes.</returns>
        public static IEnumerable<Barcode> Scan(Bitmap image, List<SupportedBarcodeType> types = null, Rectangle? zone = null)
        {
            return ExtractSingleFrame(image, NormalizeBarcodeTypes(types), 0, zone);
        }

        /// <summary>
        /// Extracts barcodes from the provided images.
        /// </summary>
        /// <param name="frames">A list of pages representing the image.</param>
        /// <param name="types">A list of barcode types to look for. All types will be checked if not provided.</param>
        /// <param name="zone">A search zone for barcodes. The whole page will be searched if not provided.</param>
        /// <param name="frame">The desired page to search. All pages will be searched if not provided.</param>
        /// <returns>A collection of selected barcodes.</returns>
        public static IEnumerable<Barcode> Scan(List<Bitmap> frames, List<SupportedBarcodeType> types = null, Rectangle? zone = null, int frame = -1)
        {
            var result = new List<Barcode>();

            if (frames.Count == 0)
            {
                throw new IndexOutOfRangeException("Barcode.Scan - Image contains no frames");
            }

            types = NormalizeBarcodeTypes(types);

            if (frame == -1)
            {
                for (int i = 0; i < frames.Count; i++)
                {
                    result.AddRange(ExtractSingleFrame(frames[i], types, i, zone));
                }
            }
            else
            {
                if (frame < 0 || frame >= frames.Count)
                {
                    throw new IndexOutOfRangeException("Barcode.Scan - Invalid frame index [0-" + (frames.Count - 1) + "]");
                }

                result.AddRange(ExtractSingleFrame(frames[frame], types, frame, zone));
            }

            return result;
        }

        /// <summary>
        /// Extracts barcodes from the provided image.
        /// </summary>
        /// <param name="image">The source image.</param>
        /// <param name="types">A list of barcode types to look for. All types will be checked if not provided.</param>
        /// <param name="zone">A search zone for barcodes. The whole page will be searched if not provided.</param>
        /// <returns>A collection of selected barcodes.</returns>
        public static IEnumerable<Barcode> Scan(BitmapSource image, List<SupportedBarcodeType> types = null, Rectangle? zone = null)
        {
            return Scan(Imaging.GetBitmap(image), types, zone);
        }

        /// <summary>
        /// Extracts barcodes from the provided images.
        /// </summary>
        /// <param name="frames">A list of pages representing the image.</param>
        /// <param name="types">A list of barcode types to look for. All types will be checked if not provided.</param>
        /// <param name="zone">A search zone for barcodes. The whole page will be searched if not provided.</param>
        /// <param name="frame">The desired page to search. All pages will be searched if not provided.</param>
        /// <returns>A collection of selected barcodes.</returns>
        public static IEnumerable<Barcode> Scan(List<BitmapSource> frames, List<SupportedBarcodeType> types = null, Rectangle? zone = null, int frame = -1)
        {
            return Scan(frames.ConvertAll(x => Imaging.GetBitmap(x)), types, zone, frame);
        }
        #endregion

        #region Static Helpers
        /// <summary>
        /// Extracts barcodes from the provided image frame.
        /// </summary>
        /// <param name="image">The provided image frame.</param>
        /// <param name="types">A list of barcode types to extract.</param>
        /// <param name="frame">The index of the provided frame. Set to 0 if not provided.</param>
        /// <param name="zone">A search zone for barcodes. The whole image is searched if not provided.</param>
        /// <returns>A collection of detected barcodes.</returns>
        private static IEnumerable<Barcode> ExtractSingleFrame(Bitmap image, List<SupportedBarcodeType> types, int frame = 0, Rectangle? zone = null)
        {
            Func<BarCodeType, string[]> scan = type => zone.HasValue ? BarcodeScanner.Scan(image, zone.Value, type) : BarcodeScanner.Scan(image, type);

            // Scan all supported types from the frame and flatten the result into a collection of Barcode objects
            return from x in types
                   from y in scan((BarCodeType)x)
                   select new Barcode() { Frame = frame, Type = x, Value = y };
        }

        /// <summary>
        /// Creates and populates a list of supported barcode types if necessary.
        /// </summary>
        /// <param name="types">The source list of supported barcode types from the caller.</param>
        /// <returns>A non-null list of supported types.</returns>
        /// <remarks>
        /// A null or empty source list denotes all supported types. The result list
        /// will be populated with all supported barcode types in the enumeration.
        /// </remarks>
        private static List<SupportedBarcodeType> NormalizeBarcodeTypes(List<SupportedBarcodeType> types = null)
        {
            if (types == null)
            {
                types = new List<SupportedBarcodeType>();
            }

            if (types.Count == 0)
            {
                // Add all supported types if none are selected
                foreach (SupportedBarcodeType item in Enum.GetValues(typeof(SupportedBarcodeType)))
                {
                    types.Add(item);
                }
            }

            return types;
        }
        #endregion
    }
}
castajiz_2 35 Posting Whiz

How is such code implemented in a device?. I suppose i could implement this only in Windows phone device or am I wrong.

deceptikon 1,790 Code Sniper Team Colleague Featured Poster

Spire.Barcode appears to only have a .NET build, so yes, that's a reasonable assumption concerning mobile devices.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.