Skip to main content
 首页 » 编程设计

python之用鼠标选择图像区域并记录选择的尺寸

2025年05月04日21soundcode

我希望能够使用鼠标光标将矩形选区拖到程序中显示的图像上,并读取选区的尺寸,以便稍后可以使用它们来裁剪该图像。我如何在 Python 3 中执行此操作?

更新:

假设我这样做:

import tkinter as tk 
from PIL import ImageTk, Image 
 
#This creates the main window of an application 
window = tk.Tk() 
window.title("Join") 
window.geometry("900x900") 
window.configure(background='grey') 
 
path = "Book.jpg" 
 
#Creates a Tkinter-compatible photo image, which can be used everywhere Tkinter expects an image object. 
img = ImageTk.PhotoImage(Image.open(path)) 
 
#The Label widget is a standard Tkinter widget used to display a text or image on the screen. 
panel = tk.Label(window, image = img) 
 
#The Pack geometry manager packs widgets in rows or columns. 
panel.pack(side = "bottom", fill = "both", expand = "yes") 
 
#Start the GUI 
window.mainloop() 

请您参考如下方法:

不幸的是,这是另一种更复杂的方法(因为它完成了您在对我的 first answer 的评论中提到的几件事情)。它使用 tkinter 的矢量图形(不是 PIL 的图像处理)功能对选择之外的区域进行着色,我认为这使它成为重量更轻,也可能更快的方法,因为它不涉及处理相对大量的图像数据和传输它。

最初我试图将阴影外部区域绘制为单个连续多边形,但这没有用,因为 tkinter 不支持这种凹多边形形状,因此绘制了四个无边框矩形取而代之的是——加上一个空的,只有一个边框来勾勒出所选区域的轮廓(下图)。

我借用了一个ActiveState Code » Recipe中使用的一些有趣的想法标题为 Pʏᴛʜᴏɴ Tᴋɪɴᴛᴇʀ Cᴀɴᴠᴀs Rᴇᴄᴛᴀɴɢʟᴇ Sᴇʟᴇᴄᴛɪᴏɴ Bᴏx Sunjay Varma 着。

该代码是面向对象的,希望能使其更易于理解(和扩展)。请注意,您可以通过调用 MousePositionTracker 类实例的 cur_selection() 方法将当前选择矩形作为两个点获取,这样可用于获取执行实际操作所需的信息图像裁剪(可能将涉及使用PIL)。

import tkinter as tk 
from PIL import Image, ImageTk 
 
 
class MousePositionTracker(tk.Frame): 
    """ Tkinter Canvas mouse position widget. """ 
 
    def __init__(self, canvas): 
        self.canvas = canvas 
        self.canv_width = self.canvas.cget('width') 
        self.canv_height = self.canvas.cget('height') 
        self.reset() 
 
        # Create canvas cross-hair lines. 
        xhair_opts = dict(dash=(3, 2), fill='white', state=tk.HIDDEN) 
        self.lines = (self.canvas.create_line(0, 0, 0, self.canv_height, **xhair_opts), 
                      self.canvas.create_line(0, 0, self.canv_width,  0, **xhair_opts)) 
 
    def cur_selection(self): 
        return (self.start, self.end) 
 
    def begin(self, event): 
        self.hide() 
        self.start = (event.x, event.y)  # Remember position (no drawing). 
 
    def update(self, event): 
        self.end = (event.x, event.y) 
        self._update(event) 
        self._command(self.start, (event.x, event.y))  # User callback. 
 
    def _update(self, event): 
        # Update cross-hair lines. 
        self.canvas.coords(self.lines[0], event.x, 0, event.x, self.canv_height) 
        self.canvas.coords(self.lines[1], 0, event.y, self.canv_width, event.y) 
        self.show() 
 
    def reset(self): 
        self.start = self.end = None 
 
    def hide(self): 
        self.canvas.itemconfigure(self.lines[0], state=tk.HIDDEN) 
        self.canvas.itemconfigure(self.lines[1], state=tk.HIDDEN) 
 
    def show(self): 
        self.canvas.itemconfigure(self.lines[0], state=tk.NORMAL) 
        self.canvas.itemconfigure(self.lines[1], state=tk.NORMAL) 
 
    def autodraw(self, command=lambda *args: None): 
        """Setup automatic drawing; supports command option""" 
        self.reset() 
        self._command = command 
        self.canvas.bind("<Button-1>", self.begin) 
        self.canvas.bind("<B1-Motion>", self.update) 
        self.canvas.bind("<ButtonRelease-1>", self.quit) 
 
    def quit(self, event): 
        self.hide()  # Hide cross-hairs. 
        self.reset() 
 
 
class SelectionObject: 
    """ Widget to display a rectangular area on given canvas defined by two points 
        representing its diagonal. 
    """ 
    def __init__(self, canvas, select_opts): 
        # Create attributes needed to display selection. 
        self.canvas = canvas 
        self.select_opts1 = select_opts 
        self.width = self.canvas.cget('width') 
        self.height = self.canvas.cget('height') 
 
        # Options for areas outside rectanglar selection. 
        select_opts1 = self.select_opts1.copy()  # Avoid modifying passed argument. 
        select_opts1.update(state=tk.HIDDEN)  # Hide initially. 
        # Separate options for area inside rectanglar selection. 
        select_opts2 = dict(dash=(2, 2), fill='', outline='white', state=tk.HIDDEN) 
 
        # Initial extrema of inner and outer rectangles. 
        imin_x, imin_y,  imax_x, imax_y = 0, 0,  1, 1 
        omin_x, omin_y,  omax_x, omax_y = 0, 0,  self.width, self.height 
 
        self.rects = ( 
            # Area *outside* selection (inner) rectangle. 
            self.canvas.create_rectangle(omin_x, omin_y,  omax_x, imin_y, **select_opts1), 
            self.canvas.create_rectangle(omin_x, imin_y,  imin_x, imax_y, **select_opts1), 
            self.canvas.create_rectangle(imax_x, imin_y,  omax_x, imax_y, **select_opts1), 
            self.canvas.create_rectangle(omin_x, imax_y,  omax_x, omax_y, **select_opts1), 
            # Inner rectangle. 
            self.canvas.create_rectangle(imin_x, imin_y,  imax_x, imax_y, **select_opts2) 
        ) 
 
    def update(self, start, end): 
        # Current extrema of inner and outer rectangles. 
        imin_x, imin_y,  imax_x, imax_y = self._get_coords(start, end) 
        omin_x, omin_y,  omax_x, omax_y = 0, 0,  self.width, self.height 
 
        # Update coords of all rectangles based on these extrema. 
        self.canvas.coords(self.rects[0], omin_x, omin_y,  omax_x, imin_y), 
        self.canvas.coords(self.rects[1], omin_x, imin_y,  imin_x, imax_y), 
        self.canvas.coords(self.rects[2], imax_x, imin_y,  omax_x, imax_y), 
        self.canvas.coords(self.rects[3], omin_x, imax_y,  omax_x, omax_y), 
        self.canvas.coords(self.rects[4], imin_x, imin_y,  imax_x, imax_y), 
 
        for rect in self.rects:  # Make sure all are now visible. 
            self.canvas.itemconfigure(rect, state=tk.NORMAL) 
 
    def _get_coords(self, start, end): 
        """ Determine coords of a polygon defined by the start and 
            end points one of the diagonals of a rectangular area. 
        """ 
        return (min((start[0], end[0])), min((start[1], end[1])), 
                max((start[0], end[0])), max((start[1], end[1]))) 
 
    def hide(self): 
        for rect in self.rects: 
            self.canvas.itemconfigure(rect, state=tk.HIDDEN) 
 
 
class Application(tk.Frame): 
 
    # Default selection object options. 
    SELECT_OPTS = dict(dash=(2, 2), stipple='gray25', fill='red', 
                          outline='') 
 
    def __init__(self, parent, *args, **kwargs): 
        super().__init__(parent, *args, **kwargs) 
 
        path = "Books.jpg" 
        img = ImageTk.PhotoImage(Image.open(path)) 
        self.canvas = tk.Canvas(root, width=img.width(), height=img.height(), 
                                borderwidth=0, highlightthickness=0) 
        self.canvas.pack(expand=True) 
 
        self.canvas.create_image(0, 0, image=img, anchor=tk.NW) 
        self.canvas.img = img  # Keep reference. 
 
        # Create selection object to show current selection boundaries. 
        self.selection_obj = SelectionObject(self.canvas, self.SELECT_OPTS) 
 
        # Callback function to update it given two points of its diagonal. 
        def on_drag(start, end, **kwarg):  # Must accept these arguments. 
            self.selection_obj.update(start, end) 
 
        # Create mouse position tracker that uses the function. 
        self.posn_tracker = MousePositionTracker(self.canvas) 
        self.posn_tracker.autodraw(command=on_drag)  # Enable callbacks. 
 
 
if __name__ == '__main__': 
 
    WIDTH, HEIGHT = 900, 900 
    BACKGROUND = 'grey' 
    TITLE = 'Image Cropper' 
 
    root = tk.Tk() 
    root.title(TITLE) 
    root.geometry('%sx%s' % (WIDTH, HEIGHT)) 
    root.configure(background=BACKGROUND) 
 
    app = Application(root, background=BACKGROUND) 
    app.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.TRUE) 
    app.mainloop() 

这里有一些图片展示了它的实际效果: