fix: seams dispersion issue on image enlargment #74

This commit is contained in:
esimov
2022-01-31 16:16:27 +02:00
parent f7c7d286cf
commit daf0b26e2f
4 changed files with 53 additions and 73 deletions

View File

@@ -29,7 +29,7 @@ func Benchmark_Carver(b *testing.B) {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height)
c.ComputeSeams(p, img)
seams := c.FindLowestEnergySeams()
seams := c.FindLowestEnergySeams(p)
img = c.RemoveSeam(img, seams, p.Debug)
}

View File

@@ -13,8 +13,9 @@ import (
const maxFaceDetAttempts = 20
var (
usedSeams []UsedSeams
detAttempts int
sobel *image.NRGBA
energySeams = make([][]Seam, 0)
)
// Carver is the main entry struct having as parameters the newly generated image width, height and seam points.
@@ -31,17 +32,6 @@ type Seam struct {
Y int
}
// UsedSeams contains the already generated seams.
type UsedSeams struct {
ActiveSeam []ActiveSeam
}
// ActiveSeam contains the current seam position and color.
type ActiveSeam struct {
Seam
Pix color.Color
}
// NewCarver returns an initialized Carver structure.
func NewCarver(width, height int) *Carver {
return &Carver{
@@ -75,7 +65,7 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) error {
var srcImg *image.NRGBA
width, height := img.Bounds().Dx(), img.Bounds().Dy()
sobel := c.SobelDetector(img, float64(p.SobelThreshold))
sobel = c.SobelDetector(img, float64(p.SobelThreshold))
if p.FaceDetect && detAttempts < maxFaceDetAttempts {
var ratio float64
@@ -132,7 +122,7 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) error {
face.Col+face.Scale/2,
face.Row+face.Scale/2,
)
draw.Draw(sobel, rect, &image.Uniform{color.White}, image.ZP, draw.Src)
draw.Draw(sobel, rect, &image.Uniform{color.White}, image.Point{}, draw.Src)
}
}
}
@@ -189,6 +179,17 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) error {
}
}
// Increase the energy value for each of the selected seam from the seams table
// in order to avoid picking the same seam over and over again.
// We expand the energy level of the selected seams to have a better redistribution.
if len(energySeams) > 0 {
for i := 0; i < len(energySeams); i++ {
for _, seam := range energySeams[i] {
sobel.Set(seam.X, seam.Y, &image.Uniform{color.White})
}
}
}
if p.BlurRadius > 0 {
srcImg = c.StackBlur(sobel, uint32(p.BlurRadius))
} else {
@@ -226,7 +227,7 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) error {
}
// FindLowestEnergySeams find the lowest vertical energy seam.
func (c *Carver) FindLowestEnergySeams() []Seam {
func (c *Carver) FindLowestEnergySeams(p *Processor) []Seam {
// Find the lowest cost seam from the energy matrix starting from the last row.
var (
min = math.MaxFloat64
@@ -246,8 +247,8 @@ func (c *Carver) FindLowestEnergySeams() []Seam {
seams = append(seams, Seam{X: px, Y: c.Height - 1})
var left, middle, right float64
// Walk up in the matrix table, check the immediate three top pixel seam level
// and add the one which has the lowest cumulative energy.
// Walk up in the matrix table, check the immediate three top pixels seam level
// and add that one which has the lowest cumulative energy.
for y := c.Height - 2; y >= 0; y-- {
middle = c.get(px, y)
// Leftmost seam, no child to the left
@@ -275,6 +276,14 @@ func (c *Carver) FindLowestEnergySeams() []Seam {
}
seams = append(seams, Seam{X: px, Y: y})
}
// compare against c.Width and NOT c.Height, because the image is rotated.
if p.NewWidth > c.Width || (p.NewHeight > 0 && p.NewHeight > c.Width) {
// Include the currently processed energy seam into the seams table,
// but only when an image enlargement operation is commenced.
// We need to take this approach in order to avoid picking the same seam each time.
energySeams = append(energySeams, seams)
}
return seams
}
@@ -304,10 +313,8 @@ func (c *Carver) RemoveSeam(img *image.NRGBA, seams []Seam, debug bool) *image.N
// AddSeam add a new seam.
func (c *Carver) AddSeam(img *image.NRGBA, seams []Seam, debug bool) *image.NRGBA {
var (
currentSeam []ActiveSeam
lr, lg, lb uint32
rr, rg, rb uint32
py int
lr, lg, lb uint32
rr, rg, rb uint32
)
bounds := img.Bounds()
@@ -320,47 +327,22 @@ func (c *Carver) AddSeam(img *image.NRGBA, seams []Seam, debug bool) *image.NRGB
if debug {
c.Seams = append(c.Seams, Seam{X: x, Y: y})
}
// Calculate the current seam pixel color by averaging the neighboring pixels color.
if y > 0 {
py = y - 1
} else {
py = y
}
if x > 0 {
lr, lg, lb, _ = img.At(x-1, py).RGBA()
if x > 0 && x != bounds.Max.X {
lr, lg, lb, _ = img.At(x-1, y).RGBA()
} else {
lr, lg, lb, _ = img.At(x, y).RGBA()
}
if y < bounds.Max.Y-1 {
py = y + 1
} else {
py = y
}
if x < bounds.Max.X-1 {
rr, rg, rb, _ = img.At(x+1, py).RGBA()
} else {
rr, rg, rb, _ = img.At(x+1, y).RGBA()
} else if x == bounds.Max.X {
rr, rg, rb, _ = img.At(x, y).RGBA()
}
avr, avg, avb := (lr+rr)/2, (lg+rg)/2, (lb+rb)/2
dst.Set(x, y, color.RGBA{uint8(avr >> 8), uint8(avg >> 8), uint8(avb >> 8), 255})
// Append the current seam position and color to the existing seams.
// To avoid picking the same optimal seam over and over again,
// each time we detect an optimal seam we assign a large positive value
// to the corresponding pixels in the energy map.
// We will increase the seams weight by duplicating the pixel value.
currentSeam = append(currentSeam,
ActiveSeam{Seam{x + 1, y},
color.RGBA{
R: uint8((avr + avr) >> 8),
G: uint8((avg + avg) >> 8),
B: uint8((avb + avb) >> 8),
A: 255,
},
})
// calculate the average color of the neighboring pixels
avr, avg, avb := (lr+rr)>>1, (lg+rg)>>1, (lb+rb)>>1
dst.Set(x, y, color.RGBA{uint8(avr >> 8), uint8(avg >> 8), uint8(avb >> 8), 0xff})
dst.Set(x+1, y, img.At(x, y))
} else if seam.X < x {
dst.Set(x, y, img.At(x-1, y))
dst.Set(x+1, y, img.At(x, y))
@@ -369,7 +351,7 @@ func (c *Carver) AddSeam(img *image.NRGBA, seams []Seam, debug bool) *image.NRGB
}
}
}
usedSeams = append(usedSeams, UsedSeams{currentSeam})
return dst
}

View File

@@ -37,7 +37,7 @@ func TestCarver_EnergySeamShouldNotBeDetected(t *testing.T) {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height)
c.ComputeSeams(p, img)
les := c.FindLowestEnergySeams()
les := c.FindLowestEnergySeams(p)
seams = append(seams, les)
}
@@ -56,7 +56,7 @@ func TestCarver_DetectHorizontalEnergySeam(t *testing.T) {
var totalEnergySeams int
img := image.NewNRGBA(image.Rect(0, 0, ImgWidth, ImgHeight))
draw.Draw(img, img.Bounds(), &image.Uniform{image.White}, image.ZP, draw.Src)
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
@@ -75,7 +75,7 @@ func TestCarver_DetectHorizontalEnergySeam(t *testing.T) {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height)
c.ComputeSeams(p, img)
les := c.FindLowestEnergySeams()
les := c.FindLowestEnergySeams(p)
seams = append(seams, les)
}
@@ -94,7 +94,7 @@ func TestCarver_DetectVerticalEnergySeam(t *testing.T) {
var totalEnergySeams int
img := image.NewNRGBA(image.Rect(0, 0, ImgWidth, ImgHeight))
draw.Draw(img, img.Bounds(), &image.Uniform{image.White}, image.ZP, draw.Src)
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
@@ -114,7 +114,7 @@ func TestCarver_DetectVerticalEnergySeam(t *testing.T) {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height)
c.ComputeSeams(p, img)
les := c.FindLowestEnergySeams()
les := c.FindLowestEnergySeams(p)
seams = append(seams, les)
}
img = c.RotateImage270(img)
@@ -135,7 +135,7 @@ func TestCarver_RemoveSeam(t *testing.T) {
// 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.ZP, draw.Src)
draw.Draw(img, bounds, &image.Uniform{image.White}, image.Point{}, draw.Src)
origImg := img
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
@@ -146,7 +146,7 @@ func TestCarver_RemoveSeam(t *testing.T) {
c := NewCarver(dx, dy)
c.ComputeSeams(p, img)
seams := c.FindLowestEnergySeams()
seams := c.FindLowestEnergySeams(p)
img = c.RemoveSeam(img, seams, false)
isEq := true
@@ -174,7 +174,7 @@ func TestCarver_AddSeam(t *testing.T) {
// 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.ZP, draw.Src)
draw.Draw(img, bounds, &image.Uniform{image.White}, image.Point{}, draw.Src)
origImg := img
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
@@ -185,7 +185,7 @@ func TestCarver_AddSeam(t *testing.T) {
c := NewCarver(dx, dy)
c.ComputeSeams(p, img)
seams := c.FindLowestEnergySeams()
seams := c.FindLowestEnergySeams(p)
img = c.AddSeam(img, seams, false)
dx, dy = img.Bounds().Dx(), img.Bounds().Dy()
@@ -212,8 +212,6 @@ func TestCarver_ComputeSeams(t *testing.T) {
// 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, img.Bounds(), &image.Uniform{image.White}, image.ZP, draw.Src)
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
// Replace the pixels in row 5 with lower intensity colors.
for x := 0; x < dx; x++ {
@@ -356,11 +354,11 @@ func TestCarver_ShouldNotRemoveFaceZone(t *testing.T) {
face.Col+face.Scale/2,
face.Row+face.Scale/2,
)
draw.Draw(sobel, rect, &image.Uniform{image.White}, image.ZP, draw.Src)
draw.Draw(sobel, rect, &image.Uniform{image.White}, image.Point{}, draw.Src)
}
}
c.ComputeSeams(p, img)
seams := c.FindLowestEnergySeams()
seams := c.FindLowestEnergySeams(p)
for _, seam := range seams {
if seam.X >= rect.Min.X && seam.X <= rect.Max.X {

View File

@@ -17,7 +17,7 @@ func TestResize_ShrinkImageWidth(t *testing.T) {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height)
c.ComputeSeams(p, img)
seams := c.FindLowestEnergySeams()
seams := c.FindLowestEnergySeams(p)
img = c.RemoveSeam(img, seams, p.Debug)
}
imgWidth := img.Bounds().Max.X
@@ -40,7 +40,7 @@ func TestResize_ShrinkImageHeight(t *testing.T) {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height)
c.ComputeSeams(p, img)
seams := c.FindLowestEnergySeams()
seams := c.FindLowestEnergySeams(p)
img = c.RemoveSeam(img, seams, p.Debug)
}
img = c.RotateImage270(img)
@@ -64,7 +64,7 @@ func TestResize_EnlargeImageWidth(t *testing.T) {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height)
c.ComputeSeams(p, img)
seams := c.FindLowestEnergySeams()
seams := c.FindLowestEnergySeams(p)
img = c.AddSeam(img, seams, p.Debug)
}
imgWidth := img.Bounds().Max.X - origImgWidth
@@ -88,7 +88,7 @@ func TestResize_EnlargeImageHeight(t *testing.T) {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height)
c.ComputeSeams(p, img)
seams := c.FindLowestEnergySeams()
seams := c.FindLowestEnergySeams(p)
img = c.AddSeam(img, seams, p.Debug)
}
img = c.RotateImage270(img)