批量修改图片对比对并保存脚本

wen 实用脚本 1

本文目录导读:

批量修改图片对比对并保存脚本

  1. Python脚本 - 批量图片处理与对比
  2. 命令行版本(进阶版)
  3. 使用示例
  4. 功能特点

我来为您创建一个批量修改图片的脚本,支持对比查看和保存功能。

Python脚本 - 批量图片处理与对比

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量图片修改、对比与保存工具
"""
import os
import sys
from PIL import Image, ImageEnhance, ImageFilter, ImageOps
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
from pathlib import Path
import threading
import shutil
class ImageBatchProcessor:
    def __init__(self, root):
        self.root = root
        self.root.title("批量图片处理与对比工具")
        self.root.geometry("1200x800")
        # 变量
        self.input_folder = tk.StringVar()
        self.output_folder = tk.StringVar()
        self.image_files = []
        self.current_index = 0
        self.original_image = None
        self.processed_image = None
        self.modified_count = 0
        # 处理参数
        self.brightness = tk.DoubleVar(value=1.0)
        self.contrast = tk.DoubleVar(value=1.0)
        self.saturation = tk.DoubleVar(value=1.0)
        self.sharpness = tk.DoubleVar(value=1.0)
        self.rotation = tk.IntVar(value=0)
        self.resize_width = tk.IntVar(value=0)
        self.resize_height = tk.IntVar(value=0)
        self.quality = tk.IntVar(value=95)
        self.format_var = tk.StringVar(value="JPEG")
        self.setup_ui()
    def setup_ui(self):
        """设置用户界面"""
        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        # 文件夹选择区域
        folder_frame = ttk.LabelFrame(main_frame, text="文件夹选择", padding="10")
        folder_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        ttk.Label(folder_frame, text="输入文件夹:").grid(row=0, column=0, sticky=tk.W)
        ttk.Entry(folder_frame, textvariable=self.input_folder, width=50).grid(row=0, column=1, padx=5)
        ttk.Button(folder_frame, text="浏览", command=self.select_input_folder).grid(row=0, column=2)
        ttk.Label(folder_frame, text="输出文件夹:").grid(row=1, column=0, sticky=tk.W, pady=5)
        ttk.Entry(folder_frame, textvariable=self.output_folder, width=50).grid(row=1, column=1, padx=5)
        ttk.Button(folder_frame, text="浏览", command=self.select_output_folder).grid(row=1, column=2)
        # 图片显示区域
        display_frame = ttk.Frame(main_frame)
        display_frame.grid(row=1, column=0, columnspan=2, pady=10)
        # 原图显示
        original_frame = ttk.LabelFrame(display_frame, text="原图", padding="5")
        original_frame.pack(side=tk.LEFT, padx=5)
        self.original_label = ttk.Label(original_frame)
        self.original_label.pack()
        # 处理后图片显示
        processed_frame = ttk.LabelFrame(display_frame, text="处理后", padding="5")
        processed_frame.pack(side=tk.LEFT, padx=5)
        self.processed_label = ttk.Label(processed_frame)
        self.processed_label.pack()
        # 处理参数区域
        control_frame = ttk.LabelFrame(main_frame, text="处理参数", padding="10")
        control_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=5)
        # 基础调整
        self.create_slider(control_frame, "亮度", self.brightness, 0, 2, 0, 1)
        self.create_slider(control_frame, "对比度", self.contrast, 0, 2, 1, 1)
        self.create_slider(control_frame, "饱和度", self.saturation, 0, 2, 2, 1)
        self.create_slider(control_frame, "锐度", self.sharpness, 0, 2, 3, 1)
        # 旋转
        ttk.Label(control_frame, text="旋转角度:").grid(row=4, column=0, sticky=tk.W)
        ttk.Spinbox(control_frame, from_=-180, to=180, textvariable=self.rotation, 
                   width=10, command=self.update_preview).grid(row=4, column=1, sticky=tk.W)
        # 尺寸调整
        ttk.Label(control_frame, text="宽度:").grid(row=5, column=0, sticky=tk.W)
        ttk.Entry(control_frame, textvariable=self.resize_width, width=10).grid(row=5, column=1, sticky=tk.W)
        ttk.Label(control_frame, text="高度:").grid(row=6, column=0, sticky=tk.W)
        ttk.Entry(control_frame, textvariable=self.resize_height, width=10).grid(row=6, column=1, sticky=tk.W)
        # 输出设置
        output_frame = ttk.LabelFrame(main_frame, text="输出设置", padding="10")
        output_frame.grid(row=2, column=1, sticky=(tk.W, tk.E), pady=5)
        ttk.Label(output_frame, text="格式:").grid(row=0, column=0, sticky=tk.W)
        format_combo = ttk.Combobox(output_frame, textvariable=self.format_var, width=15)
        format_combo['values'] = ('JPEG', 'PNG', 'BMP', 'TIFF', 'WEBP')
        format_combo.grid(row=0, column=1, padx=5)
        ttk.Label(output_frame, text="质量:").grid(row=1, column=0, sticky=tk.W)
        ttk.Scale(output_frame, from_=1, to=100, variable=self.quality, 
                 orient=tk.HORIZONTAL, command=self.update_preview).grid(row=1, column=1, padx=5)
        ttk.Label(output_frame, textvariable=self.quality).grid(row=1, column=2)
        # 按钮区域
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=3, column=0, columnspan=2, pady=10)
        ttk.Button(button_frame, text="加载图片", command=self.load_images).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="上一张", command=self.prev_image).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="下一张", command=self.next_image).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="保存当前", command=self.save_current).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="批量处理所有", command=self.batch_process).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="应用参数", command=self.update_preview).pack(side=tk.LEFT, padx=5)
        # 状态显示
        self.status_label = ttk.Label(main_frame, text="就绪")
        self.status_label.grid(row=4, column=0, columnspan=2, pady=5)
        # 进度条
        self.progress = ttk.Progressbar(main_frame, mode='determinate')
        self.progress.grid(row=5, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
    def create_slider(self, parent, label, variable, min_val, max_val, row, col):
        """创建滑块控件"""
        ttk.Label(parent, text=label).grid(row=row, column=col, sticky=tk.W)
        slider = ttk.Scale(parent, from_=min_val, to=max_val, variable=variable, 
                          orient=tk.HORIZONTAL, command=self.update_preview)
        slider.grid(row=row, column=col+1, padx=5, sticky=(tk.W, tk.E))
        ttk.Label(parent, textvariable=variable).grid(row=row, column=col+2)
        return slider
    def select_input_folder(self):
        """选择输入文件夹"""
        folder = filedialog.askdirectory()
        if folder:
            self.input_folder.set(folder)
    def select_output_folder(self):
        """选择输出文件夹"""
        folder = filedialog.askdirectory()
        if folder:
            self.output_folder.set(folder)
    def load_images(self):
        """加载图片文件"""
        if not self.input_folder.get():
            messagebox.showerror("错误", "请先选择输入文件夹")
            return
        # 支持的图片格式
        extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp')
        self.image_files = []
        input_path = Path(self.input_folder.get())
        for ext in extensions:
            self.image_files.extend(list(input_path.glob(f'*{ext}')))
            self.image_files.extend(list(input_path.glob(f'*{ext.upper()}')))
        if not self.image_files:
            messagebox.showwarning("警告", "没有找到图片文件")
            return
        self.image_files.sort()
        self.current_index = 0
        # 自动创建输出文件夹
        if not self.output_folder.get():
            output_path = input_path / "output"
            output_path.mkdir(exist_ok=True)
            self.output_folder.set(str(output_path))
        self.modified_count = 0
        self.show_current_image()
        self.update_status(f"已加载 {len(self.image_files)} 张图片")
    def show_current_image(self):
        """显示当前图片"""
        if not self.image_files:
            return
        try:
            image_path = self.image_files[self.current_index]
            self.original_image = Image.open(image_path)
            self.update_preview()
            # 更新标题
            self.root.title(f"批量图片处理 - {image_path.name} ({self.current_index + 1}/{len(self.image_files)})")
        except Exception as e:
            messagebox.showerror("错误", f"无法加载图片: {e}")
    def update_preview(self, *args):
        """更新预览"""
        if self.original_image is None:
            return
        try:
            # 复制原图
            img = self.original_image.copy()
            # 应用调整
            img = ImageEnhance.Brightness(img).enhance(self.brightness.get())
            img = ImageEnhance.Contrast(img).enhance(self.contrast.get())
            img = ImageEnhance.Color(img).enhance(self.saturation.get())
            img = ImageEnhance.Sharpness(img).enhance(self.sharpness.get())
            # 旋转
            if self.rotation.get() != 0:
                img = img.rotate(self.rotation.get(), expand=True)
            # 调整尺寸
            if self.resize_width.get() > 0 and self.resize_height.get() > 0:
                img = img.resize((self.resize_width.get(), self.resize_height.get()), 
                               Image.Resampling.LANCZOS)
            self.processed_image = img
            # 显示缩略图
            display_size = (400, 400)
            original_thumb = self.original_image.copy()
            original_thumb.thumbnail(display_size, Image.Resampling.LANCZOS)
            processed_thumb = img.copy()
            processed_thumb.thumbnail(display_size, Image.Resampling.LANCZOS)
            # 转换为PhotoImage并显示
            from io import BytesIO
            # 原图
            bio_original = BytesIO()
            original_thumb.save(bio_original, format='PNG')
            self.original_photo = tk.PhotoImage(data=bio_original.getvalue())
            self.original_label.config(image=self.original_photo)
            # 处理后
            bio_processed = BytesIO()
            processed_thumb.save(bio_processed, format='PNG')
            self.processed_photo = tk.PhotoImage(data=bio_processed.getvalue())
            self.processed_label.config(image=self.processed_photo)
        except Exception as e:
            print(f"预览更新错误: {e}")
    def prev_image(self):
        """上一张图片"""
        if self.image_files and self.current_index > 0:
            self.current_index -= 1
            self.show_current_image()
    def next_image(self):
        """下一张图片"""
        if self.image_files and self.current_index < len(self.image_files) - 1:
            self.current_index += 1
            self.show_current_image()
    def save_current(self):
        """保存当前处理的图片"""
        if self.processed_image is None:
            messagebox.showwarning("警告", "没有要保存的图片")
            return
        if not self.output_folder.get():
            messagebox.showerror("错误", "请先选择输出文件夹")
            return
        try:
            # 生成输出文件名
            input_path = self.image_files[self.current_index]
            output_name = f"modified_{input_path.stem}.{self.format_var.get().lower()}"
            output_path = Path(self.output_folder.get()) / output_name
            # 保存图片
            save_kwargs = {}
            if self.format_var.get() in ['JPEG', 'WEBP']:
                save_kwargs['quality'] = self.quality.get()
            self.processed_image.save(output_path, format=self.format_var.get(), **save_kwargs)
            self.modified_count += 1
            self.update_status(f"已保存: {output_name} (总计: {self.modified_count})")
            messagebox.showinfo("成功", f"图片已保存到:\n{output_path}")
        except Exception as e:
            messagebox.showerror("错误", f"保存失败: {e}")
    def batch_process(self):
        """批量处理所有图片"""
        if not self.image_files:
            messagebox.showwarning("警告", "没有加载图片")
            return
        if not self.output_folder.get():
            messagebox.showerror("错误", "请先选择输出文件夹")
            return
        # 确认对话框
        if not messagebox.askyesno("确认", f"确定要批量处理 {len(self.image_files)} 张图片吗?"):
            return
        # 在新线程中处理
        thread = threading.Thread(target=self._batch_process_thread)
        thread.start()
    def _batch_process_thread(self):
        """批量处理线程"""
        total = len(self.image_files)
        self.progress['maximum'] = total
        for i, img_path in enumerate(self.image_files):
            try:
                # 处理图片
                img = self._process_image(img_path)
                # 保存
                output_name = f"modified_{img_path.stem}.{self.format_var.get().lower()}"
                output_path = Path(self.output_folder.get()) / output_name
                save_kwargs = {}
                if self.format_var.get() in ['JPEG', 'WEBP']:
                    save_kwargs['quality'] = self.quality.get()
                img.save(output_path, format=self.format_var.get(), **save_kwargs)
                # 更新进度
                self.root.after(0, lambda: self.progress.step(1))
                self.root.after(0, lambda: self.update_status(f"处理中: {i+1}/{total}"))
            except Exception as e:
                print(f"处理图片失败 {img_path.name}: {e}")
        self.root.after(0, lambda: self.update_status(f"批量处理完成! 共处理 {total} 张图片"))
        self.root.after(0, lambda: messagebox.showinfo("完成", f"批量处理完成!\n共处理 {total} 张图片"))
    def _process_image(self, img_path):
        """处理单张图片"""
        img = Image.open(img_path)
        # 应用调整
        img = ImageEnhance.Brightness(img).enhance(self.brightness.get())
        img = ImageEnhance.Contrast(img).enhance(self.contrast.get())
        img = ImageEnhance.Color(img).enhance(self.saturation.get())
        img = ImageEnhance.Sharpness(img).enhance(self.sharpness.get())
        # 旋转
        if self.rotation.get() != 0:
            img = img.rotate(self.rotation.get(), expand=True)
        # 调整尺寸
        if self.resize_width.get() > 0 and self.resize_height.get() > 0:
            img = img.resize((self.resize_width.get(), self.resize_height.get()), 
                           Image.Resampling.LANCZOS)
        return img
    def update_status(self, message):
        """更新状态信息"""
        self.status_label.config(text=message)
def main():
    root = tk.Tk()
    app = ImageBatchProcessor(root)
    # 设置窗口最小尺寸
    root.minsize(800, 600)
    # 运行主循环
    root.mainloop()
if __name__ == "__main__":
    main()

命令行版本(进阶版)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量图片处理命令行版本
支持更多高级功能
"""
import os
import sys
import argparse
from pathlib import Path
from PIL import Image, ImageEnhance, ImageFilter, ImageOps
import concurrent.futures
from datetime import datetime
class BatchImageProcessor:
    def __init__(self, args):
        self.args = args
        self.input_path = Path(args.input)
        self.output_path = Path(args.output) if args.output else self.input_path / "processed"
        self.supported_formats = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp'}
    def process_single_image(self, img_path):
        """处理单张图片"""
        try:
            # 打开图片
            img = Image.open(img_path)
            # 应用调整
            if self.args.brightness != 1.0:
                img = ImageEnhance.Brightness(img).enhance(self.args.brightness)
            if self.args.contrast != 1.0:
                img = ImageEnhance.Contrast(img).enhance(self.args.contrast)
            if self.args.saturation != 1.0:
                img = ImageEnhance.Color(img).enhance(self.args.saturation)
            if self.args.sharpness != 1.0:
                img = ImageEnhance.Sharpness(img).enhance(self.args.sharpness)
            # 旋转
            if self.args.rotate != 0:
                img = img.rotate(self.args.rotate, expand=True)
            # 翻转
            if self.args.flip_horizontal:
                img = ImageOps.mirror(img)
            if self.args.flip_vertical:
                img = ImageOps.flip(img)
            # 调整尺寸
            if self.args.resize:
                width, height = map(int, self.args.resize.split('x'))
                img = img.resize((width, height), Image.Resampling.LANCZOS)
            # 裁剪
            if self.args.crop:
                left, top, right, bottom = map(int, self.args.crop.split(','))
                img = img.crop((left, top, right, bottom))
            # 滤镜效果
            if self.args.filter:
                filters = {
                    'blur': ImageFilter.BLUR,
                    'contour': ImageFilter.CONTOUR,
                    'detail': ImageFilter.DETAIL,
                    'edge_enhance': ImageFilter.EDGE_ENHANCE,
                    'emboss': ImageFilter.EMBOSS,
                    'sharpen': ImageFilter.SHARPEN,
                    'smooth': ImageFilter.SMOOTH
                }
                if self.args.filter in filters:
                    img = img.filter(filters[self.args.filter])
            # 转换为灰度
            if self.args.grayscale:
                img = ImageOps.grayscale(img)
            # 自动对比度
            if self.args.auto_contrast:
                img = ImageOps.autocontrast(img, cutoff=self.args.contrast_cutoff)
            # 水印
            if self.args.watermark:
                img = self.add_watermark(img, self.args.watermark)
            # 保存
            self.save_image(img, img_path)
            return True, img_path.name
        except Exception as e:
            return False, f"{img_path.name}: {str(e)}"
    def add_watermark(self, img, watermark_text):
        """添加水印"""
        from PIL import ImageDraw, ImageFont
        # 创建水印图层
        watermark_layer = Image.new('RGBA', img.size, (0, 0, 0, 0))
        draw = ImageDraw.Draw(watermark_layer)
        # 尝试加载字体
        try:
            font_size = max(img.size) // 20
            font = ImageFont.truetype("arial.ttf", font_size)
        except:
            font = ImageFont.load_default()
        # 计算文本位置(右下角)
        text_bbox = draw.textbbox((0, 0), watermark_text, font=font)
        text_width = text_bbox[2] - text_bbox[0]
        text_height = text_bbox[3] - text_bbox[1]
        x = img.width - text_width - 10
        y = img.height - text_height - 10
        # 绘制水印
        draw.text((x, y), watermark_text, font=font, fill=(255, 255, 255, 128))
        # 合并图片
        if img.mode != 'RGBA':
            img = img.convert('RGBA')
        img = Image.alpha_composite(img, watermark_layer)
        return img
    def save_image(self, img, source_path):
        """保存图片"""
        # 创建输出目录
        self.output_path.mkdir(parents=True, exist_ok=True)
        # 生成输出文件名
        if self.args.prefix:
            output_name = f"{self.args.prefix}_{source_path.stem}{source_path.suffix}"
        elif self.args.suffix:
            output_name = f"{source_path.stem}_{self.args.suffix}{source_path.suffix}"
        else:
            output_name = f"processed_{source_path.stem}{source_path.suffix}"
        output_path = self.output_path / output_name
        # 保存参数
        save_kwargs = {}
        if source_path.suffix.lower() in ['.jpg', '.jpeg']:
            save_kwargs['quality'] = self.args.quality
            save_kwargs['optimize'] = True
        # 转换格式
        if self.args.convert_to:
            output_path = output_path.with_suffix(f".{self.args.convert_to.lower()}")
        img.save(output_path, **save_kwargs)
        if self.args.verbose:
            print(f"已保存: {output_path}")
    def process_all(self):
        """处理所有图片"""
        # 查找所有图片
        image_files = []
        for ext in self.supported_formats:
            image_files.extend(list(self.input_path.glob(f'*{ext}')))
            image_files.extend(list(self.input_path.glob(f'*{ext.upper()}')))
        if not image_files:
            print("没有找到图片文件")
            return
        print(f"找到 {len(image_files)} 张图片")
        print(f"输出目录: {self.output_path}")
        print("-" * 50)
        # 多线程处理
        with concurrent.futures.ThreadPoolExecutor(max_workers=self.args.workers) as executor:
            futures = {executor.submit(self.process_single_image, img_path): img_path 
                      for img_path in image_files}
            success_count = 0
            fail_count = 0
            for future in concurrent.futures.as_completed(futures):
                success, message = future.result()
                if success:
                    success_count += 1
                    print(f"✓ 处理成功: {message}")
                else:
                    fail_count += 1
                    print(f"✗ 处理失败: {message}")
        print("-" * 50)
        print(f"处理完成! 成功: {success_count}, 失败: {fail_count}")
def main():
    parser = argparse.ArgumentParser(description="批量图片处理工具")
    # 基本参数
    parser.add_argument("input", help="输入文件夹路径")
    parser.add_argument("-o", "--output", help="输出文件夹路径(默认:input/processed)")
    # 处理参数
    parser.add_argument("-b", "--brightness", type=float, default=1.0, help="亮度调整 (0.0-2.0, 默认1.0)")
    parser.add_argument("-c", "--contrast", type=float, default=1.0, help="对比度调整 (0.0-2.0, 默认1.0)")
    parser.add_argument("-s", "--saturation", type=float, default=1.0, help="饱和度调整 (0.0-2.0, 默认1.0)")
    parser.add_argument("-sh", "--sharpness", type=float, default=1.0, help="锐度调整 (0.0-2.0, 默认1.0)")
    parser.add_argument("-r", "--rotate", type=int, default=0, help="旋转角度")
    # 变换参数
    parser.add_argument("--resize", help="调整尺寸 (格式: 宽度x高度, 如 1920x1080)")
    parser.add_argument("--crop", help="裁剪 (格式: left,top,right,bottom)")
    parser.add_argument("--flip-horizontal", action="store_true", help="水平翻转")
    parser.add_argument("--flip-vertical", action="store_true", help="垂直翻转")
    # 滤镜参数
    parser.add_argument("--filter", choices=['blur', 'contour', 'detail', 'edge_enhance', 
                                            'emboss', 'sharpen', 'smooth'],
                       help="应用滤镜效果")
    parser.add_argument("--grayscale", action="store_true", help="转换为灰度图")
    parser.add_argument("--auto-contrast", action="store_true", help="自动对比度")
    parser.add_argument("--contrast-cutoff", type=int, default=5, help="自动对比度裁剪百分比")
    # 水印参数
    parser.add_argument("--watermark", help="添加水印文字")
    # 输出参数
    parser.add_argument("-q", "--quality", type=int, default=95, help="JPEG质量 (1-100, 默认95)")
    parser.add_argument("--convert-to", choices=['jpg', 'png', 'bmp', 'tiff', 'webp'], help="转换格式")
    parser.add_argument("--prefix", help="输出文件名前缀")
    parser.add_argument("--suffix", help="输出文件名后缀")
    # 其他参数
    parser.add_argument("-w", "--workers", type=int, default=4, help="并行处理线程数")
    parser.add_argument("-v", "--verbose", action="store_true", help="显示详细信息")
    args = parser.parse_args()
    # 检查输入目录
    if not Path(args.input).exists():
        print(f"错误: 输入目录不存在: {args.input}")
        sys.exit(1)
    # 创建处理器并运行
    processor = BatchImageProcessor(args)
    processor.process_all()
if __name__ == "__main__":
    main()

使用示例

图形界面版本使用:

python image_batch_processor.py

命令行版本使用:

# 基本用法
python batch_processor.py /path/to/images
# 调整亮度并旋转
python batch_processor.py /path/to/images -b 1.2 -r 90
# 批量调整尺寸并添加水印
python batch_processor.py /path/to/images --resize 1920x1080 --watermark "©2024"
# 转换为灰度并增加对比度
python batch_processor.py /path/to/images --grayscale -c 1.5
# 多线程处理
python batch_processor.py /path/to/images -w 8 --prefix "modified_"

功能特点

  1. 图形界面版本

    • 实时预览原图和效果对比
    • 滑块调节参数
    • 单张保存和批量处理
    • 进度显示
  2. 命令行版本

    • 多线程并行处理
    • 支持更多高级功能
    • 适合服务器或无图形界面环境
  3. 支持的处理功能

    • 亮度、对比度、饱和度、锐度调整
    • 旋转、翻转、裁剪
    • 尺寸调整
    • 多种滤镜效果
    • 水印添加
    • 格式转换

抱歉,评论功能暂时关闭!