第13篇:非线性位移场——漩涡、鱼眼、水波纹与球面化

第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 提供高效实现

下一章进入边缘检测,从几何变换回到图像分析:微分算子如何把连续亮度变化转化为精确的边缘轮廓。


系列文章导航

标签图像处理 几何变换 旋涡 水波纹 鱼眼 球面化 反向映射 双线性插值 位移场 OpenCV Python


本文是"有趣的图像处理"系列的第13篇,所有算法基于 Python + OpenCV + NumPy 实现,完整代码见 python_filters/distortion/

相关推荐
金牌归来发现妻女流落街头1 小时前
【LeetCode 第207题】
算法·leetcode·拓扑·领接表
熬夜敲代码的猫2 小时前
AVL树(C++详解版)
数据结构·c++·算法
-To be number.wan2 小时前
算法日记 | STL-MAP
c++·算法
cjp5602 小时前
015. UG 二次开发,拉伸草图生成实体类,高级草图类封装
算法
Eric 辰东2 小时前
【C 语言程序的编译和链接】详解编译链接过程
c语言·笔记·算法·学习方法
迈巴赫车主2 小时前
蓝桥杯21247弹跳鞋java
java·开发语言·数据结构·算法·职场和发展·蓝桥杯
weixin_468466852 小时前
相机标定三大坐标系新手入门指南
图像处理·人工智能·相机标定·机器视觉·数字图像·工业自动化·光学系统
jghhh012 小时前
基于 Weiler-Atherton 算法的多边形裁剪程序实现
算法
不爱吃糖の糖糖2 小时前
RAG 04:向量数据库与索引算法
数据库·算法