The example below is a barcode reader for reading EAN-13 (and UPC) barcodes. Reading of the barcode is restricted to a single line of the camera image. However the application can read barcodes forwards as well as backwards.

#!/usr/bin/ruby
require 'hornetseye'
include Hornetseye
class Integer
def checksum
x = self / 10
retval = 0
while x > 0
retval += ( x % 10 ) * 3
x /= 10
retval += x
x /= 10
end
retval = retval % 10
retval = 10 - retval unless retval == 0
retval
end
def check?
checksum == self % 10
end
end
class Sequence
class << self
def ramp( s, b = 0 )
Sequence.int( s ).indgen! b
end
end
end
class Sequence_
def min_index
Sequence.ramp( size ).mask( self <= min )[0]
end
def max_index
Sequence.ramp( size ).mask( self >= max )[0]
end
def reverse
Sequence( element_type, num_elements, -element_type.size ).
new memory.window( element_type.typesize * ( num_elements - 1 ) ), 0
end
end
class MultiArray
class << self
def ramp( w, h, x0, y0 )
retval = MultiArray.int 2, w, h
retval.roll[ 1 ] = Sequence.ramp h, y0
retval.roll[ 0 ].roll[] = Sequence.ramp w, x0
retval
end
end
end
input = V4L2Input.new '/dev/video0', 640, 480
n = 180
grad_sigma = 1.5
moments_sigma = 20.0
noise = 10.0
err_thresh = 0.25
t = 10
w, h = input.width, input.height
index = Sequence.ramp w
ramp = MultiArray.ramp w, h, -w/2, -h/2
ridge = ( ramp.roll[0] % 32 ).eq( 0 ).or( ramp.roll[1].eq( 0 ) ).to_ubyte * 255
result = MultiArray.ubytergb( w, h + 100 )
result[ h ... h + 100 ] = 128
segment = Sequence[ *( [ 0 ] * 3 +
( 1 .. 6 ).collect { |i| [ i ] * 4 }.flatten +
[ 7 ] * 5 +
( 8 .. 13 ).collect { |i| [ i ] * 4 }.flatten +
[ 14 ] * 3 ) ]
digits = MultiArray[ [ [ 0, 0, 0, 1, 1, 0, 1 ],
[ 0, 0, 1, 1, 0, 0, 1 ],
[ 0, 0, 1, 0, 0, 1, 1 ],
[ 0, 1, 1, 1, 1, 0, 1 ],
[ 0, 1, 0, 0, 0, 1, 1 ],
[ 0, 1, 1, 0, 0, 0, 1 ],
[ 0, 1, 0, 1, 1, 1, 1 ],
[ 0, 1, 1, 1, 0, 1, 1 ],
[ 0, 1, 1, 0, 1, 1, 1 ],
[ 0, 0, 0, 1, 0, 1, 1 ] ],
[ [ 0, 1, 0, 0, 1, 1, 1 ],
[ 0, 1, 1, 0, 0, 1, 1 ],
[ 0, 0, 1, 1, 0, 1, 1 ],
[ 0, 1, 0, 0, 0, 0, 1 ],
[ 0, 0, 1, 1, 1, 0, 1 ],
[ 0, 1, 1, 1, 0, 0, 1 ],
[ 0, 0, 0, 0, 1, 0, 1 ],
[ 0, 0, 1, 0, 0, 0, 1 ],
[ 0, 0, 0, 1, 0, 0, 1 ],
[ 0, 0, 1, 0, 1, 1, 1 ] ],
[ [ 1, 1, 1, 0, 0, 1, 0 ],
[ 1, 1, 0, 0, 1, 1, 0 ],
[ 1, 1, 0, 1, 1, 0, 0 ],
[ 1, 0, 0, 0, 0, 1, 0 ],
[ 1, 0, 1, 1, 1, 0, 0 ],
[ 1, 0, 0, 1, 1, 1, 0 ],
[ 1, 0, 1, 0, 0, 0, 0 ],
[ 1, 0, 0, 0, 1, 0, 0 ],
[ 1, 0, 0, 1, 0, 0, 0 ],
[ 1, 1, 1, 0, 1, 0, 0 ] ] ].to_byte
patterns = MultiArray[ [ 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2 ],
[ 0, 0, 1, 0, 1, 1, 2, 2, 2, 2, 2, 2 ],
[ 0, 0, 1, 1, 0, 1, 2, 2, 2, 2, 2, 2 ],
[ 0, 0, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2 ],
[ 0, 1, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2 ],
[ 0, 1, 1, 0, 0, 1, 2, 2, 2, 2, 2, 2 ],
[ 0, 1, 1, 1, 0, 0, 2, 2, 2, 2, 2, 2 ],
[ 0, 1, 0, 1, 0, 1, 2, 2, 2, 2, 2, 2 ],
[ 0, 1, 0, 1, 1, 0, 2, 2, 2, 2, 2, 2 ],
[ 0, 1, 1, 0, 1, 0, 2, 2, 2, 2, 2, 2 ] ].to_byte
X11Display.show do
img = input.read_ubyte
avg = img[ h / 2 ]
mean = avg.gauss_blur moments_sigma
var = Math.sqrt( ( ( avg - mean ) ** 2 ).gauss_blur( moments_sigma ) )
binary = ( avg < mean ).and( var >= noise )
up = binary.to_ubyte.correlate( Sequence[ 1, -1 ] ).major 0
down = binary.to_ubyte.correlate( Sequence[ -1, 1 ] ).major 0
stripe = ( up + down ).integral
n = up.sum
views = Sequence.int( avg.size ).fill! 128
zebra = Sequence.ubyte( avg.size ).fill! 128
for o in 0 .. n - 30
msk = stripe.between? 2 * o + 1, 2 * o + 59
range = index.mask( msk ).range
views += msk.to_ubyte
code = ( stripe[ range ] - 2 * o - 1 ).map segment
sequence = binary[ range ]
s = sequence.mask( code.eq( 0 ) ).to_byte
m = sequence.mask( code.eq( 7 ) ).to_byte
e = sequence.mask( code.eq( 14 ) ).to_byte
c = Sequence[ 0, *( [ 1 ] * 6 + [ 0 ] + [ 1 ] * 6 + [0] ) ].to_bool
skew = code.hist( 15 ).mask( c ).range
if skew.max < skew.min * 1.3
s_ = Sequence[ 1, 0, 1 ].warp Sequence.ramp( s.size ) * 3 / s.size
m_ = Sequence[ 0, 1, 0, 1, 0 ].warp Sequence.ramp( m.size ) * 5 / m.size
e_ = Sequence[ 1, 0, 1 ].warp Sequence.ramp( e.size ) * 3 / e.size
if ( s - s_ ).abs.sum < s.size * err_thresh and
( m - m_ ).abs.sum < m.size * err_thresh and
( e - e_ ).abs.sum < m.size * err_thresh
zebra = ( ( code % 2 ) * 255 ).unmask msk, 128
number = Hash.new 0
max_err = Hash.new 0.0
exp = { :forward => 11, :backward => 0 }
( ( 1 .. 6 ).to_a + ( 8 .. 13 ).to_a ).each do |k|
cut = sequence.mask code.eq( k )
d = { :forward => cut, :backward => cut.reverse }
span = Sequence.ramp( cut.size ) * 7 / cut.size
d_ = digits.roll.warp( span ).unroll
[ :forward, :backward ].each do |dir|
err = ( 0 ... 3 ).collect do |lgr|
Sequence.tensor( 1 ) do |j,i|
( d[ dir ].to_byte[ i ] - d_[ lgr ][ j ][ i ] ).abs
end
end
for i in 0 .. 9
lgr = patterns[ i ][ 11 - exp[ dir ] ]
match = err[ lgr ].min_index
max_err[ [ dir, i ] ] =
[ max_err[ [ dir, i ] ],
err[ lgr ][ match ].to_f / cut.size ].max
number[ [ dir, i ] ] += match * 10 ** exp[ dir ]
end
end
exp[ :forward ] -= 1
exp[ :backward ] += 1
end
opt = max_err.min { |x,y| x[1] <=> y[1] }.first
number, max_err = number[ opt ] + opt[1] * 10 ** 12, max_err[ opt ]
if max_err < err_thresh and number.check?
text = Magick::Image.new 100, 20
draw = Magick::Draw.new
draw.gravity Magick::CenterGravity
draw.text 0, 0, "%013d" % number
draw.draw text
result[ 0 ... w - text.columns, h + 80 ... h + 100 ] =
result[ text.columns ... w, h + 80 ... h + 100 ]
result[ w - text.columns ... w, h + 80 ... h + 100 ] =
text.to_multiarray
break
end
end
end
end
result[ 0 ... w, 0 ... h ] = img
result[ h / 2 ] = ~img[ h / 2 ]
result[ 0 ... w, h ... h + 20 ].roll[] = avg
result[ 0 ... w, h + 20 ... h + 40 ].roll[] = binary.not.to_ubyte * 255
result[ 0 ... w, h + 40 ... h + 60 ].roll[] = views.normalise
result[ 0 ... w, h + 60 ... h + 80 ].roll[] = zebra
result
end