CCG 06-04:Jacobian 与畸变热图

本页只解决一个问题

怎样逐面计算奇异值、翻转和 Beltrami 畸变并正确可视化?

本页是原长篇讲义的单概念版本。第一次阅读只追踪本页定义的对象;看见未掌握的前置符号时,先沿链接退回,不要跳过定义硬背结论。

开始前

11. 逐三角形 Jacobian

原三角形局部边矩阵

Xt=[qj−qi  qk−qi],X_t=[q_j-q_i\;q_k-q_i],

UV 边矩阵

Ut=[zj−zi  zk−zi].U_t=[z_j-z_i\;z_k-z_i].

映射 Jacobian

Jt=UtXt−1.J_t=U_tX_t^{-1}.

def triangle_jacobian(points_3d, uv_triangle):
    q = local_triangle_2d(*points_3d)
    X = np.column_stack((q[1] - q[0], q[2] - q[0]))
    U = np.column_stack((uv_triangle[1] - uv_triangle[0],
                         uv_triangle[2] - uv_triangle[0]))
    return U @ np.linalg.inv(X)


def parameterization_metrics(vertices, faces, uv):
    dets, conformal_K, mu_abs, areas_3d = [], [], [], []

    for i, j, k in faces:
        points = vertices[[i, j, k]]
        J = triangle_jacobian(points, uv[[i, j, k]])
        sigma = np.linalg.svd(J, compute_uv=False)
        det = np.linalg.det(J)
        K = np.inf if sigma[1] <= 1e-15 else sigma[0] / sigma[1]
        mu = 1.0 if not np.isfinite(K) else (K - 1.0) / (K + 1.0)
        area = 0.5 * np.linalg.norm(
            np.cross(points[1] - points[0], points[2] - points[0])
        )

        dets.append(det)
        conformal_K.append(K)
        mu_abs.append(mu)
        areas_3d.append(area)

    dets = np.asarray(dets)
    conformal_K = np.asarray(conformal_K)
    mu_abs = np.asarray(mu_abs)
    areas_3d = np.asarray(areas_3d)

    finite_K = conformal_K[np.isfinite(conformal_K)]
    return {
        "flipped": int(np.sum(dets < 0.0)),
        "degenerate": int(np.sum(np.abs(dets) <= 1e-14)),
        "K_median": float(np.median(finite_K)),
        "K_p95": float(np.percentile(finite_K, 95)),
        "K_max": float(np.max(finite_K)),
        "mu_p95": float(np.percentile(mu_abs, 95)),
        "log_area_p95_abs": float(
            np.percentile(np.abs(np.log(np.maximum(np.abs(dets), 1e-300))), 95)
        ),
        "face_K": conformal_K,
        "face_det": dets,
        "face_area": areas_3d,
    }

11.1 为什么同时看KK 与det⁡J\det J

  • K=1K=1 表示两个方向缩放相同,但可能是统一放大、缩小或反射;
  • det⁡J\det J 给面积缩放和方向;
  • 反射满足K=1K=1 但det⁡J<0\det J<0;
  • ∣μ∣|\mu| 与KK 等价,只是压缩到[0,1)[0,1)。

12. 可视化网格与误差热图

def plot_parameterizations(faces, results):
    fig, axes = plt.subplots(
        len(results), 2, figsize=(11, 4 * len(results)), constrained_layout=True
    )
    if len(results) == 1:
        axes = np.asarray([axes])

    for row, (name, uv, metrics, diagnostics) in enumerate(results):
        tri = mtri.Triangulation(uv[:, 0], uv[:, 1], faces)

        axes[row, 0].triplot(tri, color="black", linewidth=0.35)
        axes[row, 0].set_aspect("equal")
        axes[row, 0].set_title(
            f"{name}: flipped={metrics['flipped']}, "
            f"residual={diagnostics['linear_residual_inf']:.2e}"
        )

        color = np.log(np.maximum(metrics["face_K"], 1.0))
        image = axes[row, 1].tripcolor(
            tri, facecolors=color, shading="flat", cmap="magma"
        )
        axes[row, 1].triplot(tri, color="white", alpha=0.18, linewidth=0.2)
        axes[row, 1].set_aspect("equal")
        axes[row, 1].set_title("log conformal distortion log(K)")
        fig.colorbar(image, ax=axes[row, 1])

    plt.show()

读完必须能回答

  1. 不看公式,用自己的话回答开头的核心问题。
  2. 指出本页最重要公式中每个符号的类型:标量、向量、矩阵、映射还是集合。
  3. 找出定理成立所需的假设,并举一个删除某条假设后可能失败的例子。
  4. 对最小例子手算一遍;若本页含代码,再比较手算与程序输出。

前后课程

上一课:LSCM 局部矩阵与锚点 | 下一课:完整运行、结果解释与测试