Click here to Skip to main content
Click here to Skip to main content
Go to top

Read Barcodes from an Image

, 31 Jan 2006
Rate this:
Please Sign up or sign in to vote.
How to read two-width barcodes from an image in VB.NET.

Introduction

The following article describes an algorithm in VB.NET to detect 2 of 5 interleaved barcodes, which are widely used in industrial applications like document management (DMS). The algorithm can easily be extended to detect every other two-width barcode like 2 of 5 standard and Code 39.

Background

If you want to read barcodes from an image in professional applications, for example in document processing, you mainly face two problems:

  1. You have to use an expensive barcode tool, mainly because of runtime licenses.
  2. Your customer wants you to read barcode from documents which are of bad quality (fax, copies, photos, …).

The solution: Develop an algorithm on your own and tune it to the needs of your customer. This article explains how to develop a generic algorithm for two-width barcodes and a working example to read 2 of 5 interleaved.

Using the code

Scanning the barcodes is as simple as this:

Dim bmp As New Bitmap("d:\sample2of5.bmp") 
Dim scanner As New gmseNWScanner(_
        New gmseBarcodeDef2of5interleaved)
Dim result As gmseNWResult

Scanner.Scan(bmp)
For Each result In scanner.Result
    Debug.WriteLine(result.Text)
next

The main class is gmseNWScanner. It contains the generic scanning algorithm for two-width barcodes. The parameters are defined in the abstract class gmseNWBarcodeDef. The class gmseBarcodeDef2of5interleaved implements gmseNWBarcodeDef to decode 2 of 5 interleaved. The detected barcodes are saved in the following classes: gmseNWResult and gmseNWResultCollection.

Points of interest

Two-Width barcodes are represented with only two bar types: a narrow bar (n) and a wide bar (w). The tricky part is to get this representation from an image. Translating to text is straightforward. Scanning is done row by row (see chart). The array cBarWidth is computed. It contains the width of each bar in the current row. cBarWidth is scanned from left to right. The algorithm takes a subset of cBarWidth to find a candidate for a valid barcode. The subset is considered to be a candidate if the bars can be classified as narrow and wide bars. If a candidate is found, the algorithm extends the subset until a bar is found, this cannot be classified as a narrow or wide bar. The subset is then translated to the nw representation. The nw representation is then translated to the text with a Hashtable.

If you play with the algorithm you will see that sometimes regions in the image are identified as a barcode by accident. This is a common problem with barcode detection. Being restrictive in filtering out the unwanted regions will lead to an algorithm that does not detect barcodes on low quality images. So the best solution is to add a checksum. If possible, do some additional tests like using a fixed length of the coded text.

References

History

  • 01-24-06: Original article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

mackenb

Germany Germany
No Biography provided

Comments and Discussions

 
QuestionDetails of the code ... ? [modified] PinmemberMember 1103734328-Aug-14 20:24 
AnswerRe: Details of the code ... ? PinmemberMember 1103734328-Aug-14 23:36 
QuestionReading Barcode from an image PinmemberZeenY25-Feb-08 23:25 
AnswerRe: Reading Barcode from an image Pinmembersteve7g10-Jan-12 16:31 
GeneralReading barcode type 39 Pinmemberciapal18-Feb-08 23:52 
GeneralRe: Reading barcode type 39 Pinmembercyos25-Apr-11 20:30 
GeneralRe: Reading barcode type 39 Pinmembercyos25-Apr-11 20:44 
' Original Author: Georg Mackenbrock (c) 2006
' Barcode Reader for code 39
' Edited by 39412
Imports System.Text
Imports System.Drawing
Imports System.Drawing.Imaging
 
#Region "Barcode Structures"
Public Class gmseNWResult
Public Text As String
Public Count As Integer
End Class
Public Class gmseNWResultCollection
Implements IEnumerable
 
Dim hash As New Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default)
 
ReadOnly Property Count() As Integer
Get
Return hash.Count
End Get
End Property
ReadOnly Property Item(ByVal Index As Integer) As gmseNWResult
Get
Return hash(Index)
End Get
End Property
Public Sub Add(ByVal Text As String)
Dim r As gmseNWResult
 
r = hash(Text)
If r Is Nothing Then
r = New gmseNWResult
r.Text = Text
hash.Add(r.Text, r)
End If
r.Count += 1
End Sub
Public Sub Remove(ByVal MinHeight As Integer)
Dim r As gmseNWResult
Dim tmp As New Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default)
 
For Each r In hash.Values
If r.Count >= MinHeight Then
tmp.Add(r.Text, r)
End If
Next
hash = tmp
End Sub
 

Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return hash.Values.GetEnumerator
End Function
End Class
#End Region
 
' Definition of one Symbol
Public Class gmseNWBarcodeSymbol
Public Value As Integer
Public Text As String
' Encoding of Symbol in NW notation
Public Encoding As String
 
Public Sub New(ByVal Value As Integer, ByVal Text As String, ByVal Encoding As String)
Me.Value = Value
Me.Text = Text
Me.Encoding = Encoding
End Sub
End Class
 
' Abstract parameters für barcode decoding for further extensions (2 of 5 standard,...)
Public MustInherit Class gmseNWBarcodeDef
' Minimum count of bars for a valid barcode.
MustOverride ReadOnly Property MinBarCount() As Integer
' Decode nw representation to text.
Public MustOverride Function Decode(ByVal symbol As String) As String
' NWidth * Fkt <WWidth (relation between narrow width and wide width.
Overridable ReadOnly Property MaxFktNW() As Integer
Get
Return 10
End Get
End Property
' upped the tolerance to 67% due to bad scans 39412
Overridable ReadOnly Property PctNWTolerance() As Double
Get
Return 0.67
End Get
End Property
' Min length of white space before first bar and after last bar in percent of wide bar (Quiet zone).
Overridable ReadOnly Property PctMinWhiteSpace() As Double
Get
Return 0
End Get
End Property
' Barcode must be at least MinHeight height.
Overridable ReadOnly Property MinHeight() As Integer
Get
Return 2
End Get
End Property
End Class
 
' Definition 2 of 5 interleaved
Public Class gmseBarcodeDef2of5interleaved
Inherits gmseNWBarcodeDef
 
Dim StartSymbol As gmseNWBarcodeSymbol
Dim StopSymbol As gmseNWBarcodeSymbol
Dim hash As New Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default)
 
' We only accept barcodes with at least 23 bars.
Public Overrides ReadOnly Property MinBarCount() As Integer
Get
Return 23
End Get
End Property
 
Private Sub CreateSymbolTable()
'in code 39 there are 43 chars
Dim BaseSymbol(43) As gmseNWBarcodeSymbol
Dim sym As gmseNWBarcodeSymbol
Dim x As Integer
Dim Text As String
Dim i As Integer
 
'code 39 symbols
BaseSymbol(0) = New gmseNWBarcodeSymbol(0, "0", "NNNWWNWNN")
BaseSymbol(1) = New gmseNWBarcodeSymbol(1, "1", "WNNWNNNNW")
BaseSymbol(2) = New gmseNWBarcodeSymbol(2, "2", "NNWWNNNNW")
BaseSymbol(3) = New gmseNWBarcodeSymbol(3, "3", "WNWWNNNNN")
BaseSymbol(4) = New gmseNWBarcodeSymbol(4, "4", "NNNWWNNNW")
BaseSymbol(5) = New gmseNWBarcodeSymbol(5, "5", "WNNWWNNNN")
BaseSymbol(6) = New gmseNWBarcodeSymbol(6, "6", "NNWWWNNNN")
BaseSymbol(7) = New gmseNWBarcodeSymbol(7, "7", "NNNWNNWNW")
BaseSymbol(8) = New gmseNWBarcodeSymbol(8, "8", "WNNWNNWNN")
BaseSymbol(9) = New gmseNWBarcodeSymbol(9, "9", "NNWWNNWNN")
BaseSymbol(10) = New gmseNWBarcodeSymbol(10, "A", "WNNNNWNNW")
BaseSymbol(11) = New gmseNWBarcodeSymbol(11, "B", "NNWNNWNNW")
BaseSymbol(12) = New gmseNWBarcodeSymbol(12, "C", "WNWNNWNNN")
BaseSymbol(13) = New gmseNWBarcodeSymbol(13, "D", "NNNNWWNNW")
BaseSymbol(14) = New gmseNWBarcodeSymbol(14, "E", "WNNNWWNNN")
BaseSymbol(15) = New gmseNWBarcodeSymbol(15, "F", "NNWNWWNNN")
BaseSymbol(16) = New gmseNWBarcodeSymbol(16, "G", "NNNNNWWNW")
BaseSymbol(17) = New gmseNWBarcodeSymbol(17, "H", "WNNNNWWNN")
BaseSymbol(18) = New gmseNWBarcodeSymbol(18, "I", "NNWNNWWNN")
BaseSymbol(19) = New gmseNWBarcodeSymbol(19, "J", "NNNNWWWNN")
BaseSymbol(20) = New gmseNWBarcodeSymbol(20, "K", "WNNNNNNWW")
BaseSymbol(21) = New gmseNWBarcodeSymbol(21, "L", "NNWNNNNWW")
BaseSymbol(22) = New gmseNWBarcodeSymbol(22, "M", "WNWNNNNWN")
BaseSymbol(23) = New gmseNWBarcodeSymbol(23, "N", "NNNNWNNWW")
BaseSymbol(24) = New gmseNWBarcodeSymbol(24, "O", "WNNNWNNWN")
BaseSymbol(25) = New gmseNWBarcodeSymbol(25, "P", "NNWNWNNWN")
BaseSymbol(26) = New gmseNWBarcodeSymbol(26, "Q", "NNNNNNWWW")
BaseSymbol(27) = New gmseNWBarcodeSymbol(27, "R", "WNNNNNWWN")
BaseSymbol(28) = New gmseNWBarcodeSymbol(28, "S", "NNWNNNWWN")
BaseSymbol(29) = New gmseNWBarcodeSymbol(29, "T", "NNNNWNWWN")
BaseSymbol(30) = New gmseNWBarcodeSymbol(30, "U", "WWNNNNNNW")
BaseSymbol(31) = New gmseNWBarcodeSymbol(31, "V", "NWWNNNNNW")
BaseSymbol(32) = New gmseNWBarcodeSymbol(32, "W", "WWWNNNNNN")
BaseSymbol(33) = New gmseNWBarcodeSymbol(33, "X", "NWNNWNNNW")
BaseSymbol(34) = New gmseNWBarcodeSymbol(34, "Y", "WWNNWNNNN")
BaseSymbol(35) = New gmseNWBarcodeSymbol(35, "Z", "NWWNWNNNN")
BaseSymbol(36) = New gmseNWBarcodeSymbol(36, "-", "NWNNNNWNW")
BaseSymbol(37) = New gmseNWBarcodeSymbol(37, ".", "WWNNNNWNN")
BaseSymbol(38) = New gmseNWBarcodeSymbol(38, " ", "NWWNNNWNN")
BaseSymbol(39) = New gmseNWBarcodeSymbol(39, "$", "NWNWNWNNN")
BaseSymbol(40) = New gmseNWBarcodeSymbol(40, "/", "NWNWNNNWN")
BaseSymbol(41) = New gmseNWBarcodeSymbol(41, "+", "NWNNNWNWN")
BaseSymbol(42) = New gmseNWBarcodeSymbol(42, "%", "NNNWNWNWN")
 
' Buildung the hash for decoding:
For x = 0 To 42
Text = ""
For i = 0 To 8
Text &= BaseSymbol(x).Encoding.Chars(i)
Next
sym = New gmseNWBarcodeSymbol(x, BaseSymbol(x).Text, Text)
hash.Add(sym.Encoding, sym)
Next
 
StartSymbol = New gmseNWBarcodeSymbol(-1, "Start", "NWNNWNWNN")
'in code 39 the start and stop symbols are the same
End Sub
 
Public Sub New()
CreateSymbolTable()
End Sub
 
Public Overrides Function Decode(ByVal symbol As String) As String
Return Decode1(symbol, 0)
End Function
 
' Get human readable text from the barcode.
Public Function Decode1(ByVal symbol As String, ByVal offset As Integer) As String
Dim sym As String
Dim sb As New StringBuilder
Dim Pos As Integer = offset
Dim bi As gmseNWBarcodeSymbol
 
sym = symbol.Substring(Pos, StartSymbol.Encoding.Length)
' Make sure that the first symbol is the start symbol
If String.Compare(sym, StartSymbol.Encoding, True) <> 0 Then
Return ""
End If
' Get past the start symbol so we can start decoding.
Pos += StartSymbol.Encoding.Length + 1
 
' Data
While Pos < symbol.Length
'symbols are 9 bars long plus the narrow whitespace
If symbol.Length - Pos > 10 Then
sym = symbol.Substring(Pos, 9)
bi = hash(sym)
If bi Is Nothing Then
Return ""
End If
sb.Append(bi.Text)
Pos += 10
ElseIf String.Compare(sym, StartSymbol.Encoding, True) <> 0 Then ' StopSymbol and start symbol are the same
sym = symbol.Substring(Pos, 9)
bi = hash(sym)
If bi Is Nothing Then
Return ""
Else
Return sb.ToString
End If
Else
Return ""
End If
End While
Return ""
End Function
End Class
 
' Scanning algorithm:
Public Class gmseNWScanner
Dim cBitMask(7) As Byte
Dim cScanRow() As Byte
Dim nBar As Integer
Dim cBarWidth() As Integer
Dim cBarBlack() As Boolean
Dim cOffset As Integer
Dim cLength As Integer
Dim cNWidth As Integer
Dim cWWidth As Integer
Dim cBCDef As gmseNWBarcodeDef
Dim cResult As gmseNWResultCollection
 
ReadOnly Property Result() As gmseNWResultCollection
Get
Return cResult
End Get
End Property
' Find all Code 39 barcodes in Bitmap
Public Sub Scan(ByVal bmp As Bitmap)
Dim data As BitmapData
Dim ScanRow() As Byte
Dim y As Integer
Dim x As Integer
 
data = bmp.LockBits(New Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed)
Try
cResult = New gmseNWResultCollection
ReDim ScanRow(data.Stride - 1)
' Scan all rows of bitmap.
For y = 0 To bmp.Height - 1
For x = 0 To data.Stride - 1
ScanRow(x) = System.Runtime.InteropServices.Marshal.ReadByte(data.Scan0, data.Stride * y + x)
Next
Scan(ScanRow, 0, bmp.Width)
Next
cResult.Remove(cBCDef.MinHeight)
Finally
bmp.UnlockBits(data)
End Try
End Sub
 
' ScanRow must be 1bppp format.
Private Sub Scan(ByVal ScanRow() As Byte, ByVal offset As Integer, ByVal Length As Integer)
cScanRow = ScanRow
cLength = Length
cOffset = offset
CalcBars()
Scan()
End Sub
 
' Calculate bars
Private Sub CalcBars()
Dim x As Integer
Dim bl As Boolean
 
nBar = 0
ReDim cBarWidth(10)
ReDim cBarBlack(10)
 
cBarBlack(0) = IsBlack(cOffset)
For x = 0 To cLength - 1
If nBar >= cBarWidth.Length - 1 Then
ReDim Preserve cBarWidth(nBar * 2)
ReDim Preserve cBarBlack(nBar * 2)
End If
bl = IsBlack(cOffset + x)
If cBarBlack(nBar) = bl Then
cBarWidth(nBar) += 1
Else
nBar += 1
cBarBlack(nBar) = bl
cBarWidth(nBar) = 1
End If
Next
nBar += 1
End Sub
 
' The scan
Private Sub Scan()
Dim Pos As Integer
Dim ValidNW As Boolean
Dim BCStart As Integer
Dim BCEnde As Integer
Dim Text As String
Dim sb As StringBuilder
Dim nwt As Integer
 
' We want a leading white space, so we start at position 1
Pos = 1
While Pos < nBar - cBCDef.MinBarCount
' Start with a black bar
If cBarBlack(Pos) Then
' Try to validate with a sample set, if we can classify the bars in N and W bars within a tolerance of 25%.
ValidNW = CalcNWWidth(Pos, cBCDef.MinBarCount)
If ValidNW Then ' n und w are OK.
sb = New StringBuilder
BCStart = Pos
BCEnde = Pos
' Start to decode, until no more valid bars are found.
While Pos < nBar
 
nwt = NWBarType(cBarWidth(Pos))
If nwt = -1 Then
Exit While
ElseIf nwt = 0 Then
sb.Append("n")
Else
sb.Append("w")
End If
BCEnde += 1
Pos += 1
End While
BCEnde -= 1
Text = cBCDef.Decode(sb.ToString)
If Text <> "" Then
cResult.Add(Text)
Debug.WriteLine(Text)
End If
Else
Pos += 1
End If
Else
Pos += 1
End If
End While
End Sub
 
' Classify width of a bar in -1: invalid, 0 N(arrow), 1 W(ide).
Private Function NWBarType(ByVal Width As Integer) As Integer
Dim MaxAbweichung As Double
Dim Abweichung As Integer
 
Abweichung = Math.Min(Math.Abs(Width - cNWidth), Math.Abs(Width - cWWidth))
MaxAbweichung = (cWWidth - cNWidth) * cBCDef.PctNWTolerance
If Abweichung > MaxAbweichung Then
Return -1
ElseIf Math.Abs(Width - cNWidth) < Math.Abs(Width - cWWidth) Then
Return 0
Else
Return 1
End If
 
End Function
 
' Calculate Narrow and Wide bar width.
Private Function CalcNWWidth(ByVal Pos As Integer, ByVal Length As Integer) As Boolean
Dim NWidth As Integer
Dim WWidth As Integer
Dim tmp As Integer
Dim Abweichung As Double
Dim MaxAbweichung As Double
Dim i As Integer
 
' Calc Min and Max.
NWidth = cBarWidth(Pos)
WWidth = cBarWidth(Pos)
For i = 0 To Length - 1
tmp = cBarWidth(Pos + i)
If tmp > WWidth Then
WWidth = tmp
End If
If tmp < NWidth Then
NWidth = tmp
End If
Next
 
' As wide we accept as maximum NWidth * cBCDef.MaxFktNW
If NWidth * cBCDef.MaxFktNW < WWidth Then
cWWidth = 0
cNWidth = 0
Return False
End If
 
Abweichung = 0
' Calculate Tolerance
For i = 0 To Length - 1
tmp = Math.Min(Math.Abs(cBarWidth(Pos + i) - NWidth), Math.Abs(cBarWidth(Pos + i) - WWidth))
If tmp > Abweichung Then
Abweichung = tmp
End If
Next
 
MaxAbweichung = (WWidth - NWidth) * cBCDef.PctNWTolerance
If Abweichung > MaxAbweichung Then
cWWidth = 0
cNWidth = 0
Return False
Else
cWWidth = WWidth
cNWidth = NWidth
Return True
End If
End Function
 
' Is the pixel black?
Private ReadOnly Property IsBlack(ByVal x As Integer) As Boolean
Get
Return (cScanRow(x \ 8) And cBitMask(x Mod 8)) = 0
End Get
End Property
 
' Hepler for calculating pixel color.
Private Sub CreateBitMask()
Dim b As Byte = 1
Dim i As Integer
 
For i = 0 To 7
cBitMask(7 - i) = b
If i < 7 Then
b = b * 2
End If
Next
End Sub
 
Public Sub New(ByVal bcd As gmseNWBarcodeDef)
cBCDef = bcd
CreateBitMask()
End Sub
End Class
GeneralRe: Reading barcode type 39 Pinmembercyos25-Apr-11 20:46 
QuestionThe Speed for barcode decoder Pinmemberjackjack8916-Apr-07 23:30 
QuestionChange BMP File Error! Pinmemberzfq30820-Nov-06 16:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140921.1 | Last Updated 31 Jan 2006
Article Copyright 2006 by mackenb
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid