fix: resolving carving issue on image enlargment (#65)

This commit is contained in:
esimov
2021-06-16 16:19:18 +03:00
parent 9b306380da
commit d3da34e80d
6 changed files with 69 additions and 79 deletions

View File

@@ -122,7 +122,7 @@ func (c *Carver) ComputeSeams(img *image.NRGBA, p *Processor) error {
} }
if p.BlurRadius > 0 { if p.BlurRadius > 0 {
srcImg = StackBlur(sobel, uint32(p.BlurRadius)) srcImg = c.StackBlur(sobel, uint32(p.BlurRadius))
} else { } else {
srcImg = sobel srcImg = sobel
} }

View File

@@ -62,7 +62,6 @@ var (
percentage = flag.Bool("perc", false, "Reduce image by percentage") percentage = flag.Bool("perc", false, "Reduce image by percentage")
square = flag.Bool("square", false, "Reduce image to square dimensions") square = flag.Bool("square", false, "Reduce image to square dimensions")
debug = flag.Bool("debug", false, "Use debugger") debug = flag.Bool("debug", false, "Use debugger")
scale = flag.Bool("scale", false, "Proportional scaling")
faceDetect = flag.Bool("face", false, "Use face detection") faceDetect = flag.Bool("face", false, "Use face detection")
faceAngle = flag.Float64("angle", 0.0, "Plane rotated faces angle") faceAngle = flag.Float64("angle", 0.0, "Plane rotated faces angle")
workers = flag.Int("conc", runtime.NumCPU(), "Number of files to process concurrently") workers = flag.Int("conc", runtime.NumCPU(), "Number of files to process concurrently")
@@ -89,7 +88,6 @@ func main() {
Percentage: *percentage, Percentage: *percentage,
Square: *square, Square: *square,
Debug: *debug, Debug: *debug,
Scale: *scale,
FaceDetect: *faceDetect, FaceDetect: *faceDetect,
FaceAngle: *faceAngle, FaceAngle: *faceAngle,
} }

View File

@@ -21,8 +21,6 @@ import (
"golang.org/x/image/bmp" "golang.org/x/image/bmp"
) )
const maxResizeWithoutScaling = 2000
//go:embed data/facefinder //go:embed data/facefinder
var classifier embed.FS var classifier embed.FS
@@ -48,7 +46,6 @@ type Processor struct {
Percentage bool Percentage bool
Square bool Square bool
Debug bool Debug bool
Scale bool
FaceDetect bool FaceDetect bool
FaceAngle float64 FaceAngle float64
PigoFaceDetector *pigo.Pigo PigoFaceDetector *pigo.Pigo
@@ -92,6 +89,7 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
if p.NewHeight == 0 { if p.NewHeight == 0 {
newHeight = p.NewHeight newHeight = p.NewHeight
} }
reduce := func() error { reduce := func() error {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height) c = NewCarver(width, height)
@@ -175,28 +173,26 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
} }
img = c.RotateImage270(img) img = c.RotateImage270(img)
} else if newWidth > 0 || newHeight > 0 { } else if newWidth > 0 || newHeight > 0 {
// Use this option to rescale the image proportionally prior resizing. // We are trying to rescale the image proportionally prior resizing.
// First the image is scaled down preserving the image aspect ratio, // First the image is scaled down or up by preserving the image aspect ratio,
// then the seam carving algorithm is applied only to the remaining pixels. // then the seam carving algorithm is applied only to the remaining pixels.
// Prevent memory overflow issue in case of huge images by switching to scaling first option
if img.Bounds().Dx() > maxResizeWithoutScaling ||
img.Bounds().Dy() > maxResizeWithoutScaling {
p.Scale = true
}
if p.Scale {
// Find the scale factor for width and height.
// Scale the width and height by the smaller factor (i.e Min(wScaleFactor, hScaleFactor)) // Scale the width and height by the smaller factor (i.e Min(wScaleFactor, hScaleFactor))
// This will scale one side directly to the target length,
// and the other proportionally larger than the target length.
// Example: input: 5000x2500, scale: 2160x1080, final target: 1920x1080 // Example: input: 5000x2500, scale: 2160x1080, final target: 1920x1080
wScaleFactor := float64(c.Width) / float64(p.NewWidth) wScaleFactor := float64(c.Width) / float64(p.NewWidth)
hScaleFactor := float64(c.Height) / float64(p.NewHeight) hScaleFactor := float64(c.Height) / float64(p.NewHeight)
scaleWidth := math.Round(float64(c.Width) / math.Min(wScaleFactor, hScaleFactor)) //post scale width scaleWidth := math.Round(float64(c.Width) / math.Min(wScaleFactor, hScaleFactor))
scaleHeight := math.Round(float64(c.Height) / math.Min(wScaleFactor, hScaleFactor)) // post scale height scaleHeight := math.Round(float64(c.Height) / math.Min(wScaleFactor, hScaleFactor))
newImg = imaging.Resize(img, int(scaleWidth), int(scaleHeight), imaging.Lanczos) newImg = imaging.Resize(img, int(scaleWidth), int(scaleHeight), imaging.Lanczos)
dx0, dy0 := img.Bounds().Max.X, newImg.Bounds().Max.Y
dx1, dy1 := newImg.Bounds().Max.X, newImg.Bounds().Max.Y
// Rescale the image only when it's resized both horizontally and vertically
// and the new image width or height are preserved, otherwise it might happen, that
// the generated image size does not match with the requested image size.
if !((p.NewWidth == 0 && dx0 == dx1) || (p.NewHeight == 0 && dy0 == dy1)) {
// The amount needed to remove by carving. One or both of these will be 0. // The amount needed to remove by carving. One or both of these will be 0.
newWidth = int(scaleWidth) - p.NewWidth newWidth = int(scaleWidth) - p.NewWidth
newHeight = int(scaleHeight) - p.NewHeight newHeight = int(scaleHeight) - p.NewHeight
@@ -206,13 +202,12 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
img = dst img = dst
} }
// Check if the new width does not match with the rescaled image width.
// Run the carver function if the desired image width is not identical with the rescaled image width. // Run the carver function if the desired image width is not identical with the rescaled image width.
if newWidth > 0 && newWidth != img.Bounds().Max.X { if newWidth > 0 && newWidth != img.Bounds().Max.X {
// Because scaling horizontally and vertically at the same time using the scale option // Because of scaling horizontally and vertically at the same time it might happen
// it might happen that the scaled image exceeds the desired image size, we need to make sure // that the scaled image exceeds the desired image size, we need to make sure
// that the new width and|or height is reduced and not enlarged. // that the new width and|or height is reduced and not enlarged.
if p.NewWidth > c.Width && newWidth < p.NewWidth && img.Bounds().Max.X < p.NewWidth { if p.NewWidth > c.Width && img.Bounds().Max.X < p.NewWidth {
for x := 0; x < newWidth; x++ { for x := 0; x < newWidth; x++ {
if err = enlarge(); err != nil { if err = enlarge(); err != nil {
return nil, err return nil, err
@@ -227,12 +222,11 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
} }
} }
} }
// Check if the new height does not match with the rescaled image height.
// Run the carver function if the desired image height is not identical with the rescaled image height. // Run the carver function if the desired image height is not identical with the rescaled image height.
if newHeight > 0 && newHeight != img.Bounds().Max.Y { if newHeight > 0 && newHeight != img.Bounds().Max.Y {
img = c.RotateImage90(img) img = c.RotateImage90(img)
// Check new height against the width of the image because the image is rotated 90deg. // Check new height against the width of the image because the image is rotated 90deg.
if p.NewHeight > c.Height && (newHeight < p.NewHeight && img.Bounds().Max.X < p.NewHeight) { if p.NewHeight > c.Height && img.Bounds().Max.X < p.NewHeight {
for y := 0; y < newHeight; y++ { for y := 0; y < newHeight; y++ {
if err = enlarge(); err != nil { if err = enlarge(); err != nil {
return nil, err return nil, err

View File

@@ -22,7 +22,6 @@ func reduceImageH(t *testing.T) {
Percentage: false, Percentage: false,
Square: false, Square: false,
Debug: false, Debug: false,
Scale: false,
} }
// Reduce image size horizontally // Reduce image size horizontally
for x := 0; x < newWidth; x++ { for x := 0; x < newWidth; x++ {
@@ -51,7 +50,6 @@ func reduceImageV(t *testing.T) {
Percentage: false, Percentage: false,
Square: false, Square: false,
Debug: false, Debug: false,
Scale: false,
} }
// Reduce image size horizontally // Reduce image size horizontally
img = c.RotateImage90(img) img = c.RotateImage90(img)

View File

@@ -7,10 +7,10 @@ import (
"image" "image"
) )
// blurStack is a linked list containing the color value and a pointer to the next struct. // blurstack is a linked list containing the color value and a pointer to the next struct.
type blurStack struct { type blurstack struct {
r, g, b, a uint32 r, g, b, a uint32
next *blurStack next *blurstack
} }
var mulTable = []uint32{ var mulTable = []uint32{
@@ -51,15 +51,10 @@ var shgTable = []uint32{
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
} }
// NewBlurStack is a constructor function returning a new struct of type blurStack.
func (bs *blurStack) NewBlurStack() *blurStack {
return &blurStack{bs.r, bs.g, bs.b, bs.a, bs.next}
}
// StackBlur applies a blur filter to the provided image. // StackBlur applies a blur filter to the provided image.
// The radius defines the bluring average. // The radius defines the bluring average.
func StackBlur(img *image.NRGBA, radius uint32) *image.NRGBA { func (c *Carver) StackBlur(img *image.NRGBA, radius uint32) *image.NRGBA {
var stackEnd, stackIn, stackOut *blurStack var stackEnd, stackIn, stackOut *blurstack
var width, height = uint32(img.Bounds().Dx()), uint32(img.Bounds().Dy()) var width, height = uint32(img.Bounds().Dx()), uint32(img.Bounds().Dy())
var ( var (
div, widthMinus1, heightMinus1, radiusPlus1, sumFactor uint32 div, widthMinus1, heightMinus1, radiusPlus1, sumFactor uint32
@@ -76,12 +71,11 @@ func StackBlur(img *image.NRGBA, radius uint32) *image.NRGBA {
radiusPlus1 = radius + 1 radiusPlus1 = radius + 1
sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2 sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2
bs := blurStack{} stackStart := new(blurstack)
stackStart := bs.NewBlurStack()
stack := stackStart stack := stackStart
for i = 1; i < div; i++ { for i = 1; i < div; i++ {
stack.next = bs.NewBlurStack() stack.next = new(blurstack)
stack = stack.next stack = stack.next
if i == radiusPlus1 { if i == radiusPlus1 {
stackEnd = stack stackEnd = stack

View File

@@ -136,6 +136,7 @@ func (pg *Pigo) classifyRegion(r, c, s int, pixels []uint8, dim int) float32 {
r = r * 256 r = r * 256
c = c * 256 c = c * 256
if pg.treeNum > 0 {
for i := 0; i < int(pg.treeNum); i++ { for i := 0; i < int(pg.treeNum); i++ {
idx := 1 idx := 1
@@ -160,6 +161,8 @@ func (pg *Pigo) classifyRegion(r, c, s int, pixels []uint8, dim int) float32 {
} }
return out - pg.treeThreshold[pg.treeNum-1] return out - pg.treeThreshold[pg.treeNum-1]
} }
return 0.0
}
// classifyRotatedRegion applies the face classification function over a rotated image based on the parsed binary data. // classifyRotatedRegion applies the face classification function over a rotated image based on the parsed binary data.
func (pg *Pigo) classifyRotatedRegion(r, c, s int, a float64, nrows, ncols int, pixels []uint8, dim int) float32 { func (pg *Pigo) classifyRotatedRegion(r, c, s int, a float64, nrows, ncols int, pixels []uint8, dim int) float32 {
@@ -175,6 +178,7 @@ func (pg *Pigo) classifyRotatedRegion(r, c, s int, a float64, nrows, ncols int,
qsin := s * qSinTable[int(32.0*a)] //s*(256.0*math.Sin(2*math.Pi*a)) qsin := s * qSinTable[int(32.0*a)] //s*(256.0*math.Sin(2*math.Pi*a))
qcos := s * qCosTable[int(32.0*a)] //s*(256.0*math.Cos(2*math.Pi*a)) qcos := s * qCosTable[int(32.0*a)] //s*(256.0*math.Cos(2*math.Pi*a))
if pg.treeNum > 0 {
for i := 0; i < int(pg.treeNum); i++ { for i := 0; i < int(pg.treeNum); i++ {
var idx = 1 var idx = 1
@@ -202,6 +206,8 @@ func (pg *Pigo) classifyRotatedRegion(r, c, s int, a float64, nrows, ncols int,
} }
return out - pg.treeThreshold[pg.treeNum-1] return out - pg.treeThreshold[pg.treeNum-1]
} }
return 0.0
}
// Detection struct contains the detection results composed of // Detection struct contains the detection results composed of
// the row, column, scale factor and the detection score. // the row, column, scale factor and the detection score.