110 lines
2.7 KiB
Go
110 lines
2.7 KiB
Go
package frame
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"log"
|
||
|
|
|
||
|
|
"gocv.io/x/gocv"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Comparison struct {
|
||
|
|
Guid1, Guid2 []byte
|
||
|
|
PixelsChanged int
|
||
|
|
Threshold uint8
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *Frame) compareTo(next *Frame, threshold uint8) (int, error) {
|
||
|
|
if f.Width != next.Width || f.Height != next.Height || f.Channels != next.Channels {
|
||
|
|
return 0, fmt.Errorf("frame dimension mismatch: (%dx%dx%d) vs (%dx%dx%d)",
|
||
|
|
f.Width, f.Height, f.Channels, next.Width, next.Height, next.Channels)
|
||
|
|
}
|
||
|
|
|
||
|
|
matA, err := f.ToMat()
|
||
|
|
if err != nil {
|
||
|
|
return 0, fmt.Errorf("ToMat error: %w", err)
|
||
|
|
}
|
||
|
|
defer matA.Close()
|
||
|
|
|
||
|
|
matB, err := next.ToMat()
|
||
|
|
if err != nil {
|
||
|
|
return 0, fmt.Errorf("ToMat error: %w", err)
|
||
|
|
}
|
||
|
|
defer matB.Close()
|
||
|
|
|
||
|
|
diff := gocv.NewMat()
|
||
|
|
defer diff.Close()
|
||
|
|
gocv.AbsDiff(matA, matB, &diff)
|
||
|
|
|
||
|
|
gray := gocv.NewMat()
|
||
|
|
defer gray.Close()
|
||
|
|
if diff.Channels() > 1 {
|
||
|
|
gocv.CvtColor(diff, &gray, gocv.ColorBGRToGray)
|
||
|
|
} else {
|
||
|
|
diff.CopyTo(&gray)
|
||
|
|
}
|
||
|
|
|
||
|
|
thresh := gocv.NewMat()
|
||
|
|
defer thresh.Close()
|
||
|
|
gocv.Threshold(gray, &thresh, float32(threshold), 255, gocv.ThresholdBinary)
|
||
|
|
|
||
|
|
return gocv.CountNonZero(thresh), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetHighestMotion returns the highest percentage of changed pixels between
|
||
|
|
// any two consecutive frames in the clip. It assumes CountChangedPixels has
|
||
|
|
// already populated Comparisons (i.e. motion has already been determined).
|
||
|
|
// The result is an integer percentage in the range [0, 100]. Returns 0 if
|
||
|
|
// there are no comparisons or the frame dimensions are unknown.
|
||
|
|
func (c *Clip) GetHighestMotion() int {
|
||
|
|
totalPixels := int(c.Width) * int(c.Height)
|
||
|
|
if totalPixels <= 0 || len(c.Comparisons) == 0 {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
highest := 0
|
||
|
|
for _, cmp := range c.Comparisons {
|
||
|
|
percent := int((int64(cmp.PixelsChanged) * 100) / int64(totalPixels))
|
||
|
|
if percent > highest {
|
||
|
|
highest = percent
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return highest
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Clip) CountChangedPixels(threshold uint8) error {
|
||
|
|
c.Comparisons = nil
|
||
|
|
var errs []error
|
||
|
|
for i := 0; i < len(c.PixelMats)-1; i++ {
|
||
|
|
a := Frame{
|
||
|
|
PixelBytes: c.PixelMats[i],
|
||
|
|
Width: c.Width,
|
||
|
|
Height: c.Height,
|
||
|
|
GocvImageType: c.Types,
|
||
|
|
Channels: c.Channels,
|
||
|
|
Guid: c.Guids[i],
|
||
|
|
}
|
||
|
|
b := Frame{
|
||
|
|
PixelBytes: c.PixelMats[i+1],
|
||
|
|
Width: c.Width,
|
||
|
|
Height: c.Height,
|
||
|
|
GocvImageType: c.Types,
|
||
|
|
Channels: c.Channels,
|
||
|
|
Guid: c.Guids[i+1],
|
||
|
|
}
|
||
|
|
changed, err := a.compareTo(&b, threshold)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("CountChangedPixels: frame pair %d/%d: %v", i, i+1, err)
|
||
|
|
errs = append(errs, fmt.Errorf("pair %d/%d: %w", i, i+1, err))
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
c.Comparisons = append(c.Comparisons, Comparison{
|
||
|
|
Guid1: c.Guids[i],
|
||
|
|
Guid2: c.Guids[i+1],
|
||
|
|
PixelsChanged: changed,
|
||
|
|
Threshold: threshold,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
return errors.Join(errs...)
|
||
|
|
}
|