Histograms and Warps

Histogram Equalisation

Using integral arrays and element-wise lookup (map) one can implement histogram equalisation.

require 'hornetseye_openexr'
require 'hornetseye_xorg'
include Hornetseye
class Node
  def average
    sum / size
  end
  def equalise( n = 4096, c_max = 255 )
    if typecode < RGB_
      result = MultiArray(typecode, dimension).new *shape
      max_average = [ r, g, b ].collect { |c| c.average }.max
      result.r, result.g, result.b = *[ r, g, b ].collect do |c|
        c.equalise n, c_max * c.average / max_average
      end
      result
    else
      quantised = normalise( 0 .. n - 1 ).to_int
      quantised.lut quantised.histogram( n ).integral.normalise( 0 .. c_max )
    end
  end
end
system 'wget -c http://www.wedesoft.de/hornetseye-api/images/bmw.exr'
img = MultiArray.load_sfloatrgb 'bmw.exr'
img.equalise.show

Otsu Thresholding

The Otsu algorithm is an algorithm for automatic thresholding. The algorithm assumes that the image to be thresholded contains two classes of pixels and then chooses the threshold which minimizes the intra-class variance of the two classes defined by the resulting binary image. Otsu has reformulated this problem so that it can be computed efficiently with histograms.

require 'hornetseye_rmagick'
require 'hornetseye_xorg'
include Hornetseye
class Node
  def otsu( hist_size = 256 )
    h = histogram hist_size
    w1 = h.integral
    w2 = w1[ w1.size - 1 ] - w1
    s1 = lazy { |i| h[i] * i }.integral
    s2 = sum - s1
    m1 = w1 > 0
    u1 = ( s1.mask( m1 ).to_sfloat / w1.mask( m1 ) ).unmask m1
    m2 = w2 > 0
    u2 = ( s2.mask( m2 ).to_sfloat / w2.mask( m2 ) ).unmask m2
    between_variance = ( u1 - u2 ) ** 2 * w1 * w2
    self > argmax { |i| between_variance[i] }.first
  end
end
img = MultiArray.load_ubyte 'http://www.wedesoft.de/hornetseye-api/images/lena.jpg'
( img.otsu.to_ubyte * 255 ).show

Compute Average

This example shows how to compute the average of a series of frames. You can use a program like this to reduce noise by averaging a large number of frames.

require 'hornetseye_v4l2'
require 'hornetseye_xorg'
include Hornetseye
input = V4L2Input.new
average = nil
c = 0
img = X11Display.show do
  img = input.read_ubytergb
  average = average.nil? ? img.to_uintrgb : average + img
  c += 1
  average / c
end
img.show

Bounding Box

A mask which specifies pixel locations of interest is created. The mask then is applied to an x-ramp and a y-ramp to find the bounding box. The area outside the bounding box finally is highlighted.

require 'hornetseye_rmagick'
require 'hornetseye_xorg'
include Hornetseye
img = MultiArray.load_ubyte 'http://www.wedesoft.de/hornetseye-api/images/viking.jpg'
mask = img <= 50
x = lazy( *img.shape ) { |i,j| i }
y = lazy( *img.shape ) { |i,j| j }
box = [ x.mask( mask ).range, y.mask( mask ).range ]
img[ *box ] = img[ *box ] / 2 + 0x7F
img.show

Warps

Images can be warped using vector fields. The warp vectors are indicating the location of the source pixel. The example warps an equirectangular projection to an azimuthal projection.

require 'hornetseye_rmagick'
require 'hornetseye_xorg'
include Hornetseye
img = MultiArray.load_ubytergb 'http://www.wedesoft.de/hornetseye-api/images/world.jpg'
w, h = *img.shape
c = 0.5 * h
x, y = lazy( h, h ) { |i,j| i - c }, lazy( h, h ) { |i,j| j - c }
angle = ( Math.atan2( x, y ) / Math::PI + 1 ) * w / 2
radius = Math.hypot( x, y )
img.warp( angle.to_int, radius.to_int ).show

Colour Circle

You can create images yourself. In this example an image with different colours is generated and the result is mapped to a circle using a vector-field.

require 'hornetseye_xorg'
include Hornetseye
img = MultiArray.ubytergb( 360, 128 ).fill!
x, y = lazy( *img.shape ) { |i,j| i }, lazy( *img.shape ) { |i,j| j }
img.r = ( ( ( x - 180 ).abs -  60 ).clip( 0..60 ) * y ).normalise
img.g = ( ( 120 - ( x - 120 ).abs ).clip( 0..60 ) * y ).normalise
img.b = ( ( 120 - ( x - 240 ).abs ).clip( 0..60 ) * y ).normalise
w, h = 256, 256
x, y = lazy( w, h ) { |i,j| i - 127.5 }, lazy( w, h ) { |i,j| j - 127.5 }
angle = Math.atan2( y, x ) * 180.0 / Math::PI + 179.5
radius = Math.hypot( y, x )
img.warp( angle.to_int, radius.to_int ).show

Chequerboard

Index arrays can be used to create patterns. The following example shows how one can create a chequerboard pattern using modulo, division without remainder, exclusive-or, and multiplication.

require 'hornetseye_xorg'
include Hornetseye
checker = finalise( 320, 240 ) do |i,j|
  ( ( ( i / 20 ) % 2 ) ^ ( ( j / 20 ) % 2 ) ) * 0xFF
end
checker.show

Histogram Segmentation

This is an implementation of histogram inversion. Here a ratio-histogram of the target (land) and the background (water) is computed. Histogram inversion is used to highlight the areas of the image which are considered to be land.

require 'hornetseye_rmagick'
require 'hornetseye_xorg'
include Hornetseye
div = 0x40
size = 0x100 / div
img = MultiArray.load_ubytergb 'http://www.wedesoft.de/hornetseye-api/images/world.jpg'
land = img[ 455 ... 525, 32 ... 116 ]
water = img[ 33 ... 133, 172 ... 272 ]
land_hist = ( land / div ).histogram( size, size, size ).to_int
water_hist = ( water / div ).histogram( size, size, size ).to_int
mask = ( land_hist + water_hist ) > 0
quot = ( ( land_hist - water_hist ).mask( mask ).to_dfloat /
         ( land_hist + water_hist ).mask( mask ) ).unmask mask
( img / div ).lut( quot ).normalise.show

Ordered Dithering

+----+----+----+----+----+----+----+----+
|  0 | 32 |  8 | 40 |  2 | 34 | 10 | 42 |
+----+----+----+----+----+----+----+----+
| 48 | 16 | 56 | 24 | 50 | 18 | 58 | 26 |
+----+----+----+----+----+----+----+----+
| 12 | 44 |  4 | 36 | 14 | 46 |  6 | 38 |
+----+----+----+----+----+----+----+----+
| 60 | 28 | 52 | 20 | 62 | 30 | 54 | 22 |
+----+----+----+----+----+----+----+----+
|  3 | 35 | 11 | 43 |  1 | 33 |  9 | 41 |
+----+----+----+----+----+----+----+----+
| 51 | 19 | 59 | 27 | 49 | 17 | 57 | 25 |
+----+----+----+----+----+----+----+----+
| 15 | 47 |  7 | 39 | 13 | 45 |  5 | 37 |
+----+----+----+----+----+----+----+----+
| 63 | 31 | 55 | 23 | 61 | 29 | 53 | 21 |
+----+----+----+----+----+----+----+----+

This example demonstrates dithering using a Bayer matrix. Dithering is most commonly used for displaying images on hardware with a low colour depth. The algorithm modifies the input values using an index matrix before quantising them.

require 'hornetseye_rmagick'
require 'hornetseye_xorg'
include Hornetseye
class MultiArray
  class << self
    def bayer(lsize)
      n = 1 << lsize
      idx = MultiArray(INT, 2).indgen n, n
      result = MultiArray.int(n, n).fill!
      m = Sequence[0, 2, 3, 1].to_int
      for i in 0 ... lsize
        q = idx.bit(i) | idx.bit(i + lsize) << 1
        result |= q.lut(m) << ((lsize - i - 1) << 1)
      end
      result
    end
  end
end
class Node
  def bit( i )
    (self & (1 << i)) >> i
  end
  def dither(lsize = 4)
    if typecode < RGB_
      result = MultiArray(typecode, dimension).new *shape
      result.r, result.g, result.b = [r, g, b].collect do |c|
        c.dither lsize
      end
      result
    else
      bayer = MultiArray.bayer lsize
      x = lazy(*shape) { |i,j| i & ((1 << lsize) - 1) }
      y = lazy(*shape) { |i,j| j & ((1 << lsize) - 1) }
      msk = (1 << lsize) ** 2 - 1
      (self & msk <= bayer.warp(x, y)).conditional self & ~msk, self | msk
    end
  end
end
img = MultiArray.load_ubytergb 'http://www.wedesoft.de/hornetseye-api/images/world.jpg'
img.dither(3).show

See Also