mirror of https://bitbucket.org/ausocean/av.git
code cleanup as per comments on last commit
This commit is contained in:
parent
3ab081b991
commit
86cdff0bb2
|
@ -2,6 +2,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
|
||||||
"gonum.org/v1/plot"
|
"gonum.org/v1/plot"
|
||||||
"gonum.org/v1/plot/plotter"
|
"gonum.org/v1/plot/plotter"
|
||||||
|
@ -11,62 +13,65 @@ import (
|
||||||
"gocv.io/x/gocv"
|
"gocv.io/x/gocv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
const (
|
||||||
n_images := 6
|
nImages = 6
|
||||||
n_samples := 10
|
nSamples = 10
|
||||||
|
)
|
||||||
|
|
||||||
// Load template
|
func main() {
|
||||||
|
nImages := 6
|
||||||
|
nSamples := 10
|
||||||
|
|
||||||
|
// Load template and standard image.
|
||||||
template := gocv.IMRead("template.jpg", gocv.IMReadGrayScale)
|
template := gocv.IMRead("template.jpg", gocv.IMReadGrayScale)
|
||||||
standard := gocv.IMRead("standard.jpg", gocv.IMReadGrayScale)
|
standard := gocv.IMRead("standard.jpg", gocv.IMReadGrayScale)
|
||||||
|
|
||||||
images := make([][]gocv.Mat, n_images)
|
imgs := make([][]gocv.Mat, nImages)
|
||||||
|
|
||||||
//Load images
|
// Load test images.
|
||||||
for i := 0; i < n_images; i++ {
|
for i := range imgs {
|
||||||
images[i] = make([]gocv.Mat, n_samples)
|
imgs[i] = make([]gocv.Mat, nSamples)
|
||||||
for j := 0; j < n_samples; j++ {
|
for j := range imgs[i] {
|
||||||
images[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.IMReadGrayScale)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create turbidity sensor
|
// Create turbidity sensor.
|
||||||
ts := TurbiditySensor{template: template, standard: standard, k1: 90, k2: 90, scale: 5.0, alpha: 1.0}
|
ts := TurbiditySensor{template: template, standard: standard, k1: 90, k2: 90, scale: 5.0, alpha: 1.0}
|
||||||
|
|
||||||
var final_result Results
|
var finalRes Results
|
||||||
final_result.New(n_images)
|
finalRes.New(nImages)
|
||||||
|
|
||||||
// Score each image by calculating the average score from camera burst
|
// Score each image by calculating the average score from camera burst.
|
||||||
for i := range images {
|
for i := range imgs {
|
||||||
|
|
||||||
//Evaluate camera burst
|
|
||||||
sample_result, err := ts.Evaluate(n_samples, images[i])
|
|
||||||
|
|
||||||
|
// Evaluate camera burst.
|
||||||
|
sample_result, err := ts.Evaluate(imgs[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
log.Fatalf("Evaluation Failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append the average result from camera burst
|
// Add the average result from camera burst.
|
||||||
final_result.Append(Average(sample_result.saturation), Average(sample_result.contrast), float64((i+1)*10), i)
|
finalRes.Update(average(sample_result.saturation), average(sample_result.contrast), float64(i*10), i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plot the final results
|
// Plot the final results.
|
||||||
err := PlotResults(final_result.turbidity, Normalize(final_result.saturation), Normalize(final_result.contrast))
|
err := plotResults(finalRes.turbidity, normalize(finalRes.saturation), normalize(finalRes.contrast))
|
||||||
|
|
||||||
fmt.Println(final_result.saturation)
|
|
||||||
fmt.Println(final_result.contrast)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
log.Fatalf("Plotting Failed: %v", err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PLOTTING FUNCTIONS
|
log.Printf("Saturation: %v", finalRes.saturation)
|
||||||
|
log.Printf("Contrast: %v", finalRes.contrast)
|
||||||
|
}
|
||||||
|
|
||||||
// Normalize values in a slice between 0 and 1
|
// Plotting Functions.
|
||||||
func Normalize(slice []float64) []float64 {
|
|
||||||
|
|
||||||
max := -1e10
|
// Normalize values in a slice between 0 and 1.
|
||||||
min := 1e10
|
func normalize(slice []float64) []float64 {
|
||||||
|
|
||||||
|
max := -math.MaxFloat64
|
||||||
|
min := math.MaxFloat64
|
||||||
|
|
||||||
out := make([]float64, len(slice))
|
out := make([]float64, len(slice))
|
||||||
|
|
||||||
|
@ -90,8 +95,8 @@ func Normalize(slice []float64) []float64 {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the average of a slice
|
// Return the average of a slice.
|
||||||
func Average(slice []float64) float64 {
|
func average(slice []float64) float64 {
|
||||||
|
|
||||||
out := 0.0
|
out := 0.0
|
||||||
|
|
||||||
|
@ -102,7 +107,7 @@ func Average(slice []float64) float64 {
|
||||||
return out / float64(len(slice))
|
return out / float64(len(slice))
|
||||||
}
|
}
|
||||||
|
|
||||||
func PlotResults(x, saturation, contrast []float64) error {
|
func plotResults(x, saturation, contrast []float64) error {
|
||||||
|
|
||||||
err := plotToFile(
|
err := plotToFile(
|
||||||
"Results",
|
"Results",
|
||||||
|
@ -117,7 +122,7 @@ func PlotResults(x, saturation, contrast []float64) error {
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Could not plot results: %v", err)
|
return fmt.Errorf("Could not plot results: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -127,16 +132,13 @@ func PlotResults(x, saturation, contrast []float64) error {
|
||||||
// provided draw function, and then saves to a PNG file with filename of name.
|
// provided draw function, and then saves to a PNG file with filename of name.
|
||||||
func plotToFile(name, xTitle, yTitle string, draw func(*plot.Plot) error) error {
|
func plotToFile(name, xTitle, yTitle string, draw func(*plot.Plot) error) error {
|
||||||
p := plot.New()
|
p := plot.New()
|
||||||
|
|
||||||
p.Title.Text = name
|
p.Title.Text = name
|
||||||
p.X.Label.Text = xTitle
|
p.X.Label.Text = xTitle
|
||||||
p.Y.Label.Text = yTitle
|
p.Y.Label.Text = yTitle
|
||||||
|
|
||||||
err := draw(p)
|
err := draw(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not draw plot contents: %w", err)
|
return fmt.Errorf("could not draw plot contents: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.Save(15*vg.Centimeter, 15*vg.Centimeter, "plots/"+name+".png"); err != nil {
|
if err := p.Save(15*vg.Centimeter, 15*vg.Centimeter, "plots/"+name+".png"); err != nil {
|
||||||
return fmt.Errorf("could not save plot: %w", err)
|
return fmt.Errorf("could not save plot: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -145,9 +147,7 @@ func plotToFile(name, xTitle, yTitle string, draw func(*plot.Plot) error) error
|
||||||
|
|
||||||
// plotterXY provides a plotter.XYs type value based on the given x and y data.
|
// plotterXY provides a plotter.XYs type value based on the given x and y data.
|
||||||
func plotterXY(x, y []float64) plotter.XYs {
|
func plotterXY(x, y []float64) plotter.XYs {
|
||||||
|
|
||||||
xy := make(plotter.XYs, len(x))
|
xy := make(plotter.XYs, len(x))
|
||||||
|
|
||||||
for i := range x {
|
for i := range x {
|
||||||
xy[i].X = x[i]
|
xy[i].X = x[i]
|
||||||
xy[i].Y = y[i]
|
xy[i].Y = y[i]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
// struct to hold the results of the turbidity sensor.
|
||||||
type Results struct {
|
type Results struct {
|
||||||
turbidity []float64
|
turbidity []float64
|
||||||
saturation []float64
|
saturation []float64
|
||||||
|
@ -12,7 +13,8 @@ func (r *Results) New(n int) {
|
||||||
r.contrast = make([]float64, n)
|
r.contrast = make([]float64, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Results) Append(saturation, contrast, turbidity float64, index int) {
|
// Update results to add new values at specified index.
|
||||||
|
func (r *Results) Update(saturation, contrast, turbidity float64, index int) {
|
||||||
r.saturation[index] = saturation
|
r.saturation[index] = saturation
|
||||||
r.contrast[index] = contrast
|
r.contrast[index] = contrast
|
||||||
r.turbidity[index] = turbidity
|
r.turbidity[index] = turbidity
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"math"
|
"math"
|
||||||
|
@ -8,97 +9,93 @@ import (
|
||||||
"gocv.io/x/gocv"
|
"gocv.io/x/gocv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TURBIDITY SENSOR
|
// Turbidity Sensor.
|
||||||
type TurbiditySensor struct {
|
type TurbiditySensor struct {
|
||||||
template, standard gocv.Mat
|
template, standard gocv.Mat
|
||||||
k1, k2 int //block size
|
k1, k2 int //block size
|
||||||
alpha, scale float64
|
alpha, scale float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a slice of test images of size n, return the sharpness and contrast scores
|
// Given a slice of test images, return the sharpness and contrast scores.
|
||||||
func (ts TurbiditySensor) Evaluate(n int, images []gocv.Mat) (Results, error) {
|
func (ts TurbiditySensor) Evaluate(imgs []gocv.Mat) (Results, error) {
|
||||||
|
|
||||||
var result Results
|
var result Results
|
||||||
result.New(n)
|
result.New(len(imgs))
|
||||||
|
|
||||||
for i := range images {
|
for i := range imgs {
|
||||||
|
|
||||||
// Transform image
|
|
||||||
marker, err := ts.Transform(images[i])
|
|
||||||
|
|
||||||
|
// Transform image.
|
||||||
|
marker, err := ts.Transform(imgs[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, fmt.Errorf("Image %v: %v", i, err)
|
return result, fmt.Errorf("Image %v: %w", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply sobel filter
|
// Apply sobel filter.
|
||||||
edge := ts.Sobel(marker, ts.scale)
|
edge := ts.Sobel(marker, ts.scale)
|
||||||
|
|
||||||
// Evaluate
|
// Evaluate image.
|
||||||
out, err := ts.EvaluateImage(marker, edge, ts.k1, ts.k2, ts.alpha)
|
out, err := ts.EvaluateImage(marker, edge, ts.k1, ts.k2, ts.alpha)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Append(out[0], out[1], float64((i+1)*10), i)
|
result.Update(out[0], out[1], float64(i*10), i)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate image sharpness and contrast using blocks of size k1 by k2. Return a slice of the respective scores
|
// Evaluate image sharpness and contrast using blocks of size k1 by k2. Return a slice of the respective scores.
|
||||||
func (ts TurbiditySensor) EvaluateImage(img, edge gocv.Mat, k1, k2 int, alpha float64) ([]float64, error) {
|
func (ts TurbiditySensor) EvaluateImage(img, edge gocv.Mat, k1, k2 int, alpha float64) ([]float64, error) {
|
||||||
|
|
||||||
// Slice to store results
|
// Slice to store results.
|
||||||
result := make([]float64, 2) // [0.0, 0.0]
|
result := make([]float64, 2) // [0.0, 0.0]
|
||||||
|
|
||||||
if img.Rows()%k1 != 0 || img.Cols()%k2 != 0 {
|
if img.Rows()%k1 != 0 || img.Cols()%k2 != 0 {
|
||||||
return result, fmt.Errorf("Dimensions not compatible (%v, %v)", k1, k2)
|
return nil, fmt.Errorf("Dimensions not compatible (%v, %v)", k1, k2)
|
||||||
}
|
}
|
||||||
|
|
||||||
l_step := int(img.Rows() / k1)
|
lStep := int(img.Rows() / k1)
|
||||||
k_step := int(img.Cols() / k2)
|
kStep := int(img.Cols() / k2)
|
||||||
|
|
||||||
for l := 0; l < img.Rows(); l = l + l_step {
|
for l := 0; l < img.Rows(); l = l + lStep {
|
||||||
for k := 0; k < img.Cols(); k = k + k_step {
|
for k := 0; k < img.Cols(); k = k + kStep {
|
||||||
|
|
||||||
//EME
|
|
||||||
err := ts.EvaluateBlock(edge, l, k, l+l_step, k+k_step, result, "EME", alpha)
|
|
||||||
|
|
||||||
|
// Enhancement Measure Estimation (EME), provides a measure of the sharpness.
|
||||||
|
err := ts.EvaluateBlock(edge, l, k, l+lStep, k+kStep, result, "EME", alpha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//AMEE
|
// AMEE, provides a measure of the contrast.
|
||||||
err = ts.EvaluateBlock(img, l, k, l+l_step, k+k_step, result, "AMEE", alpha)
|
err = ts.EvaluateBlock(img, l, k, l+lStep, k+kStep, result, "AMEE", alpha)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EME
|
// EME.
|
||||||
result[0] = 2.0 / (float64(k1) * float64(k1)) * result[0]
|
result[0] = 2.0 / (float64(k1) * float64(k2)) * result[0]
|
||||||
|
|
||||||
// AMEE
|
// AMEE.
|
||||||
result[1] = -1.0 / (float64(k1) * float64(k1)) * result[1]
|
result[1] = -1.0 / (float64(k1) * float64(k2)) * result[1]
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate a block within and image and add to to the result slice
|
// Evaluate a block within an image and add to to the result slice.
|
||||||
func (ts TurbiditySensor) EvaluateBlock(img gocv.Mat, l1, k1, l2, k2 int, result []float64, operation string, alpha float64) error {
|
func (ts TurbiditySensor) EvaluateBlock(img gocv.Mat, l1, k1, l2, k2 int, result []float64, operation string, alpha float64) error {
|
||||||
|
|
||||||
max := -1e10
|
max := -math.MaxFloat64
|
||||||
min := 1e10
|
min := math.MaxFloat64
|
||||||
|
|
||||||
for i := l1; i < l2; i++ {
|
for i := l1; i < l2; i++ {
|
||||||
for j := k1; j < k2; j++ {
|
for j := k1; j < k2; j++ {
|
||||||
|
|
||||||
value := float64(img.GetUCharAt(i, j))
|
value := float64(img.GetUCharAt(i, j))
|
||||||
|
|
||||||
// Check max/min conditions, zero values are ignored
|
// Check max/min conditions, zero values are ignored.
|
||||||
if value > max && value != 0.0 {
|
if value > max && value != 0.0 {
|
||||||
max = value
|
max = value
|
||||||
}
|
}
|
||||||
|
@ -108,17 +105,13 @@ func (ts TurbiditySensor) EvaluateBlock(img gocv.Mat, l1, k1, l2, k2 int, result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocks which have no information are ignored
|
// Blocks which have no information are ignored.
|
||||||
if max != -1e10 && min != 1e10 && max != min {
|
if max != -math.MaxFloat64 && min != math.MaxFloat64 && max != min {
|
||||||
if operation == "EME" {
|
if operation == "EME" {
|
||||||
|
|
||||||
result[0] += math.Log(max / min)
|
result[0] += math.Log(max / min)
|
||||||
|
|
||||||
} else if operation == "AMEE" {
|
} else if operation == "AMEE" {
|
||||||
|
|
||||||
contrast := (max + min) / (max - min)
|
contrast := (max + min) / (max - min)
|
||||||
result[1] += math.Pow(alpha*(contrast), alpha) * math.Log(contrast)
|
result[1] += math.Pow(alpha*(contrast), alpha) * math.Log(contrast)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Invalid operation: %v", operation)
|
return fmt.Errorf("Invalid operation: %v", operation)
|
||||||
}
|
}
|
||||||
|
@ -126,54 +119,50 @@ func (ts TurbiditySensor) EvaluateBlock(img gocv.Mat, l1, k1, l2, k2 int, result
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search image for matching template. Returns the transformed image which best match the template
|
// Search image 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()
|
mask := gocv.NewMat()
|
||||||
|
|
||||||
corners_img := gocv.NewMat()
|
corners_img := gocv.NewMat()
|
||||||
corners_template := gocv.NewMat()
|
corners_template := gocv.NewMat()
|
||||||
|
|
||||||
// Find corners in image
|
// Find corners in image.
|
||||||
if !gocv.FindChessboardCorners(img, image.Pt(3, 3), &corners_img, gocv.CalibCBNormalizeImage) {
|
if !gocv.FindChessboardCorners(img, image.Pt(3, 3), &corners_img, gocv.CalibCBNormalizeImage) {
|
||||||
|
// Apply default if transformation fails.
|
||||||
// Apply default if transformation fails
|
|
||||||
// fmt.Println("Corner detection failed applying standard transformation")
|
// fmt.Println("Corner detection failed applying standard transformation")
|
||||||
|
|
||||||
if !gocv.FindChessboardCorners(ts.standard, image.Pt(3, 3), &corners_img, gocv.CalibCBNormalizeImage) {
|
if !gocv.FindChessboardCorners(ts.standard, image.Pt(3, 3), &corners_img, gocv.CalibCBNormalizeImage) {
|
||||||
return out, fmt.Errorf("Could not find corners in default image")
|
return out, errors.New("Could not find corners in default image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find corners in template
|
// Find corners in template.
|
||||||
if !gocv.FindChessboardCorners(ts.template, image.Pt(3, 3), &corners_template, gocv.CalibCBNormalizeImage) {
|
if !gocv.FindChessboardCorners(ts.template, image.Pt(3, 3), &corners_template, gocv.CalibCBNormalizeImage) {
|
||||||
return out, fmt.Errorf("Could not find corners in template")
|
return out, errors.New("Could not find corners in template")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find and apply transformation
|
// Find and apply transformation.
|
||||||
H := gocv.FindHomography(corners_img, &corners_template, gocv.HomograpyMethodRANSAC, 3.0, &mask, 2000, 0.995)
|
H := gocv.FindHomography(corners_img, &corners_template, gocv.HomograpyMethodRANSAC, 3.0, &mask, 2000, 0.995)
|
||||||
gocv.WarpPerspective(img, &out, H, image.Pt(ts.template.Rows(), ts.template.Cols()))
|
gocv.WarpPerspective(img, &out, H, image.Pt(ts.template.Rows(), ts.template.Cols()))
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply sobel filter to an image with a given scale and return the result
|
// Apply sobel filter to an image with a given scale and return the result.
|
||||||
func (ts TurbiditySensor) Sobel(img gocv.Mat, scale float64) gocv.Mat {
|
func (ts TurbiditySensor) Sobel(img gocv.Mat, scale float64) gocv.Mat {
|
||||||
|
|
||||||
dx := gocv.NewMat()
|
dx := gocv.NewMat()
|
||||||
dy := gocv.NewMat()
|
dy := gocv.NewMat()
|
||||||
sobel := gocv.NewMat()
|
sobel := gocv.NewMat()
|
||||||
|
|
||||||
// Apply filter
|
// Apply filter.
|
||||||
gocv.Sobel(img, &dx, gocv.MatTypeCV64F, 0, 1, 3, scale, 0.0, gocv.BorderConstant)
|
gocv.Sobel(img, &dx, gocv.MatTypeCV64F, 0, 1, 3, scale, 0.0, gocv.BorderConstant)
|
||||||
gocv.Sobel(img, &dy, gocv.MatTypeCV64F, 1, 0, 3, scale, 0.0, gocv.BorderConstant)
|
gocv.Sobel(img, &dy, gocv.MatTypeCV64F, 1, 0, 3, scale, 0.0, gocv.BorderConstant)
|
||||||
|
|
||||||
// Convert to unsigned
|
// Convert to unsigned.
|
||||||
gocv.ConvertScaleAbs(dx, &dx, 1.0, 0.0)
|
gocv.ConvertScaleAbs(dx, &dx, 1.0, 0.0)
|
||||||
gocv.ConvertScaleAbs(dy, &dy, 1.0, 0.0)
|
gocv.ConvertScaleAbs(dy, &dy, 1.0, 0.0)
|
||||||
|
|
||||||
// Add x and y components
|
// Add x and y components.
|
||||||
gocv.AddWeighted(dx, 0.5, dy, 0.5, 0, &sobel)
|
gocv.AddWeighted(dx, 0.5, dy, 0.5, 0, &sobel)
|
||||||
|
|
||||||
return sobel
|
return sobel
|
||||||
|
|
Loading…
Reference in New Issue