turbidity: incorporate transform matrix variable into turbidity sensor

This commit is contained in:
Russell Stanley 2022-04-12 13:32:34 +09:30
parent 798e691c06
commit f6505488bb
7 changed files with 161 additions and 51 deletions

View File

@ -158,7 +158,7 @@ func main() {
log.Log(logger.Fatal, pkg+"could not initialise revid", "error", err.Error()) log.Log(logger.Fatal, pkg+"could not initialise revid", "error", err.Error())
} }
p, err = NewTurbidityProbe(*log, 60*time.Second, rv.Config().TransformMatrix) p, err = NewTurbidityProbe(*log, 60*time.Second)
if err != nil { if err != nil {
log.Log(logger.Fatal, "could not create new turbidity probe", "error", err.Error()) log.Log(logger.Fatal, "could not create new turbidity probe", "error", err.Error())
} }

View File

@ -40,6 +40,7 @@ import (
"gonum.org/v1/gonum/stat" "gonum.org/v1/gonum/stat"
"bitbucket.org/ausocean/av/codec/h264" "bitbucket.org/ausocean/av/codec/h264"
"bitbucket.org/ausocean/av/revid/config"
"bitbucket.org/ausocean/av/turbidity" "bitbucket.org/ausocean/av/turbidity"
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
) )
@ -73,18 +74,22 @@ type turbidityProbe struct {
} }
// NewTurbidityProbe returns a new turbidity probe. // NewTurbidityProbe returns a new turbidity probe.
func NewTurbidityProbe(log logger.Logger, delay time.Duration, transformMatrix []float64) (*turbidityProbe, error) { func NewTurbidityProbe(log logger.Logger, delay time.Duration) (*turbidityProbe, error) {
tp := new(turbidityProbe) tp := new(turbidityProbe)
tp.log = log tp.log = log
tp.delay = delay tp.delay = delay
tp.ticker = *time.NewTicker(delay) tp.ticker = *time.NewTicker(delay)
tp.buffer = bytes.NewBuffer(*new([]byte)) tp.buffer = bytes.NewBuffer(*new([]byte))
tp.transform = transformMatrix
tp.transform = []float64{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}
H, err := floatToMat(tp.transform)
if err != nil {
return nil, fmt.Errorf("failed to convert float slice to mat: %w", err)
}
// 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, H, 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)
} }
@ -143,6 +148,27 @@ func (tp *turbidityProbe) Close() error {
return nil return nil
} }
func (tp *turbidityProbe) Update(config config.Config) error {
if len(config.TransformMatrix) != 9 {
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 turbidity sensor with new transformation")
tp.transform = config.TransformMatrix
newTransform, err := floatToMat(tp.transform)
if err != nil {
return fmt.Errorf("failed to convert float slice to mat: %w", err)
}
tp.ts.H = newTransform
return nil
}
}
tp.log.Log(logger.Debug, "not update requried")
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()
@ -210,3 +236,13 @@ func cleanUp(file string, vc *gocv.VideoCapture) error {
} }
return nil return nil
} }
func floatToMat(array []float64) (gocv.Mat, error) {
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])
}
}
return H, nil
}

View File

@ -40,7 +40,7 @@ type turbidityProbe struct {
} }
// NewTurbidityProbe returns an empty turbidity probe for CircleCI testing only. // NewTurbidityProbe returns an empty turbidity probe for CircleCI testing only.
func NewTurbidityProbe(log logger.Logger, delay time.Duration, transformMatrix []float64) (*turbidityProbe, error) { func NewTurbidityProbe(log logger.Logger, delay time.Duration) (*turbidityProbe, error) {
tp := new(turbidityProbe) tp := new(turbidityProbe)
return tp, nil return tp, nil
} }

View File

@ -30,6 +30,7 @@ import (
"testing" "testing"
"time" "time"
"bitbucket.org/ausocean/av/revid/config"
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
) )
@ -44,12 +45,21 @@ 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)
transformMatrix := []float64{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0} 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, transformMatrix) 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")
} }
t.Log(ts.transform)
err = ts.Update(config)
if err != nil {
t.Fatalf("could not update probe: %v", err)
}
t.Log(ts.transform)
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)
@ -61,3 +71,17 @@ func TestProbe(t *testing.T) {
} }
t.Logf("contrast: %v, sharpness: %v\n", ts.contrast, ts.sharpness) 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()
}
*/

80
turbidity/transform.go Normal file
View File

@ -0,0 +1,80 @@
//go:build !nocv
// +build !nocv
/*
DESCRIPTION
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"
"fmt"
"image"
"gocv.io/x/gocv"
)
// Homography 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.
)
func FindTransform(standardPath, templatePath string) (gocv.Mat, error) {
mask := gocv.NewMat()
standard := gocv.IMRead(standardPath, gocv.IMReadColor)
standardCorners := gocv.NewMat()
template := gocv.IMRead(templatePath, gocv.IMReadGrayScale)
templateCorners := gocv.NewMat()
// Validate template image is not empty and has valid corners.
if template.Empty() {
return gocv.NewMat(), 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")
}
// Validate standard image is not empty and has valid corners.
if standard.Empty() {
return gocv.NewMat(), 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")
}
H := gocv.FindHomography(standardCorners, &templateCorners, gocv.HomograpyMethodRANSAC, ransacThreshold, &mask, maxIter, confidence)
fmt.Print(H.Type())
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
}

View File

@ -41,53 +41,27 @@ import (
"gocv.io/x/gocv" "gocv.io/x/gocv"
) )
// Homography 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.
)
// 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
standard, standardCorners gocv.Mat H gocv.Mat
H gocv.Mat 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, H 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()
mask := 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.H = H
// 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.H = gocv.FindHomography(ts.standardCorners, &ts.templateCorners, gocv.HomograpyMethodRANSAC, ransacThreshold, &mask, maxIter, confidence)
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

View File

@ -77,7 +77,10 @@ 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) H, err := FindTransform("images/default.jpg", "images/template.jpg")
if err != nil {
t.Fatalf("could not find transformation: %v", err)
}
imgs := make([][]gocv.Mat, nImages) imgs := make([][]gocv.Mat, nImages)
@ -89,18 +92,11 @@ func TestImages(t *testing.T) {
} }
} }
ts, err := NewTurbiditySensor(template, standard, k1, k2, filterSize, scale, alpha, log) ts, err := NewTurbiditySensor(template, H, 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)
} }
for i := 0; i < ts.H.Rows(); i++ {
for j := 0; j < ts.H.Cols(); j++ {
fmt.Printf(" %v,\t", ts.H.GetDoubleAt(i, j))
}
fmt.Println()
}
results, err := NewResults(nImages) results, err := NewResults(nImages)
if err != nil { if err != nil {
t.Fatalf("could not create results: %v", err) t.Fatalf("could not create results: %v", err)