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

如何实现通过实时MIDI消息切换窗口图片?(附Python代码问题)

问题与解决方案

问题概述

需要开发程序实现以下功能:

  • 从目录读取图片并存储到列表
  • 在窗口显示列表中的第一张图片
  • 通过USB MIDI控制器的MIDI音符触发同一窗口切换图片(音符值匹配图片列表索引)

当前遇到的问题:

  • 单独功能可实现,但整合后matplotlib窗口阻塞,关闭窗口后才会接收MIDI消息
  • 使用plt.show(block=False)时窗口无图片显示,但能正常接收MIDI输入
  • 找不到绑定MIDI消息到GUI事件循环的示例

核心问题分析

  1. Matplotlib的plt.show()默认会阻塞主线程,导致MIDI监听代码无法执行;block=False时需要手动触发画布刷新并维持事件循环。
  2. cv2读取的图片是BGR格式,而matplotlib的imshow默认使用RGB格式,直接显示会导致颜色异常或无图像。
  3. 手动编写的while True循环会阻塞Matplotlib的事件循环,导致窗口无法响应更新。

解决方案步骤

  1. 转换图片颜色空间:将cv2读取的BGR图片转换为RGB格式,确保matplotlib能正确显示。
  2. 整合MIDI监听与Matplotlib事件循环:使用Matplotlib的timer定期轮询MIDI消息,避免阻塞主线程。
  3. 优化图像更新逻辑:更新图像时直接修改已有的im对象的数组,而非重复创建,减少资源消耗并确保窗口实时刷新。
  4. 完善初始化流程:确保窗口初始化后立即刷新画布,避免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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 02:39:54