From 445ab1d78537f88e9a44ed5d0d29d94086b87bfe Mon Sep 17 00:00:00 2001 From: Russell Stanley Date: Thu, 28 Apr 2022 13:43:49 +0930 Subject: [PATCH] turbidity: updated naming convention and comments per comments on PR --- cmd/rv/main.go | 5 ++++- cmd/rv/probe.go | 39 +++++++++++++++++++------------------ cmd/rv/probe_circleci.go | 3 +-- cmd/rv/probe_test.go | 18 +---------------- revid/config/config.go | 4 +++- turbidity/transform.go | 35 +++++++++++++-------------------- turbidity/turbidity.go | 10 +++++----- turbidity/turbidity_test.go | 21 +++++++++++++++++--- 8 files changed, 66 insertions(+), 69 deletions(-) diff --git a/cmd/rv/main.go b/cmd/rv/main.go index 60bab7c9..fa8b7c02 100644 --- a/cmd/rv/main.go +++ b/cmd/rv/main.go @@ -226,7 +226,10 @@ func run(rv *revid.Revid, ns *netsender.Sender, l *logger.Logger, nl *netlogger. l.Log(logger.Info, "revid successfully reconfigured") // Update transform matrix based on new revid variables. - p.Update(rv.Config()) + 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") switch ns.Mode() { diff --git a/cmd/rv/probe.go b/cmd/rv/probe.go index 2585ec5d..7d5525a0 100644 --- a/cmd/rv/probe.go +++ b/cmd/rv/probe.go @@ -40,7 +40,6 @@ import ( "gonum.org/v1/gonum/stat" "bitbucket.org/ausocean/av/codec/h264" - "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/av/turbidity" "bitbucket.org/ausocean/utils/logger" ) @@ -50,6 +49,7 @@ const ( maxImages = 1 // Max number of images read when evaluating turbidity. bufferLimit = 20000 // 20KB 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. @@ -81,12 +81,12 @@ func NewTurbidityProbe(log logger.Logger, delay time.Duration) (*turbidityProbe, tp.ticker = *time.NewTicker(delay) tp.buffer = bytes.NewBuffer(*new([]byte)) - tp.transform = []float64{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0} - H := floatToMat(tp.transform) + tp.transform = make([]float64, transformSize) + transformMatrix := floatToMat(tp.transform) // Create the turbidity sensor. template := gocv.IMRead("../../turbidity/images/template.jpg", gocv.IMReadGrayScale) - ts, err := turbidity.NewTurbiditySensor(template, H, k1, k2, filterSize, scale, alpha, log) + ts, err := turbidity.NewTurbiditySensor(template, transformMatrix, k1, k2, filterSize, scale, alpha, log) if err != nil { return nil, fmt.Errorf("failed to create turbidity sensor: %w", err) } @@ -145,20 +145,21 @@ func (tp *turbidityProbe) Close() error { return nil } -// Update, will update the probe and turbidity sensor with the new transformation matrix if it has been changed. -func (tp *turbidityProbe) Update(config config.Config) error { - if len(config.TransformMatrix) != 9 { +// 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] != config.TransformMatrix[i] { - // Update the turbidity sensor with new transformation - tp.log.Log(logger.Debug, "updating the transformation matrix") - tp.transform = config.TransformMatrix - newTransform := floatToMat(tp.transform) - tp.ts.H = newTransform - return nil + 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 @@ -234,11 +235,11 @@ func cleanUp(file string, vc *gocv.VideoCapture) error { // floatToMat will convert a slice of 9 floats to a gocv.Mat. func floatToMat(array []float64) gocv.Mat { - H := gocv.NewMatWithSize(3, 3, gocv.MatTypeCV64F) - for i := 0; i < H.Rows(); i++ { - for j := 0; j < H.Cols(); j++ { - H.SetDoubleAt(i, j, array[i*H.Cols()+j]) + 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 H + return mat } diff --git a/cmd/rv/probe_circleci.go b/cmd/rv/probe_circleci.go index 7a3d0cb4..1b69e15a 100644 --- a/cmd/rv/probe_circleci.go +++ b/cmd/rv/probe_circleci.go @@ -32,7 +32,6 @@ package main import ( "time" - "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/utils/logger" ) @@ -51,7 +50,7 @@ func (tp *turbidityProbe) Write(p []byte) (int, error) { return 0, nil } -func (tp *turbidityProbe) Update(config config.Config) error { +func (tp *turbidityProbe) Update(mat []float64) error { return nil } diff --git a/cmd/rv/probe_test.go b/cmd/rv/probe_test.go index bb3faef7..a87c70ef 100644 --- a/cmd/rv/probe_test.go +++ b/cmd/rv/probe_test.go @@ -30,7 +30,6 @@ import ( "testing" "time" - "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/utils/logger" "gopkg.in/natefinch/lumberjack.v2" ) @@ -46,14 +45,13 @@ func TestProbe(t *testing.T) { } 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} - config := config.Config{TransformMatrix: updatedMatrix} ts, err := NewTurbidityProbe(*log, time.Microsecond) if err != nil { t.Fatalf("failed to create turbidity probe") } - err = ts.Update(config) + err = ts.Update(updatedMatrix) if err != nil { t.Fatalf("could not update probe: %v", err) } @@ -69,17 +67,3 @@ func TestProbe(t *testing.T) { } t.Logf("contrast: %v, sharpness: %v\n", ts.contrast, ts.sharpness) } - -/* -func printMat(mat gocv.Mat) { - for i := 0; i < mat.Rows(); i++ { - for j := 0; j < mat.Cols(); j++ { - fmt.Printf(" %.10f", mat.GetDoubleAt(i, j)) - if i < 2 || j < 2 { - fmt.Print(",") - } - } - } - fmt.Println() -} -*/ diff --git a/revid/config/config.go b/revid/config/config.go index 2e665f72..23106570 100644 --- a/revid/config/config.go +++ b/revid/config/config.go @@ -274,7 +274,9 @@ type Config struct { VerticalFlip bool // VerticalFlip flips video vertically for Raspivid input. Width uint // Width defines the input video width Raspivid input. - TransformMatrix []float64 // Describes the transformation matrix to extract the target. + // 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 diff --git a/turbidity/transform.go b/turbidity/transform.go index 21e01101..3eff1efd 100644 --- a/turbidity/transform.go +++ b/turbidity/transform.go @@ -29,52 +29,45 @@ package turbidity import ( "errors" - "fmt" "image" "gocv.io/x/gocv" ) -// Homography constants +// 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() - standard := gocv.IMRead(standardPath, gocv.IMReadColor) - standardCorners := 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 gocv.NewMat(), errors.New("template image is empty") + return transformMatrix, errors.New("template image is empty") } if !gocv.FindChessboardCorners(template, image.Pt(3, 3), &templateCorners, gocv.CalibCBNormalizeImage) { - return gocv.NewMat(), errors.New("could not find corners in template image") + return transformMatrix, errors.New("could not find corners in template image") } // Validate standard image is not empty and has valid corners. - if standard.Empty() { - return gocv.NewMat(), errors.New("standard image is empty") + if std.Empty() { + return transformMatrix, errors.New("standard image is empty") } - if !gocv.FindChessboardCorners(standard, image.Pt(3, 3), &standardCorners, gocv.CalibCBNormalizeImage) { - return gocv.NewMat(), errors.New("could not find corners in standard image") + if !gocv.FindChessboardCorners(std, image.Pt(3, 3), &stdCorners, gocv.CalibCBNormalizeImage) { + return transformMatrix, errors.New("could not find corners in standard image") } - H := gocv.FindHomography(standardCorners, &templateCorners, gocv.HomograpyMethodRANSAC, ransacThreshold, &mask, maxIter, confidence) - for i := 0; i < H.Rows(); i++ { - for j := 0; j < H.Cols(); j++ { - fmt.Printf(" %.10f", H.GetDoubleAt(i, j)) - if i < 2 || j < 2 { - fmt.Print(",") - } - } - } - fmt.Println() - return H, nil + transformMatrix = gocv.FindHomography(stdCorners, &templateCorners, gocv.HomograpyMethodRANSAC, ransacThreshold, &mask, maxIter, confidence) + return transformMatrix, nil } diff --git a/turbidity/turbidity.go b/turbidity/turbidity.go index dd5bec8a..b3b75a58 100644 --- a/turbidity/turbidity.go +++ b/turbidity/turbidity.go @@ -44,15 +44,15 @@ import ( // 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. type TurbiditySensor struct { - template gocv.Mat - H gocv.Mat + template gocv.Mat // Holds the image of the target. + TransformMatrix gocv.Mat // The current perspective transformation matrix to extract the target from the frame. k1, k2, sobelFilterSize int scale, alpha float64 log logger.Logger } // NewTurbiditySensor returns a new TurbiditySensor. -func NewTurbiditySensor(template, H 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) // Validate template image is not empty and has valid corners. @@ -61,7 +61,7 @@ func NewTurbiditySensor(template, H gocv.Mat, k1, k2, sobelFilterSize int, scale } ts.template = template - ts.H = H + ts.TransformMatrix = transformMatrix ts.k1, ts.k2, ts.sobelFilterSize = k1, k2, sobelFilterSize ts.alpha, ts.scale = alpha, scale ts.log = log @@ -184,7 +184,7 @@ func (ts TurbiditySensor) transform(img gocv.Mat) (gocv.Mat, error) { // if !gocv.FindChessboardCorners(img, image.Pt(3, 3), &imgCorners, gocv.CalibCBFastCheck) {} // Find and apply transformation. - gocv.WarpPerspective(img, &out, ts.H, image.Pt(ts.template.Rows(), ts.template.Cols())) + gocv.WarpPerspective(img, &out, ts.TransformMatrix, image.Pt(ts.template.Rows(), ts.template.Cols())) gocv.CvtColor(out, &out, gocv.ColorRGBToGray) return out, nil } diff --git a/turbidity/turbidity_test.go b/turbidity/turbidity_test.go index efe8f240..32ec1654 100644 --- a/turbidity/turbidity_test.go +++ b/turbidity/turbidity_test.go @@ -42,7 +42,7 @@ import ( ) const ( - nImages = 12 // Number of images to test. (Max 13) + nImages = 13 // Number of images to test. (Max 13) nSamples = 10 // Number of samples for each image. (Max 10) increment = 2.5 // Increment of the turbidity level. ) @@ -77,10 +77,11 @@ func TestImages(t *testing.T) { log := *logger.New(logVerbosity, io.MultiWriter(fileLog), logSuppress) template := gocv.IMRead("images/template.jpg", gocv.IMReadGrayScale) - H, err := FindTransform("images/default.jpg", "images/template.jpg") + 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) @@ -92,7 +93,7 @@ func TestImages(t *testing.T) { } } - ts, err := NewTurbiditySensor(template, H, k1, k2, filterSize, scale, alpha, log) + ts, err := NewTurbiditySensor(template, transformMatrix, k1, k2, filterSize, scale, alpha, log) if err != nil { t.Fatalf("could not create turbidity sensor: %v", err) } @@ -141,3 +142,17 @@ func plotResults(x, sharpness, contrast []float64) error { } 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 +}