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 }