写在前面
在机器学习领域常需要将数据归一化后才能进行训练等操作,一维数据很容易处理,但对于二维的不规则数据,则需要一些手段,本文就是用来解决这个问题
此外,有时候希望可以用循环遍历一个不规则的二维平面,显然难以直接实现,此时将该平面映射到一个规则的矩形范围内,就能轻松实现这个目标
整体思路
- 利用同心映射将 (u,v) 映射到单位圆内的点 (x_disk,y_disk);
- 计算该点的极坐标 (r, θ);
- 计算从多边形质心沿 θ 方向与多边形边界的交点距离 R;
- 最终映射点 = 质心 + (r * R) * (cosθ, sinθ)
具体实现
import numpy as np
import math
import matplotlib.pyplot as plt
def compute_centroid(polygon):
"""计算多边形顶点的重心"""
polygon = np.array(polygon, dtype=float)
return np.mean(polygon, axis=0)
def cross2d(a, b):
"""二维向量的叉积 a x b"""
return a[0]*b[1] - a[1]*b[0]
def compute_ray_polygon_intersection(C, d, polygon):
"""
给定射线:点 C 和方向 d(单位向量),以及凸多边形(顶点按顺时针或逆时针排列),
计算射线与多边形各边的交点距离 t(满足 C + t*d 在边上,且 t>=0)。
返回所有满足条件的 t 值中的最小值,即 R(θ)。
"""
t_min = None
n = len(polygon)
for i in range(n):
v1 = np.array(polygon[i], dtype=float)
v2 = np.array(polygon[(i+1) % n], dtype=float)
e = v2 - v1 # 边向量
denom = cross2d(d, e)
if abs(denom) < 1e-8:
continue # 平行或共线,无交点
# 根据公式 t = ((v1 - C) x e) / (d x e)
t = cross2d(v1 - C, e) / denom
s = cross2d(v1 - C, d) / denom
# 判断交点是否在线段上(s in [0,1])和 t >= 0
if t >= 0 and (s >= 0 and s <= 1):
if t_min is None or t < t_min:
t_min = t
if t_min is None:
raise ValueError("射线没有与多边形相交")
return t_min
# Peter Shirley 的同心映射法
def square_to_disk(u, v):
"""
将 (u,v) ∈ [0,1]^2 映射到 (x,y) ∈ disk,
输出的 (x,y) ∈ [-1,1]²,且满足 x²+y² <= 1。
"""
# 先将 [0,1] 映射到 [-1,1]
a = 2*u - 1
b = 2*v - 1
if a == 0 and b == 0:
return 0.0, 0.0
if abs(a) > abs(b):
r = a
theta = (math.pi/4) * (b/a)
else:
r = b
theta = (math.pi/2) - (math.pi/4) * (a/b)
return r * math.cos(theta), r * math.sin(theta)
# 将单位正方形内的点 (u,v) ∈ [0,1]^2 映射到凸多边形内的点。
def map_from_unit_square(u, v, polygon):
# 多边形质心
C = compute_centroid(polygon)
# 同心映射到单位圆盘
x_disk, y_disk = square_to_disk(u, v)
r = math.sqrt(x_disk**2 + y_disk**2)
theta = math.atan2(y_disk, x_disk)
# 计算从 C 沿方向 theta 到多边形边界的距离 R
d = np.array([math.cos(theta), math.sin(theta)])
R = compute_ray_polygon_intersection(C, d, polygon)
# 映射点
mapped_point = C + (r * R) * d
return mapped_point
polygon = [(0, 1), (6, 2), (10, 5), (9, 7), (7, 8), (3, 5)]
C = compute_centroid(polygon)
print("多边形质心:", C)
for x in np.linspace(0, 1, 50):
p_mapped = map_from_unit_square(x, 0, polygon)
plt.plot(p_mapped[0], p_mapped[1], 'bo')
for y in np.linspace(0, 1, 50):
p_mapped = map_from_unit_square(1, y, polygon)
plt.plot(p_mapped[0], p_mapped[1], 'bo')
for x in np.linspace(1, 0, 50):
p_mapped = map_from_unit_square(x, 1, polygon)
plt.plot(p_mapped[0], p_mapped[1], 'bo')
for y in np.linspace(1, 0, 50):
p_mapped = map_from_unit_square(0, y, polygon)
plt.plot(p_mapped[0], p_mapped[1], 'bo')
for x in np.linspace(0.2, 0.8, 30):
p_mapped = map_from_unit_square(x, 0.2, polygon)
plt.plot(p_mapped[0], p_mapped[1], 'bo')
for y in np.linspace(0.2, 0.8, 30):
p_mapped = map_from_unit_square(0.8, y, polygon)
plt.plot(p_mapped[0], p_mapped[1], 'bo')
for x in np.linspace(0.8, 0.2, 30):
p_mapped = map_from_unit_square(x, 0.8, polygon)
plt.plot(p_mapped[0], p_mapped[1], 'bo')
for y in np.linspace(0.8, 0.2, 30):
p_mapped = map_from_unit_square(0.2, y, polygon)
plt.plot(p_mapped[0], p_mapped[1], 'bo')
plt.show()
注意
polygon中的点一定是相邻连续的