View
98
Download
0
Category
Preview:
DESCRIPTION
Decoding Barcodes. Institute for Personal Robots in Education (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. - PowerPoint PPT Presentation
Citation preview
CS 1 with Robots
Decoding Barcodes
Institute for Personal Robots in Education
(IPRE)
Aug 29 2007 2
Barcodes are designed to be machine readableThey 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 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.
Aug 29 2007 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”
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!
Aug 29 2007 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 10*5 – 1 to make 49 bars total!
Aug 29 2007 7
All of the symbol patterns:
What symbol is on the right?
Aug 29 2007 8
It's an “I”
What symbol is on the right?
Aug 29 2007 9
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 10
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 11
Step 1: Lets clean it up!
Convert to black and white with a thresholding process!
Aug 29 2007 12
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 13
Threshold Code
Aug 29 2007 14
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 15
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 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?
Aug 29 2007 17
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 18
Our Image:
X=255X=0 Width = 256
Y=0
Y=191
Height = 192
Aug 29 2007 19
Our Image: Middle
X=255X=0 Width = 256
Y=0
Y=191
Height = 192
Middle =Height / 2
Aug 29 2007 20
Code to save pixel values along a horizontal scanline:
def makeScanLine(bwPic):
return(values)
Aug 29 2007 21
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 22
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 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]
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)]
Aug 29 2007 25
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 26
Code to spot runs of the same color
def parseScanline(scanLine):
return(barData)
Aug 29 2007 27
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 28
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 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) ]
Can you spot the narrow and wide bars?
Aug 29 2007 30
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 31
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 32
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 33
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 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) ]
Aug 29 2007 35
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 36
Another problem!
We need to remove those single pixel errors!
Zoom in:
Aug 29 2007 37
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 38
Code to remove single pixel errors:
def removeSingles(barData):
return(newBarData)
Aug 29 2007 39
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 40
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 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! What would make a good threshold?
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]
Eyeball it! A 5 or 6 would make a good threshold! But how does the computer figure that out?
Aug 29 2007 43
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 44
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 45
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 46
Code to find the width threshold:
def calculateWidthThreshold(barData):
return( threshold )
Aug 29 2007 47
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 48
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 49
Decoding the bars!
def decodeBars(barData,widthThreshold):
return(barString)
Aug 29 2007 50
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 51
Parsing the barcode string
Actual barString: bWbWbwBwBwbwBwbWbwbwBwBwbWBwbwbwbWbwBwBwbW
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 52
Parsing the barcode string
There it is! bWbWbwBwBwbwBwbWbwbwBwBwbWBwbwbwbWbwBwBwbW
Aug 29 2007 53
Parsing the barcode string
Now, a white bar will separate the start symbol from the first data symbol! bWbWbwBwBwbwBwbWbwbwBwBwbWBwbwbwbWbwBwBwbW
Aug 29 2007 54
Parsing the barcode string
The second symbol is 9 bars long, and is also followed by a white bar! BwbWbwBwBwbwBwbWbwbwBwBwbWBwbwbwbWbwBwBwbW
So if we look up “BwbWbwbwB” we can figure out what our first symbol is!
Aug 29 2007 55
All of the symbol patterns:
How do we get all those symbols into our code to do the lookup?
Aug 29 2007 56
All of the symbol patterns:
#Use a dictionary!code39dict = { 'BwbWbwbwB': "1", 'bwBWbwbwB': "2", 'BwBWbwbwb': "3", 'bwbWBwbwB': "4", 'BwbWBwbwb': "5", ... 'BwbwbWbwB': "A", 'bwBwbWbwB': "B", 'BwBwbWbwb': "C", ... 'bWbwBwBwb': "*", #Start/Stop character }
Luckily I've already typed the codes in for you, look on the website for the code39dict.py file!
Aug 29 2007 57
Parsing the barcode string
code39dict = { 'BwbWbwbwB': "1", }
answer = code39dict[“BwbWbwbwB”]print answer“1”
Our first symbol is a 1!
Aug 29 2007 58
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 59
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 60
Code to find the start symbol!
def findCode39(barString):
Aug 29 2007 61
Code to find the start symbol!
def findCode39(barString):
#Search for a start code!startLoc = barString.find("bWbwBwBwb")
if(startLoc == -1): #No start character foundreturn(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 codecodeData = “”
Aug 29 2007 62
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 codeDatacodeData = codeData + letter
#We advance by 10 to the next code symbolstartLoc = startLoc + 10
#did not find a stop code! Abort! return(None)
Aug 29 2007 63
Barcodes – Not that hard after all!
Questions?
CS 1 with Robots
Thank you for attention!
Recommended