Rust图像处理第19节-RGB 色彩矩阵变换:矩阵 × 向量 = 像素变换

4 阅读4分钟

RGB 色彩矩阵变换:矩阵 × 向量 = 像素变换

🦀 Rust + WASM 实战系列 第 19 篇 阅读时间:约 5 分钟 | 实战可运行

📌 写在前面

Part 1 任务 5 讲了 7 种"单色滤镜"——sepia、灰度、暖色调……每个都是手写 RGB 系数

r = 0.393*r_old + 0.769*g_old + 0.189*b_old
g = 0.349*r_old + 0.686*g_old + 0.168*b_old
b = 0.272*r_old + 0.534*g_old + 0.131*b_old

7 种滤镜 = 7 套系数 = 7 个不同公式——重复、难记。

这一篇用"矩阵 × 向量"统一——1 个公式搞定所有 7 种滤镜(和 Part 1 任务 5 完全相同的效果)。


🚀 TL;DR

RGB 像素 = 3 维向量 (R,G,B)(R, G, B),变换矩阵 = 3×3 系数表,新像素 = 矩阵 × 向量

[RGB]=[abcdefghi][RGB]\begin{bmatrix} R' \\ G' \\ B' \end{bmatrix} = \begin{bmatrix} a & b & c \\ d & e & f \\ g & h & i \end{bmatrix} \begin{bmatrix} R \\ G \\ B \end{bmatrix}

核心代码 5 行

let m = Matrix3::new(a, b, c, d, e, f, g, h, i);
let rgb = Vector3::new(r, g, b);
let new_rgb = m * rgb;

(计算使用 nalgebra


📖 目录

  1. 前置基础:矩阵 × 向量
  2. RGB 像素 = 3 维向量
  3. 变换矩阵 = 一张 3×3 系数表
  4. 4 个例子(亮度/对比/灰度/色调)
  5. 关键代码
  6. 前端效果展示
  7. 踩坑提醒
  8. 接下来

一、前置基础:矩阵 × 向量(可跳过)

下面只讲一个运算——如果你记得,可以直接跳到 §二

矩阵 = 数字表格

M=[0.50000.50000.5]M = \begin{bmatrix} 0.5 & 0 & 0 \\ 0 & 0.5 & 0 \\ 0 & 0 & 0.5 \end{bmatrix}

一张 3×3 的数表。没什么神秘的

向量 = 一列数

v=[200150100]v = \begin{bmatrix} 200 \\ 150 \\ 100 \end{bmatrix}

RGB 三通道就是一个 3 维向量。

矩阵 × 向量(核心)

规则:矩阵的每一行和向量的每个元素对应相乘,再加起来:

[0.50000.50000.5][200150100]=[0.5×2000.5×1500.5×100]=[1007550]\begin{bmatrix} 0.5 & 0 & 0 \\ 0 & 0.5 & 0 \\ 0 & 0 & 0.5 \end{bmatrix} \begin{bmatrix} 200 \\ 150 \\ 100 \end{bmatrix} = \begin{bmatrix} 0.5 \times 200 \\ 0.5 \times 150 \\ 0.5 \times 100 \end{bmatrix} = \begin{bmatrix} 100 \\ 75 \\ 50 \end{bmatrix}

就这么简单:一行算一个值,3 行 = 3 个新值


二、RGB 像素 = 3 维向量

每个像素的 RGB 三通道 = 一个 3 维向量:

pixel=[RGB]=[200150100]\text{pixel} = \begin{bmatrix} R \\ G \\ B \end{bmatrix} = \begin{bmatrix} 200 \\ 150 \\ 100 \end{bmatrix}

三、变换矩阵 = 一张 3×3 系数表

M=[m00m01m02m10m11m12m20m21m22]M = \begin{bmatrix} m_{00} & m_{01} & m_{02} \\ m_{10} & m_{11} & m_{12} \\ m_{20} & m_{21} & m_{22} \end{bmatrix}

9 个数字按行优先(row-major)排列:

m00 m01 m02
m10 m11 m12
m20 m21 m22

矩阵的每一行 = 怎么算新通道

  • 第 1 行RR')= 怎么算新红色
  • 第 2 行GG')= 怎么算新绿色
  • 第 3 行BB')= 怎么算新蓝色

每一行有 3 个系数 a,b,ca, b, c这 3 个系数告诉"用多少原 R、多少原 G、多少原 B 混合"


4、4 个例子

每张表 = 一个预设矩阵

例 1:单位矩阵(不变)

I=[100010001]I = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}

新像素 = 旧像素。什么都不做

例 2:对比度增强(对角线 ×1.5)

S=[1.50001.50001.5]S = \begin{bmatrix} 1.5 & 0 & 0 \\ 0 & 1.5 & 0 \\ 0 & 0 & 1.5 \end{bmatrix}

每个通道 ×1.5 → 颜色更鲜艳(注意:超过 255 会被截断)。

例 3:转灰度(BT.601 加权)

G=[0.2990.5870.1140.2990.5870.1140.2990.5870.114]G = \begin{bmatrix} 0.299 & 0.587 & 0.114 \\ 0.299 & 0.587 & 0.114 \\ 0.299 & 0.587 & 0.114 \end{bmatrix}

注意三行都一样——把 RGB 按 BT.601 系数混合成灰度。

R 系数G 系数B 系数物理含义
0.2990.5870.114人眼对绿色最敏感(绿色权重最大)

例 4:怀旧(Sepia)

sepia=[0.3930.7690.1890.3490.6860.1680.2720.5340.131]\text{sepia} = \begin{bmatrix} 0.393 & 0.769 & 0.189 \\ 0.349 & 0.686 & 0.168 \\ 0.272 & 0.534 & 0.131 \end{bmatrix}

经典的"老照片"色调。


5、关键代码

use nalgebra::{Matrix3, Vector3};
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn color_matrix(
    pixels: &[u8], width: u32, height: u32,
    m00: f32, m01: f32, m02: f32,
    m10: f32, m11: f32, m12: f32,
    m20: f32, m21: f32, m22: f32,
) -> Vec<u8> {
    let w = width as usize;
    let h = height as usize;
    let matrix = Matrix3::new(
        m00, m01, m02,
        m10, m11, m12,
        m20, m21, m22,
    );

    let mut out = vec![0u8; pixels.len()];

    for i in 0..(w * h) {
        let idx = i * 4;
        let rgb = Vector3::new(
            pixels[idx] as f32,
            pixels[idx + 1] as f32,
            pixels[idx + 2] as f32,
        );
        let new_rgb = matrix * rgb;
        out[idx]     = new_rgb[0].clamp(0.0, 255.0) as u8;
        out[idx + 1] = new_rgb[1].clamp(0.0, 255.0) as u8;
        out[idx + 2] = new_rgb[2].clamp(0.0, 255.0) as u8;
        out[idx + 3] = pixels[idx + 3];  // alpha 不变
    }

    out
}

核心循环 5 行——matrix * rgb 调库,其他都是装框。

对比:手写 7 种滤镜(任务 5)和 1 个矩阵

任务 5(7 个手写函数)

fn sepia(p: &[u8]) -> Vec<u8> {
    // 9 行手写 RGB 系数
}
fn cool(p: &[u8]) -> Vec<u8> { /* 9 行 */ }
fn warm(p: &[u8]) -> Vec<u8> { /* 9 行 */ }
fn red_boost(p: &[u8]) -> Vec<u8> { /* 9 行 */ }
// ... 共 7 个函数 × 9 行 = 63 行

任务 19(1 个矩阵函数)

fn color_matrix(p: &[u8], ..., m00..m22) -> Vec<u8> {
    // 5 行循环 + 调库
}
// 1 个函数 × 5 行 = 5 行

代码量减少 12 倍——这就是矩阵抽象的力量。


6、前端效果展示

Jul-14-2026 21-36-45.gif 打开页面后:

  1. 默认显示单位矩阵(图像不变)
  2. 点击 4 个预设按钮:原始 / 对比度增强 / 灰度 / 怀旧
  3. 9 个数字输入框可以自定义任意矩阵——拖滑块看实时效果

7、踩坑提醒

1. 矩阵超过 [0, 255] 会被截断

// 对比度 ×1.5:原本 R=200 → 300 → clamp 到 255(截断)
// 高亮区域会"爆白"——这是矩阵变换的固有问题

解决:如果不想截断,可以先做浮点运算再一起转 u8(代码里已经做了)。

2. 矩阵不一定是"颜色"变换

RGB 矩阵可以做任何线性变换

  • 转灰度
  • 反色(对角线 = -1)
  • 通道交换(红绿互换)

不限于"调色"——读者可以试试输入奇怪的矩阵看效果。

3. Alpha 通道不参与变换

out[idx + 3] = pixels[idx + 3];  // alpha 直接复制

透明度不在 RGB 颜色空间内,不能被 3×3 矩阵变换


8、接下来

任务 20:PCA 主成分分析——用协方差矩阵找出图片"最关键的方向",实现图片压缩。

Mandelbrot:每个像素一个 c,z 从 0 开始 PCA:每张图片一个协方差矩阵,找最大方差方向

把 10000 维的图片压缩到 50 维的"主成分"——视觉上还能看清原图。

任务 21:最小二乘回归——用矩阵求逆解"拟合"问题,做图像去噪。

任务 22:协方差矩阵手写——深挖 PCA 内部原理,库 vs 手算对比。


一句话总结

RGB 像素 = 3 维向量,3×3 矩阵 × 向量 = 新颜色

7 种滤镜的 7 个手写公式 → 1 个矩阵函数(5 行循环 + 调库)。

代码量减少 12 倍,这就是线性代数的"抽象力量"


📦 项目地址pixel-math-wasm 🦀 Rust + WebAssembly 实战系列


🏷️ 标签#Rust #WebAssembly #图像处理 #矩阵 #线性代数 #RGB变换 #nalgebra