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

验证方法:

  1. 运行脚本后,检查当前目录下生成两张图片:distorted_grid.png(模拟畸变输入)和 corrected_grid.png(校正输出)。
  2. 目视检查 corrected_grid.png:网格线应恢复为平直,边缘区域不再向内弯曲。
  3. 定量判定:在 corrected_grid.png 中,选取图像边缘处一条原本弯曲的水平网格线(例如最上方一条),其两端点与中点的垂直坐标差应小于 3 像素(原始畸变图中该差值通常大于 15 像素)。

判定标准:校正后图像中直线边缘的弯曲程度显著降低,且图像中心区域内容与输入基本一致,无明显的插值伪影或黑边(边界外区域除外)。

畸变校正流程示意

畸变图像 边缘向内弯曲 反向映射 径向畸变模型 x_d = x_u·(1+k₁r²+k₂r⁴) k₁ < 0 → 枕形畸变 双线性插值采样 像素填充 校正图像 网格线恢复平直 校正参数 k₁ 需与畸变程度匹配,k₁ 值越大,边缘收缩越强

参数说明与边界

本案例中 k1 = -0.15 为生成测试图时使用的畸变系数,校正时使用相同系数可完全还原。实际应用中,k1 需通过标定获得(如棋盘格角点检测)。代码仅处理单通道或三通道图像,且假设畸变中心位于图像几何中心;对于偏心畸变或非径向分量,需扩展模型。校正后图像边缘区域因映射超出原图范围会呈现黑色,属正常现象。