630 likes | 786 Views
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.
E N D
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. • Usually read by laser scanners, they can also be read using a camera.
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.
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”
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!
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!
All of the symbol patterns: What symbol is on the right?
It's an “I” What symbol is on the right?
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:
It's sort of messy! • High contrast elements (black and white lines) generate color artifacts due to the bayer filter layout in the camera.
Step 1: Lets clean it up! • Convert to black and white with a thresholding process!
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!
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)
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!
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?
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?
Our Image: X=255 X=0 Width = 256 Y=0 Height = 192 Y=191
Our Image: Middle X=255 X=0 Width = 256 Y=0 Middle = Height / 2 Height = 192 Y=191
Code to save pixel values along a horizontal scanline: def makeScanLine(bwPic): return(values)
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)
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!
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]
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)]
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”
Code to spot runs of the same color def parseScanline(scanLine): return(barData)
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
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)
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?
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!
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) ]
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:
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:
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) ]
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:
Another problem! • We need to remove those single pixel errors! Zoom in:
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) ]
Code to remove single pixel errors: def removeSingles(barData): return(newBarData)
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)
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]
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?
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?
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]
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]
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!
Code to find the width threshold: def calculateWidthThreshold(barData): return( threshold )
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 )
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)
Decoding the bars! def decodeBars(barData,widthThreshold): return(barString)
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)