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%,不存在整机内存不足的情况。
根因分析
- OpenCV接口参数使用错误,直接诱发内存异常
现有代码中所有cv.imread调用的第二个参数传的是cv.COLOR_BGR2GRAY,这是色彩空间转换码,不是imread支持的文件读取模式。灰度图读取的正确参数是cv.IMREAD_GRAYSCALE,传入错误参数会导致OpenCV内部加载图像时内存处理逻辑异常,是内存泄漏的核心诱因。 - 冗余IO流程放大内存开销,导致内存碎片化
现有截图逻辑是先把截图存到本地PNG文件,再从磁盘读取文件加载为Mat对象,完全多余。这组操作平白增加了两次IO和对应的内存申请释放流程,高频运行下会快速造成进程地址空间的内存碎片化——cv.matchTemplate运行时需要一块连续内存存储匹配结果,碎片化严重时即使总空闲内存足够,找不到符合要求的连续内存块也会报内存分配失败,这也是任务管理器显示内存占用低但报错的直接原因。另外OpenCV C++层申请的部分内存不会被任务管理器统计到Python进程的私有内存中,所以看到的50MB占用不是真实内存使用量。 - 定时重启逻辑完全失效,内存泄漏持续累积
现有startBotProcess逻辑中,ps.join()会阻塞等待子进程主动退出,运行时长判断逻辑写在ps.join()之后,只要主循环不主动退出,重启判断永远不会执行,原本设计的1小时重启机制根本没生效。另外重启时没有显式回收旧进程资源,容易产生僵尸进程残留占住内存。 - 内存回收方式无效
手动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
相关产品推荐
相关产品推荐

