Reduce stitching time

About three times as fast
This commit is contained in:
David Vogel 2019-10-24 03:05:42 +02:00
parent b11ba98db7
commit b53ec0d60b
3 changed files with 61 additions and 46 deletions

View File

@ -20,14 +20,27 @@ type imageTile struct {
offset image.Point // Correction offset of the image, so that it aligns pixel perfect with other images. Determined by image matching. 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. image image.Image // Either a rectangle or an RGBA image. The bounds of this image are determined by the filename.
imageMutex *sync.Mutex imageMutex *sync.RWMutex //
imageUsedFlag bool // Flag signalling, that the image was used recently
} }
func (it *imageTile) GetImage() (*image.RGBA, error) { func (it *imageTile) GetImage() (*image.RGBA, error) {
it.imageMutex.Lock() it.imageMutex.RLock()
defer it.imageMutex.Unlock() // TODO: Use RWMutex
it.imageUsedFlag = true // Race condition may happen on this flag, but doesn't matter here.
// Check if the image is already loaded // 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 { if img, ok := it.image.(*image.RGBA); ok {
return img, nil return img, nil
} }
@ -58,7 +71,10 @@ func (it *imageTile) GetImage() (*image.RGBA, error) {
// Free the image after some time // Free the image after some time
go func() { go func() {
for it.imageUsedFlag {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
it.imageUsedFlag = false
}
it.imageMutex.Lock() it.imageMutex.Lock()
defer it.imageMutex.Unlock() defer it.imageMutex.Unlock()
@ -69,15 +85,15 @@ func (it *imageTile) GetImage() (*image.RGBA, error) {
} }
func (it *imageTile) OffsetBounds() image.Rectangle { func (it *imageTile) OffsetBounds() image.Rectangle {
it.imageMutex.Lock() it.imageMutex.RLock()
defer it.imageMutex.Unlock() // TODO: Use RWMutex defer it.imageMutex.RUnlock()
return it.image.Bounds().Add(it.offset) return it.image.Bounds().Add(it.offset)
} }
func (it *imageTile) Bounds() image.Rectangle { func (it *imageTile) Bounds() image.Rectangle {
it.imageMutex.Lock() it.imageMutex.RLock()
defer it.imageMutex.Unlock() // TODO: Use RWMutex defer it.imageMutex.RUnlock()
return it.image.Bounds() return it.image.Bounds()
} }

View File

@ -68,7 +68,7 @@ func loadImages(path string) ([]imageTile, error) {
imageTiles = append(imageTiles, imageTile{ imageTiles = append(imageTiles, imageTile{
fileName: file, fileName: file,
image: image.Rect(x, y, x+width, y+height), 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 { func (tp tilePairs) Stitch(tiles []imageTile, destImage *image.RGBA) error {
intersectTiles := []*imageTile{} intersectTiles := []*imageTile{}
images := []*image.RGBA{}
// Get only the tiles that intersect with the destination image bounds. // 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. // Ignore alignment here, doesn't matter if an image overlaps a few pixels anyways.
for i, tile := range tiles { for i, tile := range tiles {
if tile.OffsetBounds().Overlaps(destImage.Bounds()) { 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)) 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. // 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 return
} }
func drawMedianBlended(tiles []*imageTile, destImage *image.RGBA) error { func drawMedianBlended(images []*image.RGBA, destImage *image.RGBA) {
bounds := destImage.Bounds() 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 iy := bounds.Min.Y; iy < bounds.Max.Y; iy++ {
for ix := bounds.Min.X; ix < bounds.Max.X; ix++ { 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} point := image.Point{ix, iy}
found := false found := false
// Iterate through all tiles, and create a list of colors. // Iterate through all images and create a list of colors.
for _, tile := range tiles { for _, img := range images {
tilePoint := point.Sub(tile.offset) if point.In(img.Bounds()) {
imageRGBA, err := tile.GetImage() // TODO: Optimize, as it's slow to get tiles and images every pixel col := img.RGBAAt(point.X, point.Y)
if err != nil { rList, gList, bList = append(rList, int(col.R)), append(gList, int(col.G)), append(bList, int(col.B))
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))
found = true 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 { if !found {
continue continue
} }
// Sort rList. // Sort colors.
sort.Slice(rList, func(i, j int) bool { sort.Ints(rList)
return rList[i] < rList[j] sort.Ints(gList)
}) sort.Ints(bList)
// 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]
})
// Take the middle element of each color. // Take the middle element of each color.
var r, g, b uint8 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}) destImage.SetRGBA(ix, iy, color.RGBA{r, g, b, 255})
} }
} }
return nil
} }

View File

@ -23,24 +23,26 @@ func main() {
} }
log.Printf("Got %v tiles", len(tiles)) log.Printf("Got %v tiles", len(tiles))
/*f, err := os.Create("cpu.prof") /*profFile, err := os.Create("cpu.prof")
if err != nil { if err != nil {
log.Panicf("could not create CPU profile: %v", err) log.Panicf("could not create CPU profile: %v", err)
} }
defer f.Close() defer profFile.Close()
if err := pprof.StartCPUProfile(f); err != nil { if err := pprof.StartCPUProfile(profFile); err != nil {
log.Panicf("could not start CPU profile: %v", err) log.Panicf("could not start CPU profile: %v", err)
} }
defer pprof.StopCPUProfile()*/ 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()) log.Printf("Creating output image with a size of %v", outputRect.Size())
outputImage := image.NewRGBA(outputRect) outputImage := image.NewRGBA(outputRect)
log.Printf("Stitching %v tiles into an image at %v", len(tiles), outputImage.Bounds()) log.Printf("Stitching %v tiles into an image at %v", len(tiles), outputImage.Bounds())
tp := make(tilePairs) tp := make(tilePairs)
if err := tp.StitchGrid(tiles, outputImage, 256); err != nil { if err := tp.StitchGrid(tiles, outputImage, 1024); err != nil {
log.Panic(err) log.Panic(err)
} }