第13篇:非线性位移场------漩涡、鱼眼、水波纹与球面化
系列导语 :本文是"有趣的图像处理"系列第13篇。前一章(第12篇:仿射变换与极坐标变换)讲了仿射变换和极坐标变换------这些都是线性 或全局 的几何变换。本篇聚焦非线性位移场:每个像素有独立的位移量,由一个位移函数决定,产生漩涡、水波纹、鱼眼、球面化等视觉上更丰富的扭曲效果。
← 第12篇:仿射变换与极坐标变换\] \| \[第14篇:微分算子------一阶与二阶边缘检测 →\] \| \[系列目录
导语
仿射变换(旋转、缩放、平移)有一个共同特点:变换矩阵对整张图像是常数 ,每个像素按同样的规则移动。现实中许多有趣的视觉效果需要位移随位置变化------靠近中心的像素转动角度大,远离中心的角度小;水面中央扭曲剧烈,边缘平静如初。
这类效果的数学基础是位移场(Displacement Field) :对图像每个坐标 ( x , y ) (x, y) (x,y),定义一个偏移量 ( Δ x ( x , y ) , Δ y ( x , y ) ) (\Delta x(x,y),\ \Delta y(x,y)) (Δx(x,y), Δy(x,y)),把采样点移到 ( x + Δ x , y + Δ y ) (x + \Delta x,\ y + \Delta y) (x+Δx, y+Δy)。位移函数的形状决定了视觉效果。
本文将带你理解四种典型的非线性位移场:
- 旋涡(Twirl):旋转角度随距中心距离衰减
- 水波纹(Ripple):正弦波位移场,模拟水面波动
- 鱼眼(Fisheye):径向幂函数压缩/扩展,模拟广角镜头
- 球面化(Sphere):球面折射映射,产生凸起/凹陷效果
一、反向映射------几何变换的通用框架
1.1 正向映射 vs 反向映射
几何变换有两种实现思路:
正向映射 :对每个输入像素 ( x , y ) (x, y) (x,y),计算它在输出中的位置 ( x ′ , y ′ ) (x', y') (x′,y′),把像素值写到输出。
问题 :输出坐标 ( x ′ , y ′ ) (x', y') (x′,y′) 通常是浮点数,无法直接对应整数像素格,导致部分输出像素没有被填充(空洞)。
反向映射 (所有本篇算法使用此方案):对每个输出 像素 ( x ′ , y ′ ) (x', y') (x′,y′),计算它对应的输入 坐标 ( x , y ) (x, y) (x,y),从输入图像中采样。
I out ( x ′ , y ′ ) = I in ( T − 1 ( x ′ , y ′ ) ) I_{\text{out}}(x', y') = I_{\text{in}}\bigl(T^{-1}(x', y')\bigr) Iout(x′,y′)=Iin(T−1(x′,y′))
反向映射的输入坐标 ( x , y ) (x, y) (x,y) 是浮点数,通过双线性插值从四个整数邻居加权得到精确值。
1.2 双线性插值
设采样点 ( x , y ) (x, y) (x,y) 的整数邻居为 ( x 0 , y 0 ) , ( x 1 , y 0 ) , ( x 0 , y 1 ) , ( x 1 , y 1 ) (x_0, y_0), (x_1, y_0), (x_0, y_1), (x_1, y_1) (x0,y0),(x1,y0),(x0,y1),(x1,y1),插值权重:
w x = x − x 0 , w y = y − y 0 w_x = x - x_0, \quad w_y = y - y_0 wx=x−x0,wy=y−y0
I ( x , y ) = I ( x 0 , y 0 ) ( 1 − w x ) ( 1 − w y ) + I ( x 1 , y 0 ) ⋅ w x ( 1 − w y ) I(x, y) = I(x_0,y_0)(1-w_x)(1-w_y) + I(x_1,y_0) \cdot w_x(1-w_y) I(x,y)=I(x0,y0)(1−wx)(1−wy)+I(x1,y0)⋅wx(1−wy)
- I ( x 0 , y 1 ) ( 1 − w x ) w y + I ( x 1 , y 1 ) ⋅ w x ⋅ w y \quad\quad\quad\quad + I(x_0,y_1)(1-w_x) w_y + I(x_1,y_1) \cdot w_x \cdot w_y +I(x0,y1)(1−wx)wy+I(x1,y1)⋅wx⋅wy
二、旋涡(Twirl)------角度随距离衰减的旋转
2.1 位移场设计
旋涡效果的核心:离中心越近,旋转角度越大;超出半径 R R R 后角度为零(不扭曲):
θ ( r ) = α ⋅ max ( 0 , 1 − r R ) \theta(r) = \alpha \cdot \max\!\left(0,\ 1 - \frac{r}{R}\right) θ(r)=α⋅max(0, 1−Rr)
其中 r r r 是像素到中心的距离, α \alpha α 是最大旋转角, R R R 是效果半径。
反向映射公式 :对输出像素 ( x ′ , y ′ ) (x', y') (x′,y′),设其相对中心的极坐标为 ( r , ϕ ) (r, \phi) (r,ϕ),则采样的输入坐标为:
x src = c x + r cos ( ϕ − θ ( r ) ) x_{\text{src}} = c_x + r \cos(\phi - \theta(r)) xsrc=cx+rcos(ϕ−θ(r))
y src = c y + r sin ( ϕ − θ ( r ) ) y_{\text{src}} = c_y + r \sin(\phi - \theta(r)) ysrc=cy+rsin(ϕ−θ(r))
注意是减去 旋转角(反向映射:输出中旋转了 + θ +\theta +θ 的像素,来自输入中旋转了 − θ -\theta −θ 的位置)。
2.2 伪代码
函数 旋涡扭曲(图像 I, 最大角度 α, 中心(cx,cy), 半径 R):
对每个输出像素 (x', y'):
# 转换为相对中心的坐标
dx = x' - cx
dy = y' - cy
r = sqrt(dx² + dy²)
# 计算位置相关的旋转角度
若 r < R:
θ = α × (1 - r/R)
否则:
θ = 0(超出半径,不扭曲)
# 反向旋转,找到输入坐标
φ = atan2(dy, dx)
x_src = cx + r × cos(φ - θ)
y_src = cy + r × sin(φ - θ)
输出[y', x'] = 双线性插值(I, x_src, y_src)
返回 输出

左:原图 / 右:旋涡扭曲(α=3.0 rad,半径=图像短边一半)。图像中心区域产生螺旋状旋转,越靠近中心越强,边缘保持不变。
三、水波纹(Ripple)------正弦波位移场
3.1 位移场设计
水面波纹的本质是:每个像素在水平或垂直方向上被正弦函数偏移,偏移量随另一方向的坐标周期变化:
Δ x ( x , y ) = A x sin ( 2 π y λ x ) \Delta x(x, y) = A_x \sin\!\left(\frac{2\pi y}{\lambda_x}\right) Δx(x,y)=Axsin(λx2πy)
Δ y ( x , y ) = A y sin ( 2 π x λ y ) \Delta y(x, y) = A_y \sin\!\left(\frac{2\pi x}{\lambda_y}\right) Δy(x,y)=Aysin(λy2πx)
其中 A x , A y A_x, A_y Ax,Ay 是波幅(偏移最大值), λ x , λ y \lambda_x, \lambda_y λx,λy 是波长(一个完整波形的长度)。
反向映射 :输出像素 ( x ′ , y ′ ) (x', y') (x′,y′) 的采样来自输入坐标:
x src = x ′ + A x sin ( 2 π y ′ / λ x ) x_{\text{src}} = x' + A_x \sin(2\pi y' / \lambda_x) xsrc=x′+Axsin(2πy′/λx)
y src = y ′ + A y sin ( 2 π x ′ / λ y ) y_{\text{src}} = y' + A_y \sin(2\pi x' / \lambda_y) ysrc=y′+Aysin(2πx′/λy)
3.2 波形类型
正弦波是最常见的选择,但也可以用其他周期波形产生不同质感:
| 波形 | 视觉效果 |
|---|---|
| 正弦波 | 平滑流动的水波,最自然 |
| 三角波 | 折线状水波,略显生硬 |
| 锯齿波 | 单边急速偏移,产生撕裂感 |
| 噪声波 | 不规则随机扰动,模拟乱流 |
3.3 伪代码
函数 水波纹(图像 I, 水平波幅 Ax, 水平波长 λx,
垂直波幅 Ay, 垂直波长 λy):
对每个输出像素 (x', y'):
# 正弦波位移(反向映射)
x_src = x' + Ax × sin(2π × y' / λx)
y_src = y' + Ay × sin(2π × x' / λy)
# 边界钳位后双线性采样
x_src = clamp(x_src, 0, 宽-1)
y_src = clamp(y_src, 0, 高-1)
输出[y', x'] = 双线性插值(I, x_src, y_src)
返回 输出

左:原图 / 右:水波纹(Ax=10,λx=32,水平方向波动)。花朵和背景产生水平方向的周期性波状扭曲,模拟水面倒影的折射效果。
四、鱼眼(Fisheye)------径向幂函数
4.1 位移场设计
广角(鱼眼)镜头的特征:图像边缘被向外推,产生"桶形畸变";长焦镜头相反,边缘被向内拉,产生"枕形畸变"。
归一化径向坐标 ρ ∈ 0 , 1 \rho \in 0,1 ρ∈0,1(0 = 中心,1 = 边缘),映射到新的径向坐标:
ρ ′ = ρ k \rho' = \rho^k ρ′=ρk
- k < 1 k < 1 k<1(如 0.7): ρ ′ < ρ \rho' < \rho ρ′<ρ,像素向中心压缩 → 边缘被拉伸 → 桶形畸变(鱼眼)
- k > 1 k > 1 k>1(如 1.5): ρ ′ > ρ \rho' > \rho ρ′>ρ,像素向边缘推 → 边缘被压缩 → 枕形畸变
- k = 1 k = 1 k=1:恒等,无效果
反向映射 :输出归一化坐标 ( ξ ′ , η ′ ) ∈ − 1 , 1 2 (\xi', \eta') \in -1,1^2 (ξ′,η′)∈−1,12,其径向距离 ρ ′ = ξ ′ 2 + η ′ 2 \rho' = \sqrt{\xi'^2 + \eta'^2} ρ′=ξ′2+η′2 ,方向 ( cos ϕ , sin ϕ ) (\cos\phi, \sin\phi) (cosϕ,sinϕ),对应输入:
ξ src = ρ ′ 1 / k ⋅ cos ϕ , η src = ρ ′ 1 / k ⋅ sin ϕ \xi_{\text{src}} = \rho'^{1/k} \cdot \cos\phi, \quad \eta_{\text{src}} = \rho'^{1/k} \cdot \sin\phi ξsrc=ρ′1/k⋅cosϕ,ηsrc=ρ′1/k⋅sinϕ
4.2 伪代码
函数 鱼眼变换(图像 I, 畸变指数 k):
cx, cy = 图像中心
R = min(cx, cy) # 归一化半径
对每个输出像素 (x', y'):
# 归一化到 [-1, 1]
ξ = (x' - cx) / R
η = (y' - cy) / R
ρ = sqrt(ξ² + η²)
若 ρ > 1: # 圆形边界外不变形
x_src, y_src = x', y'
否则:
# 反向径向映射
ρ_src = ρ ^ (1/k)
若 ρ > 0:
scale = ρ_src / ρ
否则:
scale = 1
x_src = cx + ξ × scale × R
y_src = cy + η × scale × R
输出[y', x'] = 双线性插值(I, x_src, y_src)
返回 输出

左:原图 / 右:鱼眼变换(k=0.7,桶形畸变)。图像中心区域被放大,边缘向外弯曲,产生广角镜头的典型视觉效果。
五、球面化(Sphere)------折射映射
5.1 物理模型
把图像想象成贴在球面内壁,透过球面(折射率为 n n n)观看,近中心区域看起来像被凸透镜放大,边缘逐渐过渡到正常。
归一化坐标 ( ξ , η ) ∈ − 1 , 1 2 (\xi, \eta) \in -1,1^2 (ξ,η)∈−1,12,球面上对应的 Z 坐标(球面方程 ξ 2 + η 2 + z 2 = 1 \xi^2+\eta^2+z^2=1 ξ2+η2+z2=1):
z = 1 − ξ 2 − η 2 z = \sqrt{1 - \xi^2 - \eta^2} z=1−ξ2−η2
法线向量 n = ( ξ , η , z ) \mathbf{n} = (\xi, \eta, z) n=(ξ,η,z),入射光线方向 d = ( 0 , 0 , − 1 ) \mathbf{d} = (0, 0, -1) d=(0,0,−1),折射后的方向用斯涅尔定律计算。
简化实现(JHLabs 版本):直接用 z z z 做径向压缩,模拟折射效果:
x src = ξ z ⋅ n ⋅ R + c x , y src = η z ⋅ n ⋅ R + c y x_{\text{src}} = \frac{\xi}{z \cdot n} \cdot R + c_x, \quad y_{\text{src}} = \frac{\eta}{z \cdot n} \cdot R + c_y xsrc=z⋅nξ⋅R+cx,ysrc=z⋅nη⋅R+cy
n > 1 n > 1 n>1 产生凸起(放大中心), n < 1 n < 1 n<1 产生凹陷(缩小中心)。
5.2 伪代码
函数 球面化(图像 I, 折射率 n, 中心(cx,cy), 半径 R):
对每个输出像素 (x', y'):
# 归一化到球面坐标
ξ = (x' - cx) / R
η = (y' - cy) / R
ρ² = ξ² + η²
若 ρ² > 1: # 球形范围外不变形
x_src, y_src = x', y'
否则:
# 球面 Z 坐标
z = sqrt(1 - ρ²)
# 折射后的采样坐标
x_src = cx + (ξ / (z × n)) × R
y_src = cy + (η / (z × n)) × R
# 边界保护
x_src = clamp(x_src, 0, 宽-1)
y_src = clamp(y_src, 0, 高-1)
输出[y', x'] = 双线性插值(I, x_src, y_src)
返回 输出

左:原图 / 右:球面化(n=1.5,凸起)。图像中心被放大膨胀,向边缘逐渐过渡到正常比例,产生透过玻璃球观看的折射效果。
六、位移场的统一框架
6.1 通用反向映射算法
所有非线性几何变换都共享同一个执行框架:
函数 通用反向映射(图像 I, 位移函数 f):
对每个输出像素 (x', y'):
# f 定义了反向位移:输出坐标 → 输入坐标
(x_src, y_src) = f(x', y')
# 双线性插值采样
输出[y', x'] = 双线性插值(I, x_src, y_src)
返回 输出
不同效果只是 f f f 的不同实现:
| 效果 | 位移函数 f f f 的形式 |
|---|---|
| 旋涡 | 极坐标旋转,角度随 r r r 衰减 |
| 水波纹 | 正弦波位移,幅度和波长控制 |
| 鱼眼 | 径向幂函数 ρ → ρ 1 / k \rho \to \rho^{1/k} ρ→ρ1/k |
| 球面化 | 球面折射公式 |
| 挤压 | 径向线性或 Sigmoid 压缩 |
| 万花筒 | 镜像折叠 + 扇形重复 |
6.2 OpenCV remap 加速
OpenCV 的 cv2.remap(src, map_x, map_y) 就是通用反向映射的高效实现:
# 预先计算整张图的位移场(向量化,一次完成)
map_x = float32 数组,每个元素是对应输出像素的 x_src
map_y = float32 数组,每个元素是对应输出像素的 y_src
# 一次调用完成所有像素的双线性插值
output = cv2.remap(input, map_x, map_y, cv2.INTER_LINEAR)
相比逐像素 Python 循环,cv2.remap 快约 100--400 倍(SIMD + 多线程),是生产环境的标准实现。
七、实现要点
7.1 边界处理策略
反向映射的采样坐标可能超出图像范围,常见处理方式:
| 策略 | 描述 | 适用场景 |
|---|---|---|
| 钳位(Clamp) | 超出边界后取最近边界像素 | 大多数扭曲效果 |
| 环绕(Wrap) | 取模后循环 | 平铺纹理 |
| 镜像(Mirror) | 折返采样 | 无缝纹理 |
| 透明(Zero) | 填充为 0(黑色/透明) | 需要明确边界的效果 |
7.2 效果半径的归一化
JHLabs 的实现通常将半径归一化为图像短边的一半,使效果在不同分辨率图像上视觉一致。建议采用相对单位(0--1)而非绝对像素值定义半径参数。
7.3 位移场的向量化计算
Python 循环逐像素计算极慢。使用 NumPy 的网格计算可以将整张图的位移场一次性算出:
# 生成坐标网格
y_grid, x_grid = np.meshgrid(np.arange(高), np.arange(宽), indexing='ij')
# 向量化计算位移(以旋涡为例)
dx = x_grid - cx
dy = y_grid - cy
r = np.sqrt(dx**2 + dy**2)
θ = α × np.maximum(0, 1 - r/R)
φ = np.arctan2(dy, dx)
map_x = cx + r * np.cos(φ - θ) # 整张图一次完成
map_y = cy + r * np.sin(φ - θ)
output = cv2.remap(input, map_x.astype(np.float32),
map_y.astype(np.float32), cv2.INTER_LINEAR)
结语
本文建立了非线性几何变换的完整理解:
- ✅ 反向映射:通用框架,避免空洞,配合双线性插值保持平滑
- ✅ 旋涡 :极坐标旋转角度随距离衰减, θ ( r ) = α ( 1 − r / R ) \theta(r) = \alpha(1-r/R) θ(r)=α(1−r/R)
- ✅ 水波纹:正弦波位移场,幅度控制强度,波长控制密度
- ✅ 鱼眼 :径向幂函数 ρ → ρ k \rho \to \rho^k ρ→ρk, k < 1 k<1 k<1 鱼眼, k > 1 k>1 k>1 枕形
- ✅ 球面化 :球面折射公式,折射率 n n n 控制凸起/凹陷
- ✅ 统一框架 :所有效果都是"位移函数 f f f + 双线性插值",
cv2.remap提供高效实现
下一章进入边缘检测,从几何变换回到图像分析:微分算子如何把连续亮度变化转化为精确的边缘轮廓。
系列文章导航:
- 📖 当前位置:第13篇 - 非线性位移场
- ➡️ 下一篇:第14篇 - 微分算子:一阶与二阶边缘检测
- 🔙 上一篇:第12篇 - 仿射变换与极坐标变换
- 🔙 系列首页:总览篇
标签 :图像处理 几何变换 旋涡 水波纹 鱼眼 球面化 反向映射 双线性插值 位移场 OpenCV Python
本文是"有趣的图像处理"系列的第13篇,所有算法基于 Python + OpenCV + NumPy 实现,完整代码见 python_filters/distortion/。