630 likes | 648 Views
Learn about decoding barcodes using the Code39 encoding scheme. Explore the structure of barcodes, the number of bars in a barcode, and how to clean up barcode images for successful decoding.
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)