EAN-13 barcode reader

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.

See also

#!/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
Close