2022-01-05 07:52:29 +03:00
|
|
|
//go:build !ignore
|
|
|
|
// +build !ignore
|
|
|
|
|
|
|
|
/*
|
|
|
|
DESCRIPTION
|
|
|
|
Holds the turbidity sensor struct. Can evaluate 4x4 chessboard markers
|
|
|
|
in an image to measure the sharpness and contrast.
|
|
|
|
|
|
|
|
AUTHORS
|
|
|
|
Russell Stanley <russell@ausocean.org>
|
|
|
|
|
|
|
|
LICENSE
|
|
|
|
Copyright (C) 2020 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.
|
|
|
|
*/
|
2022-01-05 05:46:08 +03:00
|
|
|
|
2021-12-15 06:55:14 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2021-12-21 08:03:21 +03:00
|
|
|
"errors"
|
2021-12-15 06:55:14 +03:00
|
|
|
"fmt"
|
2021-12-21 04:43:05 +03:00
|
|
|
"image"
|
|
|
|
"math"
|
2021-12-15 06:55:14 +03:00
|
|
|
|
|
|
|
"gocv.io/x/gocv"
|
|
|
|
)
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Turbidity Sensor.
|
2021-12-21 04:43:05 +03:00
|
|
|
type TurbiditySensor struct {
|
2022-01-05 03:49:31 +03:00
|
|
|
template, standard gocv.Mat
|
|
|
|
k1, k2, sobelFilterSize int
|
|
|
|
alpha, scale float64
|
2021-12-21 04:43:05 +03:00
|
|
|
}
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Given a slice of test images, return the sharpness and contrast scores.
|
|
|
|
func (ts TurbiditySensor) Evaluate(imgs []gocv.Mat) (Results, error) {
|
2021-12-21 04:43:05 +03:00
|
|
|
|
|
|
|
var result Results
|
2021-12-23 09:10:35 +03:00
|
|
|
result.new(len(imgs))
|
2021-12-21 04:43:05 +03:00
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
for i := range imgs {
|
2021-12-21 04:43:05 +03:00
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Transform image.
|
|
|
|
marker, err := ts.Transform(imgs[i])
|
2021-12-21 04:43:05 +03:00
|
|
|
if err != nil {
|
2021-12-21 08:03:21 +03:00
|
|
|
return result, fmt.Errorf("Image %v: %w", i, err)
|
2021-12-21 04:43:05 +03:00
|
|
|
}
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Apply sobel filter.
|
2022-01-05 03:49:31 +03:00
|
|
|
edge := ts.Sobel(marker)
|
2021-12-23 09:10:35 +03:00
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Evaluate image.
|
2022-01-05 03:49:31 +03:00
|
|
|
scores, err := ts.EvaluateImage(marker, edge)
|
2021-12-21 04:43:05 +03:00
|
|
|
if err != nil {
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
2022-01-05 03:49:31 +03:00
|
|
|
result.update(scores[0], scores[1], float64(i*10), i)
|
2021-12-21 04:43:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Evaluate image sharpness and contrast using blocks of size k1 by k2. Return a slice of the respective scores.
|
2022-01-05 03:49:31 +03:00
|
|
|
func (ts TurbiditySensor) EvaluateImage(img, edge gocv.Mat) ([]float64, error) {
|
2021-12-15 06:55:14 +03:00
|
|
|
|
2021-12-21 04:43:05 +03:00
|
|
|
result := make([]float64, 2) // [0.0, 0.0]
|
2021-12-15 06:55:14 +03:00
|
|
|
|
2022-01-05 03:49:31 +03:00
|
|
|
if img.Rows()%ts.k1 != 0 || img.Cols()%ts.k2 != 0 {
|
|
|
|
return nil, fmt.Errorf("Dimensions not compatible (%v, %v)", ts.k1, ts.k2)
|
2021-12-21 04:43:05 +03:00
|
|
|
}
|
|
|
|
|
2022-01-05 03:49:31 +03:00
|
|
|
lStep := int(img.Rows() / ts.k1)
|
|
|
|
kStep := int(img.Cols() / ts.k2)
|
2021-12-21 04:43:05 +03:00
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
for l := 0; l < img.Rows(); l = l + lStep {
|
|
|
|
for k := 0; k < img.Cols(); k = k + kStep {
|
2021-12-15 06:55:14 +03:00
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Enhancement Measure Estimation (EME), provides a measure of the sharpness.
|
2022-01-05 03:49:31 +03:00
|
|
|
err := ts.EvaluateBlock(edge, l, k, l+lStep, k+kStep, result, "EME", ts.alpha)
|
2021-12-21 04:43:05 +03:00
|
|
|
if err != nil {
|
2021-12-21 08:03:21 +03:00
|
|
|
return nil, err
|
2021-12-21 04:43:05 +03:00
|
|
|
}
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// AMEE, provides a measure of the contrast.
|
2022-01-05 03:49:31 +03:00
|
|
|
err = ts.EvaluateBlock(img, l, k, l+lStep, k+kStep, result, "AMEE", ts.alpha)
|
2021-12-21 04:43:05 +03:00
|
|
|
if err != nil {
|
2021-12-21 08:03:21 +03:00
|
|
|
return nil, err
|
2021-12-21 04:43:05 +03:00
|
|
|
}
|
|
|
|
}
|
2021-12-15 06:55:14 +03:00
|
|
|
}
|
2021-12-21 04:43:05 +03:00
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// EME.
|
2022-01-05 03:49:31 +03:00
|
|
|
result[0] = 2.0 / (float64(ts.k1) * float64(ts.k2)) * result[0]
|
2021-12-21 04:43:05 +03:00
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// AMEE.
|
2022-01-05 03:49:31 +03:00
|
|
|
result[1] = -1.0 / (float64(ts.k1) * float64(ts.k2)) * result[1]
|
2021-12-21 04:43:05 +03:00
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Evaluate a block within an image and add to to the result slice.
|
2022-01-05 03:49:31 +03:00
|
|
|
func (ts TurbiditySensor) EvaluateBlock(img gocv.Mat, xStart, yStart, xEnd, yEnd int, result []float64, operation string, alpha float64) error {
|
2021-12-21 04:43:05 +03:00
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
max := -math.MaxFloat64
|
|
|
|
min := math.MaxFloat64
|
2021-12-21 04:43:05 +03:00
|
|
|
|
2022-01-05 03:49:31 +03:00
|
|
|
for i := xStart; i < xEnd; i++ {
|
|
|
|
for j := yStart; j < yEnd; j++ {
|
2021-12-21 04:43:05 +03:00
|
|
|
|
|
|
|
value := float64(img.GetUCharAt(i, j))
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Check max/min conditions, zero values are ignored.
|
2021-12-21 04:43:05 +03:00
|
|
|
if value > max && value != 0.0 {
|
|
|
|
max = value
|
|
|
|
}
|
|
|
|
if value < min && value != 0.0 {
|
|
|
|
min = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Blocks which have no information are ignored.
|
|
|
|
if max != -math.MaxFloat64 && min != math.MaxFloat64 && max != min {
|
2021-12-21 04:43:05 +03:00
|
|
|
if operation == "EME" {
|
|
|
|
result[0] += math.Log(max / min)
|
|
|
|
} else if operation == "AMEE" {
|
|
|
|
contrast := (max + min) / (max - min)
|
|
|
|
result[1] += math.Pow(alpha*(contrast), alpha) * math.Log(contrast)
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("Invalid operation: %v", operation)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Search image for matching template. Returns the transformed image which best match the template.
|
2021-12-21 04:43:05 +03:00
|
|
|
func (ts TurbiditySensor) Transform(img gocv.Mat) (gocv.Mat, error) {
|
|
|
|
out := gocv.NewMat()
|
|
|
|
mask := gocv.NewMat()
|
|
|
|
corners_img := gocv.NewMat()
|
|
|
|
corners_template := gocv.NewMat()
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Find corners in image.
|
2021-12-23 09:10:35 +03:00
|
|
|
if !gocv.FindChessboardCorners(ts.standard, image.Pt(3, 3), &corners_img, gocv.CalibCBNormalizeImage) {
|
2021-12-21 08:03:21 +03:00
|
|
|
// Apply default if transformation fails.
|
2021-12-23 09:10:35 +03:00
|
|
|
fmt.Println("Corner detection failed applying standard transformation")
|
2021-12-21 04:43:05 +03:00
|
|
|
if !gocv.FindChessboardCorners(ts.standard, image.Pt(3, 3), &corners_img, gocv.CalibCBNormalizeImage) {
|
2021-12-21 08:03:21 +03:00
|
|
|
return out, errors.New("Could not find corners in default image")
|
2021-12-21 04:43:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Find corners in template.
|
2021-12-21 04:43:05 +03:00
|
|
|
if !gocv.FindChessboardCorners(ts.template, image.Pt(3, 3), &corners_template, gocv.CalibCBNormalizeImage) {
|
2021-12-21 08:03:21 +03:00
|
|
|
return out, errors.New("Could not find corners in template")
|
2021-12-21 04:43:05 +03:00
|
|
|
}
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Find and apply transformation.
|
2021-12-21 04:43:05 +03:00
|
|
|
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()))
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Apply sobel filter to an image with a given scale and return the result.
|
2022-01-05 03:49:31 +03:00
|
|
|
func (ts TurbiditySensor) Sobel(img gocv.Mat) gocv.Mat {
|
2021-12-21 04:43:05 +03:00
|
|
|
|
|
|
|
dx := gocv.NewMat()
|
|
|
|
dy := gocv.NewMat()
|
|
|
|
sobel := gocv.NewMat()
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Apply filter.
|
2022-01-05 03:49:31 +03:00
|
|
|
gocv.Sobel(img, &dx, gocv.MatTypeCV64F, 0, 1, ts.sobelFilterSize, ts.scale, 0.0, gocv.BorderConstant)
|
|
|
|
gocv.Sobel(img, &dy, gocv.MatTypeCV64F, 1, 0, ts.sobelFilterSize, ts.scale, 0.0, gocv.BorderConstant)
|
2021-12-21 04:43:05 +03:00
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Convert to unsigned.
|
2021-12-21 04:43:05 +03:00
|
|
|
gocv.ConvertScaleAbs(dx, &dx, 1.0, 0.0)
|
|
|
|
gocv.ConvertScaleAbs(dy, &dy, 1.0, 0.0)
|
|
|
|
|
2021-12-21 08:03:21 +03:00
|
|
|
// Add x and y components.
|
2021-12-21 04:43:05 +03:00
|
|
|
gocv.AddWeighted(dx, 0.5, dy, 0.5, 0, &sobel)
|
|
|
|
|
|
|
|
return sobel
|
2021-12-15 06:55:14 +03:00
|
|
|
}
|