123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- // Copyright 2011 The Graphics-Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package detect
- import (
- "image"
- "math"
- )
- // Feature is a Haar-like feature.
- type Feature struct {
- Rect image.Rectangle
- Weight float64
- }
- // Classifier is a set of features with a threshold.
- type Classifier struct {
- Feature []Feature
- Threshold float64
- Left float64
- Right float64
- }
- // CascadeStage is a cascade of classifiers.
- type CascadeStage struct {
- Classifier []Classifier
- Threshold float64
- }
- // Cascade is a degenerate tree of Haar-like classifiers.
- type Cascade struct {
- Stage []CascadeStage
- Size image.Point
- }
- // Match returns true if the full image is classified as an object.
- func (c *Cascade) Match(m image.Image) bool {
- return c.classify(newWindow(m))
- }
- // Find returns a set of areas of m that match the feature cascade c.
- func (c *Cascade) Find(m image.Image) []image.Rectangle {
- // TODO(crawshaw): Consider de-duping strategies.
- matches := []image.Rectangle{}
- w := newWindow(m)
- b := m.Bounds()
- origScale := c.Size
- for s := origScale; s.X < b.Dx() && s.Y < b.Dy(); s = s.Add(s.Div(10)) {
- // translate region and classify
- tx := image.Pt(s.X/10, 0)
- ty := image.Pt(0, s.Y/10)
- for r := image.Rect(0, 0, s.X, s.Y).Add(b.Min); r.In(b); r = r.Add(ty) {
- for r1 := r; r1.In(b); r1 = r1.Add(tx) {
- if c.classify(w.subWindow(r1)) {
- matches = append(matches, r1)
- }
- }
- }
- }
- return matches
- }
- type window struct {
- mi *integral
- miSq *integral
- rect image.Rectangle
- invArea float64
- stdDev float64
- }
- func (w *window) init() {
- w.invArea = 1 / float64(w.rect.Dx()*w.rect.Dy())
- mean := float64(w.mi.sum(w.rect)) * w.invArea
- vr := float64(w.miSq.sum(w.rect))*w.invArea - mean*mean
- if vr < 0 {
- vr = 1
- }
- w.stdDev = math.Sqrt(vr)
- }
- func newWindow(m image.Image) *window {
- mi, miSq := newIntegrals(m)
- res := &window{
- mi: mi,
- miSq: miSq,
- rect: m.Bounds(),
- }
- res.init()
- return res
- }
- func (w *window) subWindow(r image.Rectangle) *window {
- res := &window{
- mi: w.mi,
- miSq: w.miSq,
- rect: r,
- }
- res.init()
- return res
- }
- func (c *Classifier) classify(w *window, pr *projector) float64 {
- s := 0.0
- for _, f := range c.Feature {
- s += float64(w.mi.sum(pr.rect(f.Rect))) * f.Weight
- }
- s *= w.invArea // normalize to maintain scale invariance
- if s < c.Threshold*w.stdDev {
- return c.Left
- }
- return c.Right
- }
- func (s *CascadeStage) classify(w *window, pr *projector) bool {
- sum := 0.0
- for _, c := range s.Classifier {
- sum += c.classify(w, pr)
- }
- return sum >= s.Threshold
- }
- func (c *Cascade) classify(w *window) bool {
- pr := newProjector(w.rect, image.Rectangle{image.Pt(0, 0), c.Size})
- for _, s := range c.Stage {
- if !s.classify(w, pr) {
- return false
- }
- }
- return true
- }
|