《手写数字识别器》(二)Tkinter

在我们的《手写数字识别》项目中,需要提供一个用于手写数字的绘图板作为用户界面,便于获得用户手写的数字输入算法模型以便能够进行识别。这一章节,我们主要介绍关于绘图板开发过程中用到的相关技术。主要包括:

  1. 画布构建
  2. 图形绘制
  3. 相关事件

1. 画布构建

在 Tkinter 中各种图形的绘制需要在画布上进行,构建画布需要两个步骤:

  • 构建主窗口
  • 构建画布区域
import tkinter as tk


def test():

    # 1. 构建主窗口
    window = tk.Tk()
    window.geometry('800x500+200+300')
    window.resizable(False, False)
    window.title('绘图板')

    # 2. 构建画布区域
    canvas = tk.Canvas()
    canvas.config(bg='blue', width=300, height=200)
    # 铺满整个屏幕
    # canvas.pack(fill=tk.X, expand=True)
    canvas.pack(side=tk.BOTTOM)

    # 启动窗口主循环
    window.mainloop()


if __name__ == '__main__':
    test()

2. 图形绘制

这一部分我们将学习如何在 Canvas 上绘制图形、以及对图形的移动、删除操作,最后再介绍两种用于保存 Canvas 图像的方法。

import tkinter as tk
from PIL import Image
from PIL import ImageTk
from PIL import ImageGrab
from PIL import ImageDraw


# 1. 绘制图形
def test01():
    window = tk.Tk()
    window.geometry('800x500+200+300')
    window.resizable(False, False)
    window.title('绘图板')

    canvas = tk.Canvas()
    canvas.config(bg='black')
    canvas.pack(fill=tk.BOTH, expand=True)


    # 1.1 绘制矩形
    # 需要左上角和右下角坐标
    # fill : 指定矩形填充的颜色
    # outline : 指定边边框的颜色
    # width : 设置边框宽度
    canvas.create_rectangle(50, 50, 150, 150 , fill='blue', outline='red', width=5)

    # 1.2 绘制圆形
    # 需要圆形外矩形左上角、右下角坐标
    # fill : 指定矩形填充的颜色
    # outline : 指定边边框的颜色
    # width : 设置边框宽度
    canvas.create_oval(210, 210, 300, 300, fill='yellow', outline='gray', width=5)

    # 1.3 绘制线段
    # 需要线段开始和结束位置
    # fill :线段颜色
    # width : 线段宽度
    canvas.create_line(300, 100, 550, 150, fill='purple', width=10)

    # 1.4 绘制多边形
    # 需要多边形的多个点坐标
    # fill : 指定矩形填充的颜色
    # outline : 指定边边框的颜色
    # width : 设置边框宽度
    canvas.create_polygon(100, 200, 300, 200, 400, 150, outline='white', fill='red', width=5)

    # 1.5 绘制图像
    # 读取图像数据
    image = Image.open('demo.jpeg')
    # 转换为Tk图像格式
    image = ImageTk.PhotoImage(image)
    # 绘制图像需要指定绘制位置
    # image : 指定要绘制的图像
    # anchor : 指定图像的那个位置对齐到绘制位置
    canvas.create_image(0, 0, image=image, anchor=tk.NW)


    # 启动窗口主循环
    window.mainloop()


# 2. 图形操作
def test02():
    window = tk.Tk()
    window.geometry('800x500+200+300')
    window.resizable(False, False)
    window.title('绘图板')

    canvas = tk.Canvas()
    canvas.config(bg='black')
    canvas.pack(fill=tk.BOTH, expand=True)

    # 绘制矩形
    rect = canvas.create_rectangle(50, 50, 150, 150, fill='blue', outline='red', width=5)
    print('rect', rect)

    # 移动图形
    # 方法一
    # canvas.coords(rect, x1 + 300, y1 + 300, x2 + 300, y2 + 300)
    # 方法二
    canvas.addtag_withtag('rect1', rect)
    canvas.move('rect1', 300, 100)

    # 删除图形
    # canvas.delete('all')  # 删除所有图形
    # canvas.delete(rect)  # 删除指定图形

    # 启动窗口主循环
    window.mainloop()


# 3. 图像存储
def test03():

    window = tk.Tk()
    window.geometry('800x500+200+300')
    window.resizable(False, False)
    window.title('绘图板')
    canvas = tk.Canvas()
    canvas.config(bg='black')
    canvas.pack(fill=tk.BOTH, expand=True)
    rect = canvas.create_rectangle(50, 50, 150, 150, fill='blue', outline='red', width=5)

    # 3.1 截图方式
    def save_image1():
        x = canvas.winfo_rootx()
        y = canvas.winfo_rooty()
        w = x + canvas.winfo_width()
        h = y + canvas.winfo_height()
        screenshot = ImageGrab.grab(bbox=(x, y, w, h))
        screenshot.save('demo01.png')

    button = tk.Button(text='截图保存', command=save_image1)
    button.pack(side=tk.BOTTOM)

    # 3.2 重绘方式
    def save_image2():
        w = canvas.winfo_width()
        h = canvas.winfo_height()
        # 创建一张空的图像对象
        new_image = Image.new('RGB', (w, h), canvas['bg'])
        draw = ImageDraw.Draw(new_image)
        # 绘制图像上所有的对象
        x1, y1, x2, y2 =canvas.coords(rect)
        fill = canvas.itemcget(rect, 'fill')
        width = canvas.itemcget(rect, 'width')
        outline = canvas.itemcget(rect, 'outline')
        draw.rectangle([x1, y1, x2, y2], outline=outline, fill=fill, width=int(float(width)))
        # 存储图像
        new_image.save('demo02.png')

    button = tk.Button(text='重绘保存', command=save_image2)
    button.pack(side=tk.TOP)

    window.mainloop()


if __name__ == '__main__':
    test03()

anchor 参数用于指定对象(例如文本、图像、多边形等)在给定坐标位置下的对齐方式。它控制对象相对于指定的坐标点的位置,类似于对象如何”锚定”到该坐标。anchor 参数的取值可以是字符串,通常为以下值之一:

n:北方(上边界)
s:南方(下边界)
w:西方(左边界)
e:东方(右边界)
nw:西北方(左上角)
ne:东北方(右上角)
sw:西南方(左下角)
se:东南方(右下角)
center:中心

3. 相关事件

<Button-1>:鼠标左键单击事件。
<Button-2>:鼠标中键单击事件。
<Button-3>:鼠标右键单击事件。

<Double-Button-1>:鼠标左键双击事件。
<Double-Button-2>:鼠标中键双击事件。
<Double-Button-3>:鼠标右键双击事件。

<Enter>:鼠标指针进入小部件的事件。
<Leave>:鼠标指针离开小部件的事件。

<KeyPress>:键盘按键按下事件。
<KeyRelease>:键盘按键释放事件。

<Motion>:鼠标移动事件。
<B1-Motion>:鼠标左键拖动
<B3-Motion>:鼠标右键拖动

<ButtonPress-1>: 鼠标左键按下
<ButtonPress-3>: 鼠标右键按下
<ButtonRelease-1>: 鼠标左键释放
<ButtonRelease-3>: 鼠标右键释放
import tkinter as tk


def test01():
    window = tk.Tk()
    window.geometry('800x500+200+300')
    window.resizable(False, False)
    window.title('绘图板')
    canvas = tk.Canvas()
    canvas.config(bg='blue')
    canvas.pack(fill=tk.X, expand=True)

    button = tk.Button(text='按钮')
    button.pack(side=tk.BOTTOM)

    # 绑定事件(要指定给那个对象绑定)
    button.bind('<Button-1>', lambda event: print('按钮左键点击事件'))
    canvas.bind('<Button-3>', lambda event: print('画布右键点击事件'))

    # canvas.bind('<ButtonPress-1>', lambda event: print('鼠标左键按下'))
    # canvas.bind('<ButtonPress-3>', lambda event: print('鼠标右键按下'))
    #
    # canvas.bind('<ButtonRelease-1>', lambda event: print('鼠标左键释放'))
    # canvas.bind('<ButtonRelease-3>', lambda event: print('鼠标右键释放'))

    # canvas.bind('<Double-Button-1>', lambda event: print('画布左键双击事件'))
    # canvas.bind('<Double-Button-3>', lambda event: print('画布右键双击事件'))

    # canvas.bind('<Enter>', lambda event: print('鼠标指针进入画布'))
    # canvas.bind('<Leave>', lambda event: print('鼠标指针离开画布'))

    # canvas.bind('<Motion>', lambda event: print('鼠标指针在画布滑动'))

    # canvas.bind('<B1-Motion>', lambda event: print('鼠标左键按下在画布滑动'))
    # canvas.bind('<B3-Motion>', lambda event: print('鼠标右键按下在画布滑动'))

    window.mainloop()


def test02():
    window = tk.Tk()
    window.geometry('800x500+200+300')
    window.resizable(False, False)
    window.title('绘图板')
    canvas = tk.Canvas()
    canvas.config(bg='black')
    canvas.pack(fill=tk.BOTH, expand=True)

    x, y = None, None
    prev = None
    items = []
    current_tool = 'line'

    def on_mouse_pressed(event):
        nonlocal x, y
        x = event.x
        y = event.y

    def on_mouse_released(event):
        nonlocal prev
        items.append(prev)
        prev = None

    def on_mouse_motion(event):
        temp_x = event.x
        temp_y = event.y
        nonlocal prev
        if prev is not None:
            canvas.delete(prev)

        if current_tool == 'rectangle':
            prev = canvas.create_rectangle(x, y, temp_x, temp_y, outline='white', width=3)

        if current_tool == 'circle':
            prev = canvas.create_oval(x, y, temp_x, temp_y, outline='white', width=3)

        if current_tool == 'line':
            prev = canvas.create_line(x, y, temp_x, temp_y, fill='white', width=3)


    canvas.bind('<B1-Motion>', on_mouse_motion)
    canvas.bind('<ButtonPress-1>', on_mouse_pressed)
    canvas.bind('<ButtonRelease-1>', on_mouse_released)

    toolbar = tk.Frame()
    toolbar.config(bg='red', width=300)

    def save_image():
        from tkinter.filedialog import asksaveasfilename
        from PIL import ImageGrab
        filename = asksaveasfilename()
        if not filename:
            return
        x = canvas.winfo_rootx()
        y = canvas.winfo_rooty()
        w = x + canvas.winfo_width()
        h = y + canvas.winfo_height()
        screenshot = ImageGrab.grab(bbox=(x, y, w, h))
        screenshot.save(filename)

    def draw_rectangle():
        nonlocal current_tool
        current_tool = 'rectangle'

    def draw_circle():
        nonlocal current_tool
        current_tool = 'circle'

    def draw_line():
        nonlocal current_tool
        current_tool = 'line'

    button1 = tk.Button(toolbar, text='矩形', command=draw_rectangle)
    button1.pack(side=tk.LEFT)

    button2 = tk.Button(toolbar, text='圆形', command=draw_circle)
    button2.pack(side=tk.LEFT)

    button3 = tk.Button(toolbar, text='线条', command=draw_line)
    button3.pack(side=tk.LEFT)

    button4 = tk.Button(toolbar, text='清空', command=lambda : canvas.delete('all'))
    button4.pack(side=tk.LEFT)

    button4 = tk.Button(toolbar, text='保存', command=save_image)
    button4.pack(side=tk.LEFT)

    toolbar.pack()

    window.mainloop()


if __name__ == '__main__':
    test02()

未经允许不得转载:一亩三分地 » 《手写数字识别器》(二)Tkinter
评论 (1)

3 + 5 =

  1. avatar
    烬湾03-31 22:00回复

    图形绘制-图像储存
    在进行画布图片保存时,因笔记本屏幕的显示进行了缩放,所以在获取画布的屏幕位置信息时应当进行相应的缩放操作。具体的缩放比可以进入屏幕显示设置中查看。
    def save_image():
    # 获得Canvas对象相对于窗口左上角的一个坐标位置(绘图区域)
    x = canvas.winfo_rootx()*1.5
    y = canvas.winfo_rooty()*1.5
    w = x + canvas.winfo_width()
    h = y + canvas.winfo_height()
    screenshot = ImageGrab.grab(bbox=(x, y, w, h))
    screenshot.save('demo01.png')