3. 图像枕形畸变校正
任务说明
本案例实现图像枕形畸变的校正。枕形畸变表现为图像边缘向中心收缩,常见于长焦镜头或投影仪。给定一张存在枕形畸变的测试图像,通过建立畸变模型并反向映射,输出校正后的图像。校正效果通过棋盘格或直线边缘的弯曲程度进行判定。
畸变模型与校正原理
采用径向畸变模型描述枕形畸变。设归一化坐标 (x_d, y_d) 为畸变图像中的像素坐标,(x_u, y_u) 为校正后坐标,二者关系为:
x_d = x_u * (1 + k1 * r^2 + k2 * r^4)
y_d = y_u * (1 + k1 * r^2 + k2 * r^4)
其中 r^2 = x_u^2 + y_u^2
枕形畸变对应 k1 < 0。校正过程为:对输出图像每个像素,计算其归一化坐标,代入上式得到畸变图像中的采样位置,再通过双线性插值获取像素值。由于反向映射可能超出边界,越界像素填充为黑色。
代码实现
import numpy as np
from PIL import Image
def correct_pincushion(img, k1, k2=0.0):
"""
枕形畸变校正
img: PIL.Image 输入畸变图像
k1, k2: 径向畸变系数(k1 < 0 表示枕形)
返回: PIL.Image 校正后图像
"""
src = np.array(img).astype(np.float32)
h, w = src.shape[:2]
# 归一化坐标中心
cx, cy = (w - 1) / 2.0, (h - 1) / 2.0
# 归一化焦距(取图像短边的一半作为近似)
f = min(w, h) / 2.0
# 输出图像
dst = np.zeros_like(src)
# 对输出图像每个像素反向映射
for v in range(h):
for u in range(w):
# 输出像素归一化坐标
x_u = (u - cx) / f
y_u = (v - cy) / f
r2 = x_u * x_u + y_u * y_u
# 畸变系数(反向映射使用正系数)
scale = 1.0 + k1 * r2 + k2 * r2 * r2
# 畸变图像中的归一化坐标
x_d = x_u * scale
y_d = y_u * scale
# 转换回像素坐标
src_u = x_d * f + cx
src_v = y_d * f + cy
# 边界检查
if 0 <= src_u < w - 1 and 0 <= src_v < h - 1:
# 双线性插值
x0, y0 = int(src_u), int(src_v)
dx, dy = src_u - x0, src_v - y0
# 四个邻近像素
p00 = src[y0, x0]
p10 = src[y0, x0 + 1]
p01 = src[y0 + 1, x0]
p11 = src[y0 + 1, x0 + 1]
# 插值
top = p00 * (1 - dx) + p10 * dx
bottom = p01 * (1 - dx) + p11 * dx
dst[v, u] = top * (1 - dy) + bottom * dy
return Image.fromarray(dst.astype(np.uint8))
# 生成测试图像:带网格的畸变图(模拟枕形畸变)
def generate_test_image(size=400):
"""生成一张模拟枕形畸变的网格图像"""
img = np.zeros((size, size, 3), dtype=np.uint8)
img[:] = (30, 30, 30) # 深灰背景
# 绘制网格线
step = 50
for i in range(0, size, step):
# 水平线
img[i:i+2, :] = (200, 200, 200)
# 垂直线
img[:, i:i+2] = (200, 200, 200)
# 模拟枕形畸变(k1 = -0.15)
k1 = -0.15
cx, cy = (size - 1) / 2.0, (size - 1) / 2.0
f = size / 2.0
distorted = np.zeros_like(img)
for v in range(size):
for u in range(size):
# 畸变图像坐标 -> 归一化
x_d = (u - cx) / f
y_d = (v - cy) / f
r2 = x_d * x_d + y_d * y_d
# 反向求校正坐标(畸变系数取负)
scale = 1.0 + (-k1) * r2
x_u = x_d * scale
y_u = y_d * scale
src_u = x_u * f + cx
src_v = y_u * f + cy
if 0 <= src_u < size - 1 and 0 <= src_v < size - 1:
x0, y0 = int(src_u), int(src_v)
dx, dy = src_u - x0, src_v - y0
p00 = img[y0, x0]
p10 = img[y0, x0 + 1]
p01 = img[y0 + 1, x0]
p11 = img[y0 + 1, x0 + 1]
top = p00 * (1 - dx) + p10 * dx
bottom = p01 * (1 - dx) + p11 * dx
distorted[v, u] = top * (1 - dy) + bottom * dy
return Image.fromarray(distorted)
# 主流程
if __name__ == "__main__":
# 生成畸变测试图
test_img = generate_test_image(400)
test_img.save("distorted_grid.png")
# 执行校正(k1 = -0.15 与生成时一致)
corrected = correct_pincushion(test_img, k1=-0.15)
corrected.save("corrected_grid.png")
print("已生成 distorted_grid.png 和 corrected_grid.png")
运行与验证
依赖:Python 3、NumPy、Pillow。
运行方式:
python pincushion_correction.py
验证方法:
- 运行脚本后,检查当前目录下生成两张图片:
distorted_grid.png(模拟畸变输入)和corrected_grid.png(校正输出)。 - 目视检查
corrected_grid.png:网格线应恢复为平直,边缘区域不再向内弯曲。 - 定量判定:在
corrected_grid.png中,选取图像边缘处一条原本弯曲的水平网格线(例如最上方一条),其两端点与中点的垂直坐标差应小于 3 像素(原始畸变图中该差值通常大于 15 像素)。
判定标准:校正后图像中直线边缘的弯曲程度显著降低,且图像中心区域内容与输入基本一致,无明显的插值伪影或黑边(边界外区域除外)。
畸变校正流程示意
参数说明与边界
本案例中 k1 = -0.15 为生成测试图时使用的畸变系数,校正时使用相同系数可完全还原。实际应用中,k1 需通过标定获得(如棋盘格角点检测)。代码仅处理单通道或三通道图像,且假设畸变中心位于图像几何中心;对于偏心畸变或非径向分量,需扩展模型。校正后图像边缘区域因映射超出原图范围会呈现黑色,属正常现象。