Download as ppt, pdf, or txt
Download as ppt, pdf, or txt
You are on page 1of 63

Decoding Barcodes

Institute for Personal Robots in Education


CS 1 with Robots
(IPRE)
Barcodes are designed to be machine readable
They encode numbers and symbols using black and white
bars.
The examples on this page are standard 1D barcodes
using the Code39 encoding scheme.
Usually read by laser scanners, they can also be read
using a camera.

Aug 29 2007 2
Code39 (Sometimes called 3 from 9) barcodes use 9 bars
to represent each symbol.
The bars can be black or white.
The bars are either narrow or wide.
Wide bars must be 2.1 to 3 times larger than narrow bars.
Each symbol pattern starts and ends with a black bar.
A valid barcode starts and ends with the STAR (*) symbol,
which is used as a delimiter.

The STAR (*) symbol is made up of a


narrow black bar, a wide white bar, a
narrow black bar, a narrow white bar, a
wide black bar, a narrow white bar, a wide
black bar, a narrow white bar, and a
narrow black bar.

Aug 29 2007 3
Code39 (Sometimes called 3 from 9) barcodes use 9 bars
to represent each symbol.
The bars can be black or white.
The bars are either narrow or wide.
Wide bars must be 2.1 to 3 times larger than narrow bars.
Each symbol pattern starts and ends with a black bar.
A valid barcode starts and ends with the STAR (*) symbol,
which is used as a delimiter.

This could also be represented


as the string
“bWbwBwBwb”

Aug 29 2007 4
How many bars is in a barcode that encodes 3 symbols?
Although each symbol pattern starts and ends with a black
bar, patterns must be separated by a white bar (typically
narrow), so each symbol except the last is represented
with 10 bars in total. (The last symbol has 9 bars, and
does not need a separator after it.)
Don't forget the Start and Stop symbol!

Aug 29 2007 5
How many bars is in a barcode that encodes 3 symbols?
Although each symbol pattern starts and ends with a black
bar, patterns must be separated by a white bar (typically
narrow), so each symbol except the last is represented
with 10 bars in total. (The last symbol has 9 bars, and
does not need a separator after it.)
Don't forget the Start and Stop symbol!

3 symbols + start + stop = 5 symbols, at 9 bars each, plus


4 narrow white bars to separate the symbols is 9 * 5 + 4,
or 10*5 – 1 to make 49 bars total!

Aug 29 2007 6
All of the symbol patterns:

What symbol is on the


right?
Aug 29 2007 7
It's an “I”

What symbol is on the


right?
Aug 29 2007 8
But what does a barcode look like from the robot?

The robot's camera has relatively low resolution (256x192


pixels).
To decode a barcode successfully from an image, we
need multiple pixels for each bar . This means that we are
limited in the size of barcodes we can successfully use.
Here is a picture of a two symbol (4 patterns total)
barcode taken with a (VERY) carefully aimed robot
camera:

Aug 29 2007 9
It's sort of messy!

High contrast elements (black and white lines) generate


color artifacts due to the bayer filter layout in the camera.

Aug 29 2007 10
Step 1: Lets clean it up!

Convert to black and white with a thresholding process!

Aug 29 2007 11
Step 1: Lets clean it up!

Convert to black and white with a thresholding process!


For each pixel, check to see if it's brighter than a threshold
(say, 127).
If yes, set the color to white!
If no, set the color to black!

Aug 29 2007 12
Threshold Code

Aug 29 2007 13
Threshold Code

def threshold(pic):
for i in getPixels(pic):
g = getGreen(i)
if( g < 127):
setRed(i,0)
setGreen(i,0)
setBlue(i,0)
else:
setRed(i,255)
setGreen(i,255)
setBlue(i,255)
return(pic)
Aug 29 2007 14
Threshold Code: How to improve it!

Note that we are using the green value as a proxy for


the “brightness” (or luminance) of the pixel.
To do this correctly, we should calculate the luminance
of the pixel with the following formula:

Y = 0.2126 * Red + 0.7152 * Green + 0.0722 * Blue

Notice how the Green component makes up 70% of


the Luminance (Y) value?
That is why it's almost OK to cheat and just use the
green channel!

Aug 29 2007 15
Now what?

We have a thresholded image, now we have to scan


across it to look for bars.
Lets start out with a simpler task, just scan across it and
save a list of the pixel values (white=255 or black=0) in a
list.
But where do we scan?

Aug 29 2007 16
Now what?

We have a thresholded image, now we have to scan


across it to look for bars.
Lets start out with a simpler task, just scan across it and
save a list of the pixel values (white=255 or black=0) in a
list.
But where do we scan?
How about the middle?
How do you find the middle of the image?

Aug 29 2007 17
Our Image:

X=255
X=0 Width = 256
Y=0

Height =
192

Y=191
Aug 29 2007 18
Our Image: Middle

X=255
X=0 Width = 256
Y=0

Middle =
Height = Height / 2
192

Y=191
Aug 29 2007 19
Code to save pixel values along a horizontal scanline:

def makeScanLine(bwPic):

return(values)
Aug 29 2007 20
Code to save pixel values along a horizontal scanline:

def makeScanLine(bwPic):
height = getHeight(bwPic)
mid = height / 2
width = getWidth(bwPic)
values = []
for x in range(0, width):
pix = getPixel(bwPic, x, mid)
val = getGreen(pix)
values.append(val)

return(values)
Aug 29 2007 21
Example Scanline Data:

[0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255,
255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255,
255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255,
255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255,
255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0,
0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255,
255, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255,
255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0,
0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255]

Note the large runs of white at the beginning and end of the barcode!

Aug 29 2007 22
How to improve our data?

Scanline data presents the raw pixel data, but it's not very
easy to understand.
Lets scan for “runs” of pixels of the same color.
Convert this:
[0,0, 255,255,255,255,255, 0,0, 255,255, 0,0,0,0, 255,255,
0,0,0,0, 255,255, 0,0,0,0]

Aug 29 2007 23
How to improve our data?

Scanline data presents the raw pixel data, but it's not very
easy to understand.
Lets scan for “runs” of pixels of the same color.
Convert this:
[0,0, 255,255,255,255,255, 0,0, 255,255, 0,0,0,0, 255,255,
0,0,0,0, 255,255, 0,0,0,0]
to this:
[ (2,0), (5,255), (2,0), (2,255), (4,0), (2,255), (4,0), (2,255),
(2,0)]

Aug 29 2007 24
How to improve our data?

Scanline data presents the raw pixel data, but it's not very
easy to understand.
Lets scan for “runs” of pixels of the same color.
Convert this:
[0,0, 255,255,255,255,255, 0,0, 255,255, 0,0,0,0, 255,255,
0,0,0,0, 255,255, 0,0,0,0]
to this:
[ (2,0), (5,255), (2,0), (2,255), (4,0), (2,255), (4,0), (2,255),
(2,0)]
Which could be read as:
“bWbwBwBwb”

Aug 29 2007 25
Code to spot runs of the same color

def parseScanline(scanLine):

return(barData)

Aug 29 2007 26
Code to spot runs of the same color

def parseScanline(scanLine):
barData = []
previous = scanLine[0]
length = 0

for element in scanLine:


if (element != previous): #a change has occured!
myTuple = (length, previous)
barData.append( myTuple ) #add run info to barData list
length = 1
previous = element
else:
#No change.
length = length + 1

Aug 29 2007 27
Don't forget to record the last run!

def parseScanline(scanLine):
barData = []
previous = scanLine[0]
length = 0

for element in scanLine:


if (element != previous): #a change has occured!
myTuple = (length, previous)
barData.append( myTuple ) #add run info to barData list
length = 1
previous = element
else:
#No change.
length = length + 1

#Rescue the last bit of data stored in the previous


# and length variables!
myTuple = (length, previous)
barData.append( myTuple )
return(barData)
Aug 29 2007 28
Some real data!

Actual scanline data:


[ (2, 0), (26, 255), (3, 0), (8, 255), (3, 0), (3, 255), (8, 0),
(4, 255), (8, 0), (4, 255), (2, 0), (4, 255), (8, 0), (4, 255),
(4, 0), (8, 255), (2, 0), (4, 255), (4, 0), (4, 255), (8, 0),
(2, 255), (1, 0), (1, 255), (6, 0), (4, 255), (4, 0), (8, 255),
(8, 0), (4, 255), (2, 0), (4, 255), (4, 0), (4, 255), (2, 0),
(8, 255), (4, 0), (4, 255), (8, 0), (4, 255), (7, 0), (3, 255),
(4, 0), (39, 255) ]

Can you spot the narrow and wide bars?

Aug 29 2007 29
Some real data!

Actual scanline data:


[ (2, 0), (26, 255), (3, 0), (8, 255), (3, 0), (3, 255), (8, 0),
(4, 255), (8, 0), (4, 255), (2, 0), (4, 255), (8, 0), (4, 255),
(4, 0), (8, 255), (2, 0), (4, 255), (4, 0), (4, 255), (8, 0),
(2, 255), (1, 0), (1, 255), (6, 0), (4, 255), (4, 0), (8, 255),
(8, 0), (4, 255), (2, 0), (4, 255), (4, 0), (4, 255), (2, 0),
(8, 255), (4, 0), (4, 255), (8, 0), (4, 255), (7, 0), (3, 255),
(4, 0), (39, 255) ]

Narrow bars look to be around 3-4 pixels in size, and


wide bars appear to be around 6-8 pixels in size!

Aug 29 2007 30
Some real data! With real-world problems!

Wait! What's that black bar doing at the front of our image
(2,0) before all that white space (26,255)?

Actual scanline data:


[ (2, 0), (26, 255), (3, 0), (8, 255), (3, 0), (3, 255), (8, 0),
(4, 255), (8, 0), (4, 255), (2, 0), (4, 255), (8, 0), (4, 255),
(4, 0), (8, 255), (2, 0), (4, 255), (4, 0), (4, 255), (8, 0),
(2, 255), (1, 0), (1, 255), (6, 0), (4, 255), (4, 0), (8, 255),
(8, 0), (4, 255), (2, 0), (4, 255), (4, 0), (4, 255), (2, 0),
(8, 255), (4, 0), (4, 255), (8, 0), (4, 255), (7, 0), (3, 255),
(4, 0), (39, 255) ]

Aug 29 2007 31
Some real data!

Wait! What's that black bar doing at the front of our image
(2,0) before all that white space (26,255)?

Actual scanline data:


[ (2, 0), (26, 255), (3, 0), (8, 255), (3, 0), (3, 255), (8, 0),
(4, 255), (8, 0), (4, 255), (2, 0), (4, 255), (8, 0), (4, 255),
(4, 0), (8, 255), (2, 0), (4, 255), (4, 0), (4, 255), (8, 0),
(2, 255), (1, 0), (1, 255), (6, 0), (4, 255), (4, 0), (8, 255),
(8, 0), (4, 255), (2, 0), (4, 255), (4, 0), (4, 255), (2, 0),
(8, 255), (4, 0), (4, 255), (8, 0), (4, 255), (7, 0), (3, 255),
(4, 0), (39, 255) ]

Zoom in:
Aug 29 2007 32
Some real data!

The robot's camera has a bug! It produces two columns of


black pixels on the left of every image!

But no problems! We'll just make sure that our barcode


parsing code can handle random bars before the barcode
officially starts!

Zoom in:
Aug 29 2007 33
Another problem!

Wait! What are those single pixel black and white bars
doing in the middle of our image?
Actual scanline data:
[ (2, 0), (26, 255), (3, 0), (8, 255), (3, 0), (3, 255), (8, 0),
(4, 255), (8, 0), (4, 255), (2, 0), (4, 255), (8, 0), (4, 255),
(4, 0), (8, 255), (2, 0), (4, 255), (4, 0), (4, 255), (8, 0),
(2, 255), (1, 0), (1, 255), (6, 0), (4, 255), (4, 0), (8, 255),
(8, 0), (4, 255), (2, 0), (4, 255), (4, 0), (4, 255), (2, 0),
(8, 255), (4, 0), (4, 255), (8, 0), (4, 255), (7, 0), (3, 255),
(4, 0), (39, 255) ]

Aug 29 2007 34
Another problem!

Wait! What are those single pixel black and white bars
doing in the middle of our image?
Actual scanline data:
[ (2, 0), (26, 255), (3, 0), (8, 255), (3, 0), (3, 255), (8, 0),
(4, 255), (8, 0), (4, 255), (2, 0), (4, 255), (8, 0), (4, 255),
(4, 0), (8, 255), (2, 0), (4, 255), (4, 0), (4, 255), (8, 0),
(2, 255), (1, 0), (1, 255), (6, 0), (4, 255), (4, 0), (8, 255),
(8, 0), (4, 255), (2, 0), (4, 255), (4, 0), (4, 255), (2, 0),
(8, 255), (4, 0), (4, 255), (8, 0), (4, 255), (7, 0), (3, 255),
(4, 0), (39, 255) ]

Zoom in:
Aug 29 2007 35
Another problem!

We need to remove those single pixel errors!

Zoom in:
Aug 29 2007 36
Code to remove single pixel errors:

Remove single pixel errors!


Example data:
[ (4, 255), (8, 0), (2, 255), (1, 0), (1, 255), (6, 0), (4, 255) ]

We want:
[ (4, 255), (8, 0), (2, 255), (6, 0), (4, 255) ]

Aug 29 2007 37
Code to remove single pixel errors:

def removeSingles(barData):

return(newBarData)

Aug 29 2007 38
Code to remove single pixel errors:

def removeSingles(barData):
newBarData =[]
for item in barData:
length = item[0]
if (length != 1):
newBarData.append(item)
return(newBarData)

Aug 29 2007 39
Good data, but how to find Wide and Narrow bars?

[(2, 0), (26, 255), (3, 0), (8, 255), (3, 0), (3, 255), (8, 0), (4, 255), (8, 0), (4, 255),
(2, 0), (4, 255), (8, 0), (4, 255), (4, 0), (8, 255), (2, 0), (4, 255), (4, 0), (4, 255),
(8, 0), (2, 255), (6, 0), (4, 255), (4, 0), (8, 255), (8, 0), (4, 255), (2, 0), (4, 255),
(4, 0), (4, 255), (2, 0), (8, 255), (4, 0), (4, 255), (8, 0), (4, 255), (7, 0), (3, 255),
(4, 0), (255, 39)]

How do we pick the threshold that


separates wide from narrow bars?
Look at just the widths:

[2, 26, 3, 8, 3, 3, 8, 4, 8, 4, 2, 4, 8, 4, 4, 8,
2, 4, 4, 4, 8, 2, 6, 4, 4, 8, 8, 4, 2, 4, 4, 4, 2,
8, 4, 4, 8, 4, 7, 3, 4, 39]

Aug 29 2007 40
Good data, but how to find Wide and Narrow bars?

SORT the widths:


[2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 7, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 26, 39]

Eyeball it! What would make a good


threshold?

Aug 29 2007 41
Good data, but how to find Wide and Narrow bars?

SORT the widths:


[2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 7, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 26, 39]

Eyeball it! A 5 or 6 would make a good


threshold! But how does the computer
figure that out?

Aug 29 2007 42
Good data, but how to find Wide and Narrow bars?

SORT the widths:


[2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 7, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 26, 39]

What is the median width?


[2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 7, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 26, 39]
Aug 29 2007 43
Good data, but how to find Wide and Narrow bars?

What is the median width?


[2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 7, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 26, 39]

How about ¾ of the way up the list?


[2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 7, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 26, 39]
Aug 29 2007 44
Good data, but how to find Wide and Narrow bars?

Pick a threshold halfway between the


median (4 = narrow bar size) and the ¾
point ( 8 = wide bar size):
(8 – 4) / 2 = 2
( 2 bigger than median value is 6!)
4+2=6
Anything 6 pixels or larger is a wide bar!

Aug 29 2007 45
Code to find the width threshold:

def calculateWidthThreshold(barData):

return( threshold )

Aug 29 2007 46
Code to find the width threshold:

def calculateWidthThreshold(barData):
#Load just the widths!
barWidths = []
for x in barData:
barWidths.append(x[0])
barWidths.sort()
#Find the size of a narrow bar!
medianIdx = len(barWidths) / 2
narrowSize= barWidths[medianIdx]
#Go to the 3/4 point, find the size of a wide bar!
wideIdx = medianIdx + (medianIdx / 2)
wideSize = barWidths[wideIdx]
#Calculate the threshold
dist = (wideSize – narrowSize) / 2
threshold = narrowSize + dist
return( threshold )

Aug 29 2007 47
Decoding the bars!

Now that we know the width threshold, we can convert our


barData into a string representing the barcode! (made up
of the letters {b,B,w,W})
For example:
barData = [ (4, 255), (8, 0), (7, 255), (3, 0) ]
should produce a string like this:
“wBWb”
(narrow white, wide Black, white White, narrow black)

Aug 29 2007 48
Decoding the bars!

def decodeBars(barData,widthThreshold):

return(barString)
Aug 29 2007 49
Decoding the bars!

def decodeBars(barData,widthThreshold):
barString = ""
for bar in barData:
if(bar[1] == 255): #It's a white bar!
if(bar[0] >= widthThreshold):
#It's a wide white bar!
barString = barString + "W"
else:
#It's a narrow white bar
barString = barString + "w"
else: #It's a black bar!
if(bar[0] >= widthThreshold):
#It's a wide black bar!
barString = barString + "B"
else:
#it's a narrow black bar!
barString = barString + "b"

return(barString)
Aug 29 2007 50
Parsing the barcode string

Actual barString:
bWbWbwBwBwbwBwbWbwbwBwBwbWBwbwbwbWbwB
wBwbW
Now we just have to parse this string to find our barcode!
All (valid) barcodes start with the pattern:
“bWbwBwBwb” or the “*” symbol
Lets go looking for it!

Aug 29 2007 51
Parsing the barcode string

There it is!

bWbWbwBwBwbwBwbWbwbwBwBwbWBwbwbwb
WbwBwBwbW

Aug 29 2007 52
Parsing the barcode string

Now, a white bar will separate the start symbol from the
first data symbol!

bWbWbwBwBwbwBwbWbwbwBwBwbWBwbwbwb
WbwBwBwbW

Aug 29 2007 53
Parsing the barcode string

The second symbol is 9 bars long, and is also followed by


a white bar!

BwbWbwBwBwbwBwbWbwbwBwBwbWBwbwbwb
WbwBwBwbW
So if we look up “BwbWbwbwB” we can figure out what
our first symbol is!

Aug 29 2007 54
All of the symbol patterns:

How do we get all those


symbols into our code to do
the lookup?
Aug 29 2007 55
All of the symbol patterns:

#Use a dictionary! Luckily I've already typed


code39dict = { the codes in for you, look on
'BwbWbwbwB': "1", the website for the
code39dict.py file!
'bwBWbwbwB': "2",
'BwBWbwbwb': "3",
'bwbWBwbwB': "4",
'BwbWBwbwb': "5",
...
'BwbwbWbwB': "A",
'bwBwbWbwB': "B",
'BwBwbWbwb': "C",
...
'bWbwBwBwb': "*", #Start/Stop character
}
Aug 29 2007 56
Parsing the barcode string

code39dict = {
'BwbWbwbwB': "1",
}

answer = code39dict[“BwbWbwbwB”]
print answer
“1”
Our first symbol is a 1!

Aug 29 2007 57
Parsing the barcode string

Each symbol is 9 bars long, and separated by a white bar!


...wBwbWbwbwBwBwbWBwbwbwbWbwBwBwbW
Our second symbol is a “5”

Aug 29 2007 58
Parsing the barcode string

The second symbol is 9 bars long, and is also followed by


a white bar!
...BwbWBwbwbwbWbwBwBwbW
And our last symbol should look familiar, because it is the
“*” or Start/Stop symbol, and is the same as our first
symbol!
Note that the large white area after the barcode is
represented by a single W.
All together, our barcode reads: “*15*”
We don't report the *'s, so our number is 15

Aug 29 2007 59
Code to find the start symbol!

def findCode39(barString):

Aug 29 2007 60
Code to find the start symbol!

def findCode39(barString):

#Search for a start code!


startLoc = barString.find("bWbwBwBwb")
if(startLoc == -1): #No start character found
return(None)
#Beginning of first data symbol...
#each code is 9 bars long,
#plus one bar to separate them!
startLoc = startLoc + 10
#Initialize a variable to store our code
codeData = “”
Aug 29 2007 61
Code to read each symbol!

while( startLoc < len(barString)):


code = barString[startLoc:startLoc+9]
letter = code39dict.get(code,-1)
if (letter == -1): #Invalid code!
return(None)
else: #Valid code!
if(letter == '*'): #Found end of barcode
return(codeData) #Return the data!
else: #Add letter to our codeData
codeData = codeData + letter
#We advance by 10 to the next code symbol
startLoc = startLoc + 10
#did not find a stop code! Abort!
return(None)
Aug 29 2007 62
Barcodes – Not that hard after all!

 Questions?

Aug 29 2007 63

You might also like