gocv icon indicating copy to clipboard operation
gocv copied to clipboard

Severe Memory Leak During Conversion between Image and gocv.Mat

Open Sclock opened this issue 3 months ago • 1 comments

Description

In this issue, it is reported that there is a severe memory leak during the conversion between image.Image and gocv.Mat types, specifically when these conversions are done within a goroutine pool. Despite the fact that all the images are reported as Empty, a significant amount

Steps to Reproduce

package main  
  
import (  
	"fmt"  
	"image"  
	"time"  
  
	"github.com/panjf2000/ants"  
	"gocv.io/x/gocv"  
)  
  
// MatToMat converts a gocv.Mat to another gocv.Mat through an image representation  
func MatToMat(mat gocv.Mat) gocv.Mat {  
	// Convert gocv.Mat to image.Image  
	img, _ := mat.ToImage()  
  
	// Convert image.Image back to gocv.Mat (RGB format)  
	reImg, _ := gocv.ImageToMatRGB(img)  
  
	// Ensure the new gocv.Mat is closed to prevent memory leaks  
	defer reImg.Close()  
  
	return reImg  
}  
  
// ImgToImg converts an image.Image to another image.Image through a gocv.Mat representation  
func ImgToImg(img image.Image) image.Image {  
	// Convert image.Image to gocv.Mat (RGB format)  
	mat, _ := gocv.ImageToMatRGB(img)  
  
	// Convert gocv.Mat back to image.Image  
	reImg, _ := mat.ToImage()  
  
	return reImg  
}  
  
// imgInfo holds an image's gocv.Mat representation and its name  
type imgInfo struct {  
	img  gocv.Mat  
	name string  
}  
  
// sendGocv is a channel used to send imgInfo instances  
var sendGocv = make(chan imgInfo)  
  
// imgdict is a map that stores imgInfo instances  
var imgdict = make(map[string]gocv.Mat)  
  
// poolWait waits for all images to be processed and stored in imgdict  
func poolWait(count int) {  
	for {  
		select {  
		case imginfo := <-sendGocv:  
			imgdict[imginfo.name] = imginfo.img  
			count--  
			// fmt.Println("Remaining", count, "Processed", len(imgdict))  
			if count == 0 {  
				return  
			}  
		}  
	}  
}  
  
// createAndRunPool creates a goroutine pool to run a specified task multiple times  
func createAndRunPool(count, poolSize int, task func(text string)) {  
	p, err := ants.NewPool(poolSize)  
	if err != nil {  
		panic(err)  
	}  
	defer p.Release()  
  
	go func() {  
		for i := 0; i < count; i++ {  
			err = p.Submit(func() {  
				msg := fmt.Sprintf("%d", i)  
				task(msg)  
			})  
			if err != nil {  
				panic(err)  
			}  
		}  
	}()  
	poolWait(count)  
}  
  
func main() {  
	file := "./any_png.png"  
	runCount := 100  
  
	mat := gocv.IMRead(file, gocv.IMReadUnchanged)  
  
	fmt.Println("first")  
	createAndRunPool(runCount, 20, func(text string) {  
		newMat := MatToMat(mat)  
		sendGocv <- imgInfo{newMat, text}  
	})  
	fmt.Println("end")  
  
	time.Sleep(5 * time.Second)  
  
	fmt.Println("Second round")  
	createAndRunPool(runCount, 20, func(text string) {  
		newMat := MatToMat(mat)  
		sendGocv <- imgInfo{newMat, text}  
	})  
	fmt.Println("End")  
  
	time.Sleep(5 * time.Second)  
  
	for _, ig := range imgdict {  
		if ig.Empty() {  
			fmt.Println("Empty!")  
		}  
	}  
}

Your Environment

  • Operating System and version: Linux 5.4.0-126-generic #142-Ubuntu SMP Fri Aug 26 12:12:57 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
  • OpenCV version used: 4.8.1
  • How did you install OpenCV? make build
  • GoCV version used: v0.35.0
  • Go version: 1.22.1
  • Did you run the env.sh or env.cmd script before trying to go run or go build?

If my usage method is incorrect, please tell me the correct usage. Thank you

Sclock avatar Mar 25 '24 18:03 Sclock