mirror of
https://github.com/esimov/caire.git
synced 2025-10-05 16:47:15 +08:00
fix: resolving carving issue on image enlargment (#65)
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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,
|
||||||
}
|
}
|
||||||
|
48
process.go
48
process.go
@@ -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
|
// Scale the width and height by the smaller factor (i.e Min(wScaleFactor, hScaleFactor))
|
||||||
if img.Bounds().Dx() > maxResizeWithoutScaling ||
|
// Example: input: 5000x2500, scale: 2160x1080, final target: 1920x1080
|
||||||
img.Bounds().Dy() > maxResizeWithoutScaling {
|
wScaleFactor := float64(c.Width) / float64(p.NewWidth)
|
||||||
p.Scale = true
|
hScaleFactor := float64(c.Height) / float64(p.NewHeight)
|
||||||
}
|
scaleWidth := math.Round(float64(c.Width) / math.Min(wScaleFactor, hScaleFactor))
|
||||||
|
scaleHeight := math.Round(float64(c.Height) / math.Min(wScaleFactor, hScaleFactor))
|
||||||
|
|
||||||
if p.Scale {
|
newImg = imaging.Resize(img, int(scaleWidth), int(scaleHeight), imaging.Lanczos)
|
||||||
// Find the scale factor for width and height.
|
|
||||||
// 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
|
|
||||||
wScaleFactor := float64(c.Width) / float64(p.NewWidth)
|
|
||||||
hScaleFactor := float64(c.Height) / float64(p.NewHeight)
|
|
||||||
scaleWidth := math.Round(float64(c.Width) / math.Min(wScaleFactor, hScaleFactor)) //post scale width
|
|
||||||
scaleHeight := math.Round(float64(c.Height) / math.Min(wScaleFactor, hScaleFactor)) // post scale height
|
|
||||||
|
|
||||||
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
|
||||||
|
@@ -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)
|
||||||
|
20
stackblur.go
20
stackblur.go
@@ -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
|
||||||
|
74
vendor/github.com/esimov/pigo/core/pigo.go
generated
vendored
74
vendor/github.com/esimov/pigo/core/pigo.go
generated
vendored
@@ -136,29 +136,32 @@ 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
|
||||||
|
|
||||||
for i := 0; i < int(pg.treeNum); i++ {
|
if pg.treeNum > 0 {
|
||||||
idx := 1
|
for i := 0; i < int(pg.treeNum); i++ {
|
||||||
|
idx := 1
|
||||||
|
|
||||||
for j := 0; j < int(pg.treeDepth); j++ {
|
for j := 0; j < int(pg.treeDepth); j++ {
|
||||||
x1 := ((r+int(pg.treeCodes[root+4*idx+0])*s)>>8)*dim + ((c + int(pg.treeCodes[root+4*idx+1])*s) >> 8)
|
x1 := ((r+int(pg.treeCodes[root+4*idx+0])*s)>>8)*dim + ((c + int(pg.treeCodes[root+4*idx+1])*s) >> 8)
|
||||||
x2 := ((r+int(pg.treeCodes[root+4*idx+2])*s)>>8)*dim + ((c + int(pg.treeCodes[root+4*idx+3])*s) >> 8)
|
x2 := ((r+int(pg.treeCodes[root+4*idx+2])*s)>>8)*dim + ((c + int(pg.treeCodes[root+4*idx+3])*s) >> 8)
|
||||||
|
|
||||||
bintest := func(px1, px2 uint8) int {
|
bintest := func(px1, px2 uint8) int {
|
||||||
if px1 <= px2 {
|
if px1 <= px2 {
|
||||||
return 1
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
return 0
|
idx = 2*idx + bintest(pixels[x1], pixels[x2])
|
||||||
}
|
}
|
||||||
idx = 2*idx + bintest(pixels[x1], pixels[x2])
|
out += pg.treePred[treeDepth*i+idx-treeDepth]
|
||||||
}
|
|
||||||
out += pg.treePred[treeDepth*i+idx-treeDepth]
|
|
||||||
|
|
||||||
if out <= pg.treeThreshold[i] {
|
if out <= pg.treeThreshold[i] {
|
||||||
return -1.0
|
return -1.0
|
||||||
|
}
|
||||||
|
root += 4 * treeDepth
|
||||||
}
|
}
|
||||||
root += 4 * treeDepth
|
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.
|
||||||
@@ -175,32 +178,35 @@ 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))
|
||||||
|
|
||||||
for i := 0; i < int(pg.treeNum); i++ {
|
if pg.treeNum > 0 {
|
||||||
var idx = 1
|
for i := 0; i < int(pg.treeNum); i++ {
|
||||||
|
var idx = 1
|
||||||
|
|
||||||
for j := 0; j < int(pg.treeDepth); j++ {
|
for j := 0; j < int(pg.treeDepth); j++ {
|
||||||
r1 := abs(min(nrows-1, max(0, 65536*r+qcos*int(pg.treeCodes[root+4*idx+0])-qsin*int(pg.treeCodes[root+4*idx+1]))>>16))
|
r1 := abs(min(nrows-1, max(0, 65536*r+qcos*int(pg.treeCodes[root+4*idx+0])-qsin*int(pg.treeCodes[root+4*idx+1]))>>16))
|
||||||
c1 := abs(min(nrows-1, max(0, 65536*c+qsin*int(pg.treeCodes[root+4*idx+0])+qcos*int(pg.treeCodes[root+4*idx+1]))>>16))
|
c1 := abs(min(nrows-1, max(0, 65536*c+qsin*int(pg.treeCodes[root+4*idx+0])+qcos*int(pg.treeCodes[root+4*idx+1]))>>16))
|
||||||
|
|
||||||
r2 := abs(min(nrows-1, max(0, 65536*r+qcos*int(pg.treeCodes[root+4*idx+2])-qsin*int(pg.treeCodes[root+4*idx+3]))>>16))
|
r2 := abs(min(nrows-1, max(0, 65536*r+qcos*int(pg.treeCodes[root+4*idx+2])-qsin*int(pg.treeCodes[root+4*idx+3]))>>16))
|
||||||
c2 := abs(min(nrows-1, max(0, 65536*c+qsin*int(pg.treeCodes[root+4*idx+2])+qcos*int(pg.treeCodes[root+4*idx+3]))>>16))
|
c2 := abs(min(nrows-1, max(0, 65536*c+qsin*int(pg.treeCodes[root+4*idx+2])+qcos*int(pg.treeCodes[root+4*idx+3]))>>16))
|
||||||
|
|
||||||
bintest := func(px1, px2 uint8) int {
|
bintest := func(px1, px2 uint8) int {
|
||||||
if px1 <= px2 {
|
if px1 <= px2 {
|
||||||
return 1
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
return 0
|
idx = 2*idx + bintest(pixels[r1*dim+c1], pixels[r2*dim+c2])
|
||||||
}
|
}
|
||||||
idx = 2*idx + bintest(pixels[r1*dim+c1], pixels[r2*dim+c2])
|
out += pg.treePred[treeDepth*i+idx-treeDepth]
|
||||||
}
|
|
||||||
out += pg.treePred[treeDepth*i+idx-treeDepth]
|
|
||||||
|
|
||||||
if out <= pg.treeThreshold[i] {
|
if out <= pg.treeThreshold[i] {
|
||||||
return -1.0
|
return -1.0
|
||||||
|
}
|
||||||
|
root += 4 * treeDepth
|
||||||
}
|
}
|
||||||
root += 4 * treeDepth
|
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
|
||||||
|
Reference in New Issue
Block a user