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

@ -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()
}

View File

@ -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
}

View File

@ -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)
}