You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tkinter+OpenCV Python程序运行2小时cv.matchTemplate报内存分配失败

问题说明

使用OpenCV 4.6.0与64位Python开发简易游戏BOT,程序设计运行流程如下:

  • 启动tkinter构建的GUI界面
  • 点击Start按钮后启动新进程运行主循环
  • 截取屏幕850x650像素区域保存为PNG文件
  • 将截图加载为OpenCV Mat对象,调用cv.matchTemplate执行图像处理
  • 主循环重复运行,原设计每运行1小时销毁当前进程、启动新进程重复上述流程,目的是规避怀疑存在的OpenCV后台锁内存导致的泄漏问题,靠定时重启重置内存。

程序连续运行约2小时后,Python命令行抛出内存分配失败错误,报错栈指向cv.matchTemplate调用行,但任务管理器显示程序仅占用50MB内存,整机内存使用率仅为51%,不存在整机内存不足的情况。

根因分析
  1. OpenCV接口参数使用错误,直接诱发内存异常
    现有代码中所有cv.imread调用的第二个参数传的是cv.COLOR_BGR2GRAY,这是色彩空间转换码,不是imread支持的文件读取模式。灰度图读取的正确参数是cv.IMREAD_GRAYSCALE,传入错误参数会导致OpenCV内部加载图像时内存处理逻辑异常,是内存泄漏的核心诱因。
  2. 冗余IO流程放大内存开销,导致内存碎片化
    现有截图逻辑是先把截图存到本地PNG文件,再从磁盘读取文件加载为Mat对象,完全多余。这组操作平白增加了两次IO和对应的内存申请释放流程,高频运行下会快速造成进程地址空间的内存碎片化——cv.matchTemplate运行时需要一块连续内存存储匹配结果,碎片化严重时即使总空闲内存足够,找不到符合要求的连续内存块也会报内存分配失败,这也是任务管理器显示内存占用低但报错的直接原因。另外OpenCV C++层申请的部分内存不会被任务管理器统计到Python进程的私有内存中,所以看到的50MB占用不是真实内存使用量。
  3. 定时重启逻辑完全失效,内存泄漏持续累积
    现有startBotProcess逻辑中,ps.join()会阻塞等待子进程主动退出,运行时长判断逻辑写在ps.join()之后,只要主循环不主动退出,重启判断永远不会执行,原本设计的1小时重启机制根本没生效。另外重启时没有显式回收旧进程资源,容易产生僵尸进程残留占住内存。
  4. 内存回收方式无效
    手动del对象加gc.collect()只能回收Python层管理的内存,OpenCV Mat对象的底层内存是C++侧申请的,Python GC无权直接回收,这种写法完全释放不了OpenCV占用的底层内存。
修复方案
  • 修正所有cv.imread的参数,静态模板图仅在进程启动时加载一次,不要在循环中重复加载:
# 静态模板图加载,修正灰度读取参数
hp_img = cv.imread(LOCAL_DIRECTORY + 'hp.png', cv.IMREAD_GRAYSCALE)
worldmap_img = cv.imread(LOCAL_DIRECTORY + 'worldmap.png', cv.IMREAD_GRAYSCALE)
settings_menu = cv.imread(LOCAL_DIRECTORY + 'settings.png', cv.IMREAD_GRAYSCALE)
black_screen = cv.imread(LOCAL_DIRECTORY + 'blackscreen.png', cv.IMREAD_GRAYSCALE)
  • 去掉截图落盘再读取的冗余流程,直接将pyautogui返回的PIL图像转为OpenCV Mat,用完Mat对象显式调用release()释放C++层内存:
import numpy as np

def checkBlackScreen():
    global threshold
    # 直接获取截图,不再落盘存储
    screen_pil = pyautogui.screenshot(region=(0, 0, 850, 650))
    # PIL格式转OpenCV灰度Mat
    screen_img = cv.cvtColor(np.array(screen_pil), cv.COLOR_RGB2GRAY)
    result = cv.matchTemplate(screen_img, black_screen, cv.TM_CCOEFF_NORMED)
    _, max_val, _, max_loc = cv.minMaxLoc(result)

    # 显式释放OpenCV底层内存
    result.release()
    screen_img.release()
    del screen_pil

    if max_val > threshold:
        print("[BAD] Stuck in black screen")
        return True
    print("[Good] Not stuck in black screen : ", max_val)
    return False
  • 修复进程重启逻辑:把子进程对象存为全局变量,在主循环内部每轮检查进程运行时长,满1小时主动退出主循环;重启新进程前,先对旧进程调用terminate()发送终止信号,等待2秒未退出则调用kill()强制销毁,再调用join()回收进程资源,避免僵尸进程残留。
  • 所有OpenCV创建的Mat对象用完后都显式调用.release()方法,不要仅靠Python的GC回收。

内容的提问来源于stack exchange,提问作者Mineta No Cap

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 12:48:15