package frame import ( "fmt" "log" "path/filepath" "sync" "gocv.io/x/gocv" ) // Classifier pairs a loaded OpenCV cascade with the label under which its // detections are stored. type Classifier struct { Path string Label string cc gocv.CascadeClassifier mu sync.Mutex // DetectMultiScale is not safe for concurrent use } // Close releases the underlying cascade. Callers must close every classifier // built by LoadClassifiers once done with it. func (c *Classifier) Close() { c.cc.Close() } func (c *Classifier) label() string { if c.Label != "" { return c.Label } return c.Path } // LoadClassifiers builds a Classifier for every path matched by patterns. Each // pattern is a glob; a pattern that matches nothing is an error, so a typo or // missing model file fails at startup instead of silently disabling detection. func LoadClassifiers(patterns []string) ([]Classifier, error) { var classes []Classifier for _, pattern := range patterns { matches, err := filepath.Glob(pattern) if err != nil { return nil, fmt.Errorf("bad classifier pattern %q: %w", pattern, err) } if len(matches) == 0 { return nil, fmt.Errorf("classifier pattern %q matched no files", pattern) } for _, path := range matches { cc := gocv.NewCascadeClassifier() if !cc.Load(path) { cc.Close() return nil, fmt.Errorf("cannot load cascade %q", path) } classes = append(classes, Classifier{Path: path, Label: filepath.Base(path), cc: cc}) } } if len(classes) == 0 { return nil, fmt.Errorf("no classifiers configured") } return classes, nil } // DetectClassifiers runs every classifier against the frame and stores // detections under each classifier's label. The frame's Mat is converted once // and shared; classifiers serialize on their own detection mutex. func (f *Frame) DetectClassifiers(classifiers []Classifier) { if len(classifiers) == 0 { return } if f.Detections == nil { f.Detections = make(map[string][]Detection) } mat, err := f.ToMat() if err != nil { log.Printf("DetectClassifiers: converting frame to Mat: %v", err) return } defer mat.Close() var mu sync.Mutex var wg sync.WaitGroup wg.Add(len(classifiers)) for i := range classifiers { go func(c *Classifier) { defer wg.Done() c.mu.Lock() rects := c.cc.DetectMultiScale(mat) c.mu.Unlock() if len(rects) == 0 { return } label := c.label() results := make([]Detection, 0, len(rects)) for _, r := range rects { results = append(results, Detection{ DetectionTitle: label, DetectionMajorVersion: 1, DetectionMinorVersion: 0, DetectionPostfix: "", DetectionRegion: r, DetectionCertainty: 1.0, }) } mu.Lock() f.Detections[label] = results mu.Unlock() }(&classifiers[i]) } wg.Wait() }