mirror of
https://github.com/esimov/caire.git
synced 2025-10-05 08:37:01 +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 {
|
||||
srcImg = StackBlur(sobel, uint32(p.BlurRadius))
|
||||
srcImg = c.StackBlur(sobel, uint32(p.BlurRadius))
|
||||
} else {
|
||||
srcImg = sobel
|
||||
}
|
||||
|
@@ -62,7 +62,6 @@ var (
|
||||
percentage = flag.Bool("perc", false, "Reduce image by percentage")
|
||||
square = flag.Bool("square", false, "Reduce image to square dimensions")
|
||||
debug = flag.Bool("debug", false, "Use debugger")
|
||||
scale = flag.Bool("scale", false, "Proportional scaling")
|
||||
faceDetect = flag.Bool("face", false, "Use face detection")
|
||||
faceAngle = flag.Float64("angle", 0.0, "Plane rotated faces angle")
|
||||
workers = flag.Int("conc", runtime.NumCPU(), "Number of files to process concurrently")
|
||||
@@ -89,7 +88,6 @@ func main() {
|
||||
Percentage: *percentage,
|
||||
Square: *square,
|
||||
Debug: *debug,
|
||||
Scale: *scale,
|
||||
FaceDetect: *faceDetect,
|
||||
FaceAngle: *faceAngle,
|
||||
}
|
||||
|
48
process.go
48
process.go
@@ -21,8 +21,6 @@ import (
|
||||
"golang.org/x/image/bmp"
|
||||
)
|
||||
|
||||
const maxResizeWithoutScaling = 2000
|
||||
|
||||
//go:embed data/facefinder
|
||||
var classifier embed.FS
|
||||
|
||||
@@ -48,7 +46,6 @@ type Processor struct {
|
||||
Percentage bool
|
||||
Square bool
|
||||
Debug bool
|
||||
Scale bool
|
||||
FaceDetect bool
|
||||
FaceAngle float64
|
||||
PigoFaceDetector *pigo.Pigo
|
||||
@@ -92,6 +89,7 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
if p.NewHeight == 0 {
|
||||
newHeight = p.NewHeight
|
||||
}
|
||||
|
||||
reduce := func() error {
|
||||
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
|
||||
c = NewCarver(width, height)
|
||||
@@ -175,28 +173,26 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
}
|
||||
img = c.RotateImage270(img)
|
||||
} else if newWidth > 0 || newHeight > 0 {
|
||||
// Use this option to rescale the image proportionally prior resizing.
|
||||
// First the image is scaled down preserving the image aspect ratio,
|
||||
// We are trying to rescale the image proportionally prior resizing.
|
||||
// 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.
|
||||
|
||||
// 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
|
||||
}
|
||||
// Scale the width and height by the smaller factor (i.e Min(wScaleFactor, hScaleFactor))
|
||||
// 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))
|
||||
scaleHeight := math.Round(float64(c.Height) / math.Min(wScaleFactor, hScaleFactor))
|
||||
|
||||
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))
|
||||
// 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)
|
||||
|
||||
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.
|
||||
newWidth = int(scaleWidth) - p.NewWidth
|
||||
newHeight = int(scaleHeight) - p.NewHeight
|
||||
@@ -206,13 +202,12 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
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.
|
||||
if newWidth > 0 && newWidth != img.Bounds().Max.X {
|
||||
// Because scaling horizontally and vertically at the same time using the scale option
|
||||
// it might happen that the scaled image exceeds the desired image size, we need to make sure
|
||||
// Because of scaling horizontally and vertically at the same time it might happen
|
||||
// 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.
|
||||
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++ {
|
||||
if err = enlarge(); err != nil {
|
||||
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.
|
||||
if newHeight > 0 && newHeight != img.Bounds().Max.Y {
|
||||
img = c.RotateImage90(img)
|
||||
// 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++ {
|
||||
if err = enlarge(); err != nil {
|
||||
return nil, err
|
||||
|
@@ -22,7 +22,6 @@ func reduceImageH(t *testing.T) {
|
||||
Percentage: false,
|
||||
Square: false,
|
||||
Debug: false,
|
||||
Scale: false,
|
||||
}
|
||||
// Reduce image size horizontally
|
||||
for x := 0; x < newWidth; x++ {
|
||||
@@ -51,7 +50,6 @@ func reduceImageV(t *testing.T) {
|
||||
Percentage: false,
|
||||
Square: false,
|
||||
Debug: false,
|
||||
Scale: false,
|
||||
}
|
||||
// Reduce image size horizontally
|
||||
img = c.RotateImage90(img)
|
||||
|
20
stackblur.go
20
stackblur.go
@@ -7,10 +7,10 @@ import (
|
||||
"image"
|
||||
)
|
||||
|
||||
// blurStack is a linked list containing the color value and a pointer to the next struct.
|
||||
type blurStack struct {
|
||||
// blurstack is a linked list containing the color value and a pointer to the next struct.
|
||||
type blurstack struct {
|
||||
r, g, b, a uint32
|
||||
next *blurStack
|
||||
next *blurstack
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
// 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.
|
||||
// The radius defines the bluring average.
|
||||
func StackBlur(img *image.NRGBA, radius uint32) *image.NRGBA {
|
||||
var stackEnd, stackIn, stackOut *blurStack
|
||||
func (c *Carver) StackBlur(img *image.NRGBA, radius uint32) *image.NRGBA {
|
||||
var stackEnd, stackIn, stackOut *blurstack
|
||||
var width, height = uint32(img.Bounds().Dx()), uint32(img.Bounds().Dy())
|
||||
var (
|
||||
div, widthMinus1, heightMinus1, radiusPlus1, sumFactor uint32
|
||||
@@ -76,12 +71,11 @@ func StackBlur(img *image.NRGBA, radius uint32) *image.NRGBA {
|
||||
radiusPlus1 = radius + 1
|
||||
sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2
|
||||
|
||||
bs := blurStack{}
|
||||
stackStart := bs.NewBlurStack()
|
||||
stackStart := new(blurstack)
|
||||
stack := stackStart
|
||||
|
||||
for i = 1; i < div; i++ {
|
||||
stack.next = bs.NewBlurStack()
|
||||
stack.next = new(blurstack)
|
||||
stack = stack.next
|
||||
if i == radiusPlus1 {
|
||||
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
|
||||
c = c * 256
|
||||
|
||||
for i := 0; i < int(pg.treeNum); i++ {
|
||||
idx := 1
|
||||
if pg.treeNum > 0 {
|
||||
for i := 0; i < int(pg.treeNum); i++ {
|
||||
idx := 1
|
||||
|
||||
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)
|
||||
x2 := ((r+int(pg.treeCodes[root+4*idx+2])*s)>>8)*dim + ((c + int(pg.treeCodes[root+4*idx+3])*s) >> 8)
|
||||
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)
|
||||
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 {
|
||||
if px1 <= px2 {
|
||||
return 1
|
||||
bintest := func(px1, px2 uint8) int {
|
||||
if px1 <= px2 {
|
||||
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] {
|
||||
return -1.0
|
||||
if out <= pg.treeThreshold[i] {
|
||||
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.
|
||||
@@ -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))
|
||||
qcos := s * qCosTable[int(32.0*a)] //s*(256.0*math.Cos(2*math.Pi*a))
|
||||
|
||||
for i := 0; i < int(pg.treeNum); i++ {
|
||||
var idx = 1
|
||||
if pg.treeNum > 0 {
|
||||
for i := 0; i < int(pg.treeNum); i++ {
|
||||
var idx = 1
|
||||
|
||||
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))
|
||||
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))
|
||||
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))
|
||||
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))
|
||||
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))
|
||||
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))
|
||||
|
||||
bintest := func(px1, px2 uint8) int {
|
||||
if px1 <= px2 {
|
||||
return 1
|
||||
bintest := func(px1, px2 uint8) int {
|
||||
if px1 <= px2 {
|
||||
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] {
|
||||
return -1.0
|
||||
if out <= pg.treeThreshold[i] {
|
||||
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
|
||||
|
Reference in New Issue
Block a user