图像金字塔与形态学操作虽属于基础技术范畴,却在众多复杂任务中扮演着不可或缺的角色。图像金字塔通过构建多尺度图像表示,为图像的特征提取、目标检测与图像融合等任务提供了层次化的分析视角;而形态学操作则基于数学形态学理论,利用结构元素对图像进行处理,能够有效实现图像增强、噪声去除、形状分析与特征提取。
一、图像金字塔
在数字图像处理领域,图像金字塔是一种多尺度表达的经典结构,它通过对图像进行一系列的缩放操作,生成一组分辨率由高到低、尺寸逐渐变小的图像集合,形似金字塔,故而得名。图像金字塔的作用十分广泛,例如在图像融合时,利用不同分辨率的图像进行处理,能减少拼接痕迹;在目标检测中,通过在多个尺度下搜索目标,可提高检测的准确性,避免因目标大小变化而漏检。接下来,我们将重点介绍两种最常用的图像金字塔:高斯金字塔和拉普拉斯金字塔。
1.1 高斯金字塔
作用
高斯金字塔主要用于降低图像分辨率,通过对图像进行多次下采样,生成一系列低分辨率的图像版本。在实际应用中,它可以减少图像数据量,加速处理速度,比如在特征提取前对图像进行降采样;同时,多尺度的图像表达有助于处理图像中目标大小变化的情况,像在人脸检测时,不同分辨率的图像可以匹配不同距离、大小的人脸。
向下采样原理
高斯金字塔的构建基于向下采样操作,其核心步骤如下:
- 高斯滤波:在进行下采样之前,首先使用高斯滤波器对图像进行平滑处理。这是因为直接下采样会导致图像出现混叠效应(Aliasing),产生锯齿或摩尔纹等失真现象。高斯滤波通过对图像进行加权平均,其原理基于二维高斯函数:
G(x,y)=12πσ2e−x2+y22σ2G(x,y) = \frac{1}{2\pi\sigma^2} e^{-\frac{x^2+y^2}{2\sigma^2}}G(x,y)=2πσ21e−2σ2x2+y2
其中,(x,y)(x,y)(x,y) 是像素坐标,σ\sigmaσ 是高斯分布的标准差,它决定了滤波的平滑程度。通过高斯滤波,图像中的高频信息(如噪声、精细纹理)被抑制,保留低频的主要结构信息 。
- 隔行隔列采样:经过高斯滤波后,对图像进行隔行隔列采样,即去除偶数行和偶数列的像素,将图像的尺寸缩小为原来的四分之一(宽度和高度各变为原来的一半)。假设原始图像为 I(x,y)I(x,y)I(x,y),下采样后的图像 Idown(x,y)I_{down}(x,y)Idown(x,y) 满足:
Idown(x,y)=I(2x,2y)I_{down}(x,y) = I(2x,2y)Idown(x,y)=I(2x,2y)
其中 x=0,1,⋯ ,⌊M2⌋−1x = 0,1,\cdots,\lfloor\frac{M}{2}\rfloor - 1x=0,1,⋯,⌊2M⌋−1,y=0,1,⋯ ,⌊N2⌋−1y = 0,1,\cdots,\lfloor\frac{N}{2}\rfloor - 1y=0,1,⋯,⌊2N⌋−1,MMM 和 NNN 分别是原始图像的高度和宽度。
- 重复操作:对下采样后的图像重复上述高斯滤波和隔行隔列采样的步骤,就可以得到一系列分辨率逐渐降低的图像,这些图像共同构成了高斯金字塔。
函数原型
void pyrDown(InputArray src, OutputArray dst, const Size& dstsize = Size(), int borderType = BORDER\_DEFAULT);
参数说明:
-
src
:输入图像,可以是单通道或多通道图像。 -
dst
:输出的下采样图像,尺寸通常为输入图像的四分之一。 -
dstsize
:可选参数,指定输出图像的大小,如果不指定,会根据默认规则计算。 -
borderType
:边界像素的处理方式,默认使用BORDER_DEFAULT
方式。
示例代码
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat image = imread("lena.jpg");
if (image.empty()) {
cout << "Could not open or find the image" << endl;
return -1;
}
Mat gaussian_pyramid_1;
// 进行一次向下采样
pyrDown(image, gaussian_pyramid_1);
Mat gaussian_pyramid_2;
// 基于第一次下采样结果,再进行一次向下采样
pyrDown(gaussian_pyramid_1, gaussian_pyramid_2);
imshow("Original Image", image);
imshow("First Level Gaussian Pyramid", gaussian_pyramid_1);
imshow("Second Level Gaussian Pyramid", gaussian_pyramid_2);
waitKey(0);
return 0;
}
1.2 拉普拉斯金字塔
作用
拉普拉斯金字塔主要用于图像重建和图像细节增强。它记录了高斯金字塔中相邻两层图像之间的差异,也就是图像的高频细节信息。在图像融合、图像修复等应用中,拉普拉斯金字塔能够帮助恢复图像的细节,使处理后的图像更加自然、真实。
向上采样原理
拉普拉斯金字塔的构建基于向上采样和差值计算,具体步骤如下:
- 向上采样:向上采样是向下采样的逆操作,它将低分辨率的图像放大为高分辨率图像。首先对低分辨率图像进行插值操作,在每一行和每一列之间插入新的像素行和列,将图像尺寸扩大为原来的四倍(宽度和高度各变为原来的两倍)。常用的插值方法有最近邻插值、双线性插值等,OpenCV 中
pyrUp
函数默认使用更复杂的高斯插值方法,其过程可以理解为:
Iup(x,y)=∑i=−11∑j=−11k(i,j)⋅I(⌊x2⌋+i,⌊y2⌋+j)I_{up}(x,y) = \sum_{i=-1}^{1} \sum_{j=-1}^{1} k(i,j) \cdot I(\lfloor\frac{x}{2}\rfloor + i,\lfloor\frac{y}{2}\rfloor + j)Iup(x,y)=∑i=−11∑j=−11k(i,j)⋅I(⌊2x⌋+i,⌊2y⌋+j)
其中 k(i,j)k(i,j)k(i,j) 是高斯插值核,III 是低分辨率图像,IupI_{up}Iup 是上采样后的图像 。
- 高斯滤波与差值计算:对上采样后的图像进行高斯滤波,使其平滑。然后用高斯金字塔中对应的高分辨率图像减去经过高斯滤波的上采样图像,得到的差值图像就是拉普拉斯金字塔的一层。假设高斯金字塔中第 iii 层图像为 GiG_iGi,上采样并滤波后的图像为 G^i+1\hat{G}_{i+1}G^i+1,则拉普拉斯金字塔第 iii 层图像 LiL_iLi 为:
Li=Gi−G^i+1L_i = G_i - \hat{G}_{i+1}Li=Gi−G^i+1
通过重复上述操作,从高斯金字塔的底层向上构建,就可以得到完整的拉普拉斯金字塔。
函数原型
void pyrUp(InputArray src, OutputArray dst, const Size& dstsize = Size(), int borderType = BORDER\_DEFAULT);
参数说明与 pyrDown
函数类似,src
为输入的低分辨率图像,dst
为输出的上采样图像,dstsize
可选,borderType
用于指定边界处理方式。
示例代码
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat image = imread("lena.jpg");
if (image.empty()) {
cout << "Could not open or find the image" << endl;
return -1;
}
Mat gaussian_pyramid_1;
pyrDown(image, gaussian_pyramid_1);
Mat upsampled_image;
// 对下采样后的图像进行向上采样
pyrUp(gaussian_pyramid_1, upsampled_image);
Mat laplacian_image;
// 计算拉普拉斯图像(记录细节信息)
subtract(image, upsampled_image, laplacian_image);
imshow("Original Image", image);
imshow("Upsampled Image", upsampled_image);
imshow("Laplacian Image", laplacian_image);
waitKey(0);
return 0;
}
图像金字塔通过高斯金字塔和拉普拉斯金字塔的配合,为图像处理提供了多尺度的分析视角。高斯金字塔实现了图像的降采样,拉普拉斯金字塔则保留了图像的细节信息,二者结合在众多计算机视觉任务中发挥着关键作用。
二、图像形态学
在数字图像处理领域,图像形态学是一门基于形状对图像进行分析和处理的学科。它通过设计特定的结构元素(可理解为一个小的模板图像),对目标图像进行一系列操作,从而实现图像的增强、分割、特征提取等任务。图像形态学的核心思想是利用结构元素与图像之间的相互作用,改变图像中目标区域的形状和结构,其操作简单高效,在去除噪声、提取物体轮廓、连接断裂区域等方面发挥着重要作用。接下来,我们将详细介绍图像形态学中常见的操作:膨胀、腐蚀、开闭运算、顶帽黑帽以及击中不击中变换。
2.1 膨胀和腐蚀
作用
-
膨胀:膨胀操作的作用是 “扩大” 图像中前景物体的边界。在二值图像中,它会将前景物体周围的背景像素转变为前景像素,使得物体的区域变大;在灰度图像中,膨胀会增大局部区域的灰度值。膨胀常用于连接断裂的物体、填充物体内部的小空洞,以及增强图像中的细节。
-
腐蚀:与膨胀相反,腐蚀操作是 “缩小” 图像中前景物体的边界,将前景物体边界的像素转变为背景像素,使物体区域变小。在灰度图像中,腐蚀会减小局部区域的灰度值。腐蚀主要用于去除图像中的小噪声、分离粘连的物体,以及提取物体的骨架。
原理
膨胀和腐蚀操作都依赖于一个关键概念 ——结构元素。结构元素是一个预先定义的小矩阵,其形状和大小根据具体需求而定,常见的形状有矩形、圆形、菱形等。以二值图像为例,假设图像为 AAA,结构元素为 BBB:
-
膨胀:膨胀操作可以理解为将结构元素 BBB 在图像 AAA 上进行移动,对于结构元素覆盖的区域,如果其中有任何一个像素属于前景(值为 1),那么结构元素的中心像素在输出图像中就被设置为前景。数学表达式为 (A⊕B)(x,y)=⋃(s,t)∈BA(x+s,y+t)(A \oplus B)(x,y) = \bigcup_{(s,t) \in B} A(x + s, y + t)(A⊕B)(x,y)=⋃(s,t)∈BA(x+s,y+t),其中 ⊕\oplus⊕ 表示膨胀运算,(s,t)(s,t)(s,t) 是结构元素 BBB 中的坐标。
-
腐蚀:腐蚀操作是当结构元素 BBB 在图像 AAA 上移动时,只有当结构元素覆盖的所有像素都属于前景时,结构元素的中心像素在输出图像中才被设置为前景。数学表达式为 (A⊖B)(x,y)=⋂(s,t)∈BA(x+s,y+t)(A \ominus B)(x,y) = \bigcap_{(s,t) \in B} A(x + s, y + t)(A⊖B)(x,y)=⋂(s,t)∈BA(x+s,y+t),其中 ⊖\ominus⊖ 表示腐蚀运算。
函数原型
void dilate(InputArray src, OutputArray dst, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT, const Scalar& borderValue = morphologyDefaultBorderValue());
void erode(InputArray src, OutputArray dst, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT, const Scalar& borderValue = morphologyDefaultBorderValue());
参数说明:
-
src
:输入图像,可以是单通道或多通道图像。 -
dst
:输出图像,与输入图像大小和类型相同。 -
kernel
:结构元素,可以通过getStructuringElement
函数创建,也可以自定义。 -
anchor
:结构元素的锚点位置,默认位于结构元素中心(Point(-1,-1)
)。 -
iterations
:操作迭代次数,次数越多,膨胀或腐蚀的效果越明显。 -
borderType
:边界像素的处理方式。 -
borderValue
:边界填充值。
示例代码
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat image = imread("binary_image.jpg", IMREAD_GRAYSCALE);
if (image.empty()) {
cout << "Could not open or find the image" << endl;
return -1;
}
// 创建一个3x3的矩形结构元素
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat dilated_image, eroded_image;
// 进行膨胀操作
dilate(image, dilated_image, kernel);
// 进行腐蚀操作
erode(image, eroded_image, kernel);
imshow("Original Image", image);
imshow("Dilated Image", dilated_image);
imshow("Eroded Image", eroded_image);
waitKey(0);
return 0;
}
2.2 开闭运算
作用
-
开运算:开运算由腐蚀和膨胀两个操作组成,先对图像进行腐蚀,再进行膨胀。它的主要作用是去除图像中的小噪声、分离粘连的物体,同时保持物体的形状基本不变。例如在处理含有细小毛刺的物体图像时,开运算可以平滑物体边界,去除这些不必要的细节。
-
闭运算:闭运算同样由腐蚀和膨胀组成,但顺序相反,先膨胀后腐蚀。闭运算用于填充物体内部的小空洞、连接断裂的区域,使物体更加完整。比如在处理带有小缺口的物体图像时,闭运算可以将这些缺口填补上。
原理
-
开运算:设图像为 AAA,结构元素为 BBB,开运算表示为 A∘B=(A⊖B)⊕BA \circ B = (A \ominus B) \oplus BA∘B=(A⊖B)⊕B,即先对图像 AAA 进行基于结构元素 BBB 的腐蚀操作,再对腐蚀结果进行膨胀操作。
-
闭运算:闭运算表示为 A∙B=(A⊕B)⊖BA \bullet B = (A \oplus B) \ominus BA∙B=(A⊕B)⊖B,先对图像 AAA 进行膨胀,再对膨胀结果进行腐蚀。
函数原型
void morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT, const Scalar& borderValue = morphologyDefaultBorderValue());
参数说明:
-
src
:输入图像。 -
dst
:输出图像。 -
op
:形态学操作类型,当op = MORPH_OPEN
时为开运算,op = MORPH_CLOSE
时为闭运算。 -
其他参数与
dilate
和erode
函数类似。
示例代码
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat image = imread("noisy_image.jpg", IMREAD_GRAYSCALE);
if (image.empty()) {
cout << "Could not open or find the image" << endl;
return -1;
}
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat opened_image, closed_image;
// 进行开运算
morphologyEx(image, opened_image, MORPH_OPEN, kernel);
// 进行闭运算
morphologyEx(image, closed_image, MORPH_CLOSE, kernel);
imshow("Original Image", image);
imshow("Opened Image", opened_image);
imshow("Closed Image", closed_image);
waitKey(0);
return 0;
}
2.3 顶帽黑帽
作用
-
顶帽运算:顶帽运算(又称礼帽运算)是用原始图像减去开运算的结果。它可以提取出图像中比周围区域更亮的部分,常用于增强图像中的微小细节、去除不均匀光照的影响。例如在处理表面有微小凸起的物体图像时,顶帽运算能够突出这些凸起部分。
-
黑帽运算:黑帽运算是闭运算的结果减去原始图像。它可以提取出图像中比周围区域更暗的部分,常用于检测图像中的小孔洞、凹陷等特征。
原理
-
顶帽运算:设图像为 AAA,结构元素为 BBB,顶帽运算表示为 A^B=A−(A∘B)A \hat{} B = A - (A \circ B)A^B=A−(A∘B)。
-
黑帽运算:黑帽运算表示为 AˇB=(A∙B)−AA \check{} B = (A \bullet B) - AAˇB=(A∙B)−A。
同样使用 morphologyEx
函数,当 op = MORPH_TOPHAT
时为顶帽运算,op = MORPH_BLACKHAT
时为黑帽运算,参数与开闭运算时一致。
示例代码
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat image = imread("text_image.jpg", IMREAD_GRAYSCALE);
if (image.empty()) {
cout << "Could not open or find the image" << endl;
return -1;
}
Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
Mat tophat_image, blackhat_image;
// 进行顶帽运算
morphologyEx(image, tophat_image, MORPH_TOPHAT, kernel);
// 进行黑帽运算
morphologyEx(image, blackhat_image, MORPH_BLACKHAT, kernel);
imshow("Original Image", image);
imshow("Top Hat Image", tophat_image);
imshow("Black Hat Image", blackhat_image);
waitKey(0);
return 0;
}
2.4 击中不击中
作用
击中不击中变换(Hit-or-Miss Transform)是一种用于目标形状检测的形态学操作。它可以在图像中寻找与给定模板(结构元素)形状完全匹配的目标,常用于图像中特定形状物体的定位和识别,比如在电路板图像中检测特定形状的元件。
原理
击中不击中变换需要两个结构元素:一个用于检测目标区域(在图像上到处 “比对”,看哪里的形状和它匹配)(记为 B1B_1B1),另一个用于检测目标区域的背景(确认目标周围是不是符合预期的背景)(记为 B2B_2B2)。只有当图像中某个位置同时满足 B1B_1B1 与目标区域匹配,且 B2B_2B2 与该位置的背景区域匹配时,该位置在输出图像中才被标记为目标点。数学表达式为 A⊗(B1,B2)=(A⊖B1)∩(A‾⊖B2)A \otimes (B_1,B_2) = (A \ominus B_1) \cap (\overline{A} \ominus B_2)A⊗(B1,B2)=(A⊖B1)∩(A⊖B2),其中 A‾\overline{A}A 表示图像 AAA 的补集,⊗\otimes⊗ 表示击中不击中运算。
函数原型
在 OpenCV 中,可以使用 morphologyEx 函数,通过设置 op = MORPH_HITMISS 来实现击中不击中变换,也可以通过 erode
函数结合逻辑运算来实现:
// 假设已经定义好结构元素B1和B2,以及输入图像A
Mat hit_or_miss_result;
Mat eroded_A_B1, eroded_notA_B2;
erode(A, eroded_A_B1, B1);
Mat not_A;
bitwise_not(A, not_A);
erode(not_A, eroded_notA_B2, B2);
bitwise_and(eroded_A_B1, eroded_notA_B2, hit_or_miss_result);
示例代码
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat image = imread("shape_image.jpg", IMREAD_GRAYSCALE);
if (image.empty()) {
cout << "Could not open or find the image" << endl;
return -1;
}
// 自定义用于检测目标的结构元素B1
Mat B1 = (Mat_<uchar>(3, 3) << 0, 1, 0,
1, 1, 1,
0, 1, 0);
// 自定义用于检测背景的结构元素B2
Mat B2 = (Mat_<uchar>(3, 3) << 1, 0, 1,
0, 0, 0,
1, 0, 1);
Mat hit_or_miss_result;
Mat eroded_A_B1, eroded_notA_B2;
erode(image, eroded_A_B1, B1);
Mat not_A;
bitwise_not(image, not_A);
erode(not_A, eroded_notA_B2, B2);
bitwise_and(eroded_A_B1, eroded_notA_B2, hit_or_miss_result);
imshow("Original Image", image);
imshow("Hit-or-Miss Result", hit_or_miss_result);
waitKey(0);
return 0;
}
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat image = imread("shape_image.jpg", IMREAD_GRAYSCALE);
if (image.empty()) {
cout << "Could not open or find the image" << endl;
return -1;
}
// 自定义用于检测目标的结构元素B1
Mat B1 = (Mat_<uchar>(3, 3) << 0, 1, 0,
1, 1, 1,
0, 1, 0);
// 自定义用于检测背景的结构元素B2
Mat B2 = (Mat_<uchar>(3, 3) << 1, 0, 1,
0, 0, 0,
1, 0, 1);
// 将B1和B2合并成符合要求的kernel
Mat kernel;
Mat kernels[] = {B1, B2};
merge(kernels, 2, kernel);
Mat hit_or_miss_result;
// 执行击中不击中变换
morphologyEx(image, hit_or_miss_result, MORPH_HITMISS, kernel);
imshow("Original Image", image);
imshow("Hit-or-Miss Result", hit_or_miss_result);
waitKey(0);
return 0;
}
图像形态学通过这些丰富的操作,为图像处理提供了灵活且有效的手段。无论是简单的噪声去除,还是复杂的目标形状检测,这些操作都能在不同场景下发挥独特的作用,帮助开发者更好地处理和分析图像数据。