Pillow ImageMorph 模块

下面给你一个 Pillow(PIL Fork)中 ImageMorph 模块 的完整入门指南,包含模块概述、核心类与方法、参数详解、代码示例、实际应用场景及高级技巧。
(基于 Pillow ≥ 9.0,Python 3.8+;该模块主要用于二值或灰度图像的形态学操作,如膨胀、腐蚀、开运算等)


1. 安装 Pillow

pip install --upgrade pillow

2. 基本导入

from PIL import Image, ImageMorph
from PIL.ImageMorph import LutBuilder, MorphOp

3. ImageMorph 模块概述

ImageMorph 模块提供图像形态学操作(morphological operations),专为二值图像(黑白)或灰度图像设计。形态学是图像处理的基础,用于噪声去除、边缘提取、形状分析等。

  • 核心概念:通过查找表(LUT)定义操作规则。LUT 基于模式描述语言构建,支持对称变换(如旋转、镜像)。
  • 适用图像模式L(灰度,二值化后使用)。
  • 关键优势:高效处理二值图像,支持自定义规则。

常见形态学操作

  • 腐蚀(Erosion):缩小前景(白色)区域。
  • 膨胀(Dilation):扩大前景区域。
  • 开运算:腐蚀后膨胀(去噪)。
  • 闭运算:膨胀后腐蚀(填孔)。

4. 核心类一览

作用关键参数
LutBuilder从模式字符串构建 LUTpatterns(模式列表)
MorphOp应用 LUT 到图像lut(预构建 LUT)、op_name(内置操作名,如 “erosion8″)

5. 模式描述语言(Patterns Syntax)

模式字符串格式:<symmetry>:(<grid>)-><result>

  • <grid>:3×3 网格描述(9 个字符),如 “1.0 .1. 111″(空格/换行忽略)。
  • .X:忽略(通配符)。
  • 1:像素 (值 1,前景)。
  • 0:像素 (值 0,背景)。
  • -><result>:匹配后输出(10)。
  • <symmetry>:对称变换(可选,多个用组合):
  • 1:无变换(默认)。
  • 4:4 向旋转。
  • 8:8 向旋转。
  • N:否定(0↔1)。
  • M:镜像。
  • 不匹配:保留原像素值。

示例模式

  • "4:(... .1. 111)->1":4 向旋转,中心为 1 且周围有 3 个 1 → 输出 1(膨胀规则)。

6. 基础用法:使用内置操作

# 创建二值图像(假设已二值化)
img = Image.new("L", (100, 100), 0)  # 全黑
draw = ImageDraw.Draw(img)
draw.rectangle((20, 20, 80, 80), fill=255)  # 白色方块

# 腐蚀(缩小白色区域)
erode_op = MorphOp(op_name="erosion8")
changed, eroded = erode_op.apply(img)
print(f"变化像素: {changed}")
eroded.save("eroded.png")

# 膨胀(扩大白色区域)
dilate_op = MorphOp(op_name="dilation8")
changed, dilated = dilate_op.apply(img)
dilated.save("dilated.png")

内置 op_name

  • "erosion4" / "erosion8":4/8 连通腐蚀。
  • "dilation4" / "dilation8":4/8 连通膨胀。
  • "opening4" / "opening8":开运算。
  • "closing4" / "closing8":闭运算。

7. 自定义 LUT:使用 LutBuilder

# 自定义膨胀规则:如果中心或相邻有 1,则输出 1
patterns = [
    "1:(1.. ... ...)->1",  # 中心为 1
    "4:(.1. ... ...)->1",  # 4 向相邻为 1
    "8:(.1. 1.. ...)->1"   # 8 向相邻为 1
]

lb = LutBuilder(patterns=patterns)
lut = lb.build_lut()  # 生成 LUT

# 应用自定义操作
custom_op = MorphOp(lut=lut)
changed, result = custom_op.apply(img)
result.save("custom_dilation.png")

8. 核心方法详解 + 示例

8.1 LutBuilder.build_lut()

lb = LutBuilder()
lb.add_patterns(["4:(... .1. 111)->1"])  # 添加模式
lut = lb.build_lut()  # bytearray(65536) – 16 位 LUT(3x3=9位输入 + 中心位)
print(f"LUT 大小: {len(lut)} 字节")

8.2 MorphOp.apply(image)

# 返回 (变化像素数, 新图像)
op = MorphOp(op_name="opening8")  # 开运算:去小噪点
changed, cleaned = op.apply(img)
print(f"清理了 {changed} 个噪点像素")

8.3 MorphOp.match(image)

# 找出匹配模式的像素坐标
op = MorphOp(op_name="dilation8")
matches = op.match(img)  # [(x1,y1), (x2,y2), ...]
print(f"匹配位置: {len(matches)} 个")
for x, y in matches[:5]:
    print(f"  ({x}, {y})")

8.4 MorphOp.get_on_pixels(image)

# 获取所有“开”像素(值=255/1)的坐标
on_pixels = op.get_on_pixels(img)
print(f"前景像素: {len(on_pixels)} 个")

8.5 LUT 加载/保存(MRL 文件)

# 保存 LUT 到文件
op.save_lut("my_lut.mrl")

# 加载 LUT
new_op = MorphOp()
new_op.load_lut("my_lut.mrl")

9. 高级技巧

9.1 噪声去除(开运算)

def denoise_binary(img, kernel="opening8"):
    """二值图像去噪"""
    img = img.convert("L").point(lambda p: 0 if p < 128 else 255)  # 二值化
    op = MorphOp(op_name=kernel)
    changed, denoised = op.apply(img)
    return denoised, changed

img = Image.open("noisy_binary.jpg")
cleaned, changes = denoise_binary(img)
cleaned.save("denoised.png")
print(f"去除噪点: {changes}")

9.2 边缘提取(腐蚀 + 原图减法)

def extract_edges(img):
    """提取二值图像边缘"""
    img_bin = img.convert("L").point(lambda p: 0 if p < 128 else 255)

    # 腐蚀
    erode_op = MorphOp(op_name="erosion8")
    _, eroded = erode_op.apply(img_bin)

    # 原图 - 腐蚀 = 边缘
    edges = ImageChops.subtract(img_bin, eroded)
    return edges

edges = extract_edges(Image.open("shapes.jpg"))
edges.save("edges.png")

9.3 批量形态学处理

import os
from PIL import Image

def batch_morph(input_dir, output_dir, op_name="closing8"):
    os.makedirs(output_dir, exist_ok=True)
    op = MorphOp(op_name=op_name)

    for fname in os.listdir(input_dir):
        if fname.lower().endswith(('.png', '.jpg')):
            img = Image.open(os.path.join(input_dir, fname)).convert("L")
            img_bin = img.point(lambda p: 0 if p < 128 else 255)
            _, result = op.apply(img_bin)
            result.save(os.path.join(output_dir, fname))

batch_morph("binary_images", "processed")

9.4 自定义结构元素(高级模式)

# 自定义十字形膨胀(中心 + 上下左右)
patterns = [
    "1:(1.. ... ...)->1",
    "4:(.1. ... ...)->1",  # 上、下、左、右
    "M:(1.. ... ...)->1"   # 镜像确保对称
]
lb = LutBuilder(patterns=patterns)
custom_lut = lb.build_lut()

op = MorphOp(lut=custom_lut)

10. 完整示例:二值图像形态学工具箱

from PIL import Image, ImageDraw, ImageMorph
from PIL.ImageMorph import LutBuilder, MorphOp
import os

def morph_toolbox(input_path, output_dir="morph_output"):
    os.makedirs(output_dir, exist_ok=True)
    img = Image.open(input_path).convert("L")

    # 二值化
    bin_img = img.point(lambda p: 0 if p < 128 else 255)

    # 1. 腐蚀
    erode_op = MorphOp(op_name="erosion8")
    _, eroded = erode_op.apply(bin_img)
    eroded.save(f"{output_dir}/eroded.png")

    # 2. 膨胀
    dilate_op = MorphOp(op_name="dilation8")
    _, dilated = dilate_op.apply(bin_img)
    dilated.save(f"{output_dir}/dilated.png")

    # 3. 开运算(去噪)
    open_op = MorphOp(op_name="opening8")
    _, opened = open_op.apply(bin_img)
    opened.save(f"{output_dir}/opened.png")

    # 4. 自定义闭运算(填孔)
    patterns = ["8:(... ... 111)->1"]  # 简单示例
    lb = LutBuilder(patterns=patterns)
    lut = lb.build_lut()
    close_op = MorphOp(lut=lut)
    _, closed = close_op.apply(bin_img)
    closed.save(f"{output_dir}/closed.png")

    print(f"工具箱完成: {output_dir}/")

# 使用
morph_toolbox("binary_shapes.jpg")

11. 常见应用场景

场景推荐操作
去除小噪点opening8
填充小孔洞closing4
边缘检测腐蚀 + 减法
形状平滑开/闭运算组合
OCR 预处理膨胀 + 开运算

12. 性能优化建议

场景建议
大图处理resize 缩小,处理后放大
重复应用缓存 MorphOp 和 LUT
灰度转二值使用 point() 阈值化
批量预加载 LUT,避免重复构建

13. 常见问题 & 解决方案

问题原因解决
apply() 崩溃图像不是 L 模式img.convert("L") 并二值化
“No operator loaded”未指定 op_namelut使用内置 op_name 或构建 LUT
LUT 为空模式语法错误检查网格 9 字符、对称符
变化像素为 0图像无前景验证二值化阈值
旧版本崩溃Pillow < 4.2升级 Pillow

14. 官方参考

  • https://pillow.readthedocs.io/en/stable/reference/ImageMorph.html
  • 源码:https://github.com/python-pillow/Pillow/blob/main/src/PIL/ImageMorph.py

一键工具脚本:morph_tool.py

#!/usr/bin/env python3
import argparse
from PIL import Image, ImageMorph
from PIL.ImageMorph import MorphOp

def main():
    parser = argparse.ArgumentParser(description="ImageMorph 形态学工具")
    parser.add_argument("input", help="输入图像路径")
    parser.add_argument("-o", "--op", choices=["erosion8", "dilation8", "opening8", "closing8"], default="opening8")
    parser.add_argument("-t", "--threshold", type=int, default=128, help="二值化阈值")
    parser.add_argument("--output", help="输出路径", default=None)

    args = parser.parse_args()
    output = args.output or f"{args.op}_result.png"

    img = Image.open(args.input).convert("L")
    bin_img = img.point(lambda p: 255 if p > args.threshold else 0)

    op = MorphOp(op_name=args.op)
    changed, result = op.apply(bin_img)

    result.save(output)
    print(f"处理完成: {output} (变化: {changed} 像素)")

if __name__ == "__main__":
    main()

使用示例

python morph_tool.py noisy.jpg -o opening8 -t 100 --output clean.png

需要我帮你实现 OCR 预处理管道、噪声鲁棒边缘检测、自定义结构元素生成、批量二值图像修复 等高级功能吗?直接说需求,我给你完整代码!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注