本文最后更新于:2024年1月14日 晚上
图像处理中,“分割” 是重要的任务之一,本文记录 OpenCV 关于分割相关的功能。
概述
图像分割是个很大的话题,这里,我们重点研究 OpenCV 中的几种专门实现分割方法的技术实现或者后面要用到的形态学策略。
-
图像分割现在还没有一种“百试百灵”的解决方案,在计算机视觉研究中,它仍是一个非常活跃的领域。
-
尽管如此,已经开发出许多可靠的技术,至少在某些特定的领域是可靠的,并且在实践中可以产生非常好的结果。
漫水填充
相比你可能已经接触过的经典计算机绘图程序,OpenCV 中的漫水填充是一种更一般的填充方法。算法首先选取一个种子点,然后将所有与它相似的点包括本身上一种特定的颜色,区别是相邻像素不一定全部上同一种颜色。注19漫水填充的结果是一个单连通区域,如果像素灰度值与任一当前像素差异在指定范围(loDiff
至upDiff
)内或在指定原始种子像素灰度值的指定范围内,cv2.floodFill()
函数都将对它进行上色。漫水填充也可以通过可选的掩膜参数进行约束。cv2.floodFill()
有两种不同的原型,一种接受一个显式的mask参数,另一个不需要。
cv2.floodFill
填充一个连接组件与给定的颜色。
- 函数
cv2.floodFill()
用指定的颜色从种子点开始填充连接元件。连通性取决于邻近像素的颜色/亮度接近程度。 - 在(x,y)处的像素被认为属于重新绘制的域,如果:
情况 | 规则 |
---|---|
灰度图像和浮动范围 | $ \operatorname{src}\left(x^{\prime}, y^{\prime}\right)-\operatorname{loDiff} \leq \operatorname{src}(x, y) \leq \operatorname{src}\left(x^{\prime}, y^{\prime}\right)+ upDiff$ |
灰度图像和固定范围 | $ \operatorname{src}(\operatorname{seedPoint.} x , seedPoint. y)-\operatorname{loDiff} \leq \operatorname{src}(x, y) \leq \operatorname{src}(\operatorname{seedPoint.} x , seedPoint. y)+ upDiff$ |
彩色图像和浮动范围 | $\begin{array}{c} \operatorname{src}\left(x^{\prime}, y^{\prime}\right)_{r}-\operatorname{loDiff}_{r} \leq \operatorname{src}(x, y)_{r} \leq \operatorname{src}\left(x^{\prime} y^{\prime}\right)_{r}+\operatorname{upDiff}_{r} \\ \operatorname{src}\left(x^{\prime}, y^{\prime}\right)_{g}-\operatorname{loDiff}_{g} \leq \operatorname{src}(x, y)_{g} \leq \operatorname{src}\left(x^{\prime}, y^{\prime}\right)_{g}+\operatorname{upDiff}_{g} \\\operatorname{src}\left(x^{\prime} y^{\prime}\right)_{b}-\operatorname{loDiff}_{b} \leq \operatorname{src}(x, y)_{b} \leq \operatorname{src}\left(x^{\prime}, y^{\prime}\right)_{b}+\operatorname{upDiff}_{b} \end{array} $ |
彩色图像和固定范围 | $ \begin{array}{c}\operatorname{src}(\operatorname{seedPoint.} x \text {, seedPoint. } y)_{r}- loDiff _{r} \leq \operatorname{src}(x, y)_{r} \leq \operatorname{src}(\text { seedPoint. } x \text {, seedPoint. } y)_{r}+ upDiff _{r} \\ \operatorname{src}(\operatorname{seedPoint.} x, \text { seedPoint. } y)_{g}- loDiff _{g} \leq \operatorname{src}(x, y)_{g} \leq \operatorname{src}(\operatorname{seedPoint.} x \text {, seedPoint. } y)_{g}+ upDiff _{g} \\ \operatorname{src}(\operatorname{seedPoint.} x, \operatorname{seedPoint.} y)_{b}-\operatorname{loDiff}_{b} \leq \operatorname{src}(x, y)_{b} \leq \operatorname{src}(\operatorname{seedPoint.} x, \text { seedPoint. } y)_{b}+ upDiff _{b} \end{array}$ |
其中$ \operatorname{src}\left(x^{\prime}, y^{\prime}\right) $是已知属于该组件的像素邻居之一的值。也就是说,要添加到连接元件中,像素的颜色/亮度应该足够接近:
-
在浮动范围的情况下,其中一个已经属于连接元件的邻居的颜色/亮度。
-
在固定范围内种子点的颜色/亮度。
-
使用这些函数可以用指定的颜色就地标记连接的组件,或者构建一个蒙版然后提取轮廓,或者将该区域复制到另一个图像,等等。
-
函数使用
1 |
|
-
注意:由于掩码大于填充后的图像,因此图像中的一个像素$ (x,y) $对应于掩码中的像素$ (x+1,y+1)$。
-
flags : FloodFillFlags
标记 含义 cv2.FLOODFILL_FIXED_RANGE 如果设置,则考虑当前像素和种子像素之间的差异。否则,考虑相邻像素之间的差异(即范围是浮动的)。 cv.FLOODFILL_MASK_ONLY 如果设置,该函数不会更改图像(newVal 被忽略),并且仅使用标志位 8-16 中指定的值填充掩码。此选项仅在具有掩码参数的函数变体中才有意义。 flags
可取的值有:cv2.FLOODFILL_FIXED_RANGE
和cv2.FLOODFILL_MASK_ONLY
。除了这些,你还可以为它加数值4或8。这样,你就声明了连通方式采用4连通还是8连通。前一种情况,4连通数组指的是与当前点距离最近的四个邻点(左、右、上和下)。而8连通情况下,对角线上连接的邻点也算在内。实际上,
flags
是一个位字段的参数。然而,方便的是,4和8是单个位,所以你可以使用“add”或“OR”例如,flags=8|
cv2.FLOODFILL_MASK_ONLY
。 -
示例代码
1 |
|
分水岭算法
在许多实际情况中,我们想分割一幅图像,但没有任何一种单独的背景蒙版的能用。这种情况下一种有效的技巧就是分水岭算法。
-
这种算法将一幅图像中的线转化成“山”,平坦区域转换成“谷”以用于辅助物体分割。分水岭算法首先计算强度图像的梯度,这有助于在原图中形成没有纹理的谷或盆地(较低点),同时形成线条丰富的山脉或ranges(对应边缘的高脊)。然后从指定的点开始向这些盆地灌水。当图像被“填满”时,所有有标记的区域就被分割开了。这样一来,连通到标记点的盆地就属于这个标记点了,然后就把相应的标记区域从图像中分割出来。
-
更具体地说,分水岭算法允许使用者(或其他算法)标记已知是对象或背景一部分的对象或背景部分。或者,调用者可以画一条或几条简单的线,这些线条有效地告诉分水岭算法“将这些点组合在一起”。分水岭算法然后通过让标记区域“获取”梯度图中与片段连接的边界确定的峡谷来分割图像。
cv2.watershed
使用分水岭算法执行基于标记的图像分割。
- 在将图像传递给函数之前,您必须用正 (>0) 索引粗略地勾勒出图像标记中所需的区域。因此,每个区域都表示为一个或多个具有像素值 1、2、3 等的连通分量。可以使用
findContours
和drawContours
从二进制掩码中检索此类标记。 - 标记是未来图像区域的“种子”。标记中的所有其他像素,其与轮廓区域的关系未知,应由算法定义,应设置为 0。在函数输出中,标记中的每个像素都设置为“种子”组件的值,或者在区域之间的边界处设置为 -1。
- 任何两个相邻的连通分量不一定被分水岭边界(-1 的像素)分开;例如,它们可以在传递给函数的初始标记图像中相互接触。
- 函数使用
1 |
|
- 示例图像
- 示例代码
1 |
|
Grabcuts 算法
GrabCut该算法利用了图像中的纹理(颜色)信息和边界(反差)信息,只要小量的用户交互操作即可得到比较好的分割效果。
cv2.grabCut
运行 GrabCut 算法。
- 函数使用
1 |
|
- 给定一幅输入图像 img,
cv2.grabCut()
就会计算得到的标签并把它保存在输出数组mask里。mask数组也可以当作输入,用变量mode说明。如果mode包含标签cv2.GC_INIT_WITH_MASK
, 那么在mask里的值将被用于初始化图像的标签。掩膜应当是单通道的 uint8 类型的图像,掩膜里的每个像素都应按下表赋值。
枚举值 | 数值 | 含义 |
---|---|---|
cv2.GC_BGD | 0 | 确定性背景 |
cv2.GC_FGD | 1 | 确定性前景 |
cv2.GC_PR_BGD | 2 | 疑似背景 |
cv2.GC_PR_FGD | 3 | 疑似前景 |
如果没有手工标记GCD_BGD或者GCD_FGD,那么结果只会有GCD_PR_BGD或GCD_PR_FGD。
-
参数
rect
只适用于你不用掩膜初始化的时候。当模式包含标志cv2.GC_INIT_WITH_RECT
时,矩形区域之外的整个区域就被当作是“确定性背景”,而剩下的区域则默认为“疑似前景”。 -
bgdModel
和fgdModel
本质上是临时缓冲区。当你第一次调用cv2.grabCut()
时,它们可以是空数组,但如果需要迭代多次并且中间需要重启算法(可能在允许用户提供额外的“确定”像素来指导算法之后),你就需要传入由上一次运行所填充的相同(未修改)缓冲区(除了使用从上一次运行中返回的掩码作为下一次运行的输入)。 -
Grabcuts 算法需要在内部运行几次Graphcuts 算法。在每个这样的运行之间,都要重新计算混合模型。参数
itercount
一般是10或12,但其大小可能取决于图像的大小和性质。 -
mode: GrabCutModes:
取值 含义 cv2.GC_INIT_WITH_RECT 该函数使用提供的矩形初始化状态和掩码。之后,它运行算法的 iterCount
迭代。cv2.GC_INIT_WITH_MASK 该函数使用提供的掩码初始化状态。请注意, cv2.GC_INIT_WITH_RECT
和cv2.GC_INIT_WITH_MASK
可以组合使用。然后,使用cv2.GC_BGD
自动初始化 ROI 之外的所有像素。cv2.GC_EVAL 该值意味着算法只是恢复。 cv2.GC_EVAL_FREEZE_MODEL 该值意味着算法应该只使用固定模型运行 grabCut 算法(单次迭代) -
示例代码
1 |
|
Mean-Shift分割算法
Mean-Shift 分割算法找到空间上颜色分布的峰值, 它与mean-shift算法有关。基于颜色分布峰值进行这种分割的函数是cv2.pyrMeanShiftFiltering()
。
- meanShfit均值漂移算法是一种通用的聚类算法,它的基本原理是:对于给定的一定数量样本,任选其中一个样本,以该样本为中心点划定一个圆形区域,求取该圆形区域内样本的质心,即密度最大处的点,再以该点为中心继续执行上述迭代过程,直至最终收敛。可以利用均值偏移算法的这个特性,实现彩色图像分割.
cv2.pyrMeanShiftFiltering
Mean-Shift 分割算法
- 函数使用
1 |
|
在这个过程中,关键参数是
sp
和sr
的设置,二者设置的值越大,对图像色彩的平滑效果越明显,同时函数耗时也越多。
- 示例代码
1 |
|
参考资料
- 《学习 OpenCV3》 第十二章
- https://blog.csdn.net/ftimes/article/details/106839647
- https://www.bilibili.com/read/cv7418817/
- https://www.jianshu.com/p/11b5dc8f0242
- https://blog.csdn.net/qq_38309818/article/details/111699266
文章链接:
https://www.zywvvd.com/notes/study/image-processing/opencv/opencv-segmentation/opencv-segmentation/
“觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭”

微信支付

支付宝支付