hybridgroup / gocv

Go package for computer vision using OpenCV 4 and beyond. Includes support for DNN, CUDA, and OpenCV Contrib.
https://gocv.io
Other
6.62k stars 863 forks source link

Severe Memory Leak During Conversion between Image and gocv.Mat #1159

Open Sclock opened 6 months ago

Sclock commented 6 months ago

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

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

deadprogram commented 6 months ago

Hello @Sclock please see https://github.com/hybridgroup/gocv?tab=readme-ov-file#profiling for explanation.