0%

  • 两个坐标系之间的运动由一个旋转加上一个平移组成,这种运动成为刚体运动
  • 刚体运动过程中,同一个向量在各个坐标系下的长度和夹角都不会发生变化。

旋转操作

对一个向量旋转本质是对基底的旋转。

设某个单位正交基 $(\boldsymbol{e}_1, \boldsymbol{e}_2, \boldsymbol{e}_3)$ 是由 $(\boldsymbol{e}’_1, \boldsymbol{e}’_2, \boldsymbol{e}’_3)$ 经过一次旋转变成的。

对于同一个向量 $\boldsymbol{a}$,它在两个坐标系下的坐标分别为 $[a_1, a_2, a_3]$ 和 $[a’_1, a’_2, a’_3]$

由于向量本身没变,所以根据坐标定义,有

如果关注两个坐标之间的关系,且由于 $(\boldsymbol{e}_1, \boldsymbol{e}_2, \boldsymbol{e}_3)$ 是一组单位正交基,可以两边同时左乘一个 $\left [\matrix{\boldsymbol{e}_1^{\mathrm{T}} \ \boldsymbol{e}_2^{\mathrm{T}} \ \boldsymbol{e}_3^{\mathrm{T}}} \right ]$ 来消掉 $[\boldsymbol{e}_1, \boldsymbol{e}_2, \boldsymbol{e}_3]$。

然后我们将中间的那一坨定义为旋转矩阵 $\boldsymbol{R}$。

旋转矩阵的性质

  • 旋转矩阵描述了旋转本身。
  • 旋转矩阵是一个正交矩阵,即 $\boldsymbol{R}^{-1} = \boldsymbol{R}^{\mathrm{T}}$.
  • 旋转矩阵的行列式为 $1$。
  • 行列式为 $1$ 的正交矩阵是一个旋转矩阵。
  • 旋转矩阵的逆(或转置)描述了相反的旋转。

平移操作

加上一个平移向量 $\boldsymbol{t}$ 即可实现。即 $\boldsymbol{a}^{\prime} = \boldsymbol{a} + \boldsymbol{t}$ .

变换矩阵

一次欧式变换可以由一次旋转操作和一次平移操作完成,即 $\boldsymbol{a}^{\prime} = \boldsymbol{Ra} + \boldsymbol{t}$ .

但是如果要表示连续的多次欧式变换,这样又乘又加效率会很低,并且会很啰嗦。

于是就引入了变换矩阵 $\boldsymbol{T}$,有

在此引入了齐次坐标系的概念。详见 BV1vi421Y7nPBV1LS4y1b7xZ .

引入这个变换矩阵后,一次欧式变换就可以用通过左乘一个变换矩阵 $\boldsymbol{T}$ 来实现。

变换矩阵 $\boldsymbol{T}$ 的逆 $\boldsymbol{T}^{-1}$ 表示一个相反的变换。

滤波与平滑

详见 这篇文章

可以实现对图像的降噪处理、抹去小光斑等。

形态学操作

可以用来去除噪声、提取图像中的形状特征、填补区域或突出边界。

erode() - 腐蚀

  • 作用:腐蚀操作会侵蚀图像中的前景对象,使其变小。常用于去除小的白噪声、断开物体间的连接等。

  • 原理:腐蚀操作在一个卷积核(结构元素)下扫描图像,只有当卷积核完全覆盖前景时,中心像素才保留前景,否则变为背景。这使得前景物体的边界收缩。

  • 调用erode(src, dst, kernel, anchor, iterations, borderType, borderValue)

  • 参数

    • InputArray src: 输入图像(通常是二值图像)。
    • OutputArray dst: 输出图像
    • InputArray kernel: 结构元素,决定了腐蚀的形状与大小。
    • Point anchor: 锚点,表示要处理的像素位于核的什么位置。默认为 Point(-1,-1) 表示中心。
    • int iterations: 腐蚀的次数。默认为 1。
    • int borderType: 用于外推图像外部像素的边界模式。默认值为 BORDER_DEFAULT。(不支持 BORDER_WRAP )
    • const Scalar & borderValue: 仅在 borderTypeBORDER_CONSTANT 时使用,指定填充边界的具体常数值。

dilate() - 膨胀

  • 作用:膨胀操作是腐蚀的逆操作,用于扩展前景对象,使其变大。它可以填补对象内部的小孔洞,连接断开的对象等。
  • 实现原理:膨胀操作在卷积核下扫描图像,卷积核只要有一个元素覆盖前景像素,中心像素就会变为前景。这样前景物体的边界会向外扩展。
  • 调用dilate(src, dst, kernel, anchor, iterations, borderType, borderValue)
  • 参数:同 erode()

getStructuringElement() - 生成结构元素

  • 作用:生成一个特定形状的结构元素,通常用于形态学操作。

  • 调用getStructuringElement(shape, ksize, anchor)

  • 参数

    • int shape: 形状。可取MORPH_RECT(矩形)、MORPH_CROSS(十字)、MORPH_ELLIPSE(椭圆)。

    • Size kSize: 尺寸。

    • Point anchor: 锚点,表示要处理的像素位于核的什么位置。默认为 Point(-1,-1) 表示中心。

morphologyEx() - 形态学扩展操作

morphologyEx(src, dst, operation, element);

element: 结构元素,决定了操作区域的形状与大小。

operation 有以下取值:

  • 开运算(MORPH_OPEN

    • 作用:先腐蚀再膨胀,常用于去除噪声。
    • 实现原理:先应用腐蚀操作,消除细小噪点,然后再进行膨胀,恢复前景对象的原始大小。
  • 闭运算(MORPH_CLOSE

    • 作用:先膨胀再腐蚀,通常用于填补前景对象中的小孔洞或连接断开的区域。
    • 实现原理:先应用膨胀操作,填充小孔洞,再进行腐蚀,恢复对象的原始大小。
  • 形态学梯度(MORPH_GRADIENT

    • 作用:形态学梯度是膨胀与腐蚀之间的差异,用于提取图像的边缘。

    • 实现原理

      • 计算方法是对图像进行膨胀操作,然后减去腐蚀操作的结果。
      • 由于膨胀会扩展前景对象,腐蚀会缩小前景对象,二者之间的差异就形成了图像的边缘。
    • 公式Morphological Gradient = dilate(src) - erode(src)

    • 应用:通常用于检测图像中的边缘特征,尤其在二值图像中比较常见。

  • 顶帽操作(MORPH_TOPHAT)

    • 作用:顶帽操作用于提取图像中比其周围区域更亮的部分,常用于背景较均匀的图像中提取亮的细节。

    • 实现原理

      • 计算方法是原图像减去其开运算的结果。
      • 开运算(先腐蚀后膨胀)通常会去除较小的前景细节,保留大体结构。顶帽操作通过减去开运算的结果,可以将被去除的那些细节提取出来。
    • 公式Top Hat = src - open(src)

    • 应用:用于增强亮的细节,比如在背景较均匀的情况下提取文字或小亮点。

  • 黑帽操作(MORPH_BLACKHAT)

    • 作用:黑帽操作用于提取图像中比其周围区域更暗的部分,常用于背景较均匀的图像中提取暗的细节。
    • 实现原理
      • 计算方法是闭运算的结果减去原图像。
      • 闭运算(先膨胀后腐蚀)会填充前景中的小孔洞并连接断开的部分。黑帽操作通过从闭运算的结果中减去原图像,可以将原图像中被闭运算填充的暗区域提取出来。
    • 公式Black Hat = close(src) - src
    • 应用:用于增强暗的细节,比如在背景较均匀的情况下提取阴影或暗点。

参考链接

为什么会用到 BorderTypes?

当你对图像进行某些操作时,例如卷积(即图像滤波),操作需要访问像素周围的一些像素值来计算结果。如果你对图像中靠近边缘的像素执行这些操作,算法可能会尝试访问图像之外的像素,而这些像素并不存在,这时就需要指定如何处理这些边界。

cv::BorderTypes 提供了几种处理方式。

在接下来的例子中,例子被 | 分割为了三个部分,左右分别表示两侧的边界,中间表示图像数据。

BORDER_CONSTANT

iiiiii|abcdefgh|iiiiiii 这里的 i 是指定的。

BORDER_REPLICATE

aaaaaa|abcdefgh|hhhhhhh 由边界的像素扩展得到。简单来说,边缘的像素会被“复制”出来。

BORDER_REFLECT

fedcba|abcdefgh|hgfedcb 镜像反射边界区域。

BORDER_WRAP

cdefgh|abcdefgh|abcdefg 把图像当作一个环来处理,边界部分会用图像的另一边填充。

BORDER_TRANSPARENT

uvwxyz|abcdefgh|ijklmno 将异常值视为透明的。

BORDER_REFLECT_101 / BORDER_REFLECT101

gfedcb|abcdefgh|gfedcba 与 BORDER_REFLECT 类似,但是跳过了边缘的一个像素点去反射。

BORDER_DEFAULT

默认值。效果与 BORDER_REFLECT_101 相同。

参考链接

OpenCV 有一些线性滤镜可以用来平滑图像。

归一化盒过滤器 (Normalized Box Filter)

很好理解,新的图像的每一个像素点都是原图像该位置附近的像素点的强度取平均。

可以有效地平滑图像,降低其尖锐程度,降低噪声(但不能消除噪声)。

1
2
3
4
5
6
7
8
9
blur(src, dst, ksize, anchor, borderType);

/*
InputArray src: 输入图片
OutputArray dst: 输出图片(要求与输入图片有相同的大小和深度)
Size ksize: 表示核的尺寸
Point anchor: 表示需要处理的像素位于核的什么位置。默认值为 Point(-1, -1),表示锚点位于内核中心。
int     borderType: 用于外推图像外部像素的边界模式。默认值为 BORDER_DEFAULT。(不支持 BORDER_WRAP )
*/

borderType 详见 这篇文章

高斯过滤器 (Gaussian Filter)

可能是最有用的过滤器。由于要计算高斯核所以会慢一些。效果比较接近现实世界中的模糊效果。

看不太懂。

1
2
3
4
5
6
7
8
9
10
11
GaussianBlur(src, dst, ksize, sigmaX, sigmaY, borderType, hint);

/*
InputArray src: 输入图片
OutputArray dst: 输出图片(要求与输入图片有相同的大小和深度)
Size ksize: 表示核的尺寸
double sigmaX: X方向上的高斯核标准差
double sigmaY: 默认为0,表示与 sigmaX 相同。
int borderType: 用于外推图像外部像素的边界模式。默认值为 BORDER_DEFAULT。(不支持 BORDER_WRAP )
AlgorithmHint hint: 没明白。感觉没啥用。
*/

中位数过滤器 (Median Filter)

每个位置附近像素值的中位数。

效果有点像融化后混合了。

1
2
3
4
5
6
7
medianBlur(src, dst, ksize);

/*
InputArray src: 输入图片
OutputArray dst: 输出图片(要求与输入图片有相同的大小和深度)
int ksize: 表示核的尺寸(ksize * ksize)
*/

双边过滤器 (Bilateral Filter)

比较好地保留边界的前提下使色块内部变得平滑。

据说可以应用在初步的美颜上。

原理没太搞懂,大概就是像素值差与距离差同时考虑后得到的卷积核。

是结合图像的空间邻近度像素值相似度的一种折中处理。

1
2
3
4
5
6
7
8
9
10
bilateralFilter    (src, dst, d, sigmaColor, sigmaSpace, borderType);

/*
InputArray src: 输入图片
OutputArray dst: 输出图片(要求与输入图片有相同的大小和深度)
int d: 从当前像素选择的邻近区域的直径。
double sigmaColor:值域的 sigma。控制颜色差异的影响,影响图像的边缘保护能力。
double sigmaSpace:空间域的 sigma。控制空间距离的影响,决定平滑效果的范围和程度。
int borderType:用于外推图像外部像素的边界模式。默认值为 BORDER_DEFAULT。
*/
  • sigmaColor(颜色空间中的标准差)

    • 这个参数控制的是颜色相似度的影响。它决定了相邻像素之间的颜色差异在滤波过程中有多大的影响。
    • sigmaColor 较大时,即使像素之间的颜色差异较大,它们也会相互影响较多,因此滤波器会更倾向于平滑颜色变化较大的区域。
    • 反之,如果 sigmaColor 较小,滤波器只会在颜色相近的像素之间进行平滑,从而保留更多的颜色边缘和细节。
  • sigmaSpace(空间距离中的标准差)

    • 这个参数控制的是空间距离的影响,即考虑多远的像素在滤波时应该被包括进来。
    • sigmaSpace 较大时,滤波器会在更大范围内对像素进行平滑处理,可能会产生较大的模糊效果。
    • 如果 sigmaSpace 较小,只有靠近的像素会对滤波结果产生显著影响,这样可以更好地保留细节但减少模糊的效果。

参考链接

如果记 $f(x)$ 为原图片的像素信息,$g(x)$ 为输出图片的像素信息,

那么可以通过 $g(i,j) = \alpha \cdot f(i,j) + \beta$ 来对像素进行操作,此处 $i,j$ 分别表示该像素点所在的行和列,$\alpha \gt 0$。

$\alpha$ 在此被称为 “增益”,可以用于调节对比度;$\beta$ 在此被称为 “偏移” 或 “偏置”,可以用于调整亮度。

  • $\alpha$ 可以用于调节对比度的原因:

    当 $\alpha \gt 1$ 时,$\alpha \cdot f(i,j)$ 的操作会增大各像素值之间的差异,使差异从原来的 $d$ 增加到了 $\alpha d$,此时增强了对比度。

    当 $\alpha \lt 1$ 时,$\alpha \cdot f(i,j)$ 的操作会缩小各像素值之间的差异,使差异从原来的 $d$ 减小至了 $\alpha d$,此时减弱了对比度。

  • $\beta$ 可以用于调节亮度的原因:

    通过最终加算一个 $\beta$,所有像素值都增加或减少了一个固定的量。这会导致图像整体变亮或变暗,因此我们说 $\beta$ 可以改变图像的亮度。

但是,这种操作容易丢失亮部或暗部的信息。

我们希望,如果原像素的亮度已经足够高,那么继续提亮对这个像素点的影响应该是较小的。

换言之,我们需要一种非线性的方法 $g(x)$ 来改变图片的亮度。

我们可以通过 $O = \left(\dfrac{I}{255}\right)^\gamma \times 255$ 来实现这一功能。

这种操作可以在尽量多地保留亮部信息的前提下提亮暗部、在尽量多地保留暗部信息的前提下压暗亮部。

中间是原图的直方图;左侧是调 $\beta$ 后的直方图;右侧是调 $\gamma$ 后的直方图。

并且我们可以针对每一个 $\gamma$ 预处理出来一个表,代表 0-255 所有值处理后的结果。

然后就不需要每次都算一遍了,将复杂度从 $O(nm\log\gamma)$ 优化至 $O(nm+\log\gamma)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//官方提供的示例 
//https://github.com/opencv/opencv/blob/4.x/samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpphttps://github.com/opencv/opencv/blob/4.x/samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpp

void gammaCorrection(const Mat &img, const double gamma_)
{
CV_Assert(gamma_ >= 0);
//! [changing-contrast-brightness-gamma-correction]
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.ptr();
for( int i = 0; i < 256; ++i)
p[i] = saturate_cast<uchar>(pow(i / 255.0, gamma_) * 255.0);

Mat res = img.clone();
LUT(img, lookUpTable, res);
//! [changing-contrast-brightness-gamma-correction]
//这里是把原图和处理后的图左右拼到一起形成对比图
hconcat(img, res, img_gamma_corrected);
imshow("Gamma correction", img_gamma_corrected);
}