Files
bytetrack-counter-go/pkg/yolo/yolo.go
T
2026-06-30 18:49:39 +07:00

237 lines
4.9 KiB
Go

package yolo
import (
"image"
"math"
"github.com/anomalyco/bytetrack-counter-go/pkg/rknn"
"gocv.io/x/gocv"
)
type Detection struct {
BBox [4]float64
Score float64
Class int
Keypoints [][2]float64
}
type Detector struct {
rknn *rknn.Context
imgSz int
conf float64
iouThr float64
numClasses int
scoreSigmoid bool
}
func NewDetector(modelPath string, imgSz int, conf float64, numClasses int, scoreSigmoid bool) (*Detector, error) {
ctx, err := rknn.LoadModel(modelPath)
if err != nil {
return nil, err
}
return &Detector{
rknn: ctx,
imgSz: imgSz,
conf: conf,
iouThr: 0.45,
numClasses: numClasses,
scoreSigmoid: scoreSigmoid,
}, nil
}
func (d *Detector) Detect(frame gocv.Mat) ([]Detection, error) {
h0, w0 := frame.Rows(), frame.Cols()
scale := math.Min(float64(d.imgSz)/float64(h0), float64(d.imgSz)/float64(w0))
nh, nw := int(float64(h0)*scale), int(float64(w0)*scale)
resized := gocv.NewMat()
defer resized.Close()
gocv.Resize(frame, &resized, image.Point{X: nw, Y: nh}, 0, 0, gocv.InterpolationLinear)
letterbox := gocv.NewMatWithSize(d.imgSz, d.imgSz, gocv.MatTypeCV8UC3)
defer letterbox.Close()
letterbox.SetTo(gocv.NewScalar(114, 114, 114, 0))
dy := (d.imgSz - nh) / 2
dx := (d.imgSz - nw) / 2
roi := letterbox.Region(image.Rect(dx, dy, dx+nw, dy+nh))
resized.CopyTo(&roi)
rgb := gocv.NewMat()
defer rgb.Close()
gocv.CvtColor(letterbox, &rgb, gocv.ColorBGRToRGB)
data := rgb.ToBytes()
outputs, err := d.rknn.InferenceRGB(data, d.imgSz, d.imgSz)
if err != nil {
return nil, err
}
if len(outputs) == 0 {
return nil, nil
}
out := outputs[0]
return d.decodeOutput(out, h0, w0, scale, float64(dy), float64(dx))
}
func (d *Detector) decodeOutput(out []float32, h0, w0 int, scale, padY, padX float64) ([]Detection, error) {
if len(out) == 0 {
return nil, nil
}
stride := d.numClasses + 4
if len(out)%stride != 0 {
return nil, nil
}
numDet := len(out) / stride
if numDet == 0 {
return nil, nil
}
var detections []Detection
for i := 0; i < numDet; i++ {
off := i * stride
cx := float64(out[off+0])
cy := float64(out[off+1])
w := float64(out[off+2])
h := float64(out[off+3])
x1 := cx - w/2
y1 := cy - h/2
x2 := cx + w/2
y2 := cy + h/2
var maxScore float64
var bestClass int
for c := 0; c < d.numClasses; c++ {
score := float64(out[off+4+c])
if d.scoreSigmoid {
score = sigmoid(clamp(score, -10, 10))
}
if score > maxScore {
maxScore = score
bestClass = c
}
}
if maxScore <= d.conf {
continue
}
x1 = (x1 - padX) / scale
y1 = (y1 - padY) / scale
x2 = (x2 - padX) / scale
y2 = (y2 - padY) / scale
x1 = clamp(x1, 0, float64(w0))
y1 = clamp(y1, 0, float64(h0))
x2 = clamp(x2, 0, float64(w0))
y2 = clamp(y2, 0, float64(h0))
detections = append(detections, Detection{
BBox: [4]float64{x1, y1, x2, y2},
Score: maxScore,
Class: bestClass,
})
}
return nmsByClass(detections, d.iouThr, d.numClasses), nil
}
func nmsByClass(dets []Detection, iouThr float64, numClasses int) []Detection {
if len(dets) == 0 {
return nil
}
byClass := make([][]int, numClasses)
for i, d := range dets {
byClass[d.Class] = append(byClass[d.Class], i)
}
var result []Detection
for cls := 0; cls < numClasses; cls++ {
idxs := byClass[cls]
if len(idxs) == 0 {
continue
}
scores := make([]float64, len(idxs))
boxes := make([][4]float64, len(idxs))
for i, idx := range idxs {
scores[i] = dets[idx].Score
boxes[i] = dets[idx].BBox
}
keep := nmsIndices(boxes, scores, iouThr)
for _, k := range keep {
result = append(result, dets[idxs[k]])
}
}
return result
}
func nmsIndices(boxes [][4]float64, scores []float64, iouThr float64) []int {
if len(scores) == 0 {
return nil
}
order := make([]int, len(scores))
for i := range order {
order[i] = i
}
for i := 0; i < len(scores); i++ {
for j := i + 1; j < len(scores); j++ {
if scores[order[i]] < scores[order[j]] {
order[i], order[j] = order[j], order[i]
}
}
}
var keep []int
for len(order) > 0 {
idx := order[0]
keep = append(keep, idx)
if len(order) == 1 {
break
}
rest := order[1:]
var newOrder []int
for _, r := range rest {
xx1 := math.Max(boxes[idx][0], boxes[r][0])
yy1 := math.Max(boxes[idx][1], boxes[r][1])
xx2 := math.Min(boxes[idx][2], boxes[r][2])
yy2 := math.Min(boxes[idx][3], boxes[r][3])
w := math.Max(0, xx2-xx1)
h := math.Max(0, yy2-yy1)
inter := w * h
areaI := (boxes[idx][2] - boxes[idx][0]) * (boxes[idx][3] - boxes[idx][1])
areaR := (boxes[r][2] - boxes[r][0]) * (boxes[r][3] - boxes[r][1])
iou := inter / (areaI + areaR - inter + 1e-16)
if iou < iouThr {
newOrder = append(newOrder, r)
}
}
order = newOrder
}
return keep
}
func sigmoid(x float64) float64 {
return 1.0 / (1.0 + math.Exp(-x))
}
func clamp(x, lo, hi float64) float64 {
return math.Max(lo, math.Min(hi, x))
}
func (d *Detector) Release() {
d.rknn.Release()
}