后处理

semantic segmentation

Posted by DYC on September 15, 2023

数据后处理

腐蚀膨胀

用来处理图形问题上。总结性的来说: + 膨胀用来处理缺陷问题; + 腐蚀用来处理毛刺问题

膨胀

首先指定对每个像素点膨胀的范围,比如指定范围为3 * 3的矩阵,卷积计算后,该像素点的值等于以该像素点为中心的3 * 3范围内的最大值。如果是二值图像,所以只要包含周围白的部分,就变为白的。

1
2
3
kernel = np.ones((3, 3), dtype=np.uint8)
dilate = cv2.dilate(img, kernel, 1) # 1:迭代次数,也就是执行几次膨胀操作
cv_show(dilate)

腐蚀

腐蚀操作和膨胀操作相反,也就是将毛刺消除,判断方法为:在卷积核大小中对图片进行卷积。取图像中3 * 3区域内的最小值。如果是二值图像,也就是取0(黑色),所以只要原图片3 * 3范围内有黑的,该像素点就是黑的。

1
2
3
4
kernel = np.ones((3, 3), dtype=np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)
ss = np.hstack((img, erosion))
cv_show(ss)

开运算

先腐蚀,再膨胀,这样的作用是先对字体进行变细,再对字体进行变粗,整体上字体粗细不会发生变化。毛刺信息在腐蚀的时候就已经消除了,膨胀也不会膨胀出多余信息

1
2
3
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel, 1)
ss = np.hstack((img, opening))
cv_show(ss)

它的主要作用是去除图像中小的噪点、平滑图像边缘和消除细小的明亮区域。开运算可以使图像中的白色目标变小或缩小,并填充黑色目标中的细小空洞。因此,开运算通常用于图像预处理中的噪声去除阶段。

闭运算

先对图像进行膨胀操作,再进行腐蚀操作

1
2
3
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)  ## 有缺陷,填补缺陷
ss = np.hstack((img, closing))
cv_show(ss)

它的主要作用是填充图像中的小孔、平滑图像边缘和连接相邻的物体。闭运算可以使图像中的黑色目标变大或扩展,并填充白色目标中的细小空洞。因此,闭运算通常用于图像预处理中的图像重构、连通性恢复和填充轮廓等应用。

梯度运算

膨胀的图像 - 腐蚀的图像

1
2
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
cv_show(gradient)

用大一圈的图像减小一圈的图像,即表示图像的边缘信息

高帽

原始图像 - 开运算结果

1
2
3
top_hat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
ss = np.hstack((img, top_hat))
cv_show(ss)

开运算是去除毛刺,原始图像-开运算,即显示图像中的毛刺

黑帽

原始图像-闭运算结果

1
2
3
black_hat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
ss = np.hstack((img, black_hat))
cv_show(ss)

闭运算是去除缺陷,原始图像-闭运算,即显示图像中的缺陷

参考

分水岭算法

一种图像分割算法,用于将图像分割成不同的区域或物体。它基于图像中亮度和颜色的变化来确定边界

将图像的灰度空间想作地球表面结构,每个像素的灰度值代表高度,灰度值较大的部分连成线可以看做山脊,也就是分水岭

image-20230920110230980

刚开始用水填充每个孤立的山谷,当水平面上升到一定高度时,水就会溢出当前山谷,可以通过在分水岭上修大坝,从而避免两个山谷的水汇集,这样图像就被分成2个像素集,一个是被水淹没的山谷像素集,一个是分水岭线像素集

分水岭算法步骤:

  1. 把梯度图像中的所有像素按照灰度值进行分类,并设定一个测地距离阈值。
  2. 找到灰度值最小的像素点(默认标记为灰度值最低点),让threshold从最小值开始增长,这些点为起始点。
  3. 水平面在增长的过程中,会碰到周围的邻域像素,测量这些像素到起始点(灰度值最低点)的测地距离,如果小于设定阈值,则将这些像素淹没,否则在这些像素上设置大坝,这样就对这些邻域像素进行了分类
  4. 随着水平面越来越高,会设置更多更高的大坝,直到灰度值的最大值,所有区域都在分水岭线上相遇,这些大坝就对整个图像像素的进行了分区

对于算法过程中会遇到噪声的情况

  1. 对图像进行高斯平滑操作,抹除很多小的最小值,这些小分区就会合并。
  2. 不从最小值开始增长,可以将相对较高的灰度值像素作为起始点(需要用户手动标记),从标记处开始进行淹没,则很多小区域都会被合并为一个区域,这被称为基于图像标记(mark)的分水岭算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import cv2
import numpy as np

# 读取图像并进行预处理
# 对图像进行二值化
image = cv2.imread("input_image.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 对灰度图像进行阈值化操作,将图像二值化为黑白图像。THRESH_BINARY_INV表示反转二值化结果,THRESH_OTSU表示使用Otsu自适应阈值算法确定阈值
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

# 对图像进行形态学操作,以去除噪声 
# 使用开运算去除噪声
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

# 确定背景区域
#通过膨胀操作扩展背景区域。dilate函数将结构元素应用于图像,扩展图像中的白色区域。
sure_bg = cv2.dilate(opening, kernel, iterations=3)

# 查找未知区域
# 使用距离变换技术确定前景区域。distanceTransform函数可以将图像中每个像素的值替换为该像素到最近背景像素的欧氏距离。然后,根据阈值对距离变换图像进行二值化,获得确定的前景区域
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
# 将前景区域转换为8位无符号整数型。通过逐元素相减的方式,获取未知区域,即不确定属于前景还是背景的区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)

# 标记分水岭区域
#使用连通组件标记方法对前景区域进行标记,每个标记代表一个物体或区域
ret, markers = cv2.connectedComponents(sure_fg)
markers = markers + 1
markers[unknown == 255] = 0

# 应用分水岭算法
# 将前景区域标记加1,并将未知区域标记为0。
markers = cv2.watershed(image, markers)
# 应用分水岭算法,根据标记进行图像分割。watershed函数基于分水岭原理,将图像视为地形,标记点类似于山峰或山谷的位置,通过模拟注水过程来确定分割结果
image[markers == -1] = [0, 0, 255]

# 显示结果
cv2.imshow("Original Image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

参考

Canny边缘检测算法

canny算法包含下面四个步骤

  1. 高斯滤波

    滤波的主要目的是降噪,一般的图像处理算法都需要先进行降噪。而高斯滤波主要使图像变得平滑(模糊),同时也有可能增大了边缘的宽度

  2. 计算梯度值和梯度方向

    边缘是灰度值变化较大的的像素点的集合,所以通过计算梯度,来找到灰度值变化是最大的,即边缘

  3. 过滤非最大值

    在高斯滤波过程中,边缘有可能被放大了。这个步骤使用一个规则来过滤不是边缘的点,使边缘的宽度尽可能为1个像素点:如果一个像素点属于边缘,那么这个像素点在梯度方向上的梯度值是最大的。否则不是边缘,将灰度值设为0

  4. 使用上下阈值检测边缘

    确定一个上阀值和下阀值,位于下阀值之上的都可以作为边缘,这样就可能提高准确度

    设置两个阀值(threshold),分别为maxVal和minVal。其中大于maxVal的都被检测为边缘,而低于minval的都被检测为非边缘。对于中间的像素点,如果与确定为边缘的像素点邻接,则判定为边缘;否则为非边缘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import cv2

# 读取图像
image = cv2.imread('image.jpg')

# 将图像转换为灰度图像
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 高斯滤波
blur = cv2.GaussianBlur(gray, (5, 5), 0)

# Canny 边缘检测
edges = cv2.Canny(blur, 50, 150)

# 显示边缘检测结果
cv2.imshow('Edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()

分水岭+canny算法

利用 Canny 边缘检测算法可以提取图像中的边缘信息,然后,使用分水岭算法来对图像进行分割。分水岭算法基于图像中的梯度信息,将图像分割为多个区域,并在可能的边界处建立隔离(分水岭)。该算法利用了边缘像素的空间关系,通过沿着边缘像素逐渐扩张的方式实现分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import cv2
import numpy as np

# 读取图像
image = cv2.imread('image.jpg')

# 转换为灰度图像
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 进行高斯模糊
blurred = cv2.GaussianBlur(gray, (5, 5), 0)

# 使用 Canny 边缘检测
edges = cv2.Canny(blurred, 50, 150)

# 二值化边缘图像
ret, threshold = cv2.threshold(edges, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

# 对二值图像进行形态学操作,填充小洞
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(threshold, cv2.MORPH_OPEN, kernel, iterations=2)

# 执行膨胀操作,增强前景区域
sure_bg = cv2.dilate(opening, kernel, iterations=3)

# 使用距离变换获取前景区域
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)

# 计算未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)

# 标记连通区域
ret, markers = cv2.connectedComponents(sure_fg)

# 将标记区域加1,确保背景区域为0而不是1
markers = markers + 1

# 标记未知区域为0
markers[unknown == 255] = 0

# 应用分水岭算法进行图像分割
cv2.watershed(image, markers)
image[markers == -1] = [0, 0, 255]  # 标记分割线

# 显示分割结果
cv2.imshow('Segmented Image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

中值滤波

中值滤波是一种非线性滤波方法,常用于图像去噪。中值滤波的原理步骤如下:

  1. 选取一个固定尺寸的滤波器模板,通常为正方形或长方形。滤波器模板的尺寸通常定义为奇数,如 3x3、5x5 或 7x7。
  2. 将滤波器模板按照顺序放置在图像的每个像素上,使模板的中心点与该像素重合。这时候,模板包含了该像素及其周围的一些像素。
  3. 对模板中的所有像素进行排序,取其中位数的值,并将该值赋给中心像素。注意,这里的排序指的是对灰度值进行排序。
  4. 依次处理图像中的所有像素,执行步骤 2 和步骤 3,直到整幅图像都被处理完毕。
1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2

# 读取图像
image = cv2.imread('image.jpg')

# 中值滤波
blurred = cv2.medianBlur(image, 5)

# 显示滤波结果
cv2.imshow('Blurred Image', blurred)
cv2.waitKey(0)
cv2.destroyAllWindows()

双边滤波

能够在去除噪声的同时保留图像的边缘信息。相比于传统的线性滤波方法,双边滤波考虑了像素间的空间距离和像素值的相似性

双边滤波的原理步骤如下:

  1. 选取一个固定尺寸的滤波器模板,通常为正方形或长方形。滤波器模板的尺寸通常在3x3至11x11之间。
  2. 对模板中的每个像素,计算其空间距离权重和像素值相似性权重,以便确定滤波后的像素值。
  3. 空间距离权重表示模板中像素与中心像素的距离。通常采用高斯函数来计算距离权重,在距离较近时权重较大,在距离较远时权重较小。这样可以保证滤波器更加关注邻近区域的像素。
  4. 像素值相似性权重表示模板中像素与中心像素的像素值差异程度。通常采用高斯函数来计算像素值相似性权重,在像素值相似性较高时权重较大,在像素值差异较大时权重较小。这样可以保证滤波器更加关注像素值相似的区域。
  5. 将空间距离权重和像素值相似性权重组合起来,计算滤波后的像素值。这通常是通过对模板中每个像素的像素值乘以对应的权重来实现的。
  6. 依次处理图像中的所有像素,执行步骤 2 到步骤 5,直到整幅图像都被处理完毕
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2

# 读取图像
image = cv2.imread('input_image.jpg')

# 进行双边滤波
# d:表示滤波器模板的直径,一般为正数。sigmaColor:表示像素值相似性权重的高斯标准差。
# sigmaSpace:表示空间距离权重的高斯标准差
filtered_image = cv2.bilateralFilter(image, d=9, sigmaColor=75, sigmaSpace=75)

# 显示原始图像和滤波后的图像
cv2.imshow('Original Image', image)
cv2.imshow('Filtered Image', filtered_image)
cv2.waitKey(0)
cv2.destroyAllWindows()