mirror of
https://github.com/esimov/caire.git
synced 2024-05-31 14:55:55 +08:00
434 lines
12 KiB
Go
434 lines
12 KiB
Go
package caire
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/esimov/caire/utils"
|
|
pigo "github.com/esimov/pigo/core"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
const (
|
|
imgWidth = 10
|
|
imgHeight = 10
|
|
)
|
|
|
|
var p *Processor
|
|
|
|
func init() {
|
|
p = &Processor{
|
|
NewWidth: imgWidth,
|
|
NewHeight: imgHeight,
|
|
BlurRadius: 1,
|
|
SobelThreshold: 4,
|
|
Percentage: false,
|
|
Square: false,
|
|
Debug: false,
|
|
}
|
|
}
|
|
|
|
func TestCarver_EnergySeamShouldNotBeDetected(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var seams [][]Seam
|
|
var totalEnergySeams int
|
|
|
|
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
|
|
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
|
|
|
var c = NewCarver(dx, dy)
|
|
for x := 0; x < imgWidth; x++ {
|
|
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
|
|
c = NewCarver(width, height)
|
|
c.ComputeSeams(p, img)
|
|
les := c.FindLowestEnergySeams(p)
|
|
seams = append(seams, les)
|
|
}
|
|
|
|
for i := 0; i < len(seams); i++ {
|
|
for s := 0; s < len(seams[i]); s++ {
|
|
totalEnergySeams += seams[i][s].X
|
|
}
|
|
}
|
|
assert.Equal(0, totalEnergySeams)
|
|
}
|
|
|
|
func TestCarver_DetectHorizontalEnergySeam(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var seams [][]Seam
|
|
var totalEnergySeams int
|
|
|
|
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
|
|
draw.Draw(img, img.Bounds(), &image.Uniform{image.White}, image.Point{}, draw.Src)
|
|
|
|
// Replace the pixel colors in a single row from 0xff to 0xdd. 5 is an arbitrary value.
|
|
// The seam detector should recognize that line as being of low energy density
|
|
// and should perform the seam computation process.
|
|
// This way we'll make sure, that the seam detector correctly detects one and only one line.
|
|
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
|
for x := 0; x < dx; x++ {
|
|
img.Pix[(5*dx+x)*4+0] = 0xdd
|
|
img.Pix[(5*dx+x)*4+1] = 0xdd
|
|
img.Pix[(5*dx+x)*4+2] = 0xdd
|
|
img.Pix[(5*dx+x)*4+3] = 0xdd
|
|
}
|
|
|
|
var c = NewCarver(dx, dy)
|
|
for x := 0; x < imgWidth; x++ {
|
|
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
|
|
c = NewCarver(width, height)
|
|
c.ComputeSeams(p, img)
|
|
les := c.FindLowestEnergySeams(p)
|
|
seams = append(seams, les)
|
|
}
|
|
|
|
for i := 0; i < len(seams); i++ {
|
|
for s := 0; s < len(seams[i]); s++ {
|
|
totalEnergySeams += seams[i][s].X
|
|
}
|
|
}
|
|
|
|
assert.Greater(totalEnergySeams, 0)
|
|
}
|
|
|
|
func TestCarver_DetectVerticalEnergySeam(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var seams [][]Seam
|
|
var totalEnergySeams int
|
|
|
|
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
|
|
draw.Draw(img, img.Bounds(), &image.Uniform{image.White}, image.Point{}, draw.Src)
|
|
|
|
// Replace the pixel colors in a single column from 0xff to 0xdd. 5 is an arbitrary value.
|
|
// The seam detector should recognize that line as being of low energy density
|
|
// and should perform the seam computation process.
|
|
// This way we'll make sure, that the seam detector correctly detects one and only one line.
|
|
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
|
for y := 0; y < dy; y++ {
|
|
img.Pix[5*4+(dx*y)*4+0] = 0xdd
|
|
img.Pix[5*4+(dx*y)*4+1] = 0xdd
|
|
img.Pix[5*4+(dx*y)*4+2] = 0xdd
|
|
img.Pix[5*4+(dx*y)*4+3] = 0xff
|
|
}
|
|
|
|
var c = NewCarver(dx, dy)
|
|
img = c.RotateImage90(img)
|
|
for x := 0; x < imgHeight; x++ {
|
|
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
|
|
c = NewCarver(width, height)
|
|
c.ComputeSeams(p, img)
|
|
les := c.FindLowestEnergySeams(p)
|
|
seams = append(seams, les)
|
|
}
|
|
|
|
for i := 0; i < len(seams); i++ {
|
|
for s := 0; s < len(seams[i]); s++ {
|
|
totalEnergySeams += seams[i][s].X
|
|
}
|
|
}
|
|
assert.Greater(totalEnergySeams, 0)
|
|
}
|
|
|
|
func TestCarver_RemoveSeam(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
|
|
bounds := img.Bounds()
|
|
|
|
// We choose to fill up the background with an uniform white color
|
|
// and afterwards we replace the colors in a single row with lower intensity ones.
|
|
draw.Draw(img, bounds, &image.Uniform{image.White}, image.Point{}, draw.Src)
|
|
origImg := img
|
|
|
|
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
|
// Replace the pixels in row 5 with lower intensity colors.
|
|
for x := 0; x < dx; x++ {
|
|
img.Set(x, 5, color.RGBA{R: 0xdd, G: 0xdd, B: 0xdd, A: 0xff})
|
|
}
|
|
|
|
c := NewCarver(dx, dy)
|
|
c.ComputeSeams(p, img)
|
|
seams := c.FindLowestEnergySeams(p)
|
|
img = c.RemoveSeam(img, seams, false)
|
|
|
|
isEq := true
|
|
// The test should pass if the detector correctly finds the row which pixel values are of lower intensity.
|
|
for x := 0; x < dx; x++ {
|
|
for y := 0; y < dy; y++ {
|
|
// In case the seam detector correctly recognize the modified line as of low importance
|
|
// it should remove it, which means the new image width should be 1px less then the original image.
|
|
r0, g0, b0, _ := origImg.At(x, y).RGBA()
|
|
r1, g1, b1, _ := img.At(x, y).RGBA()
|
|
|
|
if r0>>8 != r1>>8 && g0>>8 != g1>>8 && b0>>8 != b1>>8 {
|
|
isEq = false
|
|
}
|
|
}
|
|
}
|
|
assert.False(isEq)
|
|
}
|
|
|
|
func TestCarver_AddSeam(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
|
|
bounds := img.Bounds()
|
|
|
|
// We choose to fill up the background with an uniform white color
|
|
// Afterwards we'll replace the colors in a single row with lower intensity ones.
|
|
draw.Draw(img, bounds, &image.Uniform{image.White}, image.Point{}, draw.Src)
|
|
origImg := img
|
|
|
|
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
|
// Replace the pixels in row 5 with lower intensity colors.
|
|
for x := 0; x < dx; x++ {
|
|
img.Set(x, 5, color.RGBA{R: 0xdd, G: 0xdd, B: 0xdd, A: 0xff})
|
|
}
|
|
|
|
c := NewCarver(dx, dy)
|
|
c.ComputeSeams(p, img)
|
|
seams := c.FindLowestEnergySeams(p)
|
|
img = c.AddSeam(img, seams, false)
|
|
|
|
dx, dy = img.Bounds().Dx(), img.Bounds().Dy()
|
|
|
|
isEq := true
|
|
// The test should pass if the detector correctly finds the row which has lower intensity colors.
|
|
for x := 0; x < dx; x++ {
|
|
for y := 0; y < dy; y++ {
|
|
r0, g0, b0, _ := origImg.At(x, y).RGBA()
|
|
r1, g1, b1, _ := img.At(x, y).RGBA()
|
|
|
|
if r0>>8 != r1>>8 && g0>>8 != g1>>8 && b0>>8 != b1>>8 {
|
|
isEq = false
|
|
}
|
|
}
|
|
}
|
|
assert.False(isEq)
|
|
}
|
|
|
|
func TestCarver_ComputeSeams(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
|
|
|
|
// We choose to fill up the background with an uniform white color
|
|
// Afterwards we'll replace the colors in a single row with lower intensity ones.
|
|
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
|
// Replace the pixels in row 5 with lower intensity colors.
|
|
for x := 0; x < dx; x++ {
|
|
img.Pix[(5*dx+x)*4+0] = 0xdd
|
|
img.Pix[(5*dx+x)*4+1] = 0xdd
|
|
img.Pix[(5*dx+x)*4+2] = 0xdd
|
|
img.Pix[(5*dx+x)*4+3] = 0xdd
|
|
}
|
|
|
|
c := NewCarver(dx, dy)
|
|
c.ComputeSeams(p, img)
|
|
|
|
otherThenZero := findNonZeroValue(c.Points)
|
|
|
|
assert.True(otherThenZero)
|
|
}
|
|
|
|
func TestCarver_ShouldDetectFace(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
p.FaceDetect = true
|
|
|
|
sampleImg := filepath.Join("./testdata", "sample.jpg")
|
|
f, err := os.Open(sampleImg)
|
|
if err != nil {
|
|
t.Fatalf("could not load sample image: %v", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
p.FaceDetector, err = p.FaceDetector.Unpack(cascadeFile)
|
|
if err != nil {
|
|
t.Fatalf("error unpacking the cascade file: %v", err)
|
|
}
|
|
|
|
src, _, err := image.Decode(f)
|
|
if err != nil {
|
|
t.Fatalf("error decoding image: %v", err)
|
|
}
|
|
img := p.imgToNRGBA(src)
|
|
dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y
|
|
|
|
c := NewCarver(dx, dy)
|
|
// Transform the image to a pixel array.
|
|
pixels := c.rgbToGrayscale(img)
|
|
|
|
cParams := pigo.CascadeParams{
|
|
MinSize: 100,
|
|
MaxSize: utils.Max(dx, dy),
|
|
ShiftFactor: 0.1,
|
|
ScaleFactor: 1.1,
|
|
|
|
ImageParams: pigo.ImageParams{
|
|
Pixels: pixels,
|
|
Rows: dy,
|
|
Cols: dx,
|
|
Dim: dx,
|
|
},
|
|
}
|
|
|
|
// Run the classifier over the obtained leaf nodes and return the detection results.
|
|
// The result contains quadruplets representing the row, column, scale and detection score.
|
|
faces := p.FaceDetector.RunCascade(cParams, p.FaceAngle)
|
|
|
|
// Calculate the intersection over union (IoU) of two clusters.
|
|
faces = p.FaceDetector.ClusterDetections(faces, 0.2)
|
|
|
|
assert.Equal(1, len(faces))
|
|
}
|
|
|
|
func TestCarver_ShouldNotRemoveFaceZone(t *testing.T) {
|
|
p.FaceDetect = true
|
|
p.BlurRadius = 10
|
|
|
|
sampleImg := filepath.Join("./testdata", "sample.jpg")
|
|
f, err := os.Open(sampleImg)
|
|
if err != nil {
|
|
t.Fatalf("could not load sample image: %v", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
p.FaceDetector, err = p.FaceDetector.Unpack(cascadeFile)
|
|
if err != nil {
|
|
t.Fatalf("error unpacking the cascade file: %v", err)
|
|
}
|
|
|
|
src, _, err := image.Decode(f)
|
|
if err != nil {
|
|
t.Fatalf("error decoding image: %v", err)
|
|
}
|
|
img := p.imgToNRGBA(src)
|
|
dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y
|
|
|
|
c := NewCarver(dx, dy)
|
|
// Transform the image to a pixel array.
|
|
pixels := c.rgbToGrayscale(img)
|
|
|
|
sobel := c.SobelDetector(img, float64(p.SobelThreshold))
|
|
img = c.StackBlur(sobel, uint32(p.BlurRadius))
|
|
|
|
cParams := pigo.CascadeParams{
|
|
MinSize: 100,
|
|
MaxSize: utils.Max(dx, dy),
|
|
ShiftFactor: 0.1,
|
|
ScaleFactor: 1.1,
|
|
|
|
ImageParams: pigo.ImageParams{
|
|
Pixels: pixels,
|
|
Rows: dy,
|
|
Cols: dx,
|
|
Dim: dx,
|
|
},
|
|
}
|
|
|
|
// Run the classifier over the obtained leaf nodes and return the detection results.
|
|
// The result contains quadruplets representing the row, column, scale and detection score.
|
|
faces := p.FaceDetector.RunCascade(cParams, p.FaceAngle)
|
|
|
|
// Calculate the intersection over union (IoU) of two clusters.
|
|
faces = p.FaceDetector.ClusterDetections(faces, 0.2)
|
|
|
|
// Range over all the detected faces and draw a white rectangle mask over each of them.
|
|
// We need to trick the sobel detector to consider them as important image parts.
|
|
var rect image.Rectangle
|
|
for _, face := range faces {
|
|
if face.Q > 5.0 {
|
|
rect = image.Rect(
|
|
face.Col-face.Scale/2,
|
|
face.Row-face.Scale/2,
|
|
face.Col+face.Scale/2,
|
|
face.Row+face.Scale/2,
|
|
)
|
|
draw.Draw(sobel, rect, &image.Uniform{image.White}, image.Point{}, draw.Src)
|
|
}
|
|
}
|
|
c.ComputeSeams(p, img)
|
|
seams := c.FindLowestEnergySeams(p)
|
|
|
|
for _, seam := range seams {
|
|
if seam.X >= rect.Min.X && seam.X <= rect.Max.X {
|
|
t.Errorf("Carver shouldn't remove seams from face zone")
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCarver_ShouldNotResizeWithFaceDistorsion(t *testing.T) {
|
|
p.FaceDetect = true
|
|
p.BlurRadius = 10
|
|
p.NewHeight = 200
|
|
|
|
sampleImg := filepath.Join("./testdata", "sample.jpg")
|
|
f, err := os.Open(sampleImg)
|
|
if err != nil {
|
|
t.Fatalf("could not load sample image: %v", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
p.FaceDetector, err = p.FaceDetector.Unpack(cascadeFile)
|
|
if err != nil {
|
|
t.Fatalf("error unpacking the cascade file: %v", err)
|
|
}
|
|
|
|
src, _, err := image.Decode(f)
|
|
if err != nil {
|
|
t.Fatalf("error decoding image: %v", err)
|
|
}
|
|
img := p.imgToNRGBA(src)
|
|
dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y
|
|
|
|
c := NewCarver(dx, dy)
|
|
// Transform the image to a pixel array.
|
|
pixels := c.rgbToGrayscale(img)
|
|
cParams := pigo.CascadeParams{
|
|
MinSize: 100,
|
|
MaxSize: utils.Max(dx, dy),
|
|
ShiftFactor: 0.1,
|
|
ScaleFactor: 1.1,
|
|
|
|
ImageParams: pigo.ImageParams{
|
|
Pixels: pixels,
|
|
Rows: dy,
|
|
Cols: dx,
|
|
Dim: dx,
|
|
},
|
|
}
|
|
|
|
// Run the classifier over the obtained leaf nodes and return the detection results.
|
|
// The result contains quadruplets representing the row, column, scale and detection score.
|
|
faces := p.FaceDetector.RunCascade(cParams, p.FaceAngle)
|
|
|
|
// Calculate the intersection over union (IoU) of two clusters.
|
|
faces = p.FaceDetector.ClusterDetections(faces, 0.2)
|
|
|
|
for _, face := range faces {
|
|
if p.NewHeight < face.Scale {
|
|
t.Errorf("Should not resize image without face deformation.")
|
|
}
|
|
}
|
|
}
|
|
|
|
// findNonZeroValue utility function to check if the slice contains values other then zeros.
|
|
func findNonZeroValue(points []float64) bool {
|
|
var found = false
|
|
for i := 0; i < len(points); i++ {
|
|
if points[i] != 0 {
|
|
found = true
|
|
}
|
|
}
|
|
return found
|
|
}
|