Title: Decoding Barcodes
1Decoding Barcodes
Institute for Personal Robots in Education
(IPRE)?
2- 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.
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.
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.
4- 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
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!
6- 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 105 1 to make 49 bars
total!
7All of the symbol patterns
What symbol is on the right?
8It's an I
What symbol is on the right?
9But 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
10It's sort of messy!
- High contrast elements (black and white lines)
generate color artifacts due to the bayer filter
layout in the camera.
11Step 1 Lets clean it up!
- Convert to black and white with a thresholding
process!
12Step 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!
13Threshold Code
14Threshold Code
def threshold(pic) for i in getPixels(pic)
g getGreen(i)? if( g setRed(i,0)? setGreen(i,0)?
setBlue(i,0)? else
setRed(i,255)? setGreen(i,255)?
setBlue(i,255)? return(pic)?
15Threshold 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
formulaY 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!
16Now 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
(white255 or black0) in a list. - But where do we scan?
17Now 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
(white255 or black0) in a list. - But where do we scan?
- How about the middle?
- How do you find the middle of the image?
18Our Image
X255
X0
Width 256
Y0
Height 192
Y191
19Our Image Middle
X255
X0
Width 256
Y0
Middle Height / 2
Height 192
Y191
20Code to save pixel values along a horizontal
scanline
def makeScanLine(bwPic)
return(values)?
21Code 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)?
22Example 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!
23How 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 this0,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
24How 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 this0,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,0to this (2,0), (5,255), (2,0),
(2,255), (4,0), (2,255), (4,0), (2,255),
(2,0)
25How 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 this0,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,0to this (2,0), (5,255), (2,0),
(2,255), (4,0), (2,255), (4,0), (2,255),
(2,0)Which could be read asbWbwBwBwb
26Code to spot runs of the same color
def parseScanline(scanLine)
return(barData)?
27Code to spot runs of the same color
def parseScanline(scanLine) barData
previous scanLine0 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
28Don't forget to record the last run!
def parseScanline(scanLine) barData
previous scanLine0 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)?
29Some 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?
30Some 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!
31Some 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)
32Some 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
33Some 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
34Another 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)
35Another 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
36Another problem!
- We need to remove those single pixel errors!
Zoom in
37Code 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)
38Code to remove single pixel errors
def removeSingles(barData) return(newBa
rData)?
39Code to remove single pixel errors
def removeSingles(barData) newBarData for
item in barData length item0 if (length
! 1) newBarData.append(item)? return(newBa
rData)?
40Good 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
41Good 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?
42Good 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?
43Good 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
44Good 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
45Good 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 bigger than median value
is 6!)? 4 2 6 Anything 6 pixels or larger
is a wide bar!
46Code to find the width threshold
def calculateWidthThreshold(barData)
return( threshold )?
47Code to find the width threshold
def calculateWidthThreshold(barData) Load
just the widths! barWidths for x in
barData barWidths.append(x0)?
barWidths.sort()? Find the size of a
narrow bar! medianIdx len(barWidths) / 2
narrowSize barWidthsmedianIdx Go to the
3/4 point, find the size of a wide bar!
wideIdx medianIdx (medianIdx / 2)?
wideSize barWidthswideIdx Calculate the
threshold dist (wideSize narrowSize) / 2
threshold narrowSize dist return(
threshold )?
48Decoding 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 examplebarData (4, 255), (8, 0), (7,
255), (3, 0) should produce a string like
thiswBWb(narrow white, wide Black, white
White, narrow black)?
49Decoding the bars!
def decodeBars(barData,widthThreshold)
return(barString)?
50Decoding the bars!
def decodeBars(barData,widthThreshold)
barString "" for bar in barData
if(bar1 255) It's a white bar!
if(bar0 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(bar0 widthThreshold) It's a wide
black bar! barString barString
"B" else it's a narrow
black bar! barString barString
"b" return(barString)?
51Parsing the barcode string
- Actual barString bWbWbwBwBwbwBwbWbwbwBwBwbWBwbwb
wbWbwBwBwbW - 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!
52Parsing the barcode string
- There it is! bWbWbwBwBwbwBwbWbwbwBwBwbWBwbwbwbWbw
BwBwbW
53Parsing the barcode string
- Now, a white bar will separate the start symbol
from the first data symbol! bWbWbwBwBwbwBwbWbwbwB
wBwbWBwbwbwbWbwBwBwbW
54Parsing the barcode string
- The second symbol is 9 bars long, and is also
followed by a white bar! BwbWbwBwBwbwBwbWbwbwBwBw
bWBwbwbwbWbwBwBwbW - So if we look up BwbWbwbwB we can figure out
what our first symbol is!
55All of the symbol patterns
How do we get all those symbols into our code to
do the lookup?
56All of the symbol patterns
Luckily I've already typed the codes in for you,
look on the website for the code39dict.py file!
Use a dictionary!code39dict
'BwbWbwbwB' "1", 'bwBWbwbwB' "2",
'BwBWbwbwb' "3", 'bwbWBwbwB' "4",
'BwbWBwbwb' "5", ... 'BwbwbWbwB' "A",
'bwBwbWbwB' "B", 'BwBwbWbwb' "C",
... 'bWbwBwBwb' "", Start/Stop character
57Parsing the barcode string
- code39dict
- 'BwbWbwbwB' "1",
-
- answer code39dictBwbWbwbwB
- print answer
- 1
- Our first symbol is a 1!
58Parsing the barcode string
- Each symbol is 9 bars long, and separated by a
white bar! ...wBwbWbwbwBwBwbWBwbwbwbWbwBwBwbW - Our second symbol is a 5
59Parsing 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
60Code to find the start symbol!
def findCode39(barString)
61Code 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
62Code to read each symbol!
while( startLoc barStringstartLocstartLoc9 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)?
63Barcodes Not that hard after all!