如何实现通过实时MIDI消息切换窗口图片?(附Python代码问题)
问题与解决方案
问题概述
需要开发程序实现以下功能:
- 从目录读取图片并存储到列表
- 在窗口显示列表中的第一张图片
- 通过USB MIDI控制器的MIDI音符触发同一窗口切换图片(音符值匹配图片列表索引)
当前遇到的问题:
- 单独功能可实现,但整合后matplotlib窗口阻塞,关闭窗口后才会接收MIDI消息
- 使用
plt.show(block=False)时窗口无图片显示,但能正常接收MIDI输入 - 找不到绑定MIDI消息到GUI事件循环的示例
核心问题分析
- Matplotlib的
plt.show()默认会阻塞主线程,导致MIDI监听代码无法执行;block=False时需要手动触发画布刷新并维持事件循环。 - cv2读取的图片是BGR格式,而matplotlib的
imshow默认使用RGB格式,直接显示会导致颜色异常或无图像。 - 手动编写的
while True循环会阻塞Matplotlib的事件循环,导致窗口无法响应更新。
解决方案步骤
- 转换图片颜色空间:将cv2读取的BGR图片转换为RGB格式,确保matplotlib能正确显示。
- 整合MIDI监听与Matplotlib事件循环:使用Matplotlib的
timer定期轮询MIDI消息,避免阻塞主线程。 - 优化图像更新逻辑:更新图像时直接修改已有的
im对象的数组,而非重复创建,减少资源消耗并确保窗口实时刷新。 - 完善初始化流程:确保窗口初始化后立即刷新画布,避免
block=False时无图像的问题。
修正后的完整代码
import matplotlib.pyplot as plt import os import mido import cv2 import glob def get_images(path: str) -> list: """递归遍历目录并加载所有图片(转换为RGB格式)""" images = [] # 匹配常见图片格式,避免加载非图片文件 image_files = glob.glob(os.path.join(path, '*.[jp][pn]g')) + glob.glob(os.path.join(path, '*.png')) + glob.glob(os.path.join(path, '*.bmp')) print(f"加载图片数量: {len(image_files)}") for img_path in image_files: # cv2读取为BGR,转换为RGB后存入列表 img = cv2.imread(img_path) if img is not None: img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) images.append(img_rgb) return images def get_midi_port(): """获取第一个非Through类型的MIDI输入端口""" input_ports = mido.get_input_names() for p in input_ports: if "Midi Through" not in p: print(f"使用MIDI端口: {p}") return p raise Exception("未找到可用的MIDI输入端口") class MIDI_Images(): def __init__(self, path="."): self.path = path self.loaded_images = get_images(self.path) if not self.loaded_images: raise Exception("指定目录下未找到图片") self.curr = 0 # 初始化Matplotlib窗口 self.fig, self.ax = plt.subplots() self.ax.axis('off') # 隐藏坐标轴 # 显示第一张图片 self.im = self.ax.imshow(self.loaded_images[self.curr]) # 刷新画布 self.fig.canvas.draw() # 初始化MIDI端口 self.midi_port = mido.open_input(get_midi_port()) print("开始监听MIDI消息...") # 使用Matplotlib定时器每100ms轮询一次MIDI消息 self.timer = self.fig.canvas.new_timer(interval=100) self.timer.add_callback(self.poll_midi) self.timer.start() # 启动Matplotlib事件循环(非阻塞) plt.show(block=True) def poll_midi(self): """轮询MIDI消息并处理""" for msg in self.midi_port.iter_pending(): # 只处理Note On消息(可根据需求调整) if msg.type == 'note_on' and msg.velocity > 0: print(f"收到MIDI音符: {msg.note}") # 计算图片索引(最低音符24对应索引0) self.curr = msg.note - 24 # 确保索引在图片列表范围内 if 0 <= self.curr < len(self.loaded_images): self.update_image() else: print(f"索引超出范围: {self.curr},图片总数: {len(self.loaded_images)}") def update_image(self): """更新显示的图片""" self.im.set_array(self.loaded_images[self.curr]) self.fig.canvas.draw_idle() # 异步刷新画布 if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description='通过MIDI控制器切换图片显示') parser.add_argument('--imagedir', action="store", dest='imagedir', default='.', help='图片目录路径') args = parser.parse_args() try: MIDI_Images(args.imagedir) except Exception as e: print(f"错误: {e}")
关键改进说明
- 颜色空间转换:添加
cv2.COLOR_BGR2RGB转换,解决cv2读取图片在matplotlib中显示异常的问题。 - MIDI轮询机制:使用Matplotlib定时器替代手动
while True循环,确保GUI事件循环正常运行,窗口可响应更新。 - 图像更新优化:直接修改
im.set_array()并调用draw_idle(),避免重复创建图像对象,提升性能。 - 边界检查:添加图片索引范围检查,防止因无效音符值导致的索引错误。
内容的提问来源于stack exchange,提问作者Korean_Of_the_Mountain
相关产品推荐
相关产品推荐

