当前位置: 首页 > news >正文

形态学图像处理(Morphological Image Processing)

形态学图像处理(Morphological Image Processing)

前言

本博客为个人总结数字图像处理一课所写,并给出适当的扩展和相应的demo。

写博客跟做 checkpoint​ 很像,毕竟个人还不能达到那种信手拈来的境界,忘了就是从零开始训练,这就令人有些头疼了。

题外话说完,开始吧。

Note:

  • 笔者环境为 Ubuntu 24.04 LTS
  • 延续以往的写作风格,我会用自己的话来理解,而非简单直接地贴上原本的概念(目的不是写paper),那当然是尽量侧重通俗易懂了,除非我才疏学浅,没词了
  • 令人感到悲哀的是,我所谓的数字图像处理课的教材没这玩意(反正我也基本不用),我得参考一些资料,尤其是英文的资料。


目录

文章目录

  • 形态学图像处理(Morphological Image Processing)
    • 前言
    • 目录
    • 什么叫形态学图像处理(Morphological Image Processing)
    • 集合论须知的公式
    • 结构元素(structuring element)
      • 简要介绍
      • 邻域“窗口”算子(Neighborhood "window" operator)与结构元素
      • 结构元素的三大属性的影响
    • 简述算子是什么
    • 二值形态学的基本运算
      • 腐蚀(Erosion)
        • 腐蚀的解释与公式
        • 腐蚀的应用
        • 腐蚀操作的demo实现
      • 膨胀(Dilation)
        • 膨胀的解释与公式
        • 膨胀的应用
        • 膨胀操作的demo实现
      • 膨胀与腐蚀的关系
      • 开运算(Opening)
        • 开运算的简介
        • 开运算的demo实现
      • 闭运算(Closing)
        • 闭运算的简介
        • 闭运算的demo实现
      • 开闭运算的侧重
      • 顶帽运算(Top Hat)
        • 顶帽运算的简介
        • 顶帽运算的demo实现
      • 黑帽运算(Black Hat)
        • 黑帽运算的简介
        • 黑帽运算的demo实现
      • 梯度运算(Gradient)
        • 梯度运算的简介
        • 梯度运算的demo实现
      • 击中击不中变换(Hit - miss filter)
        • 击中击不中变换的解释与公式
        • 击中击不中变换的应用
    • 灰度形态学的基本运算
      • 灰度图像
      • Flat/一般腐蚀算子
      • Flat/一般膨胀算子
      • Flat腐蚀和膨胀算子和一般腐蚀和膨胀算子
    • 形态学边缘检测(Morphological edge detectors)
      • 简要介绍
      • 对于二值图像
      • 对于灰度图像
    • 参考资料
      • data
      • blog and related sources


什么叫形态学图像处理(Morphological Image Processing)

什么是形态学图像处理呢?

我们打一个比方,我们讲皮肉骨,图像的形态就好比是皮肉,支撑起皮肉的是骨头,我们的形态学处理就是做生物改造,通过改变里面的骨头,进而改变其外在的皮肉的表现。

怎么改变骨头呢?换骨头,按照定好的规则对于原本的骨头用定制的骨头进行替换。

简而言之,就是侧重于图像”皮肉“下的”骨头“的处理。

下面是正式一点的介绍:

形态学图像处理在图像分析中具有重要地位,广泛应用于计算机视觉、图像分割、特征提取等领域。

其主要操作包括膨胀、腐蚀、开闭运算、边缘检测等,这些操作基于集合理论,通过定义结构元素对图像进行处理,能够有效提取图像的形状、结构和特征信息,为后续的分析和理解奠定基础。


集合论须知的公式

概念,如无必要,就直接上公式

  1. 集合的并(Union):

A ∪ B = { x ∣ x ∈ A or  x ∈ B } A \cup B = \{x \mid x \in A \text{ or } x \in B\} AB={xxA or xB}

  1. 集合的交(Intersection):

A ∩ B = { x ∣ x ∈ A and  x ∈ B } A \cap B = \{x \mid x \in A \text{ and } x \in B\} AB={xxA and xB}

  1. 集合的补(Complement):

A ‾ = { x ∣ x ∉ A } \overline{A} = \{x \mid x \notin A\} A={xx/A}

  1. 集合的差(Difference):

A − B = { x ∣ x ∈ A and  x ∉ B } A - B = \{x \mid x \in A \text{ and } x \notin B\} AB={xxA and x/B}

  1. 集合的反射(Reflection):

A ′ = { ( − x , − y ) ∣ ( x , y ) ∈ A } A^{'} = \{(-x, -y) \mid (x, y) \in A \} A={(x,y)(x,y)A}

  1. 集合的平移(Translation):

A + y = { ( x + y 1 , y + y 2 ) ∣ ( x , y ) ∈ A } A + \mathbf{y} = \{ (x + y_1, y + y_2) \mid (x, y) \in A \} A+y={(x+y1,y+y2)(x,y)A}


结构元素(structuring element)

简要介绍

提到图像形态学处理,我们绕不开的一个概念是结构元素,所谓的结构元素(也称核或模板)是一个小的二值(binary)图像,用于与输入图像进行数学操作,以提取图像的特定形状特征

什么是二值图像呢?就是一个尺寸较小 { 3 × 3 , 5 × 5 , … } \{3\times3,5\times5,\ldots\} {3×3,5×5,} 的图像(或者叫方阵),只包含0(背景,黑色),1(前景,白色)两种像素值。它是灰度/彩色图像分析系统中的中间抽象,常用于阈值分割、判断图像属性等,如文本和线条图形、文档图像处理。

结构元素有多种形状,常见的为方形圆形十字形线形等等。

确定了结构元素的大小和形状后,我们还需要确定结构元素中的原点(锚点),所谓原点,就是结构元素中用于与输入图像对齐的参考点,具体描述就是,结构元素跟原图像某个位置按运算的规则匹配上后,就会基于锚点和运算进行相应的像素值的处理。

综上,结构元素有三大属性,大小形状原点


邻域“窗口”算子(Neighborhood “window” operator)与结构元素

邻域“窗口”算子,同结构元素密切相关,其公式表示为:

W { f [ x , y ] } = { f [ x − x ′ , y − y ′ ] : [ x ′ , y ′ ] ∈ ∏ x y } W\{f[x, y]\}=\left\{f\left[x-x', y-y'\right]:\left[x', y'\right] \in \prod_{x y}\right\} W{f[x,y]}={f[xx,yy]:[x,y]xy}

这里, f [ x , y ] f[x, y] f[x,y] 是图像在坐标 ( x , y ) (x, y) (x,y) 处的像素值, Π x y \Pi_{xy} Πxy 定义了一个特定的邻域范围,它确定了在处理像素 f [ x , y ] f[x, y] f[x,y] 时,需要考虑的周围像素的集合范围。

它与结构元素的不同在于,它是用结构元素去选取图像中的像素集合,再根据具体的形态学操作对这些像素值进行组合计算,而结构元素本身并不进行计算操作,个人以为,这也是为什么结构元素又被叫做模板的原因。

说的通俗点,拿印章举例,结构元素就是印章的图案,邻域“窗口”算子则是印章的印油

这个公式也有两个变种,这两个变种,我们会在后面的二值形态学的基本运算中用到。

W ( − ) { f [ x , y ] } = { f [ x − x ′ , y − y ′ ] : [ x ′ , y ′ ] ∈ ∏ x y } W^{\left(-\right)}\{f[x,y]\}=\left\{f\left[x-x^{\prime},y-y^{\prime}\right]:\left[x^{\prime},y^{\prime}\right]\in\prod_{xy}\right\} W(){f[x,y]}={f[xx,yy]:[x,y]xy}

  1. 根据结构元素 Π x y \Pi_{xy} Πxy 的形状和大小,确定图像 f f f 中以 ( x , y ) (x, y) (x,y) 为中心的邻域->
  2. 对于结构元素 Π x y \Pi_{xy} Πxy 中的每一个点 ( x ′ , y ′ ) (x', y') (x,y) 计算图像 f f f 中对应的点 ( x − x ′ , y − y ′ ) (x - x', y - y') (xx,yy)->
  3. 获取该点的像素值 f [ x − x ′ , y − y ′ ] f[x - x', y - y'] f[xx,yy] ->
  4. 将所有这些像素值收集到一个集合中,这个集合就是 W ( − ) { f [ x , y ] } W^{(-)}\{f[x,y]\} W(){f[x,y]}​。

W ( + ) { f [ x , y ] } = { f [ x + x ′ , y + y ′ ] : [ x ′ , y ′ ] ∈ ∏ x y } W^{\left(+\right)}\{f[x,y]\}=\left\{f\left[x+x^{\prime},y+y^{\prime}\right]:\left[x^{\prime},y^{\prime}\right]\in\prod_{xy}\right\} W(+){f[x,y]}={f[x+x,y+y]:[x,y]xy}

同理。


结构元素的三大属性的影响

前面我们提到,结构元素有三大属性,那么这三大属性会怎样影响最后的处理效果呢?

大小,越大说明什么,变化大,影响的范围大,形态学的操作效果会更明显,对原本图像的“骨架”进行更大影响,相应的细节就可能在处理中发生丢失,小的话,则相反。

形状,不同的形状,意味着处理的侧重不同,结构元素的形状,决定了改造后的图像的“骨架”的倾向。

原点,可以理解为我们的工作中心,中心在哪里,处理的结果的落脚点就在在哪里。原点的不同使得最终“骨架”的变化方向,细节处理和特征的匹配有显著的影响。


简述算子是什么

我想,很多同我一样的初学者会有一个疑问,什么是算子?

我们知道函数,简单来说,函数的构成是输入,特定的规则,和将输入经过规则变换得到的结果。

算子的构成则是输入的函数,特定的规则,和将输入的函数经过规则变换得到的另一个函数的映射或操作规则。

换而言之,算子处理的对象,是函数


二值形态学的基本运算

二值形态学的基本运算包括腐蚀、膨胀、开运算和闭运算。这些运算在图像处理中用于提取图像的形状特征,如边界、区域和连通性等。

二值图像中像素用 0 0 0 (背景,黑色)和 1 1 1 (前景,白色)表示,是灰度/彩色图像分析系统中的中间抽象。

有一点值得注意,各种形态学的运算操作,都是基于腐蚀和膨胀操作的组合。

因此,我们可以说,在二值形态学下和灰度形态学下的形态学运算,我们只需要专门关注腐蚀和膨胀操作的不同即可,其余的组合运算就不需要反复强调。


腐蚀(Erosion)

腐蚀的解释与公式

首先我们得回答一个问题,为什么叫腐蚀?

字面意思来讲,就是将一个东西进行“消减”的操作,那么首先想到的效果就是,“变细”或者“变瘦”。在我们的二值图像中的表现,就是将前景物体(像素值为 1 1 1 )缩小。

把公式先端上来:

g [ x , y ] = A N D [ W ( + ) { f [ x , y ] } ] : = e r o d e ( f , W ) g[x,y]=AND[W^{(+)}\{f[x,y]\}]:=erode(f,W) g[x,y]=AND[W(+){f[x,y]}]:=erode(f,W)

其含义为对于每个像素 f [ x , y ] f[x, y] f[x,y] W ( + ) W^{(+)} W(+) 按照 Π x y \Pi_{xy} Πxy 定义的邻域范围移动结构元素,选取结构元素覆盖到的像素值进行逻辑与 A N D AND AND 操作。

当结构元素 W ( + ) W^{(+)} W(+) 在图像上移动时,只有当结构元素覆盖区域内的所有像素值都为 1 1 1 时,在腐蚀后的图像 g [ x , y ] g[x,y] g[x,y] 中,对应结构元素中心位置的像素值才为 1 1 1;只要该区域内有一个像素值为 0 0 0 ,那么腐蚀后对应位置的像素值就为 0 0 0

我想,得画图才能表示地更加清晰。

QQ20241113-142008

通过上图,再直观一点说,腐蚀操作是用结构元素扫描图像中的每一个像素,当结构元素与图像中对应区域完全匹配时,结构元素中心所对应的像素在腐蚀后的图像中才被置为 1 1 1,否则被置为 0 0 0

对于上面的公式,我们可能比较陌生,我们更熟悉的可能是下面这个公式:

A ⊖ B = { x ∣ ( B ) y ⊆ A } A \ominus B = \{ x \mid (B)_{y} \subseteq A \} AB={x(B)yA}

其中:

  • A A A:表示原始的二值图像,其中包含前景(值为1的像素)和背景(值为0的像素)。
  • B B B:表示结构元素,它是一个较小的二值图像,用于探测 A A A 中的特定形状或特征。
  • ⊖ \ominus :表示腐蚀操作。
  • ( B ) y (B)_{y} (B)y:表示结构元素 B B B 通过平移操作,使其原点移动到空间中的位置 y y y

简单地解释这个公式:若 ( B ) y (B)_{y} (B)y 仍包含于 A A A 中,则其所有 y y y 点组成的集合,称为 A A A B B B 腐蚀。


腐蚀的应用

  1. 去除噪声:去除孤立噪声点
  2. 图像细化:得到骨架结构
  3. 目标分离:分离粘连目标
  4. 特征提取:获取边界信息

腐蚀操作的demo实现

import cv2
import numpy as np
from matplotlib import pyplot as pltdef save_pair_imgs(imgs,original_title,processed_title):plt.figure(figsize=(10, 8))plt.subplot(1, 2, 1)plt.imshow(imgs[0],'gray')plt.title(original_title)plt.xticks([]), plt.yticks([])plt.subplot(1, 2, 2)plt.imshow(imgs[1],'gray')plt.title(processed_title)plt.xticks([]), plt.yticks([])title = original_title + '_' + processed_titleplt.savefig(f'../imgs/{title}.png', bbox_inches='tight')plt.show()# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取# 定义结构元素
kernel = np.ones((5,5), np.uint8)# 腐蚀操作
erosion = cv2.erode(image, kernel, iterations = 1)
save_pair_imgs([image,erosion],'Original Image','Erosion')

效果图如下:

Original Image_Erosion


膨胀(Dilation)

膨胀的解释与公式

膨胀,跟腐蚀相反,它是把图像进行一个“变胖”或者说“变粗”的操作。二值图像中的表现,就是将前景物体扩大。

端上公式:

g [ x , y ] = O R [ W ( − ) { f [ x , y ] } ] : = d i l a t e ( f , W ) g[x,y]=OR[W^{(-)}\{f[x,y]\}]:=dilate(f,W) g[x,y]=OR[W(){f[x,y]}]:=dilate(f,W)

当结构元素 W ( − ) W^{(-)} W() 在图像上移动时,只有当结构元素覆盖区域内的所有像素值都为 0 0 0 时,在膨胀后的图像 g [ x , y ] g[x,y] g[x,y] 中,对应结构元素中心位置的像素值才为 0 0 0;只要该区域内有一个像素值为 1 1 1 ,那么膨胀后对应位置的像素值就为 1 1 1

继续上图:

QQ20241113-171552

直观一点说,膨胀操作是用结构元素扫描图像中的每一个像素,当结构元素与图像中对应区域至少有一个匹配时,结构元素中心所对应的像素在膨胀后的图像中就被置为 1 1 1,否则被置为 0 0 0

同样地,我们通常看到的是下面的公式:

A ⊕ B = { x ∣ ( B ) y ∩ A ≠ ∅ } A \oplus B = \{ x \mid (B)_{y}\cap A \neq \emptyset \} AB={x(B)yA=}

其中:

  • A A A:表示原始的二值图像,其中包含前景(值为1的像素)和背景(值为0的像素)。
  • B B B:表示结构元素,它是一个较小的二值图像,用于探测 A A A 中的特定形状或特征。
  • ⊕ \oplus :表示膨胀操作。
  • ( B ) y (B)_{y} (B)y:表示结构元素 B B B 通过平移操作,使其原点移动到空间中的位置 y y y

简单地解释这个公式:若 ( B ) y (B)_{y} (B)y A A A 的交集不为空,则其所有 y y y 点组成的集合,称为 A A A B B B 膨胀。


膨胀的应用

  • 图像预处理:填充孔洞,连接断裂部分
  • 目标增强:突出物体轮廓,扩大目标区域
  • 形态学梯度计算:获取边缘信息


膨胀操作的demo实现

import cv2
import numpy as np
from matplotlib import pyplot as pltdef save_pair_imgs(imgs,original_title,processed_title):plt.figure(figsize=(10, 8))plt.subplot(1, 2, 1)plt.imshow(imgs[0],'gray')plt.title(original_title)plt.xticks([]), plt.yticks([])plt.subplot(1, 2, 2)plt.imshow(imgs[1],'gray')plt.title(processed_title)plt.xticks([]), plt.yticks([])title = original_title + '_' + processed_titleplt.savefig(f'../imgs/{title}.png', bbox_inches='tight')plt.show()# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取# 定义结构元素
kernel = np.ones((5,5), np.uint8)# 膨胀操作
dilation = cv2.dilate(image, kernel, iterations=1)
save_pair_imgs([image, dilation], 'Original Image', 'Dilation')

效果图如下:

Original Image_Dilation


膨胀与腐蚀的关系

  1. 对偶性

    d i l a t e ( f , W ) = N O T [ e r o d e ( N O T [ f ] , W ) ] dilate(f,W)=NOT[erode(NOT[f],W)] dilate(f,W)=NOT[erode(NOT[f],W)]

    e r o d e ( f , W ) = N O T [ d i l a t e ( N O T [ f ] , W ) ] erode(f,W)=NOT[dilate(NOT[f],W)] erode(f,W)=NOT[dilate(NOT[f],W)]

    也就是说,对前景进行膨胀,相当于对背景进行腐蚀,对背景进行腐蚀,相当于对前景进行膨胀。

  2. 但腐蚀不是膨胀的逆运算

    f [ x , y ] ≠ e r o d e ( d i l a t e ( f , W ) , W ) ≠ d i l a t e ( e r o d e ( f , W ) , W ) f[x,y]\neq erode(dilate(f,W),W)\neq dilate(erode(f,W),W) f[x,y]=erode(dilate(f,W),W)=dilate(erode(f,W),W)


开运算(Opening)

开运算的简介

开运算是先腐蚀后膨胀的组合操作:

o p e n ( f , W ) = d i l a t e ( e r o d e ( f , W ) , W ) open(f,W)=dilate(erode(f,W),W) open(f,W)=dilate(erode(f,W),W)

我们更常见的公式如下:

A ∘ B = ( A ⊖ B ) ⊕ B A \circ B = (A \ominus B) \oplus B AB=(AB)B

其中, A A A 是原始图像, B B B 是结构元素。

前面我们知道,腐蚀可以去除皮肉,露出"骨架",去掉噪声的同时会损失一些细节,那么我们就用膨胀操作,把它们长回来,那不就可以做到去掉噪声,又基本保留物体原本的轮廓了吗(腐蚀和膨胀没有逆运算的性质,所以说是基本)?

简单来说,开运算所做的就是去除小的前景部分(数值为1),也就是去除一些细小突出物、较小的粘连、小面积的噪声,同时保持了物体的基本形状。


开运算的demo实现

import cv2
import numpy as np
from matplotlib import pyplot as pltdef save_pair_imgs(imgs,original_title,processed_title):plt.figure(figsize=(10, 8))plt.subplot(1, 2, 1)plt.imshow(imgs[0],'gray')plt.title(original_title)plt.xticks([]), plt.yticks([])plt.subplot(1, 2, 2)plt.imshow(imgs[1],'gray')plt.title(processed_title)plt.xticks([]), plt.yticks([])title = original_title + '_' + processed_titleplt.savefig(f'../imgs/{title}.png', bbox_inches='tight')plt.show()# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取# 定义结构元素
kernel = np.ones((5,5), np.uint8)# 开运算操作
opening = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
save_pair_imgs([image, opening], 'Original Image', 'Opening')

效果图如下:

Original Image_Opening


闭运算(Closing)

闭运算的简介

闭运算是先膨胀后腐蚀的组合操作:

c l o s e ( f , W ) = e r o d e ( d i l a t e ( f , W ) , W ) close(f,W)=erode(dilate(f,W),W) close(f,W)=erode(dilate(f,W),W)

我们更常见的公式如下:

A ∙ B = ( A ⊕ B ) ⊖ B A \bullet B = (A \oplus B) \ominus B AB=(AB)B

其中, A A A 是原始图像, B B B 是结构元素。

我们为什么要闭运算?先膨胀,去除边界凹陷,包含掉背景噪声,填充孔洞,连接相邻,再腐蚀,就能将包含的背景噪声去除,也保留物体原本的主要特征。

闭运算去除的是小的背景部分(数值为0)。


闭运算的demo实现

import cv2
import numpy as np
from matplotlib import pyplot as pltdef save_pair_imgs(imgs,original_title,processed_title):plt.figure(figsize=(10, 8))plt.subplot(1, 2, 1)plt.imshow(imgs[0],'gray')plt.title(original_title)plt.xticks([]), plt.yticks([])plt.subplot(1, 2, 2)plt.imshow(imgs[1],'gray')plt.title(processed_title)plt.xticks([]), plt.yticks([])title = original_title + '_' + processed_titleplt.savefig(f'../imgs/{title}.png', bbox_inches='tight')plt.show()# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取# 定义结构元素
kernel = np.ones((5,5), np.uint8)# 闭运算操作
closing = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
save_pair_imgs([image, closing], 'Original Image', 'Closing')

效果图如下:

Original Image_Closing


开闭运算的侧重

闭运算的侧重点,是去除小的背景部分保留主要​**特征**。

开运算的侧重点,是去除小的前景部分保留主要​**轮廓**。


顶帽运算(Top Hat)

顶帽运算的简介

顶帽运算也称为礼帽运算,它是原图像与开运算结果的差值。

其公式定义如下:

T = f − ( f ∘ b ) T = f - (f \circ b) T=f(fb)

顶帽运算能够提取出图像中比周围结构元素尺寸范围内背景更亮的部分,它主要用于分离图像中的明亮部分(如比周围区域亮的物体或噪声)与整体背景。

也就是,分离出噪声信息或者比元素图像更亮的边缘信息。


顶帽运算的demo实现

import cv2
import numpy as np
from matplotlib import pyplot as pltdef save_pair_imgs(imgs,original_title,processed_title):plt.figure(figsize=(10, 8))plt.subplot(1, 2, 1)plt.imshow(imgs[0],'gray')plt.title(original_title)plt.xticks([]), plt.yticks([])plt.subplot(1, 2, 2)plt.imshow(imgs[1],'gray')plt.title(processed_title)plt.xticks([]), plt.yticks([])title = original_title + '_' + processed_titleplt.savefig(f'../imgs/{title}.png', bbox_inches='tight')plt.show()# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取# 定义结构元素
kernel = np.ones((5,5), np.uint8)# Top Hat 操作
tophat = cv2.morphologyEx(image, cv2.MORPH_TOPHAT, kernel)
save_pair_imgs([image, tophat], 'Original Image', 'Top Hat')

效果图如下:

Original Image_Top Hat


黑帽运算(Black Hat)

黑帽运算的简介

黑帽运算的功能与顶帽运算相反,它能够提取出图像中比周围结构元素尺寸范围内背景更暗的部分,主要用于分离图像中的暗部分(如比周围区域暗的物体或阴影)与整体背景。

也就是,获取图像内部的小孔,前景色中的小黑点,或者比原始图像的边缘更暗的边缘部分。

黑帽运算是闭运算结果与原图像的差值。

其公式定义如下:

B = ( f ⋅ b ) − f B=(f \cdot b) - f B=(fb)f


黑帽运算的demo实现

import cv2
import numpy as np
from matplotlib import pyplot as pltdef save_pair_imgs(imgs,original_title,processed_title):plt.figure(figsize=(10, 8))plt.subplot(1, 2, 1)plt.imshow(imgs[0],'gray')plt.title(original_title)plt.xticks([]), plt.yticks([])plt.subplot(1, 2, 2)plt.imshow(imgs[1],'gray')plt.title(processed_title)plt.xticks([]), plt.yticks([])title = original_title + '_' + processed_titleplt.savefig(f'../imgs/{title}.png', bbox_inches='tight')plt.show()# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取# 定义结构元素
kernel = np.ones((5,5), np.uint8)# Black Hat 操作
blackhat = cv2.morphologyEx(image, cv2.MORPH_BLACKHAT, kernel)
save_pair_imgs([image, blackhat], 'Original Image', 'Black Hat')

效果图如下:

Original Image_Black Hat


梯度运算(Gradient)

梯度运算的简介

什么是梯度运算,梯度,我们下意识可以想到高数中的梯度。

梯度是一个向量,其方向指向标量函数增长最快的方向,其大小(或长度)表示该方向上的增长速率。

边缘通常是图像中物体与背景之间像素值变化剧烈的地方,因此,梯度运算可以突出图像中的边缘信息(位置和强度)

梯度运算的公式很多,常见的基本差分梯度公式如下:

  • 对于二维离散图像 f ( x , y ) f(x,y) f(x,y)

    • 水平方向梯度: G x = f ( x + 1 , y ) − f ( x , y ) G_x = f(x + 1,y) - f(x,y) Gx=f(x+1,y)f(x,y),它衡量了图像在水平方向上相邻像素值的变化。例如,在一个灰度图像中,如果从左到右像素值逐渐增加,那么 G x G_x Gx 在相应位置将为正值,表示从暗到亮的变化趋势;反之,如果像素值逐渐减小, G x G_x Gx 为负值,表示从亮到暗的变化。
    • 垂直方向梯度: G y = f ( x , y + 1 ) − f ( x , y ) G_y = f(x,y + 1) - f(x,y) Gy=f(x,y+1)f(x,y),用于检测图像在垂直方向上的像素值变化情况。与 G x G_x Gx​ 类似,其正负值反映了垂直方向上像素值的增减趋势。
    • 总的梯度幅值 G = G x 2 + G y 2 G = \sqrt{G_x^2 + G_y^2} G=Gx2+Gy2 ,这种计算方式综合考虑了水平和垂直方向的梯度变化,得到一个标量值,表示每个像素点的梯度大小。梯度值越大,说明该像素点处图像的变化越剧烈,越有可能是边缘位置。


梯度运算的demo实现

import cv2
import numpy as np
from matplotlib import pyplot as pltdef save_pair_imgs(imgs,original_title,processed_title):plt.figure(figsize=(10, 8))plt.subplot(1, 2, 1)plt.imshow(imgs[0],'gray')plt.title(original_title)plt.xticks([]), plt.yticks([])plt.subplot(1, 2, 2)plt.imshow(imgs[1],'gray')plt.title(processed_title)plt.xticks([]), plt.yticks([])title = original_title + '_' + processed_titleplt.savefig(f'../imgs/{title}.png', bbox_inches='tight')plt.show()# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取# 定义结构元素
kernel = np.ones((5,5), np.uint8)# 梯度操作
gradient = cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel)
save_pair_imgs([image, gradient], 'Original Image', 'Gradient')

效果图如下:

Original Image_Gradient


击中击不中变换(Hit - miss filter)

击中击不中变换的解释与公式

击中击不中变换用于在二值图像中检测特定形状和结构,它基于结构元素与图像像素的匹配情况来确定目标形状或结构的位置。

击中击不中变换需要使用两个结构元素:结构元素 V V V(用于匹配前景,目标元素)和结构元素 W W W (用于匹配背景,目标元素的周围的背景元素)。对于图像中的每个像素点,当以该像素点为中心的图像区域与结构元素 V V V 在前景部分完全匹配,同时与结构元素 W W W 在背景部分完全匹配时,该像素点被标记为目标形状或结构的位置。

设二值图像为 f f f ,击中击不中变换后的图像为 g g g ,则击中击不中变换可以表示为:

g = f ⊛ ( V , W ) g = f \circledast (V,W) g=f(V,W)

其中 ⊛ \circledast 表示击中击不中变换操作。

具体计算时,先对图像 f f f 进行腐蚀操作,腐蚀结构元素为 V V V ,得到一个中间结果 f V f_V fV

再对图像 f f f 的补集(即背景部分)进行腐蚀操作,腐蚀结构元素为 W W W ,得到另一个中间结果 f W C f_W^C fWC ;最后将 f V f_V fV f W C f_W^C fWC 进行逻辑与操作,得到击中击不中变换的结果 g g g ,即:

g = ( f ⊖ V ) ∩ ( f C ⊖ W ) g = (f \ominus V) \cap (f^C \ominus W) g=(fV)(fCW)

其中 ⊖ \ominus 表示腐蚀操作, f C f^C fC 表示 f f f 的补集。

我们可以理解为完全匹配,前景和背景元素需要跟设定的完全一致。

上图:

image

Note:在击中击不中变换的结构元中,-1表示背景,1表示前景,0表示不关心


击中击不中变换的应用

  • 形状检测与识别:在工业检测中,用于检测产品表面特定形状的缺陷或标记,如检测电路板上的特定形状的焊点是否完整、有无缺陷等。通过设计合适的结构元素来匹配正常焊点的形状,利用击中击不中变换可以快速准确地找出不符合形状要求的焊点,从而实现产品质量的检测和控制。
  • 纹理分析:在纹理图像分析中,某些纹理特征可能具有特定的形状或结构模式,击中击不中变换可以帮助提取这些纹理特征,用于图像分类、分割等任务。例如在分析纺织品的纹理时,检测其中特定的编织图案或纹理结构,以判断纺织品的质量和类型。
  • 字符识别预处理:在光学字符识别(OCR)系统中,可作为预处理步骤,用于检测字符的特定结构部分,如字母中的孔洞(如字母“o”“p”等中的圆形孔洞)、笔画的端点和交叉点等特征,这些特征信息可以为后续的字符分类和识别提供重要依据,提高字符识别的准确性和效率。


灰度形态学的基本运算

灰度图像

灰度图像与二值图像不同,它的像素值范围为 x ∈ [ 0 − 255 ] , x ∈ Z x\in[0-255],x\in\mathbb{Z} x[0255],xZ,能够表示图像的明暗变化、纹理等细节。

对于灰度图像 f [ x , y ] f[x,y] f[x,y],我们可以通过阈值集合 T θ ( f [ x , y ] ) = { [ x , y ] : f [ x , y ] ≥ θ } , − ∞ < θ < + ∞ T_{\theta}(f[x,y])=\{[x,y]:f[x,y]\geq\theta\},-\infty<\theta<+\infty Tθ(f[x,y])={[x,y]:f[x,y]θ},<θ<+ 来分解图像,原始图像可由 f [ x , y ] = s u p { θ : [ x , y ] ∈ T θ ( f [ x , y ] ) } f[x,y]=sup\{\theta:[x,y]\in T_{\theta}(f[x,y])\} f[x,y]=sup{θ:[x,y]Tθ(f[x,y])} 重建。

阈值集合表示的是图像中灰度值大于等于的 θ \theta θ 所有像素点的集合。如果 θ = 128 \theta=128 θ=128 ,那么 T 128 ( f [ x , y ] ) T_{128}(f[x,y]) T128(f[x,y]) 就是图像中所有灰度值大于等于 128 128 128 的像素点的集合。

简单来说,对于灰度图像,我们可以通过一个叫做阈值化的操作来分解图像,然后可以通过 supremum​ 操作重建图像。


Flat/一般腐蚀算子

灰度形态学中,Flat腐蚀算子定义如下:

g [ x , y ] = min ⁡ { W ( + ) { f [ x , y ] } } : = e r o d e ( f , W ) g[x,y]=\min\{W^{(+)}\{f[x,y]\}\}:=erode(f,W) g[x,y]=min{W(+){f[x,y]}}:=erode(f,W)

其中:

  • f [ x , y ] f[x,y] f[x,y] 是原始的灰度图像在坐标 ( x , y ) (x,y) (x,y) 处的像素值
  • W ( + ) W^{(+)} W(+) 是结构元素相关的邻域“窗口”算子
  • g [ x , y ] g[x,y] g[x,y] 是腐蚀后的图像在坐标 ( x , y ) (x,y) (x,y) 处的像素值

其含义为,在图像的每个像素位置,取结构元素窗口 W W W 内的最小值作为该像素的新值。

那么,图像中的暗区域就会扩大,而明亮区域就会缩小(因为变成小值了,越小越暗)。

也就是,暗大明小。

灰度形态学中,一般腐蚀算子定义如下:

g [ x , y ] = inf ⁡ α , β { f [ x + α , y + β ] − w [ α , β ] } = e r o d e ( f , w ) g[x,y]=\inf_{\alpha,\beta}\{f[x+\alpha,y+\beta]-w[\alpha,\beta]\}=erode(f,w) g[x,y]=α,βinf{f[x+α,y+β]w[α,β]}=erode(f,w)

这里 inf​ 表示取下确界(类似最小值)。

其含义为,对于图像 f [ x , y ] f[x,y] f[x,y] 中的每个像素点 ( x , y ) (x,y) (x,y) ,要在结构元素 w [ α , β ] w[\alpha,\beta] w[α,β] 所覆盖的邻域内进行计算,即计算 f [ x + α , y + β ] f[x+\alpha,y+\beta] f[x+α,y+β](即图像在平移 ( α , β ) (\alpha,\beta) (α,β) 后的像素值)与(结构元素在 ( α , β ) (\alpha,\beta) (α,β) 位置的值)的差值,并取这些差值中的下确界作为像素点 ( x , y ) (x,y) (x,y) 经过腐蚀后的新值 g [ x , y ] g[x,y] g[x,y]


Flat/一般膨胀算子

灰度形态学中,Flat膨胀算子定义如下:

g [ x , y ] = max ⁡ { W ( − ) { f [ x , y ] } } : = d i l a t e ( f , W ) g[x,y]=\max\{W^{(-)}\{f[x,y]\}\}:=dilate(f,W) g[x,y]=max{W(){f[x,y]}}:=dilate(f,W)

与Flat腐蚀算子相反,它所做的是在图像的每个像素位置,取结构元素窗口 W W W 内的最大值作为该像素的新值。

那么,图像中的明亮区域就会扩大,而暗区域就会缩小(因为变成大值了,越大越亮)。

也就是,明大暗小。

灰度形态学中,一般膨胀算子定义如下:

g [ x , y ] = s u p α , β { f [ x − α , y − β ] + w [ α , β ] } = s u p α , β { w [ x − α , y − β ] + f [ α , β ] } g[x,y]=sup_{\alpha,\beta}\{f[x-\alpha,y-\beta]+w[\alpha,\beta]\}=sup_{\alpha,\beta}\{w[x-\alpha,y-\beta]+f[\alpha,\beta]\} g[x,y]=supα,β{f[xα,yβ]+w[α,β]}=supα,β{w[xα,yβ]+f[α,β]}

这里的 s u p sup sup 表示取上确界(类似于取最大值)。对于图像 f [ x , y ] f[x,y] f[x,y] 中的每个像素点 ( x , y ) (x,y) (x,y),在结构元素 w [ α , β ] w[\alpha,\beta] w[α,β] 的邻域内,计算 f [ x − α , y − β ] f[x-\alpha,y-\beta] f[xα,yβ](图像平移 ( − α , − β ) (-\alpha,-\beta) (α,β) 后的像素值)与 w [ α , β ] w[\alpha,\beta] w[α,β] 的和,并取这些和中的上确界作为像素点 ( x , y ) (x,y) (x,y) 经过膨胀后的新值 g [ x , y ] g[x,y] g[x,y]


Flat腐蚀和膨胀算子和一般腐蚀和膨胀算子

从名字上可以猜到,Flat算子其实是一般算子的特殊情况。

用途上就是,没特殊需要或者精度要求就用Flat算子简单处理即可,否则就要使用计算更加复杂的一般算子来处理。


形态学边缘检测(Morphological edge detectors)

简要介绍

我们知道,腐蚀去皮肉留下骨头,膨胀让骨头长出肉,那么我们将膨胀后的结果与腐蚀后的结果进行差异的比较,那么是不是就可以直接得到外面的皮肉的轮廓了?

正式点说,对原图像腐蚀会减小前景物体,对原图像膨胀会增大前景物体,那么两者的差异则是物体的边界轮廓。

但是有个前提如下:

  • e r o d e ( f , W ) ≠ f A N D d i l a t e ( f , W ) ≠ e r o d e ( f , W ) erode(f,W)\neq f \space AND \space dilate(f,W)\neq erode(f,W) erode(f,W)=f AND dilate(f,W)=erode(f,W)

在图像中,边缘检测可突出物体轮廓,有助于后续的目标识别、图像分割等任务,如检测图像中物体的边界。

  • 优点:保持边缘连通性,对噪声鲁棒性较好
  • 缺点:边缘定位精度相对较低,依赖于结构元素的选择


对于二值图像

g e d g e [ x , y ] = g d i l a t e [ x , y ] X O R g e r o d e [ x , y ] g_{edge}[x,y] = g_{dilate}[x,y] \space XOR \space g_{erode}[x,y] gedge[x,y]=gdilate[x,y] XOR gerode[x,y]

我们知道,二值图像只有 { 0 , 1 } \left\lbrace0,1\right\rbrace {0,1} 两种值,那么我们将膨胀运算后的结果与腐蚀运算后的结果进行逻辑异或运算就可以精确地提取出边缘像素,进而直接显示出物体的轮廓边界。


对于灰度图像

g e d g e [ x , y ] = g d i l a t e [ x , y ] − g e r o d e [ x , y ] g_{edge}[x,y] = g_{dilate}[x,y] - g_{erode}[x,y] gedge[x,y]=gdilate[x,y]gerode[x,y]


参考资料

data

  • Lena图等: http://www.eecs.qmul.ac.uk/~phao/IP/Images/

    检索到的出处: https://blog.csdn.net/nbu_dahe/article/details/114698790

blog and related sources

  • 图像数学形态学的基本原理与代码实现(腐蚀、膨胀、开闭运算):https://blog.csdn.net/deepsprings/article/details/107291741
  • 第8章:形态学操作: https://blog.csdn.net/weixin_57440207/article/details/122647000
  • 斯坦福大学Bernd Girod教授的《数字图像处理》课程的讲义: https://web.stanford.edu/class/ee368/Handouts/Lectures/2015_Autumn/7-Morphological_16x9.pdf

相关文章:

形态学图像处理(Morphological Image Processing)

形态学图像处理(Morphological Image Processing) 前言 ‍ 本博客为个人总结数字图像处理一课所写&#xff0c;并给出适当的扩展和相应的demo。 写博客跟做 checkpoint​ 很像&#xff0c;毕竟个人还不能达到那种信手拈来的境界&#xff0c;忘了就是从零开始训练&#xff0…...

【IDER、PyCharm】免费AI编程工具完整教程:ChatGPT Free - Support Key call AI GPT-o1 Claude3.5

文章目录 CodeMoss 简介CodeMoss 的模型集成如何安装和配置 CodeMossIDER 插件安装步骤 CodeMoss 的实战使用AI 问答功能代码优化与解释优化这段代码解释这段代码 文件上传与对话联网查询与 GPT 助手联网查询GPT 助手 提升开发效率的最佳实践结语更多文献 CodeMoss 简介 CodeM…...

C++11的一些实用特性

1.统一的列表初始化 在C98中&#xff0c;标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。 //统一的列表初始化 struct Date {int year;int month;int day; };void test1() {Date d1 { 2024,11,14 };int array1[] { 1, 2, 3, 4, 5 };int array2[5] { …...

23种设计模式-观察者(Observer)设计模式

文章目录 一.什么是观察者模式&#xff1f;二.观察者模式的结构三.观察者模式的应用场景四.观察者模式的优缺点五.观察者模式的实现&#xff08;C示例&#xff09;六.观察者模式的实现&#xff08;JAVA示例&#xff09;七.代码解释八.总结 类图&#xff1a; 观察者设计模式类图…...

【CUDA】Branch Divergence and Unrolling Loop

目录 一、避免分支发散 1.1 并行规约问题 1.2 并行规约中的发散 二、UNrolling Loops 一、避免分支发散 控制流有时依赖于 thread 索引。同一个warp中&#xff0c;一个条件分支可能导致性能很差。通过重新组织数据获取模式可以减少或避免 warp divergence。具体问题查看下…...

深度学习:卷积神经网络的计算复杂度,顺序操作,最大路径长度

卷积层的计算复杂度 在深度学习中&#xff0c;卷积层的计算复杂度主要取决于卷积核的大小、输入和输出的通道数量、以及输入序列的长度。具体来说&#xff0c;卷积层的计算复杂度可以通过以下几个因素来计算&#xff1a; 卷积核大小 k&#xff1a;卷积核的大小决定了每次卷积操…...

springboot 配置文件中 multipart.max-file-size 各个版本的写法

由于springboot具有几个版本&#xff0c;不同版本对于文件上传最大限制的配置也有所不同。 所以要注意springboot本身的版本&#xff0c;不然会一直报错 在springboot1.3版本中&#xff1a; multipart.maxFileSize在springboot1.4与springboot1.5版本中&#xff1a; spring…...

linux 中mysql查看慢日志

1、到mysql容器&#xff0c;先登录到数据库&#xff0c;查看是否开启 mysql -h 127.0.0.1 -uroot -p SHOW VARIABLES LIKE slow_query_log; 2、如果没有开启&#xff0c;需要先开启 set global slow_query_log ON; 3、查看慢日志文件 SHOW VARIABLES LIKE slow_query_log…...

单片机的基本组成与工作原理

单片机&#xff08;Microcontroller Unit, MCU&#xff09;是一种将计算机的主要部分集成在一个芯片上的小型计算机系统。它通常包括中央处理器&#xff08;CPU&#xff09;、存储器&#xff08;Memory&#xff09;、输入输出接口&#xff08;I/O Ports&#xff09;、定时器/计…...

智慧隧道和智慧交通

通过引入先进的物联网技术&#xff0c;将各种硬件设备如传感器、摄像头、控制系统等有效地连接并管理起来&#xff0c;以实现道路安全和交通流畅的目标。这些设备将能够实时监控和控制隧道内的各种设备和系统&#xff0c;从而提高道路安全、提升驾驶体验并降低管理成本。 在这个…...

List、Set、Map详解和区别

在 Java 中&#xff0c;List、Set、Map是常用的集合类型&#xff0c;它们各自具有不同的特点和用途&#xff0c;以下是对它们的详细介绍及区别分析&#xff1a; List&#xff08;列表&#xff09; 特点&#xff1a; 有序性&#xff1a;List中的元素是有序的&#xff0c;即元素…...

界面控件DevExpress WinForms v24.2新功能预览 - 支持.NET 9

DevExpress WinForms 拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…...

Postman之pm.test断言操作

Postman之pm.test断言操作 1.断言方法2.连接符3.条件判断符 用于验证请求的响应数据是否符合预期 1.断言方法 pm.test()&#xff1a;定义一个测试函数&#xff0c;接受两个参数&#xff0c;一个字符串参数用来描述该测试&#xff0c;一个返回True/False的函数 语法格式&#…...

对数几率回归

对数几率回归简介 对数几率回归&#xff08;Logistic Regression&#xff09;是一种用于解决分类问题的经典统计模型&#xff0c;其核心思想是利用逻辑函数&#xff08;Sigmoid函数&#xff09;将线性回归模型的输出值映射到概率范围 [0, 1]&#xff0c;从而实现分类预测。对数…...

docker 配置同宿主机共同网段的IP 同时通过通网段的另一个电脑实现远程连接docker

docker配置网络 #宿主机执行命令 ifconfig 查询对应的主机ip 子网掩码 网关地址 #[网卡名称]&#xff1a;inet[主机IP] netmask[子网掩码] broadcast[网关地址]这里需要重点关注&#xff1a;eno1[网卡名称]以及【192.168.31.225】网关地址 在宿主机执行docker命令创建一个虚拟…...

4-7-1.C# 数据容器 - LinkedList(LinkedList 的定义、LinkedList 结点的遍历、LinkedList 的常用方法)

LinkedList 概述 LinkedList<T> 通过节点&#xff08;Node&#xff09;来存储数据&#xff0c;每个节点包含数据和指向下一个节点的引用 LinkedList<T> 存储的元素是可重复的 LinkedList<T> 支持泛型&#xff0c;可以指定存储的元素的类型 LinkedList<…...

「三」体验HarmonyOS端云一体化开发模板——使用DevEco Studio直接创建端云一体化工程

关于作者 白晓明 宁夏图尔科技有限公司董事长兼CEO、坚果派联合创始人 华为HDE、润和软件HiHope社区专家、鸿蒙KOL、仓颉KOL 华为开发者学堂/51CTO学堂/CSDN学堂认证讲师 开放原子开源基金会2023开源贡献之星 「目录」 「一」HarmonyOS端云一体化概要 「二」体验HarmonyOS端云一…...

确保以管理员权限运行 Visual Studio 开发者命令提示符

文章目录 解决方法&#xff1a;1. 以管理员身份运行命令提示符2. 改变目录权限3. 改变项目目录位置4. 检查文件系统权限 总结&#xff1a; ********************************************************************** ** Visual Studio 2022 Developer Command Prompt v17.12.0 …...

命令执行简单(棱角社区有毒)

前言&#xff1a;小迪安全2022第一节反弹shell&#xff0c;小迪用的是两台都是云服务器&#xff0c;没有服务器可以在自己的主机上搭建也是可以的&#xff0c;主机上搭两个网站 思路&#xff1a;生成一个木马文件&#xff0c;下载到本机&#xff0c;然后利用本机上传到目标主机…...

Keil基于ARM Compiler 5的工程迁移为ARM Compiler 6的工程

环境&#xff1a; keil版本为5.38&#xff0c;版本务必高于5.30 STM32F4的pack包版本要高于2.9 软件包下载地址&#xff1a;https://zhuanlan.zhihu.com/p/262507061 一、更改Keil中编译器 更改后编译&#xff0c;会报很多错&#xff0c;先不管。 二、更改头文件依赖 观察…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...