By wangchaoqiang, 31 August, 2025
Forums

【金山文档 | WPS云文档】 图像分割工具 https://www.kdocs.cn/l/csG1c7YMGmrU

在构建图像识别或语义分割数据集时候,往往会将大疆智图或者其他航拍图像处理软件生成的整张图片分割成适应模型框架的小尺寸照片,用于图片标定构建我们的数据集。

此工具可将图片分割可视化,以软件形式便于操作,利用python即可实现,可手动输入尺寸,输入路径以及输出路径。

 

实现代码:

import os

import tkinter as tk

from tkinter import filedialog, ttk, messagebox

from PIL import Image, ImageTk

import math

 

 

class ImageSplitterApp:

def __init__(self, root):

self.root = root

self.root.title("图片自动分割工具")

self.root.geometry("800x600")

self.root.configure(bg='#f0f0f0')

 

# 变量初始化

self.image_path = None

self.original_image = None

self.tiles = []

self.tile_width = tk.IntVar(value=200)

self.tile_height = tk.IntVar(value=200)

self.output_dir = tk.StringVar(value=os.path.expanduser("~/Desktop"))

self.preview_image = None

 

# 创建UI

self.create_widgets()

 

def create_widgets(self):

# 控制面板

control_frame = tk.Frame(self.root, bg='#e0e0e0', padx=10, pady=10)

control_frame.pack(fill=tk.X, padx=10, pady=10)

 

# 选择图片按钮

tk.Button(control_frame, text="选择图片", command=self.load_image,

bg="#4CAF50", fg="white", font=("Arial", 10, "bold")).grid(row=0, column=0, padx=5, pady=5)

 

# 分辨率设置

tk.Label(control_frame, text="小图宽度:", bg='#e0e0e0').grid(row=0, column=1, padx=5, pady=5)

tk.Entry(control_frame, textvariable=self.tile_width, width=6).grid(row=0, column=2, padx=5, pady=5)

 

tk.Label(control_frame, text="小图高度:", bg='#e0e0e0').grid(row=0, column=3, padx=5, pady=5)

tk.Entry(control_frame, textvariable=self.tile_height, width=6).grid(row=0, column=4, padx=5, pady=5)

 

# 输出目录

tk.Label(control_frame, text="输出目录:", bg='#e0e0e0').grid(row=0, column=5, padx=5, pady=5)

tk.Entry(control_frame, textvariable=self.output_dir, width=30).grid(row=0, column=6, padx=5, pady=5)

tk.Button(control_frame, text="浏览", command=self.select_output_dir).grid(row=0, column=7, padx=5, pady=5)

 

# 处理按钮

tk.Button(control_frame, text="分割图片", command=self.split_image,

bg="#2196F3", fg="white", font=("Arial", 10, "bold")).grid(row=0, column=8, padx=5, pady=5)

 

# 预览区域

preview_frame = tk.Frame(self.root, bg='white')

preview_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))

 

self.canvas = tk.Canvas(preview_frame, bg='white')

self.canvas.pack(fill=tk.BOTH, expand=True)

 

# 状态栏

self.status = tk.StringVar(value="准备就绪")

status_bar = tk.Label(self.root, textvariable=self.status, bd=1, relief=tk.SUNKEN, anchor=tk.W, bg='#e0e0e0')

status_bar.pack(side=tk.BOTTOM, fill=tk.X)

 

def load_image(self):

file_path = filedialog.askopenfilename(

filetypes=[("图像文件", "*.jpg *.jpeg *.png *.bmp *.gif *.tiff")]

)

if file_path:

self.image_path = file_path

try:

self.original_image = Image.open(file_path)

self.status.set(

f"已加载: {os.path.basename(file_path)} - {self.original_image.size[0]}×{self.original_image.size[1]}像素")

self.show_preview()

except Exception as e:

messagebox.showerror("错误", f"无法加载图片: {str(e)}")

 

def select_output_dir(self):

dir_path = filedialog.askdirectory()

if dir_path:

self.output_dir.set(dir_path)

 

def show_preview(self):

if not self.original_image:

return

 

self.canvas.delete("all")

 

# 调整预览大小以适应窗口

canvas_width = self.canvas.winfo_width()

canvas_height = self.canvas.winfo_height()

 

if canvas_width < 10 or canvas_height < 10:

canvas_width = 700

canvas_height = 400

 

img = self.original_image.copy()

img.thumbnail((canvas_width, canvas_height), Image.LANCZOS)

 

self.preview_image = ImageTk.PhotoImage(img)

self.canvas.create_image(

canvas_width // 2,

canvas_height // 2,

image=self.preview_image

)

 

# 绘制网格线预览分割效果

width, height = self.original_image.size

tile_w = self.tile_width.get()

tile_h = self.tile_height.get()

 

if tile_w <= 0 or tile_h <= 0:

return

 

cols = width // tile_w

rows = height // tile_h

 

# 计算缩放比例

scale_x = img.width / width

scale_y = img.height / height

 

# 绘制网格线

for i in range(1, cols):

x = i * tile_w * scale_x

self.canvas.create_line(x, 0, x, canvas_height, fill="red", dash=(4, 4))

 

for i in range(1, rows):

y = i * tile_h * scale_y

self.canvas.create_line(0, y, canvas_width, y, fill="red", dash=(4, 4))

 

# 显示分割信息

self.canvas.create_text(

10, 10,

text=f"原图: {width}×{height}像素 | 分割: {cols}×{rows} = {cols * rows}张小图",

anchor=tk.NW, fill="blue", font=("Arial", 10, "bold")

)

 

def split_image(self):

if not self.original_image:

messagebox.showwarning("警告", "请先选择一张图片")

return

 

tile_w = self.tile_width.get()

tile_h = self.tile_height.get()

 

if tile_w <= 0 or tile_h <= 0:

messagebox.showwarning("警告", "小图尺寸必须大于0")

return

 

width, height = self.original_image.size

cols = width // tile_w

rows = height // tile_h

 

if cols <= 0 or rows <= 0:

messagebox.showwarning("警告", "小图尺寸太大,无法分割")

return

 

output_dir = self.output_dir.get()

if not os.path.exists(output_dir):

try:

os.makedirs(output_dir)

except:

messagebox.showerror("错误", f"无法创建输出目录: {output_dir}")

return

 

# 获取文件名(不含扩展名)

filename = os.path.splitext(os.path.basename(self.image_path))[0]

 

self.tiles = []

progress = ttk.Progressbar(self.root, orient=tk.HORIZONTAL, length=300, mode='determinate')

progress.pack(pady=5)

self.root.update()

 

try:

total_tiles = cols * rows

processed = 0

 

for i in range(rows):

for j in range(cols):

# 计算裁剪区域

left = j * tile_w

upper = i * tile_h

right = left + tile_w

lower = upper + tile_h

 

# 裁剪并保存

tile = self.original_image.crop((left, upper, right, lower))

output_path = os.path.join(output_dir, f"{filename}_{i}_{j}.png")

tile.save(output_path)

 

self.tiles.append(tile)

processed += 1

progress['value'] = (processed / total_tiles) * 100

self.root.update()

 

progress.destroy()

messagebox.showinfo("完成", f"成功分割图片为 {total_tiles} 张小图\n保存到: {output_dir}")

self.status.set(f"分割完成: {total_tiles} 张小图保存到 {output_dir}")

 

except Exception as e:

progress.destroy()

messagebox.showerror("错误", f"分割图片时出错: {str(e)}")

 

def run(self):

self.root.mainloop()

 

 

if __name__ == "__main__":

root = tk.Tk()

app = ImageSplitterApp(root)

app.run()