如何在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_file | top | bottom | left | right |
|---|---|---|---|---|
| 幻灯片1的图片路径 | 目标1上边界坐标 | 目标1下边界坐标 | 目标1左边界坐标 | 目标1右边界坐标 |
| 幻灯片1的图片路径 | 目标2上边界坐标 | 目标2下边界坐标 | 目标2左边界坐标 | 目标2右边界坐标 |
| 幻灯片2的图片路径 | 目标1上边界坐标 | 目标1下边界坐标 | 目标1左边界坐标 | 目标1右边界坐标 |
内容的提问来源于stack exchange,提问作者new2python
相关产品推荐
相关产品推荐

