如何逐像素更新2D彩色图不重绘旧像素及解决绘图冻结问题
实时2D电压测量可视化优化问题
我有一台可持续测量电压的仪器,通过调用get_sample_r函数将某一时刻的测量电压存入v_measured_array数组,希望实现实时2D彩色图可视化。以下是我用matplotlib写的尝试代码:
import numpy as np import matplotlib matplotlib.use('TkAgg') import matplotlib.pyplot as plt signal_path="/dev6541/demods/0/sample" v_range= [[0,15], [0,15]] npoints_x=20 npoints_y=20 n_point_x = np.linspace(v_range[0][0], v_range[0][1], npoints_x) n_point_y = np.linspace(v_range[1][0], v_range[1][1], npoints_y) v_measured_array = np.ones(shape=(npoints_x,npoints_y)) plt.show(block=False) fig = plt.figure() for each_x in range(0,npoints_x): for each_y in range(0,npoints_y): v_measured_array[each_x][each_y]= get_sample_r(daq_1, signal_path) ax = fig.add_subplot(111) ax.imshow(v_measured_array, cmap='Greens', interpolation='None') fig.canvas.draw() fig.canvas.flush_events()
原理上代码能运行,但当2D数组较大时,因为每次都要重绘整个v_measured_array,速度会变得极慢。我有两个技术问题:
- 是否可以仅更新新采集的数据点(像素),而非重绘整个数据集来更新绘图?
- 循环结束后绘图总会冻结,我试过多种后端值
['GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo', 'MacOSX', 'nbAgg', 'QtAgg', 'QtCairo', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', 'WebAgg', 'WX', 'WXAgg', 'WXCairo', 'agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template'],都没解决(我用Spyder作为Python IDE)。
问题1解答:仅更新单个像素点,避免全量重绘
可以实现局部更新,核心思路是只初始化一次绘图对象,后续仅修改数据而非重建绘图:
- 把
imshow和子图创建的代码移到循环外,获取对应的AxesImage对象 - 每次更新单个数据点后,调用
set_data()更新图像数据,用draw_idle()替代全量draw(),只重绘需要更新的区域
优化后的核心代码示例:
# 初始化绘图(移到循环外) plt.show(block=False) fig = plt.figure() ax = fig.add_subplot(111) # 初始化图像对象,后续只更新它的数据 im = ax.imshow(v_measured_array, cmap='Greens', interpolation='None') for each_x in range(npoints_x): for each_y in range(npoints_y): # 更新数据数组 v_measured_array[each_x][each_y] = get_sample_r(daq_1, signal_path) # 仅更新图像数据,无需重新调用imshow im.set_data(v_measured_array) # 只重绘需要更新的部分,比draw()更高效 fig.canvas.draw_idle() fig.canvas.flush_events()
如果要进一步提速,还可以:
- 减少
flush_events()的调用频率,比如每采集5个点更新一次,平衡实时性和速度 - 若颜色范围固定,提前用
im.set_clim()锁定范围,避免每次更新时重新计算颜色映射
问题2解答:解决循环结束后绘图冻结的问题
Spyder中绘图冻结的核心原因是循环结束后事件循环未正常运行,或代码存在内存泄漏。可以尝试以下方案:
方案1:循环结束后保留事件循环
在循环结束后添加事件循环处理代码,让绘图窗口保持响应:
# 循环结束后添加 while plt.get_fignums(): fig.canvas.flush_events()
方案2:让Spyder自动管理后端
打开Spyder的工具 > 偏好设置 > IPython控制台 > 图形,将后端设置为Automatic或QtAgg,然后重启Spyder。删除代码中手动指定matplotlib.use()的行,避免和Spyder的内置后端冲突。
方案3:删除循环内重复创建子图的代码
原代码每次循环都调用ax = fig.add_subplot(111),会不断创建新子图导致内存占用飙升,这也是冻结的潜在原因。把创建ax的代码移到循环外,如问题1的优化代码所示。
方案4:改用matplotlib.animation模块
用动画模块管理实时更新,让matplotlib自动处理事件循环,从根源避免冻结:
import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation signal_path="/dev6541/demods/0/sample" v_range= [[0,15], [0,15]] npoints_x=20 npoints_y=20 v_measured_array = np.ones(shape=(npoints_x,npoints_y)) fig, ax = plt.subplots() im = ax.imshow(v_measured_array, cmap='Greens', interpolation='None') # 记录当前要更新的坐标 current_x, current_y = 0, 0 def update(frame): global current_x, current_y # 更新当前点数据 v_measured_array[current_x][current_y] = get_sample_r(daq_1, signal_path) im.set_data(v_measured_array) # 切换到下一个坐标 current_y += 1 if current_y >= npoints_y: current_y = 0 current_x += 1 # 所有点更新完成后停止动画 if current_x >= npoints_x: anim.event_source.stop() return im, # interval控制更新间隔(毫秒),blit=True只更新变化区域 anim = FuncAnimation(fig, update, interval=10, blit=True) plt.show()
内容的提问来源于stack exchange,提问作者Blackwidow
相关产品推荐
相关产品推荐

