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 | 从模式字符串构建 LUT | patterns(模式列表) |
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>:匹配后输出(1或0)。<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_name 或 lut | 使用内置 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 预处理管道、噪声鲁棒边缘检测、自定义结构元素生成、批量二值图像修复 等高级功能吗?直接说需求,我给你完整代码!