python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python图片批量处理

基于Python自制一个图片批量处理工具实现格式统一和大小压缩

作者:Jack电子实验室

在日常工作和生活中,我们经常需要处理大量图片,今天就给大家分享一款自制的Python图片批量处理工具,无需复杂操作,图形化界面一键搞定所有需求

在日常工作和生活中,我们经常需要处理大量图片——比如电商商品图需要统一正方形尺寸、公众号配图要控制在3MB以内、多格式图片需转为JPG统一管理。手动处理几十上百张图效率极低,今天就给大家分享一款自制的Python图片批量处理工具,无需复杂操作,图形化界面一键搞定所有需求!

工具核心功能

这款工具专为批量图片处理设计,整合了4个高频需求,彻底解放双手:

环境准备

1. 基础环境

2. 依赖安装

核心依赖仅需Pillow库(Python图像处理神器),打开命令行执行以下命令:

pip install pillow

如果需要批量安装,也可以创建requirements.txt文件,写入以下内容后执行pip install -r requirements.txt

pillow>=9.0.0

完整代码(可直接复制运行)

import os
import sys
from PIL import Image
import time
from io import BytesIO
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
import threading
import queue

# 打包命令:pyinstaller --noconsole --onefile --icon=icon.ico --name="图片批量处理工具" image_processor.py
class ImageProcessor:
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("图片批量处理工具")
        # 设置窗口大小和位置(居中显示)
        window_width = 800
        window_height = 600
        screen_width = self.window.winfo_screenwidth()
        screen_height = self.window.winfo_screenheight()
        x = (screen_width - window_width) // 2
        y = (screen_height - window_height) // 2
        self.window.geometry(f"{window_width}x{window_height}+{x}+{y}")
        self.window.resizable(False, False)
        
        # 界面样式配置(现代简洁风格)
        self.style = ttk.Style()
        self.style.theme_use('clam')
        self.style.configure("TButton", padding=10, font=('微软雅黑', 10))
        self.style.configure("TLabel", font=('微软雅黑', 10))
        self.style.configure("Header.TLabel", font=('微软雅黑', 16, 'bold'))
        self.style.configure("Info.TLabel", font=('微软雅黑', 9))
        self.style.configure("Custom.TButton", padding=10, font=('微软雅黑', 10, 'bold'))
        
        # 线程通信队列(避免界面卡顿)
        self.message_queue = queue.Queue()
        
        self.create_widgets()
        self.update_messages()
    
    def create_widgets(self):
        # 主框架(统一内边距,布局更整洁)
        main_frame = ttk.Frame(self.window, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 标题区域
        title_frame = ttk.Frame(main_frame)
        title_frame.pack(fill=tk.X, pady=(0, 20))
        title_label = ttk.Label(title_frame, text="图片批量处理工具", style="Header.TLabel")
        title_label.pack(side=tk.LEFT)
        
        # 功能说明区域
        features_frame = ttk.LabelFrame(main_frame, text="功能说明", padding="10")
        features_frame.pack(fill=tk.X, pady=(0, 20))
        features_text = """本工具提供以下功能:
• 将图片转换为正方形(短边两侧填充白色)
• 统一转换为JPG格式,保证最佳图片质量
• 智能控制图片大小在3MB以下
• 自动创建输出文件夹,原图保持不变"""
        features_label = ttk.Label(features_frame, text=features_text, justify=tk.LEFT)
        features_label.pack(pady=5)
        
        # 文件夹选择区域
        folder_frame = ttk.LabelFrame(main_frame, text="选择文件夹", padding="10")
        folder_frame.pack(fill=tk.X, pady=(0, 20))
        self.folder_path = tk.StringVar()
        folder_entry = ttk.Entry(folder_frame, textvariable=self.folder_path, width=70)
        folder_entry.pack(side=tk.LEFT, padx=(0, 10), fill=tk.X, expand=True)
        folder_button = ttk.Button(folder_frame, text="浏览...", command=self.select_folder, style="Custom.TButton")
        folder_button.pack(side=tk.LEFT)
        
        # 操作按钮区域
        control_frame = ttk.Frame(main_frame)
        control_frame.pack(fill=tk.X, pady=(0, 10))
        self.process_button = ttk.Button(control_frame, text="开始处理", 
                                       command=self.start_processing, 
                                       style="Custom.TButton")
        self.process_button.pack(pady=10)
        
        # 进度显示区域
        progress_frame = ttk.LabelFrame(main_frame, text="处理进度", padding="10")
        progress_frame.pack(fill=tk.X, pady=(0, 20))
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(progress_frame, 
                                          variable=self.progress_var, 
                                          maximum=100,
                                          length=300)
        self.progress_bar.pack(fill=tk.X, pady=(5, 10))
        self.progress_label = ttk.Label(progress_frame, text="等待开始...", style="Info.TLabel")
        self.progress_label.pack()
        
        # 处理详情日志区域
        log_frame = ttk.LabelFrame(main_frame, text="处理详情", padding="10")
        log_frame.pack(fill=tk.BOTH, expand=True)
        text_frame = ttk.Frame(log_frame)
        text_frame.pack(fill=tk.BOTH, expand=True)
        self.log_text = tk.Text(text_frame, height=10, width=70, 
                               font=('微软雅黑', 9),
                               wrap=tk.WORD)
        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, 
                                command=self.log_text.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_text.configure(yscrollcommand=scrollbar.set)
        
        # 状态栏
        self.status_label = ttk.Label(main_frame, text="就绪", style="Info.TLabel")
        self.status_label.pack(pady=(10, 0))
    
    def select_folder(self):
        # 选择文件夹并更新路径
        folder_path = filedialog.askdirectory(title="选择包含图片的文件夹")
        if folder_path:
            self.folder_path.set(folder_path)
            self.log_message(f"已选择文件夹: {folder_path}")
            self.status_label.config(text="已选择文件夹,可以开始处理")
    
    def log_message(self, message):
        # 日志信息入队(线程安全)
        self.message_queue.put(message)
    
    def update_messages(self):
        # 实时更新日志显示
        try:
            while True:
                message = self.message_queue.get_nowait()
                self.log_text.insert(tk.END, message + "\n")
                self.log_text.see(tk.END)
                self.log_text.update()
        except queue.Empty:
            pass
        self.window.after(100, self.update_messages)
    
    def start_processing(self):
        # 预处理校验(避免无效操作)
        folder_path = self.folder_path.get()
        if not folder_path:
            messagebox.showerror("错误", "请先选择要处理的文件夹!")
            return
        if not os.path.exists(folder_path):
            messagebox.showerror("错误", "选择的文件夹不存在!")
            return
        
        # 禁用按钮防止重复点击
        self.process_button.state(['disabled'])
        self.status_label.config(text="正在处理中...")
        self.progress_label.config(text="正在准备处理...")
        
        # 开启子线程处理图片(避免界面冻结)
        thread = threading.Thread(target=self.process_images_thread, args=(folder_path,))
        thread.daemon = True
        thread.start()
    
    def process_images_thread(self, input_folder):
        try:
            # 支持的图片格式(覆盖主流格式)
            supported_formats = ['.png', '.jpeg', '.jpg', '.bmp', '.gif', '.webp']
            
            # 创建输出文件夹(原文件夹名+“调整”)
            dir_name = os.path.basename(input_folder)
            output_dir_name = dir_name + "调整"
            parent_dir = os.path.dirname(input_folder)
            output_dir = os.path.join(parent_dir, output_dir_name)
            try:
                os.makedirs(output_dir, exist_ok=True)
            except Exception as e:
                self.log_message(f"创建输出文件夹失败:{str(e)}")
                messagebox.showerror("错误", f"创建输出文件夹失败:{str(e)}")
                return
            
            # 筛选图片文件
            image_files = [f for f in os.listdir(input_folder) 
                         if any(f.lower().endswith(fmt) for fmt in supported_formats)]
            if not image_files:
                self.log_message("没有找到支持的图片文件!")
                messagebox.showinfo("提示", "文件夹中没有找到支持的图片文件!")
                return
            
            # 初始化统计参数
            total_files = len(image_files)
            processed_files = 0
            success_count = 0
            error_count = 0
            size_reduced_count = 0
            total_size_saved = 0
            
            # 日志初始化
            self.log_message(f"\n开始处理图片...")
            self.log_message(f"输入目录: {input_folder}")
            self.log_message(f"输出目录: {output_dir}\n")
            self.log_message(f"共发现 {total_files} 个图片文件\n")
            
            # 批量处理图片
            for filename in image_files:
                file_path = os.path.join(input_folder, filename)
                try:
                    # 更新进度
                    processed_files += 1
                    progress = (processed_files / total_files) * 100
                    self.progress_var.set(progress)
                    self.progress_label.config(
                        text=f"正在处理: {filename} ({processed_files}/{total_files})"
                    )
                    
                    # 打开图片并处理
                    with Image.open(file_path) as img:
                        # 保留EXIF信息(避免图片方向异常)
                        exif = img.info.get('exif')
                        
                        # 处理透明背景(PNG转JPG时填充白色)
                        if img.mode in ('RGBA', 'P'):
                            background = Image.new('RGB', img.size, 'white')
                            if img.mode == 'P':
                                img = img.convert('RGBA')
                            background.paste(img, mask=img.split()[3] if img.mode == 'RGBA' else None)
                            img = background
                        
                        # 转为正方形
                        square_img = self.convert_to_square(img)
                        if square_img is None:
                            continue
                        
                        # 生成输出路径
                        new_filename = os.path.splitext(filename)[0] + '.jpg'
                        new_path = os.path.join(output_dir, new_filename)
                        
                        # 计算大小变化
                        original_size = os.path.getsize(file_path) / (1024 * 1024)
                        
                        # 优化图片大小(控制在3MB内)
                        quality = self.optimize_image_size(square_img)
                        
                        # 保存图片
                        save_args = {'format': 'JPEG', 'quality': quality}
                        if exif:
                            save_args['exif'] = exif
                        square_img.save(new_path, **save_args)
                        
                        # 更新统计信息
                        new_size = os.path.getsize(new_path) / (1024 * 1024)
                        success_count += 1
                        if new_size < original_size:
                            size_reduced_count += 1
                            size_reduction = original_size - new_size
                            total_size_saved += size_reduction
                            self.log_message(f"✓ 已处理: {filename} -> {new_filename}")
                            self.log_message(
                                f"  大小: {original_size:.1f}MB → {new_size:.1f}MB "
                                f"(节省 {size_reduction:.1f}MB)"
                            )
                        else:
                            self.log_message(f"✓ 已处理: {filename} -> {new_filename}")
                            self.log_message(f"  大小: {new_size:.1f}MB")
                
                except Exception as e:
                    error_count += 1
                    self.log_message(f"✗ 处理 {filename} 时出错: {str(e)}")
            
            # 输出处理总结
            summary = f"""\n处理完成!
----------------------------------------
成功处理:{success_count} 个文件
处理失败:{error_count} 个文件
文件大小优化:{size_reduced_count} 个文件
总共节省空间:{total_size_saved:.1f}MB
输出文件夹:{output_dir}
----------------------------------------"""
            self.log_message(summary)
            
            # 弹窗提示结果
            if success_count > 0:
                messagebox.showinfo("完成", 
                    f"图片处理完成!\n成功:{success_count} 个\n失败:{error_count} 个")
            else:
                messagebox.showwarning("警告", "没有成功处理任何文件!")
        
        except Exception as e:
            self.log_message(f"处理过程出错:{str(e)}")
            messagebox.showerror("错误", f"处理过程出错:{str(e)}")
        
        finally:
            # 恢复界面状态
            self.process_button.state(['!disabled'])
            self.progress_var.set(0)
            self.progress_label.config(text="处理完成")
            self.status_label.config(text="就绪")
    
    def convert_to_square(self, image):
        """将图片转为正方形,最大尺寸限制800x800(避免过大)"""
        try:
            width, height = image.size
            max_size = max(width, height)
            
            # 缩放过大图片
            if max_size > 800:
                scale = 800 / max_size
                new_width = int(width * scale)
                new_height = int(height * scale)
                image = image.resize((new_width, new_height), Image.LANCZOS)  # 高质量缩放
                width, height = image.size
                max_size = max(width, height)
            
            # 创建白色正方形背景
            square_img = Image.new('RGB', (max_size, max_size), 'white')
            
            # 居中粘贴原图
            paste_x = (max_size - width) // 2 if height > width else 0
            paste_y = (max_size - height) // 2 if width > height else 0
            square_img.paste(image, (paste_x, paste_y))
            return square_img
        except Exception as e:
            self.log_message(f"转换图片时出错:{str(e)}")
            return None
    
    def get_file_size(self, img, quality):
        """计算指定质量下的图片大小(字节)"""
        buffer = BytesIO()
        img.save(buffer, format='JPEG', quality=quality)
        size = buffer.tell()
        buffer.close()
        return size
    
    def optimize_image_size(self, img, max_size_mb=2.9):
        """二分查找最优质量,确保图片<3MB(留0.1MB冗余)"""
        max_size_bytes = int(max_size_mb * 1024 * 1024)
        quality = 95
        current_size = self.get_file_size(img, quality)
        
        # 原图已达标,直接返回高质量
        if current_size <= max_size_bytes:
            return quality
        
        # 二分查找最优质量
        min_quality = 5
        max_quality = 95
        best_quality = max_quality
        while min_quality <= max_quality:
            quality = (min_quality + max_quality) // 2
            current_size = self.get_file_size(img, quality)
            if current_size > max_size_bytes:
                max_quality = quality - 1
            else:
                best_quality = quality
                min_quality = quality + 1
        
        # 最终校验(确保不超标)
        final_size = self.get_file_size(img, best_quality)
        while final_size > max_size_bytes and best_quality > 5:
            best_quality -= 1
            final_size = self.get_file_size(img, best_quality)
        
        return best_quality
    
    def run(self):
        # 启动GUI
        self.window.mainloop()

def main():
    app = ImageProcessor()
    app.run()

if __name__ == '__main__':
    main()

使用教程(超简单!)

运行程序

将上述代码保存为`image_processor.py

到此这篇关于基于Python自制一个图片批量处理工具实现格式统一和大小压缩的文章就介绍到这了,更多相关Python图片批量处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文