github.com/aloncn/graphics-go@v0.0.1/graphics/detect/detect.go (about)

     1  // Copyright 2011 The Graphics-Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package detect
     6  
     7  import (
     8  	"image"
     9  	"math"
    10  )
    11  
    12  // Feature is a Haar-like feature.
    13  type Feature struct {
    14  	Rect   image.Rectangle
    15  	Weight float64
    16  }
    17  
    18  // Classifier is a set of features with a threshold.
    19  type Classifier struct {
    20  	Feature   []Feature
    21  	Threshold float64
    22  	Left      float64
    23  	Right     float64
    24  }
    25  
    26  // CascadeStage is a cascade of classifiers.
    27  type CascadeStage struct {
    28  	Classifier []Classifier
    29  	Threshold  float64
    30  }
    31  
    32  // Cascade is a degenerate tree of Haar-like classifiers.
    33  type Cascade struct {
    34  	Stage []CascadeStage
    35  	Size  image.Point
    36  }
    37  
    38  // Match returns true if the full image is classified as an object.
    39  func (c *Cascade) Match(m image.Image) bool {
    40  	return c.classify(newWindow(m))
    41  }
    42  
    43  // Find returns a set of areas of m that match the feature cascade c.
    44  func (c *Cascade) Find(m image.Image) []image.Rectangle {
    45  	// TODO(crawshaw): Consider de-duping strategies.
    46  	matches := []image.Rectangle{}
    47  	w := newWindow(m)
    48  
    49  	b := m.Bounds()
    50  	origScale := c.Size
    51  	for s := origScale; s.X < b.Dx() && s.Y < b.Dy(); s = s.Add(s.Div(10)) {
    52  		// translate region and classify
    53  		tx := image.Pt(s.X/10, 0)
    54  		ty := image.Pt(0, s.Y/10)
    55  		for r := image.Rect(0, 0, s.X, s.Y).Add(b.Min); r.In(b); r = r.Add(ty) {
    56  			for r1 := r; r1.In(b); r1 = r1.Add(tx) {
    57  				if c.classify(w.subWindow(r1)) {
    58  					matches = append(matches, r1)
    59  				}
    60  			}
    61  		}
    62  	}
    63  	return matches
    64  }
    65  
    66  type window struct {
    67  	mi      *integral
    68  	miSq    *integral
    69  	rect    image.Rectangle
    70  	invArea float64
    71  	stdDev  float64
    72  }
    73  
    74  func (w *window) init() {
    75  	w.invArea = 1 / float64(w.rect.Dx()*w.rect.Dy())
    76  	mean := float64(w.mi.sum(w.rect)) * w.invArea
    77  	vr := float64(w.miSq.sum(w.rect))*w.invArea - mean*mean
    78  	if vr < 0 {
    79  		vr = 1
    80  	}
    81  	w.stdDev = math.Sqrt(vr)
    82  }
    83  
    84  func newWindow(m image.Image) *window {
    85  	mi, miSq := newIntegrals(m)
    86  	res := &window{
    87  		mi:   mi,
    88  		miSq: miSq,
    89  		rect: m.Bounds(),
    90  	}
    91  	res.init()
    92  	return res
    93  }
    94  
    95  func (w *window) subWindow(r image.Rectangle) *window {
    96  	res := &window{
    97  		mi:   w.mi,
    98  		miSq: w.miSq,
    99  		rect: r,
   100  	}
   101  	res.init()
   102  	return res
   103  }
   104  
   105  func (c *Classifier) classify(w *window, pr *projector) float64 {
   106  	s := 0.0
   107  	for _, f := range c.Feature {
   108  		s += float64(w.mi.sum(pr.rect(f.Rect))) * f.Weight
   109  	}
   110  	s *= w.invArea // normalize to maintain scale invariance
   111  	if s < c.Threshold*w.stdDev {
   112  		return c.Left
   113  	}
   114  	return c.Right
   115  }
   116  
   117  func (s *CascadeStage) classify(w *window, pr *projector) bool {
   118  	sum := 0.0
   119  	for _, c := range s.Classifier {
   120  		sum += c.classify(w, pr)
   121  	}
   122  	return sum >= s.Threshold
   123  }
   124  
   125  func (c *Cascade) classify(w *window) bool {
   126  	pr := newProjector(w.rect, image.Rectangle{image.Pt(0, 0), c.Size})
   127  	for _, s := range c.Stage {
   128  		if !s.classify(w, pr) {
   129  			return false
   130  		}
   131  	}
   132  	return true
   133  }