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

如何在Python Tkinter GUI幻灯片的点击选中图像周围绘制边框

Python幻灯片点击图像加边框实现方案

实现效果:点击幻灯片上的任意图像(无论正误),立即在点击的图像周围显示边框,切换幻灯片时自动清空上一页的选中状态。

核心修改说明

  • 新增原始图像缓存机制,每次绘制边框都基于未修改的原始图像生成,不会出现边框叠加残留的问题
  • 调整CSV数据加载逻辑,支持同一张幻灯片存储多个图像的边界框,可匹配所有可点击元素
  • 点击事件触发时自动匹配点击坐标对应的图像边界框,匹配成功后直接绘制边框更新界面

完整可运行代码

import PIL.Image
import PIL.ImageDraw
import tkinter as tk
import PIL.ImageTk
import csv
from PIL import Image

MAX_HEIGHT = 500
# 窗口高度(图像统一缩放到该高度)

class App(tk.Frame):
    def __init__(self, imageData, master=None):
        tk.Frame.__init__(self, master)
        self.clickStatus = tk.StringVar()
        self.loadedImages = dict()
        self.master.title('Slideshow')
        # 存储当前显示的图像引用,避免被GC回收导致图像不显示
        self.current_display_img = None
        
        fram = tk.Frame(self)
        tk.Button(fram, text="上一张", command=self.prev).pack(side=tk.LEFT)
        tk.Button(fram, text="下一张", command=self.next).pack(side=tk.LEFT)
        tk.Label(fram, textvariable=self.clickStatus, font='Helvetica 18 bold').pack(side=tk.RIGHT)
        fram.pack(side=tk.TOP, fill=tk.BOTH)
        
        self.imageLabel = tk.Label(self)
        self.imageData = imageData
        self.currentIndex = 0
        self.__loadImage__()
        self.imageLabel.bind("<Button-1>", self.clicked_evt)
        self.imageLabel.pack()
        self.pack()

    def clicked_evt(self, evt):
        x, y = evt.x, evt.y
        current_img_info = self.loadedImages[self.imageData[self.currentIndex]['image_file']]
        # 遍历当前页所有边界框,匹配点击位置
        selected_bbox = None
        for (t, b, l, r) in current_img_info['scaled_bbox_list']:
            if t <= y <= b and l <= x <= r:
                selected_bbox = (l, t, r, b)
                break
        if not selected_bbox:
            return
        # 基于原始图像复制临时副本绘制边框
        temp_img = current_img_info['original_image'].copy()
        draw = PIL.ImageDraw.Draw(temp_img)
        # 边框颜色和粗细可自行调整,这里用3像素宽的红色边框
        draw.rectangle(selected_bbox, outline="red", width=3)
        # 转换为tkinter支持的图像格式
        if temp_img.mode == "1":
            display_img = PIL.ImageTk.BitmapImage(temp_img, foreground="white")
        else:
            display_img = PIL.ImageTk.PhotoImage(temp_img)
        # 更新界面显示
        self.current_display_img = display_img
        self.imageLabel.config(image=display_img)

    def __loadImage__(self):
        current_img_data = self.imageData[self.currentIndex]
        img_file = current_img_data['image_file']
        if img_file not in self.loadedImages:
            # 加载原始图像
            original_im = PIL.Image.open(img_file)
            ratio = MAX_HEIGHT / original_im.height
            height, width = int(original_im.height * ratio), int(original_im.width * ratio)
            scaled_im = original_im.resize((width, height))
            # 缩放所有边界框坐标
            scaled_bbox_list = []
            for (t, b, l, r) in current_img_data['bbox_list']:
                scaled_t = int(t * ratio)
                scaled_b = int(b * ratio)
                scaled_l = int(l * ratio)
                scaled_r = int(r * ratio)
                scaled_bbox_list.append((scaled_t, scaled_b, scaled_l, scaled_r))
            # 转换为tkinter支持的原始显示图像
            if scaled_im.mode == "1":
                original_display = PIL.ImageTk.BitmapImage(scaled_im, foreground="white")
            else:
                original_display = PIL.ImageTk.PhotoImage(scaled_im)
            # 缓存所有信息
            self.loadedImages[img_file] = {
                'original_image': scaled_im,
                'original_display': original_display,
                'scaled_bbox_list': scaled_bbox_list
            }
        # 加载原始无框图像显示
        original_display = self.loadedImages[img_file]['original_display']
        self.current_display_img = original_display
        self.imageLabel.config(image=original_display, width=original_display.width(), height=original_display.height())

    def prev(self):
        self.currentIndex = (self.currentIndex + len(self.imageData) - 1) % len(self.imageData)
        self.__loadImage__()

    def next(self):
        self.currentIndex = (self.currentIndex + 1) % len(self.imageData)
        self.__loadImage__()

def loadData(fname):
    with open(fname, mode='r', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        rows = [dict(row) for row in reader]
    # 按图片文件合并同一页的所有边界框
    image_data_map = {}
    for row in rows:
        img_file = row['image_file']
        bbox = (int(row['top']), int(row['bottom']), int(row['left']), int(row['right']))
        if img_file not in image_data_map:
            image_data_map[img_file] = {
                'image_file': img_file,
                'bbox_list': []
            }
        image_data_map[img_file]['bbox_list'].append(bbox)
    return list(image_data_map.values())

if __name__ == "__main__":
    # 你的CSV文件需要包含image_file、top、bottom、left、right五个字段,同一个幻灯片的所有图像边界框共用同一个image_file值
    data = loadData('bounding_box.csv')
    app = App(data)
    app.mainloop()

CSV格式说明

你的bounding_box.csv需要按以下格式存储所有图像的边界框信息:

image_filetopbottomleftright
幻灯片1的图片路径目标1上边界坐标目标1下边界坐标目标1左边界坐标目标1右边界坐标
幻灯片1的图片路径目标2上边界坐标目标2下边界坐标目标2左边界坐标目标2右边界坐标
幻灯片2的图片路径目标1上边界坐标目标1下边界坐标目标1左边界坐标目标1右边界坐标

内容的提问来源于stack exchange,提问作者new2python

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 08:39:01