图片 Patch 划分与可视化工具

本工具用于将图片划分为规则的 patch 网格,并生成带网格叠加的可视化效果图(支持高亮某一块 patch),效果类似典型的 Vision Transformer 输入可视化。

  • 脚本文件: image_patcher.py
  • 主要依赖: Pillow, opencv-python, numpy

1. 环境准备

在项目根目录 /home/xxx/tool 下创建并使用虚拟环境(推荐):

cd /home/xxx/tool

# 创建虚拟环境
python3 -m venv .venv

# 激活虚拟环境(Linux)
source .venv/bin/activate

# 安装依赖
pip install --upgrade pip
pip install pillow opencv-python

如果你已经按照上述步骤创建过 .venv 并安装过依赖,可以直接激活虚拟环境开始使用。


2. 脚本功能概览

脚本支持两种划分方式:

  • 按 patch 像素尺寸划分:指定 --patch_w--patch_h
  • 按行列数划分:指定 --rows--cols,脚本会根据图片宽高自动计算合适的 patch 尺寸

并且可以:

  • 将所有 patch 裁剪并保存到 out_dir/patches 目录
  • 生成一张带网格线的可视化图片 out_dir/grid_visualization.png
  • 可选高亮某一块 patch(通过行列索引指定)

脚本代码如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
图片 patch 划分与可视化工具

功能:
1. 将图片按给定 patch 大小裁剪保存。
2. 生成一张带网格叠加的可视化图。
3. 可选择高亮某一个 patch(行、列索引,从 0 开始)。
"""

import argparse
from pathlib import Path

import cv2
from PIL import Image


def make_dirs(path: Path) -> None:
    """创建目录(若不存在)。"""
    path.mkdir(parents=True, exist_ok=True)


def split_into_patches(
    image_path: Path,
    out_dir: Path,
    patch_w: int,
    patch_h: int,
    drop_last: bool = True,
) -> None:
    """
    将图片切成 patch 并保存到 out_dir/patches 下。
    drop_last=True 时,不足一整块的边缘会被舍弃。
    """
    img = Image.open(image_path).convert("RGB")
    w, h = img.size

    patches_dir = out_dir / "patches"
    make_dirs(patches_dir)

    patch_id = 0
    for y in range(0, h, patch_h):
        if drop_last and y + patch_h > h:
            break
        for x in range(0, w, patch_w):
            if drop_last and x + patch_w > w:
                break
            box = (x, y, x + patch_w, y + patch_h)
            patch = img.crop(box)
            patch_file = patches_dir / f"patch_{patch_id:04d}_r{y//patch_h}_c{x//patch_w}.png"
            patch.save(patch_file)
            patch_id += 1

    print(f"共保存 {patch_id} 个 patch 到 {patches_dir}")


def draw_grid_and_highlight(
    image_path: Path,
    out_path: Path,
    patch_w: int,
    patch_h: int,
    line_color=(255, 255, 255),
    line_thickness: int = 1,
    highlight_row: int | None = None,
    highlight_col: int | None = None,
    highlight_color_outer=(255, 215, 0),
    highlight_color_inner=(0, 160, 220),
    highlight_thickness_outer: int = 4,
    highlight_thickness_inner: int = 4,
) -> None:
    """
    在图片上画网格线,并可选高亮某一格 patch。
    highlight_row / highlight_col 为 patch 的行列索引(从 0 开始)。
    """
    img = cv2.imread(str(image_path))
    if img is None:
        raise FileNotFoundError(f"Cannot read image: {image_path}")

    h, w, _ = img.shape

    # 画竖线
    for x in range(0, w, patch_w):
        cv2.line(img, (x, 0), (x, h), line_color, line_thickness)

    # 画横线
    for y in range(0, h, patch_h):
        cv2.line(img, (0, y), (w, y), line_color, line_thickness)

    # 高亮某个 patch
    if highlight_row is not None and highlight_col is not None:
        x1 = highlight_col * patch_w
        y1 = highlight_row * patch_h
        x2 = min(x1 + patch_w, w)
        y2 = min(y1 + patch_h, h)

        # 外层框
        cv2.rectangle(
            img,
            (x1, y1),
            (x2, y2),
            highlight_color_outer,
            thickness=highlight_thickness_outer,
        )
        # 内层框,稍微缩进一点,形成两层效果
        offset = highlight_thickness_outer + 2
        cv2.rectangle(
            img,
            (x1 + offset, y1 + offset),
            (x2 - offset, y2 - offset),
            highlight_color_inner,
            thickness=highlight_thickness_inner,
        )

    cv2.imwrite(str(out_path), img)
    print(f"网格可视化保存到: {out_path}")


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="图片 patch 划分工具")
    parser.add_argument("--image", type=str, required=True, help="输入图片路径")
    # 两种指定方式:1)给出 patch 像素大小;2)给出行列数
    parser.add_argument("--patch_w", type=int, help="patch 宽度(像素)")
    parser.add_argument("--patch_h", type=int, help="patch 高度(像素)")
    parser.add_argument("--rows", type=int, help="按行数自动划分(例如 9 表示 9 行)")
    parser.add_argument("--cols", type=int, help="按列数自动划分(例如 9 表示 9 列)")
    parser.add_argument("--out_dir", type=str, required=True, help="输出目录(将自动创建)")

    parser.add_argument(
        "--keep_edge",
        action="store_true",
        help="默认丢弃不能整除的边缘;加上该参数则保留(仅影响网格线是否画到边缘)",
    )
    parser.add_argument(
        "--no_split",
        action="store_true",
        help="只画网格,不切 patch",
    )
    parser.add_argument(
        "--draw_grid",
        action="store_true",
        help="是否生成带网格叠加的图片",
    )
    parser.add_argument(
        "--highlight_row",
        type=int,
        default=None,
        help="要高亮 patch 的行索引(从 0 开始)",
    )
    parser.add_argument(
        "--highlight_col",
        type=int,
        default=None,
        help="要高亮 patch 的列索引(从 0 开始)",
    )

    return parser.parse_args()


def main() -> None:
    args = parse_args()

    image_path = Path(args.image)
    out_dir = Path(args.out_dir)
    make_dirs(out_dir)

    # 根据参数决定 patch 大小:
    # - 若给定 patch_w/patch_h,则直接使用;
    # - 否则若给定 rows/cols,则根据图片尺寸自动计算;
    # - 其他情况视为参数错误。
    img = Image.open(image_path)
    img_w, img_h = img.size

    if args.patch_w and args.patch_h:
        patch_w = args.patch_w
        patch_h = args.patch_h
    elif args.rows and args.cols:
        patch_w = img_w // args.cols
        patch_h = img_h // args.rows
        print(f"按 {args.rows}x{args.cols} 网格自动计算 patch 大小: {patch_w}x{patch_h}")
    else:
        raise ValueError(
            "参数错误:需要二选一\n"
            "1) 指定 --patch_w 和 --patch_h\n"
            "2) 或者指定 --rows 和 --cols(例如 --rows 9 --cols 9)"
        )

    drop_last = not args.keep_edge

    # 1. 切 patch
    if not args.no_split:
        split_into_patches(
            image_path=image_path,
            out_dir=out_dir,
            patch_w=patch_w,
            patch_h=patch_h,
            drop_last=drop_last,
        )

    # 2. 生成带网格/高亮 patch 的可视化
    if args.draw_grid:
        grid_out_path = out_dir / "grid_visualization.png"
        draw_grid_and_highlight(
            image_path=image_path,
            out_path=grid_out_path,
            patch_w=patch_w,
            patch_h=patch_h,
            highlight_row=args.highlight_row,
            highlight_col=args.highlight_col,
        )


if __name__ == "__main__":
    main()




3. 基本用法

激活虚拟环境后,在项目目录下运行:

cd /home/xxx/tool
source .venv/bin/activate

3.1 帮助信息

python image_patcher.py -h

可以看到主要参数(摘要):

  • 必选
    • --image: 输入图片路径
    • --out_dir: 输出目录(会自动创建)
  • 划分方式(二选一)
    • --patch_w: patch 宽度(像素)
    • --patch_h: patch 高度(像素)
    • --rows: 按行数自动划分(例如 9 表示 9 行)
    • --cols: 按列数自动划分(例如 9 表示 9 列)
  • 可选功能
    • --draw_grid: 生成带网格叠加的图片
    • --no_split: 只画网格,不实际裁剪 patch
    • --keep_edge: 默认丢弃不能整除的边缘;加上该参数则网格线会画到图片最边缘
    • --highlight_row: 需要高亮的 patch 行索引(从 0 开始)
    • --highlight_col: 需要高亮的 patch 列索引(从 0 开始)

注意:需要二选一

  • 要么指定 --patch_w--patch_h
  • 要么指定 --rows--cols
    否则脚本会报参数错误。

4. 按像素大小划分示例

将图片按 32 x 32 像素 patch 切分,并生成带网格的可视化图:

python image_patcher.py \
  --image '/home/xxx/tool/喜鹊.jpg' \
  --patch_w 32 --patch_h 32 \
  --out_dir /home/xxx/tool/output_xique_32 \
  --draw_grid

运行后:

  • 所有 patch 会保存在 output_xique_32/patches 目录下
  • 网格可视化图为 output_xique_32/grid_visualization.png

可以额外高亮某一块 patch(例如第 0 行第 1 列):

python image_patcher.py \
  --image '/home/xxx/tool/喜鹊.jpg' \
  --patch_w 32 --patch_h 32 \
  --out_dir /home/xxx/tool/output_xique_32_hl \
  --draw_grid \
  --highlight_row 0 --highlight_col 1

5. 按行列数划分示例(9×9 网格)

如果你更习惯指定“网格行数 × 网格列数”,可以使用 --rows--cols,脚本会根据图片尺寸自动算出每个 patch 的宽高。

喜鹊.jpg 为例,将其划分为 9×9 网格:

python image_patcher.py \
  --image '/home/xxx/tool/喜鹊.jpg' \
  --rows 9 --cols 9 \
  --out_dir /home/xxx/tool/output_xique_9x9 \
  --draw_grid

脚本会输出类似日志:

按 9x9 网格自动计算 patch 大小: 26x23
共保存 81 个 patch 到 /home/xxx/tool/output_xique_9x9/patches
网格可视化保存到: /home/xxx/tool/output_xique_9x9/grid_visualization.png
  • 一共会得到 (9 \times 9 = 81) 个 patch
  • 所有 patch 文件在 output_xique_9x9/patches 目录中
  • 网格可视化图是 output_xique_9x9/grid_visualization.png,上面能看到 9×9 的白色网格

5.1 高亮某一块 patch

例如高亮第 2 行第 3 列(索引从 0 开始)的 patch:

python image_patcher.py \
  --image '/home/xxx/tool/喜鹊.jpg' \
  --rows 9 --cols 9 \
  --out_dir /home/xxx/tool/output_xique_9x9_hl \
  --draw_grid \
  --highlight_row 1 --highlight_col 2

生成的 grid_visualization.png 中,对应的 patch 会被金黄色和蓝色双层矩形框高亮,类似示例图片中的效果。


5.2 生成目录结构示例

以本仓库为例,分别运行 32×32 像素划分和 9×9 行列划分后,目录结构大致如下(省略部分 patch 文件):

/home/xxx/tool
├── image_patcher.py
├── README.md
├── 喜鹊.jpg
├── .venv/                     # Python 虚拟环境(可选)
├── output_xique_32/           # 32x32 像素划分输出示例
│   ├── grid_visualization.png # 带 32x32 网格的整体可视化
│   └── patches/
│       ├── patch_0000_r0_c0.png
│       ├── patch_0001_r0_c1.png
│       ├── patch_0002_r0_c2.png
│       ├── ...                # 其它 patch,命名中包含行列索引
├── output_xique_9x9/          # 9x9 行列划分输出示例
│   ├── grid_visualization.png # 带 9x9 网格的整体可视化
│   └── patches/
│       ├── patch_0000_r0_c0.png
│       ├── patch_0001_r0_c1.png
│       ├── patch_0002_r0_c2.png
│       ├── ...                # 一共 81 个 patch
└── ...

每个 patch 文件名中都包含:

  • 顺序编号:例如 patch_0007
  • 行列索引:例如 _r2_c3 表示第 3 行、第 4 列(索引从 0 开始)

这样在做调试或可视化时,可以很方便地根据文件名定位到原图中的具体位置。

5.3 可视化效果示例

如果你用自己的 Markdown 查看器或在 IDE 中打开本仓库,可以直接看到如下示意图(以下路径均相对于仓库根目录):

  • 9×9 网格整体效果

在这里插入图片描述

  • 左上角 patch 示例patch_0000_r0_c0.png,对应第 0 行第 0 列):

在这里插入图片描述

  • 如果你按 32×32 像素划分并生成了 output_xique_32 目录,也可以类似地查看:
output_xique_32/grid_visualization.png
output_xique_32/patches/patch_0000_r0_c0.png

在大部分支持图片预览的 Markdown 工具(例如 VS Code、Cursor、Typora)中,这些路径会自动渲染为图片,从而直观展示脚本运行结果。


6. 参数说明小结

  • --image: 需要处理的图片路径(支持中文路径)
  • --out_dir: 输出目录,若不存在会自动创建
  • --patch_w / --patch_h: patch 的宽高(像素);与 --rows / --cols 不能混用
  • --rows / --cols: 网格的行数和列数,用于自动计算 patch 大小
  • --draw_grid: 是否生成带网格的可视化图
  • --no_split: 若只想看网格,不想实际保存 patch,可以加上该参数
  • --keep_edge: 默认会丢弃不能整除的边缘区域;加上该参数可以让网格线画到整张图的边缘(注意:当前裁剪逻辑仍按整块 patch 保存)
  • --highlight_row / --highlight_col: 指定要高亮的 patch 的行、列索引(从 0 开始计数)
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐