运行矩形例程并尝试实现动态roi,发生Out of fast frame buffer stack memory

Viewed 76

问题描述


我想通过官方的矩形识别例程来实现稳定识别矩形,用于获得偏移量操作电机。我写了一个简单的动态roi变化,但是一旦运行时间稍微长一些或者标靶离远,就会出现反复丢失检测然后重新计算roi的情况,这时候就会爆内存。问题大概率出在else中频繁的roi修改。请问各位有任何关于追踪上一次识别位置的方法吗?

from machine import UART
from machine import FPIOA
import time
import _thread
import time, os, sys
from libs.PipeLine import PipeLine
from libs.YOLO import YOLO11
from libs.Utils import *
from media.sensor import *
from media.display import *
import os, sys, gc
import ulab.numpy as np
import image
import time
from machine import TOUCH
import utime
import machine
import time, utime
from machine import Pin, UART
import ustruct
import math
import time
import os
import sys
import gc
from media.sensor import *
from media.display import *
from media.media import *
from time import ticks_ms

sensor = None

# ----------------------------------------------------------
# 以下这三个是用于筛选矩形的计算函数

def calculate_area(corner):
    """计算四边形面积"""
    # corner 是 (4, 2) 的坐标数组
    area = 0.0
    n = len(corner)

    for i in range(n):
        x1, y1 = corner[i]
        x2, y2 = corner[(i + 1) % n]
        area += (x1 * y2) - (x2 * y1)

    return abs(area) / 2.0

def calculate_ratio(corner):
    """计算四边形长宽比"""
    x_coords = [corner[0][0], corner[1][0], corner[2][0], corner[3][0]]
    y_coords = [corner[0][1], corner[1][1], corner[2][1], corner[3][1]]

    width = max(x_coords) - min(x_coords)
    height = max(y_coords) - min(y_coords)

    if height == 0:
        return 0

    return width / height

def calculate_center(corner):
    x_coords = [corner[0][0], corner[1][0], corner[2][0], corner[3][0]]
    y_coords = [corner[0][1], corner[1][1], corner[2][1], corner[3][1]]
    cx = (min(x_coords) + max(x_coords)) / 2
    cy = (min(y_coords) + max(y_coords)) / 2
    return cx, cy

def distance(c1, c2):
    return ((c1[0] - c2[0])**2 + (c1[1] - c2[1])**2)**0.5

# ------------------------------------------------------------------

# 以下是矩形检测的函数,动态roi用if else实现,当检测到时,roi以margin为距离套在
# 上一次检测到的矩形上

# 初始化全局变量:上次使用的ROI
# 初始化全局变量(调整适应400×240)
last_roi = [50, 20, 300, 180]  # 初始ROI也要改小
last_threshold = 12000
first_detect = True
last_area = 8000  # 目标面积也变小了

def rect_detect():
    global last_roi, first_detect, last_threshold, last_area, diff_x, diff_y

    img = sensor.snapshot(chn=CAM_CHN_ID_0)
    img_rect = img.binary([(50, 255)])

    rects = img_rect.find_rects(roi=last_roi, threshold=last_threshold)

    if rects is not None and len(rects) > 0:
        valid_data = []

        for rect in rects:
            corner = rect.corners()
            ratio = calculate_ratio(corner)
            area = calculate_area(corner)

            if 1.8 > ratio > 1.2 and area > 2000:  # 面积下限降低
                valid_data.append((rect, corner, area))

        if len(valid_data) > 0:
            min_diff = float('inf')
            best_rect = None
            best_corner = None

            for rect, corner, area in valid_data:
                diff = abs(area - last_area)

                if diff < min_diff:
                    min_diff = diff
                    best_rect = rect
                    best_corner = corner

            if best_rect is not None:
                corner = best_corner
                last_area = calculate_area(corner)
                x_coords = [corner[0][0], corner[1][0], corner[2][0], corner[3][0]]
                y_coords = [corner[0][1], corner[1][1], corner[2][1], corner[3][1]]
                cx = int((min(x_coords) + max(x_coords)) / 2)
                cy = int((min(y_coords) + max(y_coords)) / 2)

                diff_x = cx - 200  # 中心点改成200,120
                diff_y = 120 - cy

                img.draw_circle(cx, cy, 10, color=(0, 255, 0), thickness=5)
                print(last_roi)

                if first_detect:
                    first_detect = False

                margin = 25  # margin也适当减小
                roi_x = min(x_coords) - margin
                roi_y = min(y_coords) - margin
                roi_w = (max(x_coords) - min(x_coords)) + 2 * margin
                roi_h = (max(y_coords) - min(y_coords)) + 2 * margin

                roi_x = max(0, roi_x)
                roi_y = max(0, roi_y)
                roi_w = max(80, min(400, roi_w))   # 最大宽度改成300
                roi_h = max(60, min(240, roi_h))   # 最大高度改成180
                roi_w = min(600 - roi_x, roi_w)    # 边界检查改成400
                roi_h = min(300 - roi_y, roi_h)    # 边界检查改成240

                last_roi = [roi_x, roi_y, roi_w, roi_h]
                last_threshold = int(8000 * (roi_w * roi_h) / (200 * 150))  # 调整基准面积
                last_threshold = max(5000, min(20000, last_threshold))  # 调整阈值范围
            else:
                last_roi[2] = min(600, int(last_roi[2] * 1.1))
                last_roi[3] = min(300, int(last_roi[3] * 1.1))
                last_roi[0] = max(0, (400 - last_roi[2]) // 2)
                last_roi[1] = max(0, (240 - last_roi[3]) // 2)

    else:
        last_roi[2] = min(600, int(last_roi[2] * 1.1))
        last_roi[3] = min(300, int(last_roi[3] * 1.1))
        last_roi[0] = max(0, (400 - last_roi[2]) // 2)
        last_roi[1] = max(0, (240 - last_roi[3]) // 2)

    img.draw_circle(200, 120, 10, color=(245, 135, 235), thickness=3)  # 中心点改成200,120
    img.draw_string_advanced(300, 40, 30, "th:{}".format(last_threshold), color=(255, 0, 0))
    img.draw_string_advanced(50, 40, 30, "fps: {}".format(clock.fps()), color=(255, 0, 0))
    img.draw_rectangle(last_roi[0], last_roi[1], last_roi[2], last_roi[3], (255, 0, 0), 5, False)
    Display.show_image(img)
# ------------------------------------------------------------------------

try:
    gc.collect()
    print("camera_test")

    sensor = Sensor(width=1920,height=1080)
    sensor.reset()


    sensor.set_framesize(width=400,height=240)
    sensor.set_pixformat(Sensor.RGB565)

    Display.init(Display.ST7701, to_ide=True)

    MediaManager.init()    # 初始化媒体管理器


   # 启动 sensor
    sensor.run()
    clock = time.clock()

    while True:

        clock.tick()
        os.exitpoint()

        rect_detect()



except KeyboardInterrupt as e:

    print("用户停止: ", e)


except BaseException as e:

    print(f"异常: {e}")



finally:
    # 停止传感器运行
    if isinstance(sensor, Sensor):
        sensor.stop()
    # 反初始化显示模块
    Display.deinit()
    os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
    time.sleep_ms(100)
    # 释放媒体缓冲区
    MediaManager.deinit()

'''

## 复现步骤
----------
执行函数

## 硬件板卡
----------
庐山派

## 软件版本
----------
CanMV_K230_LCKFB_micropython_v1.3-0-g8dd764f_nncase_v2.9.0

## 其他信息
----------
我尝试过降低摄像头分辨率到720p,限制sensor到30帧,都还是会出现这个问题:(
2 Answers
You're absolutely right! The memory leak is caused by **creating new `img` objects every frame without properly releasing them**. In the K210/MicroPython environment, each `sensor.snapshot()` creates a new image object, and if you don't explicitly free it, memory accumulates.

## Here's the fixed version:

```python
def rect_detect():
    global last_roi, first_detect, last_threshold, last_area, diff_x, diff_y

    img = sensor.snapshot(chn=CAM_CHN_ID_0)
    img_rect = img.binary([(50, 255)])
    
    # 关键修复:使用临时变量并主动释放
    rects = img_rect.find_rects(roi=last_roi, threshold=last_threshold)

    if rects is not None and len(rects) > 0:
        valid_data = []

        for rect in rects:
            corner = rect.corners()
            ratio = calculate_ratio(corner)
            area = calculate_area(corner)

            if 1.8 > ratio > 1.2 and area > 2000:
                valid_data.append((rect, corner, area))
            
            # 释放rect对象引用
            del rect

        if len(valid_data) > 0:
            min_diff = float('inf')
            best_rect = None
            best_corner = None

            for rect, corner, area in valid_data:
                diff = abs(area - last_area)

                if diff < min_diff:
                    min_diff = diff
                    best_rect = rect
                    best_corner = corner

            if best_rect is not None:
                corner = best_corner
                last_area = calculate_area(corner)
                x_coords = [corner[0][0], corner[1][0], corner[2][0], corner[3][0]]
                y_coords = [corner[0][1], corner[1][1], corner[2][1], corner[3][1]]
                cx = int((min(x_coords) + max(x_coords)) / 2)
                cy = int((min(y_coords) + max(y_coords)) / 2)

                diff_x = cx - 200
                diff_y = 120 - cy

                img.draw_circle(cx, cy, 10, color=(0, 255, 0), thickness=5)
                print(last_roi)

                if first_detect:
                    first_detect = False

                margin = 25
                roi_x = min(x_coords) - margin
                roi_y = min(y_coords) - margin
                roi_w = (max(x_coords) - min(x_coords)) + 2 * margin
                roi_h = (max(y_coords) - min(y_coords)) + 2 * margin

                roi_x = max(0, roi_x)
                roi_y = max(0, roi_y)
                roi_w = max(80, min(400, roi_w))
                roi_h = max(60, min(240, roi_h))
                roi_w = min(600 - roi_x, roi_w)
                roi_h = min(300 - roi_y, roi_h)

                last_roi = [roi_x, roi_y, roi_w, roi_h]
                last_threshold = int(8000 * (roi_w * roi_h) / (200 * 150))
                last_threshold = max(5000, min(20000, last_threshold))
                
                # 清理valid_data中的引用
                del valid_data
            else:
                last_roi[2] = min(600, int(last_roi[2] * 1.1))
                last_roi[3] = min(300, int(last_roi[3] * 1.1))
                last_roi[0] = max(0, (400 - last_roi[2]) // 2)
                last_roi[1] = max(0, (240 - last_roi[3]) // 2)
        else:
            # 清理valid_data
            del valid_data

    else:
        last_roi[2] = min(600, int(last_roi[2] * 1.1))
        last_roi[3] = min(300, int(last_roi[3] * 1.1))
        last_roi[0] = max(0, (400 - last_roi[2]) // 2)
        last_roi[1] = max(0, (240 - last_roi[3]) // 2)

    img.draw_circle(200, 120, 10, color=(245, 135, 235), thickness=3)
    img.draw_string_advanced(300, 40, 30, "th:{}".format(last_threshold), color=(255, 0, 0))
    img.draw_string_advanced(50, 40, 30, "fps: {}".format(clock.fps()), color=(255, 0, 0))
    img.draw_rectangle(last_roi[0], last_roi[1], last_roi[2], last_roi[3], (255, 0, 0), 5, False)
    Display.show_image(img)
    
    # 关键修复:主动释放图像内存
    del img_rect
    del img
    gc.collect()  # 可选,但建议周期性调用

More important - fix the main loop with periodic GC:

# 在主循环中添加计数器
frame_count = 0

while True:
    clock.tick()
    os.exitpoint()
    
    rect_detect()
    
    frame_count += 1
    
    # 每30帧强制进行一次垃圾回收
    if frame_count >= 30:
        gc.collect()
        frame_count = 0
    
    # 可选:监控内存使用
    # print("Free memory:", gc.mem_free())

Alternative - More aggressive fix with error handling:

def rect_detect():
    global last_roi, first_detect, last_threshold, last_area, diff_x, diff_y
    
    img = None
    img_rect = None
    
    try:
        img = sensor.snapshot(chn=CAM_CHN_ID_0)
        img_rect = img.binary([(50, 255)])
        rects = img_rect.find_rects(roi=last_roi, threshold=last_threshold)

        # ... 你的处理逻辑 ...

        Display.show_image(img)
        
    except Exception as e:
        print(f"Error in rect_detect: {e}")
        
    finally:
        # 确保释放内存
        if img_rect is not None:
            del img_rect
        if img is not None:
            del img
        # 定期垃圾回收
        gc.collect()

Additional optimizations to reduce memory usage:

# 在文件开头设置更大的GC阈值
gc.threshold(50000)  # 当内存分配超过50KB时自动GC

# 或者禁用自动GC,手动控制(更高效但需要谨慎)
# gc.disable()
# 然后在主循环中手动调用 gc.collect()

Complete main loop with memory monitoring:

try:
    gc.collect()
    print("Free memory at start:", gc.mem_free())
    
    sensor = Sensor(width=1920, height=1080)
    sensor.reset()
    sensor.set_framesize(width=400, height=240)
    sensor.set_pixformat(Sensor.RGB565)
    Display.init(Display.ST7701, to_ide=True)
    MediaManager.init()
    sensor.run()
    clock = time.clock()
    
    frame_count = 0
    
    while True:
        clock.tick()
        os.exitpoint()
        
        rect_detect()
        
        frame_count += 1
        
        # 每20帧强制GC并打印内存状态
        if frame_count >= 20:
            gc.collect()
            free_mem = gc.mem_free()
            print("Frame: {}, Free memory: {} bytes".format(frame_count, free_mem))
            frame_count = 0
            
            # 如果内存太低,主动释放更多
            if free_mem < 100000:  # 少于100KB
                gc.collect()
                time.sleep_ms(10)
                
except KeyboardInterrupt as e:
    print("User stopped: ", e)
except BaseException as e:
    print(f"Exception: {e}")
finally:
    # ... cleanup code ...

The key changes are:

  1. Explicitly delete image objects (del img, del img_rect)
  2. Periodically call gc.collect() in the main loop
  3. Use try-finally blocks to ensure cleanup even on errors
  4. Delete temporary objects like valid_data when done

This should eliminate the memory leak and allow your program to run indefinitely.



参考一下ai的回答吧。。。主要还是要把对象给手动回收了。。

收到,当时我有在尝试用gc.collect来回收垃圾,但是效果几乎为0,不知道为什么

你好,请发一下完整的代码。送去识别的图像大小是多少?

你好!这个问题目前改用cv_lite解决了,但是没法实现动态roi。我稍后会将我完整的代码贴上来。原来的程序我是用width=400,height=240。主函数其实就是一直循环调用这个rect_detect(),用全局变量last_roi来存储动态roi。进入函数,当寻找到矩形后进入判断,符合条件的才画出,单次检测没有找到的的话,增大last_roi,然后进入下一次寻找。问题出在这个动态roi上,当物体离得稍远,就会出现非常高频的丢失找到的循环,这时候就会报错。

我目前更换了最新版本的固件,这个程序还是会继续报错。具体的表现是,上电后第一次运行的时间是比较长的,能坚持一分钟到两分钟(帧数较高情况下),ide停止后,第二次往后运行,几秒后就会弹爆内存然后停止运行。

这个可能需要在使用完图像(Image)之后,对他进行手动的回收,否则就会耗尽内存。