mirror of https://bitbucket.org/ausocean/av.git
Merged in target-location (pull request #478)
Add functionality to set the location of the chessboard Approved-by: Saxon Milton
This commit is contained in:
commit
88062eef87
|
@ -175,12 +175,12 @@ func main() {
|
||||||
time.Sleep(runPreDelay)
|
time.Sleep(runPreDelay)
|
||||||
|
|
||||||
log.Log(logger.Debug, "beginning main loop")
|
log.Log(logger.Debug, "beginning main loop")
|
||||||
run(rv, ns, log, netLog)
|
run(rv, ns, log, netLog, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// run starts the main loop. This will run netsender on every pass of the loop
|
// run starts the main loop. This will run netsender on every pass of the loop
|
||||||
// (sleeping inbetween), check vars, and if changed, update revid as appropriate.
|
// (sleeping inbetween), check vars, and if changed, update revid as appropriate.
|
||||||
func run(rv *revid.Revid, ns *netsender.Sender, l *logger.Logger, nl *netlogger.Logger) {
|
func run(rv *revid.Revid, ns *netsender.Sender, l *logger.Logger, nl *netlogger.Logger, p *turbidityProbe) {
|
||||||
var vs int
|
var vs int
|
||||||
for {
|
for {
|
||||||
l.Log(logger.Debug, "running netsender")
|
l.Log(logger.Debug, "running netsender")
|
||||||
|
@ -225,6 +225,12 @@ func run(rv *revid.Revid, ns *netsender.Sender, l *logger.Logger, nl *netlogger.
|
||||||
}
|
}
|
||||||
l.Log(logger.Info, "revid successfully reconfigured")
|
l.Log(logger.Info, "revid successfully reconfigured")
|
||||||
|
|
||||||
|
// Update transform matrix based on new revid variables.
|
||||||
|
err = p.Update(rv.Config().TransformMatrix)
|
||||||
|
if err != nil {
|
||||||
|
l.Log(logger.Error, "could not update turbidity probe", "error", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
l.Log(logger.Debug, "checking mode")
|
l.Log(logger.Debug, "checking mode")
|
||||||
switch ns.Mode() {
|
switch ns.Mode() {
|
||||||
case modePaused:
|
case modePaused:
|
||||||
|
|
|
@ -49,6 +49,7 @@ const (
|
||||||
maxImages = 1 // Max number of images read when evaluating turbidity.
|
maxImages = 1 // Max number of images read when evaluating turbidity.
|
||||||
bufferLimit = 20000 // 20KB
|
bufferLimit = 20000 // 20KB
|
||||||
trimTolerance = 200 // Number of times trim can be called where no keyframe is found.
|
trimTolerance = 200 // Number of times trim can be called where no keyframe is found.
|
||||||
|
transformSize = 9 // Size of the square projective matrix.
|
||||||
)
|
)
|
||||||
|
|
||||||
// Turbidity sensor constants.
|
// Turbidity sensor constants.
|
||||||
|
@ -68,6 +69,7 @@ type turbidityProbe struct {
|
||||||
ts *turbidity.TurbiditySensor
|
ts *turbidity.TurbiditySensor
|
||||||
log logger.Logger
|
log logger.Logger
|
||||||
buffer *bytes.Buffer
|
buffer *bytes.Buffer
|
||||||
|
transform []float64
|
||||||
trimCounter int
|
trimCounter int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,10 +81,12 @@ func NewTurbidityProbe(log logger.Logger, delay time.Duration) (*turbidityProbe,
|
||||||
tp.ticker = *time.NewTicker(delay)
|
tp.ticker = *time.NewTicker(delay)
|
||||||
tp.buffer = bytes.NewBuffer(*new([]byte))
|
tp.buffer = bytes.NewBuffer(*new([]byte))
|
||||||
|
|
||||||
|
tp.transform = make([]float64, transformSize)
|
||||||
|
transformMatrix := floatToMat(tp.transform)
|
||||||
|
|
||||||
// Create the turbidity sensor.
|
// Create the turbidity sensor.
|
||||||
standard := gocv.IMRead("../../turbidity/images/default.jpg", gocv.IMReadGrayScale)
|
|
||||||
template := gocv.IMRead("../../turbidity/images/template.jpg", gocv.IMReadGrayScale)
|
template := gocv.IMRead("../../turbidity/images/template.jpg", gocv.IMReadGrayScale)
|
||||||
ts, err := turbidity.NewTurbiditySensor(template, standard, k1, k2, filterSize, scale, alpha, log)
|
ts, err := turbidity.NewTurbiditySensor(template, transformMatrix, k1, k2, filterSize, scale, alpha, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create turbidity sensor: %w", err)
|
return nil, fmt.Errorf("failed to create turbidity sensor: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -141,6 +145,26 @@ func (tp *turbidityProbe) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update will update the probe and turbidity sensor with the new transformation matrix if it has been changed.
|
||||||
|
func (tp *turbidityProbe) Update(transformMatrix []float64) error {
|
||||||
|
if len(transformMatrix) != transformSize {
|
||||||
|
return errors.New("transformation matrix has incorrect size")
|
||||||
|
}
|
||||||
|
for i := range tp.transform {
|
||||||
|
if tp.transform[i] == transformMatrix[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Update the turbidity sensor with new transformation.
|
||||||
|
tp.log.Log(logger.Debug, "updating the transformation matrix")
|
||||||
|
tp.transform = transformMatrix
|
||||||
|
newTransform := floatToMat(tp.transform)
|
||||||
|
tp.ts.TransformMatrix = newTransform
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tp.log.Log(logger.Debug, "no change to the transformation matrix")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (tp *turbidityProbe) turbidityCalculation() error {
|
func (tp *turbidityProbe) turbidityCalculation() error {
|
||||||
var imgs []gocv.Mat
|
var imgs []gocv.Mat
|
||||||
img := gocv.NewMat()
|
img := gocv.NewMat()
|
||||||
|
@ -208,3 +232,14 @@ func cleanUp(file string, vc *gocv.VideoCapture) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// floatToMat will convert a slice of 9 floats to a gocv.Mat.
|
||||||
|
func floatToMat(array []float64) gocv.Mat {
|
||||||
|
mat := gocv.NewMatWithSize(3, 3, gocv.MatTypeCV64F)
|
||||||
|
for i := 0; i < mat.Rows(); i++ {
|
||||||
|
for j := 0; j < mat.Cols(); j++ {
|
||||||
|
mat.SetDoubleAt(i, j, array[i*mat.Cols()+j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mat
|
||||||
|
}
|
||||||
|
|
|
@ -46,10 +46,8 @@ func NewTurbidityProbe(log logger.Logger, delay time.Duration) (*turbidityProbe,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write performs no operation for CircleCI testing only.
|
// Write performs no operation for CircleCI testing only.
|
||||||
func (tp *turbidityProbe) Write(p []byte) (int, error) {
|
func (tp *turbidityProbe) Write(p []byte) (int, error) { return 0, nil }
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tp *turbidityProbe) Close() error {
|
func (tp *turbidityProbe) Update(mat []float64) error { return nil }
|
||||||
return nil
|
|
||||||
}
|
func (tp *turbidityProbe) Close() error { return nil }
|
||||||
|
|
|
@ -44,11 +44,18 @@ func TestProbe(t *testing.T) {
|
||||||
MaxAge: logMaxAge,
|
MaxAge: logMaxAge,
|
||||||
}
|
}
|
||||||
log := logger.New(logVerbosity, io.MultiWriter(fileLog), logSuppress)
|
log := logger.New(logVerbosity, io.MultiWriter(fileLog), logSuppress)
|
||||||
|
updatedMatrix := []float64{-0.2731048063, -0.0020501869, 661.0275911942, 0.0014327789, -0.2699443748, 339.3921028016, 0.0000838317, 0.0000476486, 1.0}
|
||||||
|
|
||||||
ts, err := NewTurbidityProbe(*log, time.Microsecond)
|
ts, err := NewTurbidityProbe(*log, time.Microsecond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create turbidity probe")
|
t.Fatalf("failed to create turbidity probe")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = ts.Update(updatedMatrix)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not update probe: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
video, err := ioutil.ReadFile("logo.h264")
|
video, err := ioutil.ReadFile("logo.h264")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to read file: %v", err)
|
t.Fatalf("failed to read file: %v", err)
|
||||||
|
|
|
@ -273,6 +273,10 @@ type Config struct {
|
||||||
|
|
||||||
VerticalFlip bool // VerticalFlip flips video vertically for Raspivid input.
|
VerticalFlip bool // VerticalFlip flips video vertically for Raspivid input.
|
||||||
Width uint // Width defines the input video width Raspivid input.
|
Width uint // Width defines the input video width Raspivid input.
|
||||||
|
|
||||||
|
// TransformMatrix describes the projective transformation matrix to extract a target from the
|
||||||
|
// video data for turbidty calculations.
|
||||||
|
TransformMatrix []float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks for any errors in the config fields and defaults settings
|
// Validate checks for any errors in the config fields and defaults settings
|
||||||
|
|
|
@ -122,6 +122,7 @@ func TestUpdate(t *testing.T) {
|
||||||
"VBRQuality": "excellent",
|
"VBRQuality": "excellent",
|
||||||
"VerticalFlip": "true",
|
"VerticalFlip": "true",
|
||||||
"Width": "300",
|
"Width": "300",
|
||||||
|
"TransformMatrix": "0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8,0.9",
|
||||||
}
|
}
|
||||||
|
|
||||||
dl := &dumbLogger{}
|
dl := &dumbLogger{}
|
||||||
|
@ -172,6 +173,7 @@ func TestUpdate(t *testing.T) {
|
||||||
VBRQuality: QualityExcellent,
|
VBRQuality: QualityExcellent,
|
||||||
VerticalFlip: true,
|
VerticalFlip: true,
|
||||||
Width: 300,
|
Width: 300,
|
||||||
|
TransformMatrix: []float64{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9},
|
||||||
}
|
}
|
||||||
|
|
||||||
got := Config{Logger: dl}
|
got := Config{Logger: dl}
|
||||||
|
|
|
@ -99,6 +99,7 @@ const (
|
||||||
KeyVBRQuality = "VBRQuality"
|
KeyVBRQuality = "VBRQuality"
|
||||||
KeyVerticalFlip = "VerticalFlip"
|
KeyVerticalFlip = "VerticalFlip"
|
||||||
KeyWidth = "Width"
|
KeyWidth = "Width"
|
||||||
|
KeyTransformMatrix = "TransformMatrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config map parameter types.
|
// Config map parameter types.
|
||||||
|
@ -140,38 +141,61 @@ const (
|
||||||
// this variable in a Config, and a function for validating the value of the variable.
|
// this variable in a Config, and a function for validating the value of the variable.
|
||||||
var Variables = []struct {
|
var Variables = []struct {
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
Update func(*Config, string)
|
Update func(*Config, string)
|
||||||
Validate func(*Config)
|
Validate func(*Config)
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
Name: KeyTransformMatrix,
|
||||||
|
Type: typeString,
|
||||||
|
Update: func(c *Config, v string) {
|
||||||
|
c.Logger.Log(logger.Debug, "updating transform matrix", "string", v)
|
||||||
|
v = strings.Replace(v, " ", "", -1)
|
||||||
|
vals := make([]float64, 0)
|
||||||
|
if v == "" {
|
||||||
|
c.TransformMatrix = vals
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
elements := strings.Split(v, ",")
|
||||||
|
for _, e := range elements {
|
||||||
|
vFloat, err := strconv.ParseFloat(e, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger.Log(logger.Warning, "invalid TransformMatrix param", "value", e)
|
||||||
|
}
|
||||||
|
vals = append(vals, vFloat)
|
||||||
|
}
|
||||||
|
c.TransformMatrix = vals
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: KeyAutoWhiteBalance,
|
Name: KeyAutoWhiteBalance,
|
||||||
Type: "enum:off,auto,sun,cloud,shade,tungsten,fluorescent,incandescent,flash,horizon",
|
Type: "enum:off,auto,sun,cloud,shade,tungsten,fluorescent,incandescent,flash,horizon",
|
||||||
Update: func(c *Config, v string) { c.AutoWhiteBalance = v },
|
Update: func(c *Config, v string) { c.AutoWhiteBalance = v },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyAWBGains,
|
Name: KeyAWBGains,
|
||||||
Type: typeString,
|
Type: typeString,
|
||||||
Update: func(c *Config, v string) { c.AWBGains = v },
|
Update: func(c *Config, v string) { c.AWBGains = v },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyBitDepth,
|
Name: KeyBitDepth,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.BitDepth = parseUint(KeyBitDepth, v, c) },
|
Update: func(c *Config, v string) { c.BitDepth = parseUint(KeyBitDepth, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyBitrate,
|
Name: KeyBitrate,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.Bitrate = parseUint(KeyBitrate, v, c) },
|
Update: func(c *Config, v string) { c.Bitrate = parseUint(KeyBitrate, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyBrightness,
|
Name: KeyBrightness,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.Brightness = parseUint(KeyBrightness, v, c) },
|
Update: func(c *Config, v string) { c.Brightness = parseUint(KeyBrightness, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyBurstPeriod,
|
Name: KeyBurstPeriod,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.BurstPeriod = parseUint(KeyBurstPeriod, v, c) },
|
Update: func(c *Config, v string) { c.BurstPeriod = parseUint(KeyBurstPeriod, v, c) },
|
||||||
Validate: func(c *Config) {
|
Validate: func(c *Config) {
|
||||||
if c.BurstPeriod <= 0 {
|
if c.BurstPeriod <= 0 {
|
||||||
|
@ -182,12 +206,12 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyCameraChan,
|
Name: KeyCameraChan,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.CameraChan = uint8(parseUint(KeyCameraChan, v, c)) },
|
Update: func(c *Config, v string) { c.CameraChan = uint8(parseUint(KeyCameraChan, v, c)) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyCameraIP,
|
Name: KeyCameraIP,
|
||||||
Type: typeString,
|
Type: typeString,
|
||||||
Update: func(c *Config, v string) { c.CameraIP = v },
|
Update: func(c *Config, v string) { c.CameraIP = v },
|
||||||
Validate: func(c *Config) {
|
Validate: func(c *Config) {
|
||||||
if c.CameraIP == "" {
|
if c.CameraIP == "" {
|
||||||
|
@ -198,11 +222,11 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyCBR,
|
Name: KeyCBR,
|
||||||
Type: typeBool,
|
Type: typeBool,
|
||||||
Update: func(c *Config, v string) { c.CBR = parseBool(KeyCBR, v, c) },
|
Update: func(c *Config, v string) { c.CBR = parseBool(KeyCBR, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyClipDuration,
|
Name: KeyClipDuration,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
_v, err := strconv.Atoi(v)
|
_v, err := strconv.Atoi(v)
|
||||||
|
@ -220,27 +244,27 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyChannels,
|
Name: KeyChannels,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.Channels = parseUint(KeyChannels, v, c) },
|
Update: func(c *Config, v string) { c.Channels = parseUint(KeyChannels, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyContrast,
|
Name: KeyContrast,
|
||||||
Type: typeInt,
|
Type: typeInt,
|
||||||
Update: func(c *Config, v string) { c.Contrast = parseInt(KeyContrast, v, c) },
|
Update: func(c *Config, v string) { c.Contrast = parseInt(KeyContrast, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyEV,
|
Name: KeyEV,
|
||||||
Type: typeInt,
|
Type: typeInt,
|
||||||
Update: func(c *Config, v string) { c.EV = parseInt(KeyEV, v, c) },
|
Update: func(c *Config, v string) { c.EV = parseInt(KeyEV, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyExposure,
|
Name: KeyExposure,
|
||||||
Type: "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks",
|
Type: "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks",
|
||||||
Update: func(c *Config, v string) { c.Exposure = v },
|
Update: func(c *Config, v string) { c.Exposure = v },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyFileFPS,
|
Name: KeyFileFPS,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.FileFPS = parseUint(KeyFileFPS, v, c) },
|
Update: func(c *Config, v string) { c.FileFPS = parseUint(KeyFileFPS, v, c) },
|
||||||
Validate: func(c *Config) {
|
Validate: func(c *Config) {
|
||||||
if c.FileFPS <= 0 || (c.FileFPS > 0 && c.Input != InputFile) {
|
if c.FileFPS <= 0 || (c.FileFPS > 0 && c.Input != InputFile) {
|
||||||
|
@ -250,7 +274,7 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyFilters,
|
Name: KeyFilters,
|
||||||
Type: "enums:NoOp,MOG,VariableFPS,KNN,Difference,Basic",
|
Type: "enums:NoOp,MOG,VariableFPS,KNN,Difference,Basic",
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
filters := strings.Split(v, ",")
|
filters := strings.Split(v, ",")
|
||||||
|
@ -267,7 +291,7 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyFrameRate,
|
Name: KeyFrameRate,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.FrameRate = parseUint(KeyFrameRate, v, c) },
|
Update: func(c *Config, v string) { c.FrameRate = parseUint(KeyFrameRate, v, c) },
|
||||||
Validate: func(c *Config) {
|
Validate: func(c *Config) {
|
||||||
if c.FrameRate <= 0 || c.FrameRate > 60 {
|
if c.FrameRate <= 0 || c.FrameRate > 60 {
|
||||||
|
@ -278,21 +302,21 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyHeight,
|
Name: KeyHeight,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.Height = parseUint(KeyHeight, v, c) },
|
Update: func(c *Config, v string) { c.Height = parseUint(KeyHeight, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyHorizontalFlip,
|
Name: KeyHorizontalFlip,
|
||||||
Type: typeBool,
|
Type: typeBool,
|
||||||
Update: func(c *Config, v string) { c.HorizontalFlip = parseBool(KeyHorizontalFlip, v, c) },
|
Update: func(c *Config, v string) { c.HorizontalFlip = parseBool(KeyHorizontalFlip, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyHTTPAddress,
|
Name: KeyHTTPAddress,
|
||||||
Type: typeString,
|
Type: typeString,
|
||||||
Update: func(c *Config, v string) { c.HTTPAddress = v },
|
Update: func(c *Config, v string) { c.HTTPAddress = v },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyInput,
|
Name: KeyInput,
|
||||||
Type: "enum:raspivid,raspistill,rtsp,v4l,file,audio",
|
Type: "enum:raspivid,raspistill,rtsp,v4l,file,audio",
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
c.Input = parseEnum(
|
c.Input = parseEnum(
|
||||||
|
@ -319,7 +343,7 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyInputCodec,
|
Name: KeyInputCodec,
|
||||||
Type: "enum:h264,h265,mjpeg,jpeg,pcm,adpcm",
|
Type: "enum:h264,h265,mjpeg,jpeg,pcm,adpcm",
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
c.InputCodec = v
|
c.InputCodec = v
|
||||||
|
@ -333,16 +357,16 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyInputPath,
|
Name: KeyInputPath,
|
||||||
Type: typeString,
|
Type: typeString,
|
||||||
Update: func(c *Config, v string) { c.InputPath = v },
|
Update: func(c *Config, v string) { c.InputPath = v },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyISO,
|
Name: KeyISO,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.ISO = parseUint(KeyISO, v, c) },
|
Update: func(c *Config, v string) { c.ISO = parseUint(KeyISO, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyLogging,
|
Name: KeyLogging,
|
||||||
Type: "enum:Debug,Info,Warning,Error,Fatal",
|
Type: "enum:Debug,Info,Warning,Error,Fatal",
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
switch v {
|
switch v {
|
||||||
|
@ -371,18 +395,18 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyLoop,
|
Name: KeyLoop,
|
||||||
Type: typeBool,
|
Type: typeBool,
|
||||||
Update: func(c *Config, v string) { c.Loop = parseBool(KeyLoop, v, c) },
|
Update: func(c *Config, v string) { c.Loop = parseBool(KeyLoop, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyMinFPS,
|
Name: KeyMinFPS,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.MinFPS = parseUint(KeyMinFPS, v, c) },
|
Update: func(c *Config, v string) { c.MinFPS = parseUint(KeyMinFPS, v, c) },
|
||||||
Validate: func(c *Config) { c.MinFPS = lessThanOrEqual(KeyMinFPS, c.MinFPS, 0, c, defaultMinFPS) },
|
Validate: func(c *Config) { c.MinFPS = lessThanOrEqual(KeyMinFPS, c.MinFPS, 0, c, defaultMinFPS) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyMinFrames,
|
Name: KeyMinFrames,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.MinFrames = parseUint(KeyMinFrames, v, c) },
|
Update: func(c *Config, v string) { c.MinFrames = parseUint(KeyMinFrames, v, c) },
|
||||||
Validate: func(c *Config) {
|
Validate: func(c *Config) {
|
||||||
const maxMinFrames = 1000
|
const maxMinFrames = 1000
|
||||||
|
@ -393,7 +417,7 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyMode,
|
Name: KeyMode,
|
||||||
Type: "enum:Normal,Paused,Burst,Loop",
|
Type: "enum:Normal,Paused,Burst,Loop",
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
c.Loop = false
|
c.Loop = false
|
||||||
|
@ -404,26 +428,26 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyMotionDownscaling,
|
Name: KeyMotionDownscaling,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.MotionDownscaling = parseUint(KeyMotionDownscaling, v, c) },
|
Update: func(c *Config, v string) { c.MotionDownscaling = parseUint(KeyMotionDownscaling, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyMotionHistory,
|
Name: KeyMotionHistory,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.MotionHistory = parseUint(KeyMotionHistory, v, c) },
|
Update: func(c *Config, v string) { c.MotionHistory = parseUint(KeyMotionHistory, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyMotionInterval,
|
Name: KeyMotionInterval,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.MotionInterval = parseUint(KeyMotionInterval, v, c) },
|
Update: func(c *Config, v string) { c.MotionInterval = parseUint(KeyMotionInterval, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyMotionKernel,
|
Name: KeyMotionKernel,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.MotionKernel = parseUint(KeyMotionKernel, v, c) },
|
Update: func(c *Config, v string) { c.MotionKernel = parseUint(KeyMotionKernel, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyMotionMinArea,
|
Name: KeyMotionMinArea,
|
||||||
Type: typeFloat,
|
Type: typeFloat,
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
f, err := strconv.ParseFloat(v, 64)
|
f, err := strconv.ParseFloat(v, 64)
|
||||||
|
@ -435,16 +459,16 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyMotionPadding,
|
Name: KeyMotionPadding,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.MotionPadding = parseUint(KeyMotionPadding, v, c) },
|
Update: func(c *Config, v string) { c.MotionPadding = parseUint(KeyMotionPadding, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyMotionPixels,
|
Name: KeyMotionPixels,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.MotionPixels = parseUint(KeyMotionPixels, v, c) },
|
Update: func(c *Config, v string) { c.MotionPixels = parseUint(KeyMotionPixels, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyMotionThreshold,
|
Name: KeyMotionThreshold,
|
||||||
Type: typeFloat,
|
Type: typeFloat,
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
f, err := strconv.ParseFloat(v, 64)
|
f, err := strconv.ParseFloat(v, 64)
|
||||||
|
@ -455,7 +479,7 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyOutput,
|
Name: KeyOutput,
|
||||||
Type: "enum:File,HTTP,RTMP,RTP",
|
Type: "enum:File,HTTP,RTMP,RTP",
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
c.Outputs = make([]uint8, 1)
|
c.Outputs = make([]uint8, 1)
|
||||||
|
@ -477,11 +501,11 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyOutputPath,
|
Name: KeyOutputPath,
|
||||||
Type: typeString,
|
Type: typeString,
|
||||||
Update: func(c *Config, v string) { c.OutputPath = v },
|
Update: func(c *Config, v string) { c.OutputPath = v },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyOutputs,
|
Name: KeyOutputs,
|
||||||
Type: "enums:File,HTTP,RTMP,RTP",
|
Type: "enums:File,HTTP,RTMP,RTP",
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
outputs := strings.Split(v, ",")
|
outputs := strings.Split(v, ",")
|
||||||
|
@ -512,18 +536,18 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyPSITime,
|
Name: KeyPSITime,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.PSITime = parseUint(KeyPSITime, v, c) },
|
Update: func(c *Config, v string) { c.PSITime = parseUint(KeyPSITime, v, c) },
|
||||||
Validate: func(c *Config) { c.PSITime = lessThanOrEqual(KeyPSITime, c.PSITime, 0, c, defaultPSITime) },
|
Validate: func(c *Config) { c.PSITime = lessThanOrEqual(KeyPSITime, c.PSITime, 0, c, defaultPSITime) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyQuantization,
|
Name: KeyQuantization,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.Quantization = parseUint(KeyQuantization, v, c) },
|
Update: func(c *Config, v string) { c.Quantization = parseUint(KeyQuantization, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyPoolCapacity,
|
Name: KeyPoolCapacity,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.PoolCapacity = parseUint(KeyPoolCapacity, v, c) },
|
Update: func(c *Config, v string) { c.PoolCapacity = parseUint(KeyPoolCapacity, v, c) },
|
||||||
Validate: func(c *Config) {
|
Validate: func(c *Config) {
|
||||||
c.PoolCapacity = lessThanOrEqual(KeyPoolCapacity, c.PoolCapacity, 0, c, defaultPoolCapacity)
|
c.PoolCapacity = lessThanOrEqual(KeyPoolCapacity, c.PoolCapacity, 0, c, defaultPoolCapacity)
|
||||||
|
@ -531,7 +555,7 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyPoolStartElementSize,
|
Name: KeyPoolStartElementSize,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.PoolStartElementSize = parseUint("PoolStartElementSize", v, c) },
|
Update: func(c *Config, v string) { c.PoolStartElementSize = parseUint("PoolStartElementSize", v, c) },
|
||||||
Validate: func(c *Config) {
|
Validate: func(c *Config) {
|
||||||
c.PoolStartElementSize = lessThanOrEqual("PoolStartElementSize", c.PoolStartElementSize, 0, c, defaultPoolStartElementSize)
|
c.PoolStartElementSize = lessThanOrEqual("PoolStartElementSize", c.PoolStartElementSize, 0, c, defaultPoolStartElementSize)
|
||||||
|
@ -539,14 +563,14 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyPoolWriteTimeout,
|
Name: KeyPoolWriteTimeout,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.PoolWriteTimeout = parseUint(KeyPoolWriteTimeout, v, c) },
|
Update: func(c *Config, v string) { c.PoolWriteTimeout = parseUint(KeyPoolWriteTimeout, v, c) },
|
||||||
Validate: func(c *Config) {
|
Validate: func(c *Config) {
|
||||||
c.PoolWriteTimeout = lessThanOrEqual(KeyPoolWriteTimeout, c.PoolWriteTimeout, 0, c, defaultPoolWriteTimeout)
|
c.PoolWriteTimeout = lessThanOrEqual(KeyPoolWriteTimeout, c.PoolWriteTimeout, 0, c, defaultPoolWriteTimeout)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyRecPeriod,
|
Name: KeyRecPeriod,
|
||||||
Type: typeFloat,
|
Type: typeFloat,
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
_v, err := strconv.ParseFloat(v, 64)
|
_v, err := strconv.ParseFloat(v, 64)
|
||||||
|
@ -558,17 +582,17 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyRotation,
|
Name: KeyRotation,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.Rotation = parseUint(KeyRotation, v, c) },
|
Update: func(c *Config, v string) { c.Rotation = parseUint(KeyRotation, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyRTMPURL,
|
Name: KeyRTMPURL,
|
||||||
Type: typeString,
|
Type: typeString,
|
||||||
Update: func(c *Config, v string) { c.RTMPURL = v },
|
Update: func(c *Config, v string) { c.RTMPURL = v },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyRTPAddress,
|
Name: KeyRTPAddress,
|
||||||
Type: typeString,
|
Type: typeString,
|
||||||
Update: func(c *Config, v string) { c.RTPAddress = v },
|
Update: func(c *Config, v string) { c.RTPAddress = v },
|
||||||
Validate: func(c *Config) {
|
Validate: func(c *Config) {
|
||||||
if c.RTPAddress == "" {
|
if c.RTPAddress == "" {
|
||||||
|
@ -579,21 +603,21 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeySampleRate,
|
Name: KeySampleRate,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.SampleRate = parseUint(KeySampleRate, v, c) },
|
Update: func(c *Config, v string) { c.SampleRate = parseUint(KeySampleRate, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeySaturation,
|
Name: KeySaturation,
|
||||||
Type: typeInt,
|
Type: typeInt,
|
||||||
Update: func(c *Config, v string) { c.Saturation = parseInt(KeySaturation, v, c) },
|
Update: func(c *Config, v string) { c.Saturation = parseInt(KeySaturation, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeySharpness,
|
Name: KeySharpness,
|
||||||
Type: typeInt,
|
Type: typeInt,
|
||||||
Update: func(c *Config, v string) { c.Sharpness = parseInt(KeySharpness, v, c) },
|
Update: func(c *Config, v string) { c.Sharpness = parseInt(KeySharpness, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyJPEGQuality,
|
Name: KeyJPEGQuality,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
_v, err := strconv.Atoi(v)
|
_v, err := strconv.Atoi(v)
|
||||||
|
@ -604,7 +628,7 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeySuppress,
|
Name: KeySuppress,
|
||||||
Type: typeBool,
|
Type: typeBool,
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
c.Suppress = parseBool(KeySuppress, v, c)
|
c.Suppress = parseBool(KeySuppress, v, c)
|
||||||
|
@ -612,7 +636,7 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyTimelapseInterval,
|
Name: KeyTimelapseInterval,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
_v, err := strconv.Atoi(v)
|
_v, err := strconv.Atoi(v)
|
||||||
|
@ -623,7 +647,7 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyTimelapseDuration,
|
Name: KeyTimelapseDuration,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
_v, err := strconv.Atoi(v)
|
_v, err := strconv.Atoi(v)
|
||||||
|
@ -635,11 +659,11 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyVBRBitrate,
|
Name: KeyVBRBitrate,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.VBRBitrate = parseUint(KeyVBRBitrate, v, c) },
|
Update: func(c *Config, v string) { c.VBRBitrate = parseUint(KeyVBRBitrate, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyVBRQuality,
|
Name: KeyVBRQuality,
|
||||||
Type: "enum:standard,fair,good,great,excellent",
|
Type: "enum:standard,fair,good,great,excellent",
|
||||||
Update: func(c *Config, v string) {
|
Update: func(c *Config, v string) {
|
||||||
c.VBRQuality = Quality(parseEnum(
|
c.VBRQuality = Quality(parseEnum(
|
||||||
|
@ -658,12 +682,12 @@ var Variables = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyVerticalFlip,
|
Name: KeyVerticalFlip,
|
||||||
Type: typeBool,
|
Type: typeBool,
|
||||||
Update: func(c *Config, v string) { c.VerticalFlip = parseBool(KeyVerticalFlip, v, c) },
|
Update: func(c *Config, v string) { c.VerticalFlip = parseBool(KeyVerticalFlip, v, c) },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: KeyWidth,
|
Name: KeyWidth,
|
||||||
Type: typeUint,
|
Type: typeUint,
|
||||||
Update: func(c *Config, v string) { c.Width = parseUint(KeyWidth, v, c) },
|
Update: func(c *Config, v string) { c.Width = parseUint(KeyWidth, v, c) },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
//go:build !nocv
|
||||||
|
// +build !nocv
|
||||||
|
|
||||||
|
/*
|
||||||
|
DESCRIPTION
|
||||||
|
Provides a function which can extract the transformation matrix.
|
||||||
|
|
||||||
|
AUTHORS
|
||||||
|
Russell Stanley <russell@ausocean.org>
|
||||||
|
|
||||||
|
LICENSE
|
||||||
|
Copyright (C) 2021-2022 the Australian Ocean Lab (AusOcean)
|
||||||
|
|
||||||
|
It is free software: you can redistribute it and/or modify them
|
||||||
|
under the terms of the GNU General Public License as published by the
|
||||||
|
Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
option) any later version.
|
||||||
|
|
||||||
|
It is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package turbidity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"gocv.io/x/gocv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Perspective transformation constants.
|
||||||
|
const (
|
||||||
|
ransacThreshold = 3.0 // Maximum allowed reprojection error to treat a point pair as an inlier.
|
||||||
|
maxIter = 2000 // The maximum number of RANSAC iterations.
|
||||||
|
confidence = 0.995 // Confidence level, between 0 and 1.
|
||||||
|
)
|
||||||
|
|
||||||
|
// FindTransform, given a template and standard image the perspetive transformation matrix will be determined.
|
||||||
|
// the matrix will be returned and logged for use in vidgrind.
|
||||||
|
func FindTransform(standardPath, templatePath string) (gocv.Mat, error) {
|
||||||
|
mask := gocv.NewMat()
|
||||||
|
std := gocv.IMRead(standardPath, gocv.IMReadColor)
|
||||||
|
stdCorners := gocv.NewMat()
|
||||||
|
|
||||||
|
template := gocv.IMRead(templatePath, gocv.IMReadGrayScale)
|
||||||
|
templateCorners := gocv.NewMat()
|
||||||
|
transformMatrix := gocv.NewMat()
|
||||||
|
|
||||||
|
// Validate template image is not empty and has valid corners.
|
||||||
|
if template.Empty() {
|
||||||
|
return transformMatrix, errors.New("template image is empty")
|
||||||
|
}
|
||||||
|
if !gocv.FindChessboardCorners(template, image.Pt(3, 3), &templateCorners, gocv.CalibCBNormalizeImage) {
|
||||||
|
return transformMatrix, errors.New("could not find corners in template image")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate standard image is not empty and has valid corners.
|
||||||
|
if std.Empty() {
|
||||||
|
return transformMatrix, errors.New("standard image is empty")
|
||||||
|
}
|
||||||
|
if !gocv.FindChessboardCorners(std, image.Pt(3, 3), &stdCorners, gocv.CalibCBNormalizeImage) {
|
||||||
|
return transformMatrix, errors.New("could not find corners in standard image")
|
||||||
|
}
|
||||||
|
|
||||||
|
transformMatrix = gocv.FindHomography(stdCorners, &templateCorners, gocv.HomograpyMethodRANSAC, ransacThreshold, &mask, maxIter, confidence)
|
||||||
|
return transformMatrix, nil
|
||||||
|
}
|
|
@ -44,39 +44,24 @@ import (
|
||||||
// TurbiditySensor is a software based turbidity sensor that uses CV to determine sharpness and constrast level
|
// TurbiditySensor is a software based turbidity sensor that uses CV to determine sharpness and constrast level
|
||||||
// of a chessboard-like target submerged in water that can be correlated to turbidity/visibility values.
|
// of a chessboard-like target submerged in water that can be correlated to turbidity/visibility values.
|
||||||
type TurbiditySensor struct {
|
type TurbiditySensor struct {
|
||||||
template, templateCorners gocv.Mat
|
template gocv.Mat // Holds the image of the target.
|
||||||
standard, standardCorners gocv.Mat
|
TransformMatrix gocv.Mat // The current perspective transformation matrix to extract the target from the frame.
|
||||||
k1, k2, sobelFilterSize int
|
k1, k2, sobelFilterSize int
|
||||||
scale, alpha float64
|
scale, alpha float64
|
||||||
log logger.Logger
|
log logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTurbiditySensor returns a new TurbiditySensor.
|
// NewTurbiditySensor returns a new TurbiditySensor.
|
||||||
func NewTurbiditySensor(template, standard gocv.Mat, k1, k2, sobelFilterSize int, scale, alpha float64, log logger.Logger) (*TurbiditySensor, error) {
|
func NewTurbiditySensor(template, transformMatrix gocv.Mat, k1, k2, sobelFilterSize int, scale, alpha float64, log logger.Logger) (*TurbiditySensor, error) {
|
||||||
ts := new(TurbiditySensor)
|
ts := new(TurbiditySensor)
|
||||||
templateCorners := gocv.NewMat()
|
|
||||||
standardCorners := gocv.NewMat()
|
|
||||||
|
|
||||||
// Validate template image is not empty and has valid corners.
|
// Validate template image is not empty and has valid corners.
|
||||||
if template.Empty() {
|
if template.Empty() {
|
||||||
return nil, errors.New("template image is empty")
|
return nil, errors.New("template image is empty")
|
||||||
}
|
}
|
||||||
if !gocv.FindChessboardCorners(template, image.Pt(3, 3), &templateCorners, gocv.CalibCBNormalizeImage) {
|
|
||||||
return nil, errors.New("could not find corners in template image")
|
|
||||||
}
|
|
||||||
ts.template = template
|
ts.template = template
|
||||||
ts.templateCorners = templateCorners
|
ts.TransformMatrix = transformMatrix
|
||||||
|
|
||||||
// Validate standard image is not empty and has valid corners.
|
|
||||||
if standard.Empty() {
|
|
||||||
return nil, errors.New("standard image is empty")
|
|
||||||
}
|
|
||||||
if !gocv.FindChessboardCorners(standard, image.Pt(3, 3), &standardCorners, gocv.CalibCBNormalizeImage) {
|
|
||||||
return nil, errors.New("could not find corners in standard image")
|
|
||||||
}
|
|
||||||
ts.standard = standard
|
|
||||||
ts.standardCorners = standardCorners
|
|
||||||
|
|
||||||
ts.k1, ts.k2, ts.sobelFilterSize = k1, k2, sobelFilterSize
|
ts.k1, ts.k2, ts.sobelFilterSize = k1, k2, sobelFilterSize
|
||||||
ts.alpha, ts.scale = alpha, scale
|
ts.alpha, ts.scale = alpha, scale
|
||||||
ts.log = log
|
ts.log = log
|
||||||
|
@ -191,13 +176,6 @@ func (ts TurbiditySensor) evaluateBlockAMEE(img gocv.Mat, xStart, yStart, xEnd,
|
||||||
// transform will search img for matching template. Returns the transformed image which best match the template.
|
// transform will search img for matching template. Returns the transformed image which best match the template.
|
||||||
func (ts TurbiditySensor) transform(img gocv.Mat) (gocv.Mat, error) {
|
func (ts TurbiditySensor) transform(img gocv.Mat) (gocv.Mat, error) {
|
||||||
out := gocv.NewMat()
|
out := gocv.NewMat()
|
||||||
mask := gocv.NewMat()
|
|
||||||
imgCorners := ts.standardCorners
|
|
||||||
const (
|
|
||||||
ransacThreshold = 3.0 // Maximum allowed reprojection error to treat a point pair as an inlier.
|
|
||||||
maxIter = 2000 // The maximum number of RANSAC iterations.
|
|
||||||
confidence = 0.995 // Confidence level, between 0 and 1.
|
|
||||||
)
|
|
||||||
|
|
||||||
if img.Empty() {
|
if img.Empty() {
|
||||||
return out, errors.New("image is empty, cannot transform")
|
return out, errors.New("image is empty, cannot transform")
|
||||||
|
@ -206,8 +184,7 @@ func (ts TurbiditySensor) transform(img gocv.Mat) (gocv.Mat, error) {
|
||||||
// if !gocv.FindChessboardCorners(img, image.Pt(3, 3), &imgCorners, gocv.CalibCBFastCheck) {}
|
// if !gocv.FindChessboardCorners(img, image.Pt(3, 3), &imgCorners, gocv.CalibCBFastCheck) {}
|
||||||
|
|
||||||
// Find and apply transformation.
|
// Find and apply transformation.
|
||||||
H := gocv.FindHomography(imgCorners, &ts.templateCorners, gocv.HomograpyMethodRANSAC, ransacThreshold, &mask, maxIter, confidence)
|
gocv.WarpPerspective(img, &out, ts.TransformMatrix, image.Pt(ts.template.Rows(), ts.template.Cols()))
|
||||||
gocv.WarpPerspective(img, &out, H, image.Pt(ts.template.Rows(), ts.template.Cols()))
|
|
||||||
gocv.CvtColor(out, &out, gocv.ColorRGBToGray)
|
gocv.CvtColor(out, &out, gocv.ColorRGBToGray)
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
nImages = 10 // Number of images to test. (Max 13)
|
nImages = 13 // Number of images to test. (Max 13)
|
||||||
nSamples = 1 // Number of samples for each image. (Max 10)
|
nSamples = 10 // Number of samples for each image. (Max 10)
|
||||||
increment = 2.5 // Increment of the turbidity level.
|
increment = 2.5 // Increment of the turbidity level.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ const (
|
||||||
func TestImages(t *testing.T) {
|
func TestImages(t *testing.T) {
|
||||||
|
|
||||||
const (
|
const (
|
||||||
k1, k2 = 8, 8
|
k1, k2 = 4, 4
|
||||||
filterSize = 3
|
filterSize = 3
|
||||||
scale, alpha = 1.0, 1.0
|
scale, alpha = 1.0, 1.0
|
||||||
)
|
)
|
||||||
|
@ -77,7 +77,11 @@ func TestImages(t *testing.T) {
|
||||||
log := *logger.New(logVerbosity, io.MultiWriter(fileLog), logSuppress)
|
log := *logger.New(logVerbosity, io.MultiWriter(fileLog), logSuppress)
|
||||||
|
|
||||||
template := gocv.IMRead("images/template.jpg", gocv.IMReadGrayScale)
|
template := gocv.IMRead("images/template.jpg", gocv.IMReadGrayScale)
|
||||||
standard := gocv.IMRead("images/default.jpg", gocv.IMReadGrayScale)
|
transformMatrix, err := FindTransform("images/default.jpg", "images/template.jpg")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not find transformation: %v", err)
|
||||||
|
}
|
||||||
|
t.Log(formatMat(transformMatrix))
|
||||||
|
|
||||||
imgs := make([][]gocv.Mat, nImages)
|
imgs := make([][]gocv.Mat, nImages)
|
||||||
|
|
||||||
|
@ -85,11 +89,11 @@ func TestImages(t *testing.T) {
|
||||||
for i := range imgs {
|
for i := range imgs {
|
||||||
imgs[i] = make([]gocv.Mat, nSamples)
|
imgs[i] = make([]gocv.Mat, nSamples)
|
||||||
for j := range imgs[i] {
|
for j := range imgs[i] {
|
||||||
imgs[i][j] = gocv.IMRead(fmt.Sprintf("images/t-%v/000%v.jpg", i, j), gocv.IMReadGrayScale)
|
imgs[i][j] = gocv.IMRead(fmt.Sprintf("images/t-%v/000%v.jpg", i, j), gocv.IMReadColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ts, err := NewTurbiditySensor(template, standard, k1, k2, filterSize, scale, alpha, log)
|
ts, err := NewTurbiditySensor(template, transformMatrix, k1, k2, filterSize, scale, alpha, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not create turbidity sensor: %v", err)
|
t.Fatalf("could not create turbidity sensor: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -111,7 +115,7 @@ func TestImages(t *testing.T) {
|
||||||
results.Update(stat.Mean(sample_result.Sharpness, nil), stat.Mean(sample_result.Contrast, nil), float64(i)*increment, i)
|
results.Update(stat.Mean(sample_result.Sharpness, nil), stat.Mean(sample_result.Contrast, nil), float64(i)*increment, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = plotResults(results.Turbidity, results.Sharpness, results.Contrast)
|
err = plotResults(results.Turbidity, normalize(results.Sharpness), normalize(results.Contrast))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("plotting Failed: %v", err)
|
t.Fatalf("plotting Failed: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -138,3 +142,17 @@ func plotResults(x, sharpness, contrast []float64) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// formatMat creates a formatted transformation matrix string for use in vidgrind.
|
||||||
|
func formatMat(transformMatrix gocv.Mat) string {
|
||||||
|
var out string
|
||||||
|
for i := 0; i < transformMatrix.Rows(); i++ {
|
||||||
|
for j := 0; j < transformMatrix.Cols(); j++ {
|
||||||
|
out += fmt.Sprintf(" %.10f", transformMatrix.GetDoubleAt(i, j))
|
||||||
|
if i < 2 || j < 2 {
|
||||||
|
out += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue