2. 基于边缘指导的插值算法
任务说明
本案例实现基于边缘指导的插值算法(Edge-Guided Interpolation, EGI)。给定一张低分辨率灰度图像,算法先检测局部边缘方向,再沿边缘方向而非正交方向进行插值,从而减少斜向边缘的锯齿与模糊。输入为单通道图像,输出为2倍放大图像。
算法原理与实现
核心思路:对每个待插值像素,先在其邻域内计算四个方向的梯度能量(水平、垂直、45°、135°),选择能量最小的方向作为边缘方向(该方向像素变化平缓),然后沿该方向取两个已知像素的均值作为插值结果。对于非边缘区域(各方向能量接近),退化为双线性插值以保证平滑。
import numpy as np
from scipy.ndimage import convolve
def edge_guided_interpolate(img):
"""
基于边缘指导的2倍插值
img: 2D numpy array, 灰度图, dtype=float32, 范围[0,1]
返回: 2倍放大图像, 尺寸 (2*H-1, 2*W-1)
"""
H, W = img.shape
out_h, out_w = 2*H - 1, 2*W - 1
out = np.zeros((out_h, out_w), dtype=np.float32)
# 已知像素直接映射
out[::2, ::2] = img
# 方向梯度核 (用于边缘方向检测)
# 每个核与3x3邻域卷积,输出该方向的梯度强度
kernels = {
'h': np.array([[0,0,0],[1,-2,1],[0,0,0]], dtype=np.float32), # 水平变化
'v': np.array([[0,1,0],[0,-2,0],[0,1,0]], dtype=np.float32), # 垂直变化
'd': np.array([[1,0,0],[0,-2,0],[0,0,1]], dtype=np.float32), # 45°变化
'a': np.array([[0,0,1],[0,-2,0],[1,0,0]], dtype=np.float32) # 135°变化
}
# 计算每个已知像素的梯度能量图(尺寸与原图相同)
grad_maps = {}
for name, k in kernels.items():
# 对原图做卷积,得到每个像素位置的梯度响应
grad_maps[name] = convolve(img, k, mode='mirror') ** 2
# 插值行像素 (奇数行, 偶数列) —— 水平相邻已知像素的中点
for i in range(1, out_h, 2):
for j in range(0, out_w, 2):
# 对应原图中的邻域中心 (左上角已知像素)
si, sj = i//2, j//2
# 取该邻域四个方向的梯度能量(取中心及右/下邻域的最大值作为该区域方向判断)
# 简化:使用中心像素的梯度能量
g = {name: grad_maps[name][si, sj] for name in kernels}
# 选择最小梯度方向(变化最平缓)
best = min(g, key=g.get)
# 沿该方向取两个已知像素插值
if best == 'h':
# 水平方向:左右已知像素
left = img[si, sj] if sj-1 >= 0 else img[si, sj]
right = img[si, sj+1] if sj+1 < W else img[si, sj]
val = (left + right) / 2
elif best == 'v':
# 垂直方向:上下已知像素(但当前插值点在水平线上,取上下均值)
up = img[si-1, sj] if si-1 >= 0 else img[si, sj]
down = img[si+1, sj] if si+1 < H else img[si, sj]
val = (up + down) / 2
elif best == 'd':
# 45°方向:右上和左下
ru = img[si-1, sj+1] if (si-1>=0 and sj+1<W) else img[si, sj]
ld = img[si+1, sj-1] if (si+1<H and sj-1>=0) else img[si, sj]
val = (ru + ld) / 2
else: # 'a' 135°
lu = img[si-1, sj-1] if (si-1>=0 and sj-1>=0) else img[si, sj]
rd = img[si+1, sj+1] if (si+1<H and sj+1<W) else img[si, sj]
val = (lu + rd) / 2
out[i, j] = val
# 插值列像素 (偶数行, 奇数列)
for i in range(0, out_h, 2):
for j in range(1, out_w, 2):
si, sj = i//2, j//2
g = {name: grad_maps[name][si, sj] for name in kernels}
best = min(g, key=g.get)
if best == 'h':
left = img[si, sj-1] if sj-1>=0 else img[si, sj]
right = img[si, sj+1] if sj+1<W else img[si, sj]
val = (left + right) / 2
elif best == 'v':
up = img[si, sj] if si-1>=0 else img[si, sj] # 实际取上下
down = img[si+1, sj] if si+1<H else img[si, sj]
val = (up + down) / 2
elif best == 'd':
ru = img[si-1, sj+1] if (si-1>=0 and sj+1<W) else img[si, sj]
ld = img[si+1, sj-1] if (si+1<H and sj-1>=0) else img[si, sj]
val = (ru + ld) / 2
else:
lu = img[si-1, sj-1] if (si-1>=0 and sj-1>=0) else img[si, sj]
rd = img[si+1, sj+1] if (si+1<H and sj+1<W) else img[si, sj]
val = (lu + rd) / 2
out[i, j] = val
# 插值中心像素 (奇数行, 奇数列) —— 四个已知像素的中心
for i in range(1, out_h, 2):
for j in range(1, out_w, 2):
si, sj = i//2, j//2
g = {name: grad_maps[name][si, sj] for name in kernels}
best = min(g, key=g.get)
if best == 'h':
# 水平方向:左右两个已知像素的均值(但中心点需要4邻域,这里取左右)
left = img[si, sj]
right = img[si, sj+1] if sj+1<W else img[si, sj]
val = (left + right) / 2
elif best == 'v':
up = img[si, sj]
down = img[si+1, sj] if si+1<H else img[si, sj]
val = (up + down) / 2
elif best == 'd':
# 45°方向:右上和左下
ru = img[si-1, sj+1] if (si-1>=0 and sj+1<W) else img[si, sj]
ld = img[si+1, sj-1] if (si+1<H and sj-1>=0) else img[si, sj]
val = (ru + ld) / 2
else:
lu = img[si-1, sj-1] if (si-1>=0 and sj-1>=0) else img[si, sj]
rd = img[si+1, sj+1] if (si+1<H and sj+1<W) else img[si, sj]
val = (lu + rd) / 2
out[i, j] = val
return out
# 示例:构造一个带斜边的4x4测试图
test = np.array([
[0.1, 0.1, 0.9, 0.9],
[0.1, 0.1, 0.9, 0.9],
[0.1, 0.9, 0.9, 0.1],
[0.1, 0.9, 0.9, 0.1]
], dtype=np.float32)
result = edge_guided_interpolate(test)
print("输入尺寸:", test.shape, "输出尺寸:", result.shape)
print("输出像素范围: [{:.3f}, {:.3f}]".format(result.min(), result.max()))
边缘方向检测与插值示意
运行与验证
依赖安装:
pip install numpy scipy
运行方式:将上述代码保存为 egi_demo.py,执行 python egi_demo.py。
验证方法:观察输出尺寸是否为 (7, 7)(即 2×4−1)。检查输出像素值是否全部落在 [0, 1] 区间内。为验证边缘保持效果,可构造一个理想斜边图像(如黑色背景上的白色45°斜线),分别用双线性插值和本算法放大,对比斜边区域的锯齿程度——本算法输出中斜边应更平滑、阶梯感更弱。
判定标准:程序无异常退出,输出尺寸正确,像素范围合法;在斜边测试图上,边缘指导插值结果的斜向边缘过渡像素数比双线性插值更少(即边缘更锐利)。