From b53ec0d60b55887dd7a60f38ea2ccafc00eef710 Mon Sep 17 00:00:00 2001 From: David Vogel Date: Thu, 24 Oct 2019 03:05:42 +0200 Subject: [PATCH] Reduce stitching time About three times as fast --- bin/stitch/imagetile.go | 34 ++++++++++++++++------ bin/stitch/imagetiles.go | 61 +++++++++++++++++++--------------------- bin/stitch/stitch.go | 12 ++++---- 3 files changed, 61 insertions(+), 46 deletions(-) diff --git a/bin/stitch/imagetile.go b/bin/stitch/imagetile.go index 903109e..3b2d490 100644 --- a/bin/stitch/imagetile.go +++ b/bin/stitch/imagetile.go @@ -19,15 +19,28 @@ type imageTile struct { offset image.Point // Correction offset of the image, so that it aligns pixel perfect with other images. Determined by image matching. - image image.Image // Either a rectangle or an RGBA image. The bounds of this image are determined by the filename. - imageMutex *sync.Mutex + image image.Image // Either a rectangle or an RGBA image. The bounds of this image are determined by the filename. + imageMutex *sync.RWMutex // + imageUsedFlag bool // Flag signalling, that the image was used recently } func (it *imageTile) GetImage() (*image.RGBA, error) { - it.imageMutex.Lock() - defer it.imageMutex.Unlock() // TODO: Use RWMutex + it.imageMutex.RLock() + + it.imageUsedFlag = true // Race condition may happen on this flag, but doesn't matter here. // Check if the image is already loaded + if img, ok := it.image.(*image.RGBA); ok { + it.imageMutex.RUnlock() + return img, nil + } + + it.imageMutex.RUnlock() + // It's possible that the image got changed in between here + it.imageMutex.Lock() + defer it.imageMutex.Unlock() + + // Check again if the image is already loaded if img, ok := it.image.(*image.RGBA); ok { return img, nil } @@ -58,7 +71,10 @@ func (it *imageTile) GetImage() (*image.RGBA, error) { // Free the image after some time go func() { - time.Sleep(5 * time.Second) + for it.imageUsedFlag { + time.Sleep(5 * time.Second) + it.imageUsedFlag = false + } it.imageMutex.Lock() defer it.imageMutex.Unlock() @@ -69,15 +85,15 @@ func (it *imageTile) GetImage() (*image.RGBA, error) { } func (it *imageTile) OffsetBounds() image.Rectangle { - it.imageMutex.Lock() - defer it.imageMutex.Unlock() // TODO: Use RWMutex + it.imageMutex.RLock() + defer it.imageMutex.RUnlock() return it.image.Bounds().Add(it.offset) } func (it *imageTile) Bounds() image.Rectangle { - it.imageMutex.Lock() - defer it.imageMutex.Unlock() // TODO: Use RWMutex + it.imageMutex.RLock() + defer it.imageMutex.RUnlock() return it.image.Bounds() } diff --git a/bin/stitch/imagetiles.go b/bin/stitch/imagetiles.go index d52b37f..ab7b747 100644 --- a/bin/stitch/imagetiles.go +++ b/bin/stitch/imagetiles.go @@ -68,7 +68,7 @@ func loadImages(path string) ([]imageTile, error) { imageTiles = append(imageTiles, imageTile{ fileName: file, image: image.Rect(x, y, x+width, y+height), - imageMutex: &sync.Mutex{}, + imageMutex: &sync.RWMutex{}, }) } @@ -206,12 +206,21 @@ func (tp tilePairs) AlignTiles(tiles []*imageTile) error { func (tp tilePairs) Stitch(tiles []imageTile, destImage *image.RGBA) error { intersectTiles := []*imageTile{} + images := []*image.RGBA{} // Get only the tiles that intersect with the destination image bounds. // Ignore alignment here, doesn't matter if an image overlaps a few pixels anyways. for i, tile := range tiles { if tile.OffsetBounds().Overlaps(destImage.Bounds()) { - intersectTiles = append(intersectTiles, &tiles[i]) + tilePtr := &tiles[i] + intersectTiles = append(intersectTiles, tilePtr) + img, err := tilePtr.GetImage() + if err != nil { + return fmt.Errorf("Couldn't get image: %w", err) + } + imgCopy := *img + imgCopy.Rect = imgCopy.Rect.Add(tile.offset).Inset(4) // Reduce image bounds by 4 pixels on each side, because otherwise there will be artifacts. + images = append(images, &imgCopy) } } @@ -233,7 +242,9 @@ func (tp tilePairs) Stitch(tiles []imageTile, destImage *image.RGBA) error { drawLabel(destImage, intersectTile.image.Bounds().Min.X, intersectTile.image.Bounds().Min.Y, fmt.Sprintf("%v", intersectTile.fileName)) }*/ - return drawMedianBlended(intersectTiles, destImage) + drawMedianBlended(images, destImage) + + return nil } // StitchGrid calls stitch, but divides the workload into a grid of chunks. @@ -275,48 +286,36 @@ func (tp tilePairs) StitchGrid(tiles []imageTile, destImage *image.RGBA, gridSiz return } -func drawMedianBlended(tiles []*imageTile, destImage *image.RGBA) error { +func drawMedianBlended(images []*image.RGBA, destImage *image.RGBA) { bounds := destImage.Bounds() + // Create arrays to be reused every pixel + rListEmpty, gListEmpty, bListEmpty := make([]int, 0, len(images)), make([]int, 0, len(images)), make([]int, 0, len(images)) + for iy := bounds.Min.Y; iy < bounds.Max.Y; iy++ { for ix := bounds.Min.X; ix < bounds.Max.X; ix++ { - rList, gList, bList := []int16{}, []int16{}, []int16{} + rList, gList, bList := rListEmpty, gListEmpty, bListEmpty point := image.Point{ix, iy} found := false - // Iterate through all tiles, and create a list of colors. - for _, tile := range tiles { - tilePoint := point.Sub(tile.offset) - imageRGBA, err := tile.GetImage() // TODO: Optimize, as it's slow to get tiles and images every pixel - if err != nil { - return fmt.Errorf("Couldn't load image: %w", err) - } - if tilePoint.In(imageRGBA.Bounds().Inset(4)) { // Reduce image bounds by 4 pixels on each side, because otherwise there will be artifacts. - col := imageRGBA.RGBAAt(tilePoint.X, tilePoint.Y) - rList, gList, bList = append(rList, int16(col.R)), append(gList, int16(col.G)), append(bList, int16(col.B)) + // Iterate through all images and create a list of colors. + for _, img := range images { + if point.In(img.Bounds()) { + col := img.RGBAAt(point.X, point.Y) + rList, gList, bList = append(rList, int(col.R)), append(gList, int(col.G)), append(bList, int(col.B)) found = true } } - // If there were no tiles to get data from, ignore the pixel. + // If there were no images to get data from, ignore the pixel. if !found { continue } - // Sort rList. - sort.Slice(rList, func(i, j int) bool { - return rList[i] < rList[j] - }) - - // Sort gList. - sort.Slice(gList, func(i, j int) bool { - return gList[i] < gList[j] - }) - - // Sort bList. - sort.Slice(bList, func(i, j int) bool { - return bList[i] < bList[j] - }) + // Sort colors. + sort.Ints(rList) + sort.Ints(gList) + sort.Ints(bList) // Take the middle element of each color. var r, g, b uint8 @@ -345,6 +344,4 @@ func drawMedianBlended(tiles []*imageTile, destImage *image.RGBA) error { destImage.SetRGBA(ix, iy, color.RGBA{r, g, b, 255}) } } - - return nil } diff --git a/bin/stitch/stitch.go b/bin/stitch/stitch.go index 7e25b55..3f691ab 100644 --- a/bin/stitch/stitch.go +++ b/bin/stitch/stitch.go @@ -23,24 +23,26 @@ func main() { } log.Printf("Got %v tiles", len(tiles)) - /*f, err := os.Create("cpu.prof") + /*profFile, err := os.Create("cpu.prof") if err != nil { log.Panicf("could not create CPU profile: %v", err) } - defer f.Close() - if err := pprof.StartCPUProfile(f); err != nil { + defer profFile.Close() + if err := pprof.StartCPUProfile(profFile); err != nil { log.Panicf("could not start CPU profile: %v", err) } defer pprof.StopCPUProfile()*/ - outputRect := image.Rect(-10000, -10000, 10000, 10000) + // TODO: Flags / Program arguments + + outputRect := image.Rect(-35000, -35000, 35000, 35000) log.Printf("Creating output image with a size of %v", outputRect.Size()) outputImage := image.NewRGBA(outputRect) log.Printf("Stitching %v tiles into an image at %v", len(tiles), outputImage.Bounds()) tp := make(tilePairs) - if err := tp.StitchGrid(tiles, outputImage, 256); err != nil { + if err := tp.StitchGrid(tiles, outputImage, 1024); err != nil { log.Panic(err) }