GraphicsMagick 图像处理完整教程 / 第08章 批量处理
第08章 批量处理
8.1 mogrify 批量操作
8.1.1 基本批量操作
# 批量缩放(⚠️ 直接覆盖原文件!)
gm mogrify -resize 800x600 *.jpg
# 批量转换格式
gm mogrify -format png *.jpg
# 批量设置质量
gm mogrify -quality 85 *.jpg
# 批量添加边框
gm mogrify -border 5 -bordercolor white *.jpg
8.1.2 使用 -path 避免覆盖
# 输出到指定目录(原文件不变)
mkdir -p output/
gm mogrify -path output/ -resize 800x600 input/*.jpg
# 保留目录结构
gm mogrify -path output/ -resize 800x600 \
input/subdir1/*.jpg input/subdir2/*.jpg
8.1.3 mogrify 完整参数
| 参数 | 说明 | 示例 |
|---|
-path DIR | 输出目录 | -path output/ |
-format FMT | 输出格式 | -format png |
-resize G | 缩放 | -resize 800x600 |
-quality N | 质量 | -quality 85 |
-strip | 去除元数据 | -strip |
-auto-orient | EXIF 校正 | -auto-orient |
-normalize | 自动对比度 | -normalize |
-sharpen G | 锐化 | -sharpen 0x1 |
-blur G | 模糊 | -blur 0x2 |
-rotate N | 旋转 | -rotate 90 |
-flip | 垂直翻转 | -flip |
-flop | 水平翻转 | -flop |
-crop G | 裁剪 | -crop 400x300+0+0 |
-gravity G | 锚点 | -gravity center |
-trim | 去空白边 | -trim |
-border G | 边框 | -border 5 |
-bordercolor C | 边框色 | -bordercolor white |
-colorspace S | 色彩空间 | -colorspace Gray |
-density N | DPI | -density 72 |
-filter F | 滤波器 | -filter Lanczos |
-monitor | 进度显示 | -monitor |
8.1.4 条件处理
# 仅处理特定尺寸以上的图片
for img in *.jpg; do
W=$(gm identify -format "%w" "$img")
if [ "$W" -gt 1920 ]; then
gm convert "$img" -resize 1920x -quality 85 "resized/$img"
echo "缩放: $img ($W -> 1920)"
fi
done
# 仅处理特定格式
gm mogrify -path output/ -resize 800x600 *.png
# 仅处理大于特定大小的文件
find . -name "*.jpg" -size +1M -exec \
gm mogrify -path large_output/ -resize 1200x {} \;
8.2 并行处理
8.2.1 GNU Parallel 并行
# 使用 GNU Parallel 加速批量处理
ls *.jpg | parallel -j 4 \
gm convert {} -resize 800x600 -quality 85 output/{}
# 指定 CPU 核心数
ls *.jpg | parallel -j $(nproc) \
gm convert {} -resize 800x600 output/{}
# 复杂处理流水线
ls *.jpg | parallel -j 4 '
input={}
output="output/$(basename $input)"
gm convert "$input" \
-auto-orient \
-resize "1200x1200>" \
-quality 85 \
-strip \
"$output"
'
8.2.2 xargs 并行
# xargs 并行(-P 参数)
ls *.jpg | xargs -P 4 -I {} \
gm convert {} -resize 800x600 output/{}
# 处理子目录中的所有图片
find . -name "*.jpg" | xargs -P $(nproc) -I {} \
gm convert {} -resize 800x600 output/{}
8.2.3 批量处理脚本框架
#!/bin/bash
# batch_process.sh — 高效批量处理框架
# 用法: ./batch_process.sh input_dir output_dir [jobs]
INPUT_DIR="${1:-.}"
OUTPUT_DIR="${2:-output}"
JOBS="${3:-$(nproc)}"
mkdir -p "$OUTPUT_DIR"
# 统计
TOTAL=$(find "$INPUT_DIR" -maxdepth 1 -name "*.jpg" | wc -l)
echo "找到 $TOTAL 张图片,使用 $JOBS 个并行任务"
# 处理函数
process_image() {
local input="$1"
local output="$OUTPUT_DIR/$(basename "$input")"
gm convert "$input" \
-auto-orient \
-resize "1200x1200>" \
-quality 85 \
-strip \
-unsharp-mask 0x0.5+0.5+0 \
"$output" 2>/dev/null && echo "✅ $(basename "$input")" || echo "❌ $(basename "$input")"
}
export -f process_image
export OUTPUT_DIR
# 并行执行
find "$INPUT_DIR" -maxdepth 1 -name "*.jpg" | \
parallel -j "$JOBS" process_image {}
echo ""
echo "处理完成!输出目录: $OUTPUT_DIR"
echo "共处理 $(ls "$OUTPUT_DIR"/*.jpg 2>/dev/null | wc -l) 张图片"
8.2.4 并行方式对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|
for 循环 | 简单、兼容性好 | 单线程 | 少量文件 |
xargs -P | 无需额外安装 | 控制粒度粗 | 中等批量 |
parallel | 功能强大、进度显示 | 需安装 | 大规模批量 |
mogrify | 最简单 | 功能有限 | 简单批量 |
8.3 高效脚本编写
8.3.1 进度显示
#!/bin/bash
# batch_with_progress.sh — 带进度条的批量处理
INPUT_DIR="$1"
OUTPUT_DIR="output"
mkdir -p "$OUTPUT_DIR"
FILES=($INPUT_DIR/*.jpg)
TOTAL=${#FILES[@]}
COUNT=0
for img in "${FILES[@]}"; do
((COUNT++))
PERCENT=$((COUNT * 100 / TOTAL))
PROGRESS=$((PERCENT / 2))
BAR=$(printf '%*s' "$PROGRESS" '' | tr ' ' '█')
SPACE=$(printf '%*s' $((50 - PROGRESS)) '' | tr ' ' '░')
printf "\r[%s%s] %d/%d (%d%%) %s" \
"$BAR" "$SPACE" "$COUNT" "$TOTAL" "$PERCENT" "$(basename "$img")"
gm convert "$img" \
-auto-orient -resize "800x600>" \
-quality 85 -strip \
"$OUTPUT_DIR/$(basename "$img")" 2>/dev/null
done
echo ""
echo "✅ 全部完成!"
8.3.2 错误处理与日志
#!/bin/bash
# batch_with_logging.sh — 带错误处理的批量处理
LOG_FILE="batch_$(date +%Y%m%d_%H%M%S).log"
OUTPUT_DIR="output"
mkdir -p "$OUTPUT_DIR"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
SUCCESS=0
FAILED=0
log "开始批量处理"
log "输出目录: $OUTPUT_DIR"
for img in *.jpg; do
[ -f "$img" ] || continue
if gm convert "$img" \
-auto-orient -resize "800x600>" \
-quality 85 -strip \
"$OUTPUT_DIR/$img" 2>>"$LOG_FILE"; then
((SUCCESS++))
log "✅ 成功: $img"
else
((FAILED++))
log "❌ 失败: $img"
fi
done
log "处理完成: 成功=$SUCCESS, 失败=$FAILED"
log "日志: $LOG_FILE"
8.4 内存管理
8.4.1 内存限制设置
# 查看当前资源限制
gm convert -list resource
# 设置环境变量限制内存使用
export MAGICK_MEMORY_LIMIT=2GiB # 内存使用限制
export MAGICK_MAP_LIMIT=4GiB # 内存映射限制
export MAGICK_DISK_LIMIT=16GiB # 磁盘临时文件限制
export MAGICK_FILE_LIMIT=768 # 打开文件数限制
export MAGICK_AREA_LIMIT=128MP # 最大像素面积
export MAGICK_TMPDIR=/dev/shm/gm-tmp # 临时文件目录
# 在命令行中临时设置
MAGICK_MEMORY_LIMIT=1GiB gm convert huge_input.jpg output.jpg
8.4.2 内存限制参数说明
| 环境变量 | 说明 | 推荐值(生产环境) |
|---|
MAGICK_MEMORY_LIMIT | 总内存限制 | 2GiB-8GiB |
MAGICK_MAP_LIMIT | mmap 映射限制 | 4GiB-16GiB |
MAGICK_DISK_LIMIT | 临时文件总大小 | 8GiB-32GiB |
MAGICK_FILE_LIMIT | 最大打开文件数 | 768-2048 |
MAGICK_AREA_LIMIT | 最大图像面积 | 128MP-256MP |
MAGICK_TMPDIR | 临时目录 | /dev/shm (RAM盘) |
8.4.3 内存估算
估算公式(近似值):
内存 ≈ 宽 × 高 × 通道数 × (quantum-depth/8) × 2
示例:
8000×6000 RGB JPEG (quantum-depth=16):
8000 × 6000 × 3 × (16/8) × 2 = 576,000,000 bytes ≈ 576MB
4000×3000 RGBA PNG (quantum-depth=16):
4000 × 3000 × 4 × 2 × 2 = 192,000,000 bytes ≈ 192MB
8.4.4 大图处理策略
# 策略 1:分块处理
# 对于超大图像,先缩小再处理
gm convert huge_20000x15000.jpg \
-resize 25% \
-blur 0x2 \
-resize 400% \
output.jpg
# 策略 2:使用 -limit 临时限制
gm convert -limit memory 1GiB -limit map 2GiB \
huge_input.jpg output.jpg
# 策略 3:减少色彩深度
gm convert huge_input.jpg -depth 8 -quality 85 output.jpg
# 策略 4:使用 map 减少内存占用
gm convert -map huge_input.jpg output.jpg
8.5 性能优化
8.5.1 临时文件优化
# 使用 RAM 盘(/dev/shm)作为临时目录
export MAGICK_TMPDIR=/dev/shm/gm-tmp
mkdir -p /dev/shm/gm-tmp
# 对比:SSD vs RAM 盘
# /tmp (SSD): ~500MB/s 写入
# /dev/shm (RAM): ~5000MB/s 写入
# 大量临时文件操作时性能提升 5-10x
8.5.2 流式处理
# 流式处理(减少磁盘 I/O)
# 错误方式:多次读写
gm convert input.jpg -resize 800 /tmp/step1.jpg
gm convert /tmp/step1.jpg -quality 85 /tmp/step2.jpg
gm convert /tmp/step2.jpg -strip output.jpg
# 正确方式:单次处理
gm convert input.jpg -resize 800 -quality 85 -strip output.jpg
8.5.3 减少中间步骤
# 不好:创建中间文件
gm convert input.jpg -resize 800x600 /tmp/resized.jpg
gm convert /tmp/resized.jpg -rotate 90 /tmp/rotated.jpg
gm convert /tmp/rotated.jpg -quality 85 output.jpg
rm -f /tmp/resized.jpg /tmp/rotated.jpg
# 好:管道处理
gm convert input.jpg -resize 800x600 -rotate 90 -quality 85 output.jpg
# 好:使用 subshell(需要多步操作时)
gm convert input.jpg \
\( -clone 0 -resize 400x300 \) \
\( -clone 0 -resize 200x150 \) \
-delete 0 \
output_%d.jpg
8.5.4 批量处理性能对比
| 处理方式 | 100 张图片耗时 | 内存峰值 |
|---|
| 串行 for 循环 | ~45s | 200MB |
| xargs -P 4 | ~12s | 800MB |
| parallel -j 4 | ~11s | 800MB |
| mogrify | ~40s | 150MB |
| parallel + RAM盘 | ~8s | 800MB |
8.6 实战场景
场景:图片 CDN 服务
#!/bin/bash
# cdn_resize.sh — CDN 图片动态处理
# 用法: ./cdn_resize.sh <input> <width> <height> <quality> <format>
INPUT="$1"
WIDTH="${2:-800}"
HEIGHT="${3:-600}"
QUALITY="${4:-85}"
FORMAT="${5:-jpg}"
OUTPUT="cdn_${WIDTH}x${HEIGHT}_q${QUALITY}.${FORMAT}"
gm convert "$INPUT" \
-filter Lanczos \
-resize "${WIDTH}x${HEIGHT}>" \
-gravity center \
-extent "${WIDTH}x${HEIGHT}" \
-quality "$QUALITY" \
-strip \
-interlace Plane \
-sampling-factor 4:2:0 \
"$OUTPUT"
# 输出文件大小信息
SIZE=$(stat -f%z "$OUTPUT" 2>/dev/null || stat -c%s "$OUTPUT")
echo "$OUTPUT: ${SIZE} bytes"
场景:自动化图片审核流水线
#!/bin/bash
# image_audit.sh — 自动化图片审核与优化流水线
INPUT_DIR="uploads/"
AUDIT_DIR="audit/"
APPROVED_DIR="approved/"
REJECTED_DIR="rejected/"
LOG="audit.log"
mkdir -p "$AUDIT_DIR" "$APPROVED_DIR" "$REJECTED_DIR"
log() { echo "[$(date '+%H:%M:%S')] $1" >> "$LOG"; }
for img in "$INPUT_DIR"*.{jpg,jpeg,png,webp}; do
[ -f "$img" ] || continue
BASENAME=$(basename "$img")
# 审核检查
INFO=$(gm identify -format "%w %h %b %m" "$img" 2>/dev/null)
[ -z "$INFO" ] && { log "❌ 无法读取: $BASENAME"; continue; }
W=$(echo $INFO | awk '{print $1}')
H=$(echo $INFO | awk '{print $2}')
# 检查最小尺寸
if [ "$W" -lt 200 ] || [ "$H" -lt 200 ]; then
log "❌ 尺寸不足: $BASENAME (${W}x${H})"
cp "$img" "$REJECTED_DIR"
continue
fi
# 检查是否过大
if [ "$W" -gt 8000 ] || [ "$H" -gt 8000 ]; then
log "⚠️ 尺寸过大,自动缩放: $BASENAME (${W}x${H})"
gm convert "$img" -resize "4000x4000>" "$AUDIT_DIR$BASENAME"
else
cp "$img" "$AUDIT_DIR$BASENAME"
fi
# 优化处理
gm mogrify -path "$APPROVED_DIR" \
-auto-orient -strip -quality 85 \
"$AUDIT_DIR$BASENAME"
log "✅ 通过: $BASENAME"
done
log "审核完成: 通过=$(ls $APPROVED_DIR | wc -l), 拒绝=$(ls $REJECTED_DIR | wc -l)"
场景:定时图片清理与归档
#!/bin/bash
# image_archive.sh — 定时清理与归档旧图片
ARCHIVE_DIR="archive/$(date +%Y/%m)"
TEMP_DIR="temp_uploads/"
KEEP_DAYS=30
mkdir -p "$ARCHIVE_DIR"
# 找到超过保留天数的文件
find "$TEMP_DIR" -name "*.jpg" -mtime +$KEEP_DAYS | while read img; do
BASENAME=$(basename "$img")
# 压缩归档
gm convert "$img" -quality 70 -strip "$ARCHIVE_DIR/$BASENAME"
# 删除原文件
rm "$img"
echo "归档: $BASENAME"
done
# 清理空目录
find "$TEMP_DIR" -type d -empty -delete 2>/dev/null
8.7 高级批量技巧
8.7.1 使用文件列表批量处理
# 从文件列表读取
cat filelist.txt | while read img; do
gm convert "$img" -resize 800x600 "output/$(basename "$img")"
done
# 生成文件列表(带尺寸信息)
gm identify -format "%f %wx%h\n" *.jpg > image_list.txt
# 按尺寸筛选后处理
awk '$2 > 1920 {print $1}' image_list.txt | while read img; do
gm convert "$img" -resize 1920x "resized/$img"
done
8.7.2 条件批量处理
# 根据文件大小选择不同质量
for img in *.jpg; do
SIZE=$(stat -c%s "$img" 2>/dev/null || stat -f%z "$img")
if [ "$SIZE" -gt 5242880 ]; then
QUALITY=70 # >5MB 的用低质量
elif [ "$SIZE" -gt 1048576 ]; then
QUALITY=80 # >1MB 的用中等质量
else
QUALITY=90 # <1MB 的用高质量
fi
gm convert "$img" -quality "$QUALITY" "optimized/$img"
done
8.7.3 多尺寸批量输出
#!/bin/bash
# multi_size_batch.sh — 批量生成多尺寸
# 用法: ./multi_size_batch.sh input_dir
INPUT_DIR="$1"
SIZES=("150x150" "400x300" "800x600" "1200x900")
SIZE_NAMES=("thumb" "small" "medium" "large")
for img in "$INPUT_DIR"/*.jpg; do
[ -f "$img" ] || continue
BASENAME=$(basename "${img%.*}")
for i in "${!SIZES[@]}"; do
SIZE="${SIZES[$i]}"
NAME="${SIZE_NAMES[$i]}"
mkdir -p "output/$NAME"
gm convert "$img" \
-resize "${SIZE}>" \
-gravity center -extent "${SIZE}" \
-quality 85 -strip \
"output/$NAME/${BASENAME}.jpg" &
done
# 等待当前图片的所有尺寸处理完成
wait
done
echo "批量处理完成!"
for NAME in "${SIZE_NAMES[@]}"; do
echo " $NAME: $(ls output/$NAME/ | wc -l) 张"
done
8.8 批量处理操作速查表
| 场景 | 命令 |
|---|
| 批量缩放 | gm mogrify -path out/ -resize 800 *.jpg |
| 批量转格式 | gm mogrify -format png *.jpg |
| 批量去元数据 | gm mogrify -strip *.jpg |
| 批量自动旋转 | gm mogrify -auto-orient *.jpg |
| 并行处理 | ls *.jpg | parallel -j4 gm convert ... {} |
| 条件处理 | 结合 for 循环和 if 判断 |
| 多尺寸输出 | 遍历尺寸数组 |
| 流式处理 | 合并命令到单个 gm convert |
| RAM 盘优化 | MAGICK_TMPDIR=/dev/shm |
8.9 本章小结
| 要点 | 说明 |
|---|
mogrify 适合简单批量 | 配合 -path 避免覆盖 |
| GNU Parallel 是最佳并行工具 | -j $(nproc) 充分利用 CPU |
| RAM 盘优化临时文件 | /dev/shm 提升 5-10x I/O |
| 流式处理减少磁盘 I/O | 一步完成多步操作 |
| 设置 MAGICK_* 限制 | 避免 OOM 和磁盘爆满 |
| 错误处理和日志很重要 | 生产环境必须 |
扩展阅读
- GNU Parallel 教程
- Bash 高级脚本编写
- Linux 内存管理与 tmpfs
- GraphicsMagick 资源管理
- Shell 脚本最佳实践
上一章:第07章 图像特效
下一章:第09章 图像格式详解