RGB下的色彩变换:用线性代数解构色彩世界

发布于:2025-07-04 ⋅ 阅读:(18) ⋅ 点赞:(0)

    在上一篇文章从RGB到HSI:深入理解色彩空间中,我们借助直观的人类感知模型,将色彩从物理的 RGB 表示转换为包含色相(Hue)、饱和度(Saturation)和亮度(Intensity)的 HSI 空间。这种变换让我们能够更贴近“人眼”的理解方式去操控图像色彩,比如直接调整色相角度或控制色彩的鲜艳程度。

    在某些应用中我们并不需要跳出 RGB 空间。比如在色相的增量调节,饱和度的增量调节以及相机色彩校正等算法中,色彩的操作仍然可以在 RGB 空间内完成 —— 通过矩阵的旋转、投影等手段,我们同样能够精准控制色相与饱和度的变化。当然,在某些应用中,我们必须借助HSI空间进行定量描述,比如不同风格的变换等应用。

    本篇文章将从线性代数与几何视角出发,继续探索色彩的变换之路——不过这一次,我们不再依赖空间的变换(如 RGB → HSI),而是留在RGB空间中,通过构造变换矩阵,实现对色相、饱和度的灵活调节。

预备知识:旋转矩阵与投影矩阵

    本节内容包括了二维与三维空间的旋转,投影变换,通过矩阵语言对变换进行描述,作为后续线性色彩变换的理论基础。如果你对此类变换已经比较熟悉,可直接跳过该章节。如果你对公式的推导不感兴趣或者一时间难以完全理解,记住一些基本结论即能满足后续工程应用。我们在后续章节中会将所有的数学细节封装到一个个函数中,通过理解函数的基本意图即可以达到应用的目的。

二维空间中的旋转矩阵

对基向量旋转构成了旋转矩阵

    如上图所示,向量\begin{bmatrix} x\\ y \end{bmatrix}绕原点逆时针旋转角度\theta,可表示为:\begin{bmatrix} x^{'}\\ y^{'} \end{bmatrix}=\begin{bmatrix} cos\theta & -sin\theta\\ sin\theta& cos\theta \end{bmatrix}\begin{bmatrix} x\\ y \end{bmatrix},通过该变换,向量\begin{bmatrix} x\\ y \end{bmatrix}被旋转到\begin{bmatrix} x^{'}\\ y^{'} \end{bmatrix},其旋转角度为\theta,我们称矩阵\begin{bmatrix} cos\theta & -sin\theta\\ sin\theta& cos\theta \end{bmatrix}为二维空间的旋转矩阵。

    如何证明\begin{bmatrix} cos\theta & -sin\theta\\ sin\theta& cos\theta \end{bmatrix}为一个旋转矩阵,大致思路如下:

  • 将旋转矩阵R表示为\begin{bmatrix} R_{1} & R_{2} \end{bmatrix},其中R_{1},R_{2}为列向量。
  • 对单位矩阵\begin{bmatrix} 1 &0 \\ 0 & 1 \end{bmatrix}施加旋转变换:\begin{bmatrix} R_{1} &R_{2} \end{bmatrix}\begin{bmatrix} 1 &0 \\ 0&1 \end{bmatrix} = R
  • 以上表明要获得旋转矩阵,只需要对基向量\begin{bmatrix} 1\\0 \end{bmatrix},\begin{bmatrix} 0\\1 \end{bmatrix}分别施加同样的旋转,所得到的向量构成了旋转矩阵R的列向量。

    通过几个简单的示例,我们可以更加直观的理解旋转矩阵的作用。

    示例1:向量\begin{bmatrix} 1\\0 \end{bmatrix}逆时针旋转90度

    带入\theta等于90度,旋转矩阵可表示为\begin{bmatrix} 0 &-1 \\ 1 & 0 \end{bmatrix},则旋转变换为:\begin{bmatrix} 0 &-1 \\ 1& 0 \end{bmatrix}\begin{bmatrix} 1\\ 0 \end{bmatrix}=\begin{bmatrix} 0\\ 1 \end{bmatrix}。结合笛卡尔坐标系,很显然x轴上单位向量逆时针旋转90度则变为y轴方向上单位向量。

    示例2:向量\begin{bmatrix} 0\\ 1 \end{bmatrix}逆时针旋转90度

    由于旋转角度\theta仍旧等于90度,旋转矩阵保持不变,则旋转变换为:\begin{bmatrix} 0 &-1 \\ 1 & 0 \end{bmatrix}\begin{bmatrix} 0\\ 1 \end{bmatrix}=\begin{bmatrix} -1\\ 0 \end{bmatrix}。结合坐标系,很显然y轴上单位向量逆时针旋转90度则变为x轴反方向上单位向量。

    通过以上示例,我们发现旋转矩阵没有改变向量长度,该性质在后续色彩变换中比较重要。下面给出简短证明。

  • 旋转变换可表示为:v^{'}=Rvv为原始向量,v^{'}为对原始向量v施加旋转矩阵R得到的新向量,现在需证明||v^{'}||=||v||,也即||v^{'}||^{2}=||v||^{2}
  • ||v^{'}||^{2}=v^{'^{T}}v{'}=(Rv)^{T}(Rv)=v^{T}R^{T}Rv,由于旋转矩阵R满足R^{T}R=I(该性质为线性代数中的一个基本性质,这里不做证明),可证明||v^{'}||^{2}=||v||^{2}

二维空间的投影变换

向量b到向量a的投影
  • 向量\vec{b}在向量\vec{a}上的投影与向量\vec{a}共线,可表示为k\vec{a}
  • 黑色虚线可表示为\vec{b}-k\vec{a}
  • 黑色虚线与向量\vec{a}正交(垂直),满足\vec{a}\perp(\vec{b}-k\vec{a})
  • 由于正交向量点乘为0,有\vec{a}^{T}(\vec{b}-k\vec{a})=0,k=\frac{\vec{a}^{T}\vec{a}}{\vec{a}^{T}\vec{b}}
  • 因此,向量\vec{b}在向量\vec{a}上的投影为\vec{p}=\frac{\vec{a}^{T}\vec{b}}{\vec{a}^{T}\vec{a}}\vec{a}
  • 通过改写以上公式可得到投影矩阵为P=\frac{\vec{a}\vec{a}^{T}}{\vec{a}^{T}\vec{a}},使其满足\vec{p}=P\vec{a}
示例:在向量\begin{bmatrix} cos\theta\\ sin\theta \end{bmatrix}上的投影矩阵

    根据投影矩阵公式可计算P=\frac{\begin{bmatrix} cos\theta\\ sin\theta \end{bmatrix}\begin{bmatrix} cos\theta & sin\theta \end{bmatrix}}{\begin{bmatrix} cos\theta & sin\theta \end{bmatrix}\begin{bmatrix} cos\theta\\ sin\theta \end{bmatrix}}=\frac{\begin{bmatrix} cos^{2}\theta & cos\theta sin\theta\\ sin\theta cos\theta& sin^{2}\theta \end{bmatrix}}{cos^{2}\theta+sin^{2}\theta},因此投影矩阵为\begin{bmatrix} cos^{2}\theta & cos\theta sin\theta\\ sin\theta cos\theta& sin^{2}\theta \end{bmatrix}

三维空间上的投影与旋转变换

    针对三维空间上的投影变换,其表达形式与二维空间一致,只要已知投影向量\vec{a}就可以获取任意向量到向量\vec{a}的投影矩阵。

    但对于三维空间的旋转变换,则存在很大差异。在二维空间中,我们的旋转变量只有旋转角度,但在三维空间中,除了旋转角度之外还有旋转轴。如我们可绕X轴旋转\theta,也可以绕Y轴旋转\theta,更加普遍的情况是绕任意轴\begin{bmatrix} x\\y \\ z \end{bmatrix}旋转\theta

    关于绕任意轴旋转的旋转矩阵可以用罗德里格斯公式表示,以下给出一个其基本思想。

三维空间中的旋转
  • 上图表示向量\vec{v}绕旋转轴\vec{k}旋转\theta后得到\vec{v_{rot}}
  • 我们可以向量\vec{v}分解为平行于\vec{k}与垂直于\vec{k}的两个分量,分别表示为\vec{v_{\parallel }}, \vec{v_{\perp} }
构造垂直于k的平面
  • 对向量\vec{v}旋转\theta可以拆解为对\vec{v_{\perp }}旋转\theta后再叠加\vec{v_{\parallel }},而\vec{v_{\perp }}位于垂直于\vec{k}的平面上
  • 如上图所示,在平面上旋转\vec{v_{\perp }}则是一个二维平面上的旋转问题,求解\vec{v_{\parallel }}是一个投影变换问题
  • 综上,旋转后向量\vec{v_{rot}}等于原向量\vec{v}在旋转轴\vec{k}上的投影加上\vec{v_{\perp }}在旋转平面上旋转\theta后的向量

    以上就是三维空间中构造旋转矩阵基本思路,我在之前的博客中有详细的推导,这里只是给出了一个基本的思想,详细内容见三维旋转矩阵的推导

RGB空间中色彩调节矩阵构造原理

    在从RGB到HSI:深入理解色彩空间文章中,我们讨论了HSI模型在RGB空间中的关系。总结来说有两点:

  1. 在RGB空间,任意颜色向量绕灰度轴旋转可以逐渐改变该颜色的色相,我们命名为色相调整矩阵R
  2. 任意颜色与其在灰度轴上的投影向量构成一个直角三角形,通过改变到灰度轴的距离可逐渐改进颜色的饱和度。
RGB空间上的色彩表达

    如上图所示,红色向量为原始颜色向量,灰色虚线为灰度轴,绿色向量为原始颜色向量在灰度轴上的投影,蓝色向量为原始向量与投影向量的差值。

    通过绕灰度轴旋转可改变原始颜色向量的色相,我们已经讨论了三维空间的旋转矩阵,此处的具体应用可描述为将原始颜色向量\begin{bmatrix} r\\ g \\b \end{bmatrix}\begin{bmatrix} 1\\ 1 \\ 1 \end{bmatrix}旋转\theta,即获得新的颜色向量。

    通过改变蓝色差向量模长后再与绿色投影向量重构为新的颜色向量,则改变了原始颜色向量的饱和度。这里需要一些推导来构造饱和度变换矩阵,具体如下:

  • 在灰度轴\begin{bmatrix} 1\\ 1 \\ 1 \end{bmatrix}上的投影矩阵可表示为P=\frac{aa^{T}}{a^{T}a}=\frac{ \begin{bmatrix} 1\\ 1 \\ 1 \end{bmatrix}\begin{bmatrix} 1 & 1 & 1 \end{bmatrix}}{\begin{bmatrix} 1 & 1 & 1 \end{bmatrix} \begin{bmatrix} 1\\ 1 \\ 1 \end{bmatrix}},化简计算得P=\begin{bmatrix} \frac{1}{3} & \frac{1}{3} &\frac{1}{3} \\ \frac{1}{3}&\frac{1}{3} & \frac{1}{3}\\ \frac{1}{3} &\frac{1}{3} &\frac{1}{3} \end{bmatrix}
  • 设原始颜色向量为\vec{c}=\begin{bmatrix} r\\ g \\ b \end{bmatrix},蓝色差向量可表示为\vec{d}=\vec{c}-P\vec{c}
  • 设任意常量\lambda(\lambda >0),将差向量\vec{d}乘以\lambda则改变了差向量的长度。
  • 使用投影向量P\vec{c}加上\lambda \vec{d}则得到改变饱和度后的颜色向量\vec{c^{'}}=P\vec{c} + \lambda \vec{d}
  • 简化以上公式得\vec{c^{'}}=(P + \lambda I - \lambda P)\vec{c},则M=P+\lambda I - \lambda P构成了饱和度调节矩阵。

    综合色相调整矩阵R与饱和度调整矩阵M,我们可以整合成一个变换矩阵T=RM,该矩阵同时实现了在RGB空间中调整色相与饱和度。至此,我们完成了RGB空间的色彩调节矩阵的构造。

RGB空间中色彩调节实战

    通过以上理论分析,色彩调节矩阵的核心组件包括色相调整矩阵与饱和度调整矩阵的构造,然后在应用cv::transform函数对每个像素实施色彩变换。以下是整个代码实现与变换效果:

void hueSatLinearTran()
{
	cv::Mat img_src = cv::imread("src.jpg", cv::IMREAD_COLOR);
	if (img_src.empty()) return;  // 检查是否成功读取图像

	// 转换为浮点图像,后续变换均基于浮点数据
	cv::Mat img_float;
	img_src.convertTo(img_float, CV_32F);

	cv::imwrite("1_src.jpg", img_float);

	// 色相调整范围为[-pi,pi]
	// 饱和度调整:大于1增加饱和度,小于1降低饱和度
	const double hue_rad = CV_PI / 10.;
	const double sat_scale = 1.3;

	// step1: 构造色相调整矩阵

	// 定义色相旋转轴,该轴固定为灰度轴
	cv::Vec3d axis(1, 1, 1);
	axis = axis / cv::norm(axis);  // 归一化

	// rvec编码了旋转轴与旋转角度:方向表示旋转轴,模长表示旋转角度
	cv::Mat rvec = cv::Mat(axis * hue_rad).reshape(1, 3);

	// 计算色相调整矩阵
	cv::Mat HUE_MAT;
	cv::Rodrigues(rvec, HUE_MAT);

	//step2: 构造饱和度调整矩阵

	// 首先构造灰度轴上的投影矩阵
	const double temp_val = 1. / 3.;
	cv::Mat P(3, 3, CV_64F, cv::Scalar(temp_val));

	// 根据公式P+sat_scale*I-sat_scale*P构造饱和度矩阵
	cv::Mat I = cv::Mat::eye(P.size(), P.type());
	cv::Mat SAT_MAT = P * (1.0f - sat_scale) + I * sat_scale;

	// step3: 构造色相饱和度调整矩阵
	cv::Mat SAT_HUE_MAT = HUE_MAT * SAT_MAT;
	std::cout << "SAT_HUE_MAT = \n" << SAT_HUE_MAT << std::endl;

	// step4: 使用一次线性变换完成色相与饱和度调节
	cv::Mat img_tran;
	cv::transform(img_float, img_tran, SAT_HUE_MAT);

	cv::imwrite("2_transformed.jpg", img_tran);
}
左:深秋的银杏      右:色相与饱和度变换后图像(线性变换)

    利用线性变换,我们可以高效的调节彩色图像的色相与饱和度,从而实现图像色彩的实时调节。

总结

    本文探讨了在RGB空间中进行色相与饱和度调节的基本方法。通过线性代数中的旋转矩阵和投影矩阵理论,我们展示了如何直接在RGB空间中构造色相调整矩阵(绕灰度轴旋转)和饱和度调整矩阵(改变颜色向量到灰度轴的距离)。我们结合图文深入浅出讲解了相关矩阵的推导,并提供了具体实现代码,包括cv::Rodrigues与cv::transform等函数的基本使用方法。通过单次线性变换高效的调整图像色相和饱和度,我们可以将其应用到需要实时色彩调节的场景中。