1.1 连通区域概要
连通区域(Connected Component)一般是指图像中具有相同像素值且位置相邻的前景像素点组成的图像区域,连通区域分析是指图像中的各个连通区域找出并标记。
连通区域分析是一种在 CV 和图像分析处理的众多应用领域中较为常用和基本的方法。例如:OCR 识别中字符分割提取(车牌识别、文本识别、字母识别等)、视觉跟踪中的运动前景目标分割与提取(星人入侵检测、遗留物体检测、基于视觉的车辆检测与跟踪等)、医学图像处理(感兴趣目标区域提取)等。也就是说,在需要将前景目标提取出来以便后续进行处理的应用场景中都能够用到连通区域分析方法,通常连通区域分析处理的对象是一张二值化后的图像。
在图像中,最小的单位是像素,每个像素周围有 8 个邻接像素,常见的邻接关系有两种:4 邻接与 8 邻接。
如果 A 与 B 连通,B 与 C 连通,则 A 与 C 连通,在视觉上看来,彼此联通的点形成了一个区域,而不连通的点形成了不同的区域。这样一个所有彼此连通的点构成的集合,我们成为一个连通区域。下图为二值化处理之后的连通区域,在 4 邻域情况下是 3 个连通区,8 邻域下是 两个连通区
1.2 Two-Pass 算法
两边扫描法(Two-Pass),正如其名,指的就是通过扫描两遍图像,将图像中存在的所有连通域找出并标记。
-
第一次扫描:访问当前像素 B ( x , y ) B(x, y) B(x,y),如果 B ( x , y ) = = 1 B(x, y)==1 B(x,y)==1:
- 如果
B
(
x
,
y
)
B(x, y)
B(x,y) 的邻域中有像素值 > 1 的像素 Neighbors:
- 将 Neighbor 中的最小值赋予给 B ( x , y ) B(x, y) B(x,y):KaTeX parse error: Expected '}', got 'EOF' at end of input: …=min{Neighbors}
- 记录 Neighbors 中各个值(label)之间的相等关系,即这些值(label)同属同一个连通区域;
- 如果
B
(
x
,
y
)
B(x, y)
B(x,y) 的邻域中有像素值 > 1 的像素 Neighbors:
-
第二次扫描:访问当前像素B(x, y),如果B(x, y)>1;
找到与 label=B(x, y) 同属相等关系的一个最小 label 值,赋予给 B(x, y)
完成扫描后,图像中具有相同 label 值的像素就组成了同一个连通区域。
2.1 区域生长算法概要
区域生长是一种串行区域分割的图像分割方法。区域生长是指从某个像素出发,按照一定的准则,逐步加入临近像素,当满足一定的条件时,区域生长终止。
区域生长的好坏决定于
- 初始点(种子点)的选取
- 生长准则
- 终止条件
区域生长是从某个或者某些像素点出发,最后得到整个区域,进而实现目标的提取。
2.2 区域生长原理
基本思想:将具有相似性质的像素集合起来构成区域。
步骤:
-
- 对图像顺序扫描,找到第 1 个还没有归属的像素,设该像素为 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)
-
- 以 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)为中心,考虑 ( x 0 , y 0 ) (x_0, y_0) (x0,y0) 的 4 邻域像素 (x, y), 如果 ( x 0 , y 0 ) (x_0, y_0) (x0,y0) 满足生长准则,将 ( x , y ) (x, y) (x,y)与 ( x 0 , y 0 ) (x_0, y_0) (x0,y0) 合并(在同一区域内),同时将(x, y)压入堆栈;
3.1 分水岭算法概要
任意的灰度图像可以被看作是地质学表面,高亮度的地方是山峰,低亮度的地方是山谷。
3.2 分水岭算法
步骤:
- 将白色背景变成黑色背景-目的是为了后面的变换做准备
- 使用 filter2D 与拉普拉斯算子实现图像对比度的提高
- 转为二值图像
- 距离变换
- 对距离变换结果进行归一化 [0-1] 之间
- 使用阈值,在次二值化,得到标记
- 腐蚀每个 peak erode
- 发现轮廓 fingContours
- 绘制轮廓 drawContours
- 分水岭变换 watershed
- 对每个分割区域着色输出结果
'''
完成分水岭算法步骤
1、加载原始图像
2、阈值分割,将图像分割为黑白两个部分
3、对图像进行开运算,即先腐蚀再膨胀
4、对开运算的结果再进行膨胀,得到大部分是背景的区域
5、通过距离变换 Distance Transform 获取前景区域
6、背景区域 sure_bg 和前景区域 sure_fg 相减,得到既有前景又有背景的重合区域
7、连通区域处理
8、最后使用分水岭算法
'''
import cv2
import numpy as np
# step1 加载图像
img = cv2.imread('test.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#step2 阈值分割,将图像分为黑白两部分
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
cv2.imshow("thresh", thresh)
#step3 对图像进行“开运算”,先腐蚀再膨胀
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
cv2.imshow("opening", opening)
# step4 对“开运算”的结果进行膨胀,得到大部分都是背景的区域
sure_bg = cv2.dilate(opening, kernel, iterations=3)
cv2.imshow("sure_bg", sure_bg)
#step5 通过distanceTransform 获取前景区域
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.1 * dist_transform.max(), 255, 0)
cv2.imshow("sure_fg", sure_fg)
# step6 sure_bg 与 sure_fg 相减,得到既有背景又有前景的重合区域
sure_fg = np.uint8(sure_fg)
unknow = cv2.subtract(sure_bg, sure_fg)
# step7, 连通区域处理
ret, markers = cv2.connectedComponents(sure_fg, connectivity=8) #对连通区域进行编号
markers = markers + 1 #opencv 分水岭算法对物体做的标注必须都大于1,背景标号为0
# 去掉属于背景区域的部分)(即让其变为0,成为背景)
# 此语句的 python 语法,类似于 if ,“unknown==255” 返回的是图像矩阵的真值表
markers[markers==255] = 0
markers = cv2.watershed(img, markers)
print(markers)
img[markers == -1] = [0, 0, 255]
cv2.imshow("dst", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
下图为处理后的猫咪