Streamlit + OpenCV: Memory leak (~100 MB/min)

Hi everyone,

I’m running into a serious issue when using Streamlit with OpenCV to display RTSP video streams. The memory usage of the Streamlit process grows steadily — about 100 MB per minute — and it never goes down, even after stopping the stream.

Some key observations:

  • If I display the same RTSP stream with cv2.imshow(), there is no memory leak.

  • Multiprocessing is not the cause — I tested a minimal version with just OpenCV and Streamlit.

  • The issue seems to appear specifically when calling st.image / box.image(...).


Example code (close to real usage, but simplified):

# app.py
import cv2
import time
import streamlit as st
from typing import Optional

class RTSPApp:
    def __init__(self):
        st.set_page_config(page_title="RTSP Memory Leak Test", layout="wide")
        st.title("RTSP Memory Leak Test")

        st.session_state.setdefault("rtsp_started", False)
        st.session_state.setdefault("cap", None)
        st.session_state.setdefault("last_url", "")

    def run(self):
        url = st.text_input("RTSP URL", "rtsp://...")
        start = st.button("▶️ Start")
        stop = st.button("⏹️ Stop")

        if start:
            self.start_stream(url)
        if stop:
            self.stop_stream()

        if st.session_state["rtsp_started"]:
            self.loop()

    def start_stream(self, url: str):
        url = (url or "").strip()
        if not url:
            st.warning("URL is empty.")
            return
        if st.session_state["rtsp_started"] and st.session_state["last_url"] != url:
            self.stop_stream()
        if not st.session_state["rtsp_started"]:
            cap = cv2.VideoCapture(url)
            if not cap.isOpened():
                st.error("Failed to open RTSP stream.")
                return
            st.session_state["cap"] = cap
            st.session_state["last_url"] = url
            st.session_state["rtsp_started"] = True

    def stop_stream(self):
        cap = st.session_state.get("cap")
        if cap is not None:
            cap.release()
        st.session_state["cap"] = None
        st.session_state["rtsp_started"] = False
        st.session_state["last_url"] = ""

    def loop(self):
        box = st.empty()
        try:
            while st.session_state["rtsp_started"]:
                cap: Optional[cv2.VideoCapture] = st.session_state.get("cap")
                if cap is None:
                    break
                ret, frame = cap.read()
                if not ret:
                    break
                # Memory keeps growing here
                box.image(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB), channels="RGB")
                time.sleep(0.03)
        except Exception as e:
            st.error(f"Loop error: {e}")

if __name__ == "__main__":
    RTSPApp().run()


What I observe:

  • Memory grows at ~100 MB per minute.

  • Stopping the stream does not release memory.

  • With plain OpenCV (cv2.imshow), memory stays stable.


Questions:

  1. Is this a known issue with st.image (maybe due to caching or history of frames)?

  2. Is there a way to explicitly free memory after calling box.image(...)?

  3. Would a different approach (e.g., st.video with MJPEG stream) be recommended to avoid leaks?

Thanks in advance for any insights or workarounds!


I have been seeing reports like yours for as long as I can remember. Objects referenced by widgets seem to be freed only when the app reruns.

Alternative approaches that may or may not fit your use case are:

  • Create a video stream that can be passed to st.video.
  • Make the app rerun for each frame.