资讯动态

向量投影原理详解:从点积到平面投影的完整推导与numpy实现

发布时间:2026/10/17 13:23:07 来源:尧图企业网站定制
向量投影这件事说简单是真简单——一个公式、一次点积、一个比例系数考试的时候能写出来就过关了。但说复杂也真复杂我最早做三维几何处理的时候遇到“把一个向量投影到某个平面上”的需求当时第一反应是翻书找公式翻了半天也没找到现成的后来才反应过来这东西根本不需要背公式想明白几何关系就能自己推出来。这篇就好好聊聊向量投影的底层逻辑重点放在向量在平面上的投影怎么做会带上具体推导和可以直接抄的代码实现。很多人在学线性代数的时候投影这个概念就是“背公式”一带而过但它在图形学、物理模拟、数据分析这些领域里几乎是天天都要用的基础操作。做物理引擎的时候要把速度分解成平行于地面的分量和垂直于地面的分量做降维算法的时候要把高维数据往低维子空间上做投影做光照计算的时候法向量和光线方向的投影关系就更直接了。可以说投影是连接线性代数理论和实际工程问题的桥梁之一。这篇文章适合谁正在学线性代数、想搞懂公式背后原理的学生以及在实际开发中需要用投影解决问题的工程师和算法爱好者。我不会停留在“套公式”层面而是把从点积到平面投影的完整思路链拆开每一步都讲清楚为什么这么做最后给出一份可以验证结果的实现代码。1. 从头理解向量的投影到底在算什么要理解投影先要建立一个直观的认识。想象一束平行光垂直照射下来一个倾斜的木棍在地面上留下影子这个影子就是木棍在“地面平面”上的投影。不过向量世界里的投影要比这个“影子”抽象一点它不依赖光线而是依赖你要“压向”的那条直线或那个平面的方向。1.1 从标量投影到矢量投影向量投影有两种表达形式一种是标量投影一种是矢量投影很多人学的时候会混在一起其实区分很简单。标量投影描述的是“影子有多长”它是一个数值。向量 a 在向量 b 方向上的标量投影长度等于 |a|·cosθ其中 θ 是两个向量的夹角。这个长度可能是正数也可能是负数——当夹角超过90度时投影方向和 b 的方向相反标量投影就是负的。这里要特别注意标量投影不是一个绝对的“长度”它是一个带符号的值。矢量投影则是一个完整的向量它不仅包含长度信息还包含方向信息。矢量投影的公式是proj_b(a) (a·b / b·b) × b这个公式读起来就是先把 a 和 b 做点积再除以 b 和自身的点积得到一个比例系数然后用这个系数去缩放 b得到的就是 a 在 b 方向上的投影向量。结果向量的方向和 b 同向比例系数为正时或反向比例系数为负时。标量投影和矢量投影的关系也很直接矢量投影的长度绝对值就等于标量投影的绝对值。矢量投影除以 b 的单位向量就能得到标量投影。1.2 为什么点积能算投影这是理解整个投影概念的关键点点积的代数定义是 a·b a₁b₁ a₂b₂ ... aₙbₙ几何定义是 a·b |a||b|cosθ。为什么这两种定义是等价的这在教科书里通常是用余弦定理证明的但不用记证明过程只要抓住一个核心结论点积天然包含了“夹角信息”。如果你把 b 换成单位向量 u b/|b|那么 a·u |a||b|cosθ / |b| |a|cosθ这个结果恰好就是 a 在 b 方向上的标量投影长度。这时候回头看矢量投影公式就一目了然了a·b / b·b 其实就是 (a·u) / |b|也就是“单位方向上的投影长度除以 b 的长度”。这个比例系数乘上 b得到的就是那个在 b 方向上的投影向量。如果这一段觉得抽象直接记结论也行点积的结果是“一个向量在另一个向量方向上贡献了多少”而投影就是把这个“贡献”提取出来变成一个指向明确方向的矢量。1.3 投影的几何意义把向量拆成两个正交分量投影还有一个特别重要的几何视角——正交分解。任何一个向量 a都可以沿另一个向量 b 的方向拆成两个部分一部分平行于 b这就是投影另一部分垂直于 b。写成公式就是a a_parallel a_perpendicular其中 a_parallel 就是 proj_b(a)而 a_perpendicular a - a_parallel。这个分解特别有用因为拆出来的两个分量是正交的它们彼此夹角90度点积为0。这个特性在后续推导平面投影时是核心工具。生活里的类比就是斜着拉一个箱子拉力可以分解成让箱子往前走的分量和让箱子往上抬的分量这两个分量的作用方向完全不同互不干扰。向量投影就是把这个“分解”动作数学化了。2. 动手算一遍二维平面上的投影推导理解了公式之后还是需要自己手动推演一遍才能真正建立起几何直觉。我建议你在纸上跟着下面的步骤画一画坐标系效果会好很多。2.1 从几何关系推导投影向量假设有两个二维向量 a (3, 4) 和 b (2, 1)。我们的目标是找出 a 在 b 方向上的投影向量。第一步先算点积 a·ba·b 3×2 4×1 6 4 10第二步算 b·b 也就是 |b|²|b|² 2² 1² 4 1 5第三步得到比例系数 tt (a·b) / (b·b) 10 / 5 2第四步得到投影向量proj_b(a) t × b 2 × (2, 1) (4, 2)这个结果说明a 在 b 方向上的投影是 (4, 2)它的方向与 b 完全一致长度是 b 的两倍。2.2 验证正交分解的正确性投影做完了怎么知道自己算得对不对验证方法很笨但很可靠把 a 拆成投影分量和垂直分量然后检查它们是否正交。垂直分量 a - proj_b(a) (3, 4) - (4, 2) (-1, 2)然后检查 (4, 2) 和 (-1, 2) 的点积(4, 2)·(-1, 2) 4×(-1) 2×2 -4 4 0点积为零说明两个分量确实是正交的。这个小验证是以后做任何投影计算的“体检项目”——如果算完之后没法满足正交性那大概率是哪一步算错了或者浮点数精度问题累积太严重。这里有一个非常关键的操作当 b 的方向和 a 的方向夹角是锐角时投影系数是正的夹角是钝角时投影系数是负的。你可以自己试一下把 b 改成 (-2, -1)算出来的投影就会变成 (-4, -2)方向和 a 的镜像方向一致你会发现这其实反映的是“负方向上的投影”。2.3 如果目标向量是单位向量会怎样很多工程实践里我们预先会对目标方向做归一化处理也就是把 b 变成单位向量 u b/|b|。这种情况下投影公式会简化成proj_u(a) (a·u) × u之所以简化是因为 u·u 1比例系数不需要再除以模长了。这个形式更干净也更容易记住。实际写代码做矢量投影的时候先归一化目标向量是常见做法原因就在这里——少一次除法代码更稳也不容易丢失精度。建议任何时候你需要在某个方向上反复做多次投影先把方向向量归一化不仅公式简洁还能避免重复计算模长。这是我在实际工作中养成的小习惯。3. 升级到三维向量在平面上的投影计算二维投影是在一条直线上做三维的平面投影则是把一个向量压到一个平面里。这条线从“一维目标”变成“二维目标”听起来好像复杂了不少但思路其实完全一样关键在于你要找到那个“正交分解”的隐含关系。3.1 定义清楚向量在平面上的投影是什么平面可以用法向量 n 来唯一定义平面上的所有向量都与 n 正交。当一个空间向量 v 要投影到这个平面上时我们要找的是一个位于该平面内的向量 v_parallel它满足一个条件v - v_parallel 垂直于这个平面。v - v_parallel 垂直于平面等价于说这个差值向量是沿法向量 n 方向的。也就是说v 可以唯一拆解成两项v v_parallel v_perp其中 v_perp 是垂直于平面的分量它肯定与 n 平行。那么v_perp 怎么求其实就是把 v 往 n 方向上投影——这刚好回到了前面讲过的直线投影问题v_perp proj_n(v) (v·n / n·n) × n那么平面内分量就自然而然地出来了v_parallel v - v_perp v - (v·n / n·n) × n这样一来向量在平面上的投影问题就转化成了两个步骤先往法向量方向投影再做一次减法。这里不需要记忆任何新公式全是第一性原理推导的结果。3.2 完整计算一个三维实例用具体数字走一遍流程。设向量 v (1, 2, 3)平面由法向量 n (1, 2, -1) 定义。这个平面的方程是 x 2y - z 0平面上任意向量都必须满足这个方程。第一步求 v 和 n 的点积v·n 1×1 2×2 3×(-1) 1 4 - 3 2第二步求 n·n |n|²n·n 1² 2² (-1)² 1 4 1 6第三步算出垂直分量 v_perpv_perp (2/6) × (1, 2, -1) (1/3, 2/3, -1/3)第四步得到 v 在平面上的投影 v_parallelv_parallel v - v_perp (1, 2, 3) - (1/3, 2/3, -1/3) (2/3, 4/3, 10/3)第五步验证。先检查 v_parallel 是否在平面内也就是看它是否与法向量 n 正交v_parallel·n (2/3)×1 (4/3)×2 (10/3)×(-1) 2/3 8/3 - 10/3 0点积为0妥妥地在平面内。再检查分解是否成立v_parallel v_perp (2/3, 4/3, 10/3) (1/3, 2/3, -1/3) (1, 2, 3) v完全还原验证通过。整个计算过程只用了四则运算和点积没有任何新概念。3.3 单位法向量的特殊情况如果法向量 n 事先经过了归一化处理|n| 1那么 n·n 1公式会变得更加简洁v_parallel v - (v·n) × n这时候只需要一次点积、一次数乘、一次减法计算量是四种平面投影方案里最小的。这也是为什么很多图形学代码里平面信息往往不光存法向量还额外存一个归一化的法向量——就是方便这种高频计算的。不过要提醒的是归一化法向量本身也是有成本的如果只是偶尔做一次平面投影用原始的非归一化公式完全没问题反而能省掉归一化的开销。工程选择的本质就是在不同场景下做性能和代码可读性之间的权衡。4. 写个函数批量做投影numpy实现与结果对照理论推完了实操还是得落到代码上。这里我用 numpy 写一套足够通用的投影函数包含标量投影、直线投影、平面投影三个层次方便你在不同场景下按需调用。代码不长但每个函数的用途和边界条件我都会注释清楚。import numpy as np def scalar_projection(a, b): 计算向量 a 在向量 b 方向上的标量投影带符号长度 a np.asarray(a, dtypefloat) b np.asarray(b, dtypefloat) norm_b np.linalg.norm(b) if norm_b 0: raise ValueError(b 不能是零向量) return np.dot(a, b) / norm_b def vector_projection(a, b): 计算向量 a 在向量 b 方向上的矢量投影 a np.asarray(a, dtypefloat) b np.asarray(b, dtypefloat) b_dot_b np.dot(b, b) if b_dot_b 0: raise ValueError(b 不能是零向量) return (np.dot(a, b) / b_dot_b) * b def plane_projection(v, n): 计算向量 v 在法向量为 n 的平面上的投影 v np.asarray(v, dtypefloat) n np.asarray(n, dtypefloat) n_dot_n np.dot(n, n) if n_dot_n 0: raise ValueError(n 不能是零向量) v_perp (np.dot(v, n) / n_dot_n) * n return v - v_perp4.1 用上面的实例做一次跑分验证把 3.2 节的手工计算实例喂给函数看看和手工推导结果是否一致v np.array([1.0, 2.0, 3.0]) n np.array([1.0, 2.0, -1.0]) result plane_projection(v, n) print(平面投影结果:, result) print(与法向量点积(应为0):, np.dot(result, n))预期输出平面投影结果: [0.66666667 1.33333333 3.33333333] 与法向量点积(应为0): 0.0这里 [0.66666667, 1.33333333, 3.33333333] 就是 (2/3, 4/3, 10/3) 的浮点数表示。如果你的输出里点积不是严格的0而是像 1.2e-16 这种极小值那也很正常这是浮点数运算的正常误差范围不是代码逻辑错了。4.2 批量处理多组数据时要注意什么真实场景下很少只投影一个向量更多是对几千个向量做批量投影。这时候有几个优化思路可以参考。第一如果是对同一组平面重复投影不同向量可以预先计算好法向量信息。比如把法向量归一化这样每个向量只需要一次点积和一次减法更进一步如果对性能极敏感可以把 v - (v·n)·n 的线性变换合成为一个投影矩阵 P I - nnᵀ然后直接做矩阵乘法numpy 底层会用 BLAS 加速。第二要留意输入数据的类型。np.asarray(dtypefloat) 这一步很重要如果你的输入是整数型的 numpy 数组点积和除法会触发类型转换问题轻则精度丢失重则直接报错。我踩过这个坑所以函数开头都加了类型强制转换。第三批量处理时如果存在异常数据比如某些行是零向量最好提前筛掉否则会出现除零错误。一个健壮的做法是先算模长把模长接近0的行置为 NaN 或者直接过滤掉而不是等函数报错再处理。# 批量处理示例 vectors np.array([ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [-1.0, 0.0, 2.0] ]) n np.array([1.0, 2.0, -1.0]) norm_n n / np.linalg.norm(n) projected vectors - np.outer(vectors, norm_n) norm_n简化操作对每行向量 v_i计算 v_i - (v_i·n̂)·n̂等价于对向量矩阵做一次低秩修正。这个批量版本的执行效率比 for 循环高很多数据量大的时候感受会非常明显。4.3 投影在工程里的几个典型用途有了现成函数它到底能干什么这里列一下我在实际开发中用到过的一些场景每个都能直接对应到上面的某个函数。物理引擎里处理物体沿斜面运动时需要把重力加速度分解成沿斜面方向和垂直斜面方向。沿斜面方向的分量决定物体怎么加速滑行垂直方向分量则被支持力抵消。这个分解本质上就是一次平面投影计算。图形学里做顶点法线贴图时经常要把切线空间里的向量投影到某个平面上或者把光照方向拆成表面切平面内的分量和法线方向的分量才能计算高光强度。光照模型中的半程向量分解也大量依赖这种正交分解。数据降维里主成分分析的核心思想是把原始数据投影到主成分方向上投影后数据的方差最大化。每一步迭代都在做投影运算只不过目标方向不是某个固定向量而是通过特征值分解找出来的。数值优化里投影梯度法处理带约束的优化问题每次迭代都要把梯度投影到约束条件定义的可行域切平面内。如果约束是一个球面、平面或其他流形投影运算都是其中的关键步骤。这些场景的共同特点是都需要把一个向量“压”到一个子空间里同时保持它在子空间内的分量尽可能不变而丢失的只是垂直于子空间的那部分。理解和掌握投影等于掌握了空间分解的语言很多复杂问题都能用“投影正交补”这种视角重新简化。5. 常见问题与避坑指南投影计算看起来简单但实操中的坑不少我从自己的经验和身边的案例里整理了几条高频问题方便你排查。5.1 踩坑记录这些坑我都替你试过了最经典的坑是把向量的模长和投影长度搞混。模长是向量的固有属性和参考方向无关投影长度则依赖参考方向。有人会在计算标量投影时直接用 |a| 当结果忽略了 cosθ 的影响夹角一大误差就很离谱。第二个坑是符号问题。标量投影是有符号的夹角超过90度会得到负值。很多人在做物理分解时忘记了这一点导致力的方向判断错误。矢量投影则不同它的“方向”已经包含在向量本身里了不需要额外讨论符号。第三个坑是零向量问题。如果投影目标方向 b 是零向量公式分母就是零程序直接崩。实际数据里出现零向量很常见尤其实时计算中一帧数据异常就可能导致全线飘红。代码里做防御性检查是必须的。第四个坑和浮点数精度有关。做正交验证时点积经常会得到 1e-16 这种“接近零但不完全零”的结果这其实是正常的不代表算错了。但如果误差累积到1e-4级别就要怀疑是不是归一化步骤出了问题或者输入数据本身就不是精确的单位向量。5.2 快速自查清单算完投影之后建议按这套流程检查一遍检查项操作期望结果投影方向用投影向量与目标方向点积比例系数为正则同向为负则反向正交性投影向量与垂直分量点积接近0误差在1e-10以内还原性投影向量 垂直分量 - 原向量接近零向量平面归属平面投影结果与平面法向量点积接近0这套清单看起来朴素但每次遇到“算出来的结果不对”问题时我都是靠它快速定位错误来源的。如果前三条全挂大概率是公式里除错了如果只挂最后一条那可能是平面定义本身有问题。5.3 一些提升直觉的小练习如果想让自己的投影直觉更敏锐可以做一些简单的可视化练习用二维坐标系最合适。选两个向量 a 和 b随意画在网格纸上然后用上面的公式计算投影。画完后用直尺量一下投影向量的长度和方向和计算结果对一下你会发现“哦原来投影真的就是影子”的感觉。然后把 b 旋转从0度转到90度再转到钝角范围观察投影的长度变化和方向翻转你就能直观理解为什么标量投影会有正负了。再进阶一点尝试把 a 固定让 b 绕着原点转一圈投影向量在空间里会画出一个什么样轨迹答案是投影的末端点在 b 方向的直线上来回滑动这正是“影子长度随夹角变化”的轨迹。这种图形化的直觉一旦建立起来以后再遇到投影相关的问题脑子里的图景会自动出现而不是死记硬背公式。6. 最后的实操建议这篇文章从点积出发讲清楚了标量投影、矢量投影、二维直线投影、三维平面投影的完整推导链。核心就一句话任何投影问题都可以转化成“往某个方向做一维投影 从原向量里减掉它”的组合。就我个人使用习惯来说最常用的其实是平面投影那一个函数很多看起来复杂的几何问题只要锁定平面的法向量剩下的就是十几行代码的事。建议你也实现一套自己的投影工具库把标量投影、矢量投影、平面投影、投影矩阵这些基础操作都封装好后面不管做什么项目随手就能拿出来用。最后还有一个扩展方向值得你继续往下探索投影矩阵的本质意义。把 A 投影到 S span{b₁, b₂, ..., bₖ} 上实际上是在求一个最小二乘解。如果你以后碰到线性回归、信号处理里的逼近问题会发现它们本质上都是在做投影只不过投影的目标空间从一条直线、一个平面变成了更高维的子空间。理解这一点就能把很多看似无关的领域串联起来这也是向量投影真正迷人的地方。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑