OpenCV函数应用:基于二值图像的三种孔洞填充方法记录(附python,C++代码)

系列文章目录

函数系列:

  1. OpenCV函数简记_第一章数字图像的基本概念(邻域,连通,色彩空间)
  2. OpenCV函数简记_第二章数字图像的基本操作(图像读写,图像像素获取,图像ROI获取,图像混合,图形绘制)
  3. OpenCV函数简记_第三章数字图像的滤波处理(方框,均值,高斯,中值和双边滤波)
  4. OpenCV函数简记_第四章数字图像的形态学处理和图像金字塔。(腐蚀、膨胀、开,闭运算、形态学梯度、顶帽和黑帽以及图像金字塔)

应用系列:

  1. OpenCV函数应用:基于二值图像的三种孔洞填充方法记录(附python,C++代码)【本文】


前言

在做二分类语义分割任务中,预测的结果总会中间出现孔洞。因此,需要进行孔洞填充。

基于这种需求,我在网上找到了3种可以实现孔洞填充的方法。通过进行学习和分析,总结出该篇文章。本篇文章不会涉及很多原理性概念的介绍,更多的是通过例子和代码进行总结。如果需要更深层次或者更细节的理论解释可以查询我列出来的参考资料。每篇博客都写的十分详细。

如果您觉得对您有帮助的话,可以给小弟一个赞。

本文C++使用的OpenCV版本是的4.5.5。
python使用的OpenCV版本是4.5.3.56
代码示例包含C++和python两个语言的代码


本文内容:
本文描述三种孔洞填充的方法,分别是:
1.形态学重建之孔洞填充
2.轮廓填充
3.漫水填充

参考资料:
1.作者:白菜苗,形态学重建之孔洞填充
2.作者:\lambda,形态学重建:孔洞填充的python实现
3.OpenCV官方文档
4.作者:-牧野-,findContours函数参数详解
5.作者:我的她像朵花,图像分割经典算法–《泛洪算法》(Flood Fill)
6.毛星云-OpenCV3编程入门


1. 形态学重建之孔洞填充

1.1 原理

本方法主要利用形态学重建的思想进行孔洞填充。形态学重建是一种形态学变换。之前的形态学处理都是包含一幅图像和一个结构元的变化,而形态学重建则是包含两幅图像和一个结构元的变化。这两幅图像中,一个是不断迭代膨胀的图,一个是用来约束膨胀结果的图。具体公式如下:
d s t = ( M a r k e r ⊕ S E ) ∩ M a s k dst = (Marker⊕SE)\cap Mask dst=(MarkerSE)Mask
Marker 图像:不断迭代膨胀的图。
Mask 图像:约束膨胀结果的图。
SE:膨胀的结构元。

1.2 示例

从上述公式中一般不是很好理解。我们通过示例来更好的解释:
如下图所示,我们需要将图中左边圆圈内的孔洞进行填充。

形态学重建需要注意以下两点:
1.需要转化为二值图或者灰度图。
2.填充的是黑色孔洞。

在这里插入图片描述
首先,对该图进行取反来获取约束膨胀的Mask图。
在这里插入图片描述
然后,先创建一个全黑图,再在全黑图的边沿设置白色边来生成迭代膨胀的Masker图,其中白色边为膨胀起始点。代码展示如下。

    marker = np.zeros_like(img)
    marker[0, :] = 255
    marker[-1, :] = 255
    marker[:, 0] = 255
    marker[:, -1] = 255

紧接着,按照公式
d s t = ( M a r k e r ⊕ S E ) ∩ M a s k dst = (Marker⊕SE)\cap Mask dst=(MarkerSE)Mask
先对Masker图进行膨胀,然后和Mask图取交集。如下面代码所示:

dilation = cv2.dilate(marker, kernel=SE)
marker = np.min((dilation, mask), axis=0)

再将交集的结果和之前未膨胀的Masker进行对比,如果不一致,则将交集的结果重新赋值给Masker,然后继续膨胀,直到结果一致。

        # 判断经过膨胀后的结果是否和上次迭代一致,如果一致则完成孔洞填充。
        if (marker_pre == marker).all():
            break
        marker_pre = marker

该方法能够进行孔洞填充的关键在于,膨胀的白色像素,一旦遇到黑色边界则会停止扩张。因此如果遇到黑色边界的闭合区域,则内部的孔洞自然就被填充

整个过程可以用以下动图进行展示:
在这里插入图片描述
经过不断迭代后,获得最终的Masker图如下所示:
在这里插入图片描述
最后,再取反就可以得到经过孔洞填充后的结果图。如下图所示:
在这里插入图片描述

1.2.1 Python代码

# PYTHON
import cv2
import numpy as np

def main(img_path, out_path):
    """
    形态学重建之孔洞填充。
    :param img_path: 输入图像的路径
    :param out_path: 需要保存图像的路径根路径。
    :return:
    """
    # 读取并处理图像,
    img = cv2.imread(img_path, 0)

    # 对原图取反,‘255’可以根据图像中类别值进行修改。(例如,图像中二值为0和1,那么255则修改为1)
    # 此时mask用来约束膨胀结果。原图白色为边界,黑色为孔洞和背景,取反后黑色为边界,白色为孔洞和背景。
    mask = 255 - img
    # cv2.imwrite(out_path+ "mask.jpg", mask)
    cv2.namedWindow('mask')
    cv2.imshow('mask', mask)
    cv2.waitKey(5)

    # 以带有白色边框的黑色图像为初始Marker,用来SE来连续膨胀,该图通过迭代生成填充图像。
    marker = np.zeros_like(img)
    marker[0, :] = 255
    marker[-1, :] = 255
    marker[:, 0] = 255
    marker[:, -1] = 255

    # 形态学重建
    SE = cv2.getStructuringElement(shape=cv2.MORPH_CROSS, ksize=(3, 3))
    count = 0
    while True:
        count += 1
        marker_pre = marker
        # 膨胀marker
        dilation = cv2.dilate(marker, kernel=SE)
        # cv2.imwrite(out_path + "dilation_" + str(count) + ".jpg", dilation)
        cv2.imshow('dilation', dilation)
        cv2.waitKey(5)
        # 和mask进行比对,用来约束膨胀。由于mask中黑色为边界,白色为孔洞和背景。
        # 当遇到黑色边界后,就无法继续继续前进膨胀。当遇到白色后,会继续向里面膨胀。孔洞闭合的,遇到全部的黑色边界后,内部自然就已经被填充。
        marker = np.min((dilation, mask), axis=0)
        # cv2.imwrite(out_path + "marker_" + str(count) + ".jpg", marker)
        cv2.imshow('marker', marker)
        cv2.waitKey(5)

        # 判断经过膨胀后的结果是否和上次迭代一致,如果一致则完成孔洞填充。
        if (marker_pre == marker).all():
            break

    # 将结果取反,还原为原来的图像情况。即白色为边界,黑色为孔洞和背景,
    dst = 255 - marker
    cv2.destroyAllWindows()
    # cv2.imwrite(out_path + "dst.jpg", dst)
    cv2.namedWindow('dst')
    cv2.imshow('dst', dst)
    cv2.waitKey(0)
    print("Finished!")


if __name__ == "__main__":
    image_path = r"./1.png"
    output_path = "F:/img_utils/build_postprocess/"
    main(image_path, output_path)

1.2.1 C++代码

//Cpp
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main() {
    
    
	Mat img = imread("./1.png", 0);
	int imgHeight = img.rows;
	int imgWidth = img.cols;
	
	//生成mask图
	Mat mask = 255 - img;
	
	//生成masker图
	Mat masker = Mat::zeros(Size(imgWidth, imgHeight), CV_8U);
	for (int row = 0; row < imgHeight; row++)
	{
    
    
		if (row == 0 or row == imgHeight - 1)
		{
    
    
			for (int col = 0; col < imgWidth; col++)
			{
    
    
				masker.at<uchar>(row, col) = 255;
			}	
		}
		else {
    
    
			masker.at<uchar>(row, 0) = 255;
			masker.at<uchar>(row, imgWidth - 1) = 255;
		}
	}
	
	//iteration
	Mat masker_pre;
	Mat diletion = Mat::zeros(Size(imgWidth, imgHeight), CV_8U);
	Mat se = getStructuringElement(MORPH_CROSS, Size(3, 3));
	while (true) 
	{
    
    
		masker_pre = masker.clone();
		dilate(masker, diletion, se);
		for (int row = 0; row < imgHeight; row++)
		{
    
    
			for (int col = 0; col < imgWidth; col++)
			{
    
    
				masker.at<uchar>(row, col) = diletion.at<uchar>(row, col) < mask.at<uchar>(row, col)? diletion.at<uchar>(row, col): mask.at<uchar>(row, col);
			}
		}
		namedWindow("temp");
		imshow("temp", masker);
		waitKey(5);

		Mat diff = masker != masker_pre;
		bool eq = cv::countNonZero(diff) == 0;
		if (eq == true) {
    
     break; }
	}
	Mat dst = 255 - masker;
	namedWindow("dst");
	imshow("dst", dst);
	waitKey(0);
	return 0;
}

2. 轮廓填充

2.1 原理

轮廓填充主要是应用到Opencv中的两个函数来实现的,分别是findContours和fillPoly。其中findContours是用来寻找图像中轮廓的坐标点,而fillPoly是用来填充轮廓点包围的内部像素。
具体函数的参数介绍如下:

findContours()
作用:
在二值图像中查找轮廓。
函数形式:
C++:
void cv::findContours(
        InputArray image,
        OutputArrayOfArrays contours,
        OutputArray hierarchy,
        int mode,
        int method,
        Point offset = Point() )
Python:
cv.findContours( image, mode, method[, contours[, hierarchy[, offset]]] ) -> contours, hierarchy
参数解释(以C++展示的参数为例):
1.InputArray image:一个8位单通道图像。非零像素被视为1。零像素保持为0,因此图像被视为二值。您可以使用compare、inRange、threshold、adaptiveThreshold、Canny和其他方法从灰度或彩色图像中创建二值图像。如果mode等于RETR_CCOMP或RETR_FLOODFILL,则输入也可以是标签的32位整数图像(CV_32SC1)。
2.OutputArrayOfArrays contours:检测到的轮廓。每个轮廓被存储为一个点的向量(例如std::vector<std::vectorcv::Point >)。
3.OutputArray hierarchy,:可选输出向量(std::vectorcv::Vec4i),包含关于图像拓扑的信息。它的元素和轮廓的数量一样多。
对于contours[i],hierarchy包含4个值。分别是hierarchy[i][0]、hierarchy[i][1]、hierarchy[i][2]和hierarchy[i][3]。这四个值分别为同一hierarchy上的上一条轮廓、下一条轮廓、父轮廓和内嵌轮廓的索引编号。如果对于轮廓 i i i没有下一个、上一个、父或嵌套的轮廓,hierarchy[i]的相应元素将是-1。
注意:在Python中,hierarchy结构嵌套在顶层数组中。使用hierarchy[0][i]访问第i个contours的hierarchy元素。
4.int mode:轮廓检索模式,具体参考内容见附录A
5.int method:轮廓逼近法,具体参考内容见附录B
6.Point offset:所有的轮廓信息相对于原始图像对应点的偏移量。

fillPoly()
作用:
填充由一个或多个多边形所包围的区域。
函数形式:
C++:
void cv::fillPoly(
        InputOutputArray img,
        InputArrayOfArrays pts,
        const Scalar & color,
        int lineType = LINE_8,
        int shift = 0,
        Point offset = Point() )
Python:
cv.fillPoly( img, pts, color[, lineType[, shift[, offset]]] ) -> img
参数解释(以C++展示的参数为例):
1.InputOutputArray img:图像。
2.InputArrayOfArrays pts:多边形数组,其中每个多边形表示为点的数组。
3.const Scalar & color:填充的颜色。
4.int lineType = LINE_8:多边形边界的类型,默认是LINE_8。参见附录C
5.int shift = 0:顶点坐标中的小数位数。
6.Point offset = Point():轮廓所有点的可选偏移量。

2.2 示例

代码示例如下,图像仍然是上节示例。
在这里插入图片描述
再添加一个新图:
在这里插入图片描述

2.2.1 Python代码

import cv2

def main():
    img = cv2.imread(r"./1.png", 0)
    # 如果是RGB图像的话,则需要进行阈值处理。通过imread直接读取灰度则效果有时候欠佳。
    # ret, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
    # 获取图像轮廓点
    contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    out_img = np.zeros((img.shape[0], img.shape[1]))
    
    for i in range(len(contours)):
        cnt = contours[i]
        # 通过轮廓填充。
        cv2.fillPoly(out_img, [cnt], color=255)
    cv2.imshow("cnt", out_img)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

2.2.2 C++代码

#include<iostream>
#include<opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main() {
    
    
	Mat img = imread("./1.png", 0);
	vector<vector<Point>> contours;
	findContours(img, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
	Mat dst = Mat::zeros(Size(img.cols, img.rows), CV_8U);
	for (int i = 0; i < contours.size(); i++) {
    
    
		fillPoly(dst, contours[i], 255);
	}
	namedWindow("dst");
	imshow("dst", dst);
	waitKey(0);
	return 0;
}

python和C++代码的结果都如下所示:
图1:
在这里插入图片描述
图2:
在这里插入图片描述

3 漫水填充

漫水填充,也有称为泛洪算法等。该方法已经被Opencv封装为floodFill() 函数。本节不过多介绍该方法的原理,后续会单独出一章进行讲解。本节主要简单介绍漫水填充的实现方式以及具体函数介绍。并以python和C++代码进行举例说明。

3.1 原理

漫水填充简单理解就是基于一个初始点像素,通过比较它和周边邻域的像素的差值。如果满足设定范围,则用预先设定的值来填充邻域像素,如果不满足设定范围,则不填充邻域像素。基于上述方法,逐渐向外扩散直到无法填充为止。

根据邻域的选择可以分为4邻域泛洪算法,8邻域泛洪算法。

邻域的理解可以参考我的另一篇博客OpenCV函数简记_第一章数字图像的基本概念(邻域,连通,色彩空间)

以四领域为例,可以从下述动图中理解漫水填充原理:

该图来自于作者:我的她像朵花,图像分割经典算法–《泛洪算法》(Flood Fill),该博客介绍比较详细,读者朋友可以参考。

在这里插入图片描述

floodFill()
作用:
函数cv::floodFill从种子点开始用指定的颜色填充连接的邻域。是否连通是由相邻像素的颜色/亮度密切程度决定的。如果满足以下条件,则认为(x,y)处的像素属于重绘区域(填充区域),

  1. 如果是灰度图像和浮动范围
    其中 s r c ( x ′ , y ′ ) src(x^{'},y^{'}) src(x,y)是中心像素, s r c ( x , y ) src(x,y) src(x,y)是邻域像素,由于中心点随着扩散不断变化,因此是浮动范围
    s r c ( x ′ , y ′ ) − l o d i f f ≤ s r c ( x , y ) ≤ s r c ( x ′ , y ′ ) + u p d i f f src(x^{'},y^{'}) - lodiff \leq src(x,y) \leq src(x^{'},y^{'}) + updiff src(x,y)lodiffsrc(x,y)src(x,y)+updiff
  2. 如果是灰度图像和固定范围
    其中 s r c ( s e e d P o i n t . x , s e e d P o i n t . y ) src(seedPoint.x, seedPoint.y) src(seedPoint.x,seedPoint.y)是种子像素, s r c ( x , y ) src(x,y) src(x,y)是填充像素。种子像素不会改变,因此是固定范围。
    s r c ( s e e d P o i n t . x , s e e d P o i n t . y ) − l o d i f f ≤ s r c ( x , y ) ≤ s r c ( s e e d P o i n t . x , s e e d P o i n t . y ) + u p d i f f src(seedPoint.x, seedPoint.y) - lodiff \leq src(x,y) \leq src(seedPoint.x, seedPoint.y) + updiff src(seedPoint.x,seedPoint.y)lodiffsrc(x,y)src(seedPoint.x,seedPoint.y)+updiff
  3. 如果是彩色图像和浮动范围
    s r c ( x ′ , y ′ ) r − l o d i f f r ≤ s r c ( x , y ) r ≤ s r c ( x ′ , y ′ ) r + u p d i f f r src(x^{'},y^{'})_r - lodiff_r \leq src(x,y)_r \leq src(x^{'},y^{'})_r + updiff_r src(x,y)rlodiffrsrc(x,y)rsrc(x,y)r+updiffr
    s r c ( x ′ , y ′ ) g − l o d i f f g ≤ s r c ( x , y ) g ≤ s r c ( x ′ , y ′ ) g + u p d i f f g src(x^{'},y^{'})_g - lodiff_g \leq src(x,y)_g \leq src(x^{'},y^{'})_g + updiff_g src(x,y)glodiffgsrc(x,y)gsrc(x,y)g+updiffg
    s r c ( x ′ , y ′ ) b − l o d i f f b ≤ s r c ( x , y ) b ≤ s r c ( x ′ , y ′ ) b + u p d i f f b src(x^{'},y^{'})_b - lodiff_b \leq src(x,y)_b \leq src(x^{'},y^{'})_b + updiff_b src(x,y)blodiffbsrc(x,y)bsrc(x,y)b+updiffb
  4. 如果是彩色图像和固定的范围
    s r c ( s e e d P o i n t . x , s e e d P o i n t . y ) r − l o d i f f r ≤ s r c ( x , y ) r ≤ s r c ( s e e d P o i n t . x , s e e d P o i n t . y ) r + u p d i f f r src(seedPoint.x, seedPoint.y)_r - lodiff_r \leq src(x,y)_r \leq src(seedPoint.x, seedPoint.y)_r + updiff_r src(seedPoint.x,seedPoint.y)rlodiffrsrc(x,y)rsrc(seedPoint.x,seedPoint.y)r+updiffr
    s r c ( s e e d P o i n t . x , s e e d P o i n t . y ) g − l o d i f f g ≤ s r c ( x , y ) g ≤ s r c ( s e e d P o i n t . x , s e e d P o i n t . y ) g + u p d i f f g src(seedPoint.x, seedPoint.y)_g - lodiff_g \leq src(x,y)_g \leq src(seedPoint.x, seedPoint.y)_g + updiff_g src(seedPoint.x,seedPoint.y)glodiffgsrc(x,y)gsrc(seedPoint.x,seedPoint.y)g+updiffg
    s r c ( s e e d P o i n t . x , s e e d P o i n t . y ) b − l o d i f f b ≤ s r c ( x , y ) b ≤ s r c ( s e e d P o i n t . x , s e e d P o i n t . y ) b + u p d i f f b src(seedPoint.x, seedPoint.y)_b - lodiff_b \leq src(x,y)_b \leq src(seedPoint.x, seedPoint.y)_b + updiff_b src(seedPoint.x,seedPoint.y)blodiffbsrc(x,y)bsrc(seedPoint.x,seedPoint.y)b+updiffb
    函数形式:
    C++:
    int cv::floodFill (
            InputOutputArray image,
            InputOutputArray mask,
            Point seedPoint,
            Scalar newVal,
            Rect * rect = 0,
            Scalar loDiff = Scalar(),
            Scalar upDiff = Scalar(),
            int flags = 4 )
    Python:
    cv.floodFill( image, mask, seedPoint, newVal[, loDiff[, upDiff[, flags]]] ) -> retval, image, mask, rect
    参数解释(以C++展示的参数为例):
    1.InputOutputArray img:输入/输出1通道或3通道、8位或浮点图像。
    2.InputArrayOfArrays mask:掩码用来限制填充的区域,它是一个单通道8位图像,比图像宽2像素,高2像素。如果传递了一个空的Mat,它将自动创建。由于这既是一个输入参数也是一个输出参数,您必须负责初始化它。漫水填充不会填充掩码中的非零像素,因此可以用来限制填充的范围。例如,边缘检测器输出可以用作掩码,以停止填充边缘。
    3.Point seedPoint:漫水填充的起始点。
    4.Scalar newVal:填充领域像素的值。
    5.Rect * rect = 0:可选输出参数,用于设置floodfill函数将要填充区域的最小边界矩形区域。(这个我还不是很理解。)
    6.Scalar loDiff = Scalar():当前像素和其邻域像素的最大低亮度/色差。低于该值不填充。
    7.Scalar upDiff = Scalar():当前像素和其邻域像素的最大高亮度/色差。高于该值不填充。
    8.int flags = 4:操作标识符,此参数包含3个部分。
  • 低八位(0-7位):设置连通域,4是4连通,8是8连通。默认值4表示只考虑四个最近的相邻像素。连通性值为8意味着将考虑8个最近的相邻像素。
  • 高八位(16-23位):可以为0,也可以是标识符的组合。
    包括FLOODFILL_FIXED_RANGE如果设置该操作符,则会和种子像素相比,即固定范围。如果不设置则为浮动范围);FLOODFILL_MASK_ONLY-如果设置该操作符,函数则不会填充改变原始图像,而是去填充掩码图像。列表请参阅FloodFillFlags 附录D
  • 中八位(8-16位): 针对的是FLOODFILL_MASK_ONLY-下的填充掩码的值, 其范围在1到255之间(默认值是1)。

flags可以用or操作符(即‘|’)来进行连接:
举例:8|FLOODFILL_MASK_ONLY-|FLOODFILL_FIXED_RANGE|(38<<8)表示8邻域填充,填充固定范围,且用38去填充掩码。ps:我没理解<<8是啥意思,有知道的读者朋友麻烦从评论区告知。
默认值4表示是4邻域,浮动范围,且填充原始图像。

3.2 示例

同样以上面两个图为示例,原图如下:
在这里插入图片描述
在这里插入图片描述
通过选择背景作为起始点。(由于示例比较简单,所以我直接指定背景的像素。具体应用场景需要读者自己确定起始点。)

   # 漫水填充的初始点,此处为背景。
    seePoint = (5, 5)

通过漫水填充后可以得到。
在这里插入图片描述
在这里插入图片描述
对漫水填充后取反。
在这里插入图片描述
在这里插入图片描述
再和原图相加,即可得到填充后的结果。
在这里插入图片描述
在这里插入图片描述

3.2.1 python代码

import cv2
import numpy as np

def main():
    imgPath = "./1.png"
    # imgPath = r"C:\Users\Lee\Pictures\Opencv\fill\2.png"
    img = cv2.imread(imgPath, 0)

    flood_fill_img = img.copy()
    
    # Mask 用于 floodFill,官方要求长宽+2
    h, w = img.shape[:2]
    mask = np.zeros((h+2, w+2), np.uint8)

    # 漫水填充的初始点,此处为背景。
    seePoint = (5, 5)

    # 漫水填充。获得背景填充的结果。
    cv2.floodFill(flood_fill_img, mask, seePoint, 255)

    # 将填充好的孔洞和原始图像合并,即可获得最终图像。
    # 如果seePoint是孔洞内的坐标,则可以不进行以下步骤。
    dst = (255 - flood_fill_img) + img

    #显示
    cv2.imshow('fill_img', flood_fill_img)
    cv2.imshow('dst_img', dst)
    cv2.imshow('org_img', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

3.2.2 C++代码

#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main() {
    
    
	//Mat img = imread("C:\\Users\\Lee\\Pictures\\Opencv\\fill\\2.png", 0);
	Mat img = imread("C:\\Users\\Lee\\Pictures\\Opencv\\fill\\1.png", 0);
	// 表示需要填充的图像。
	Mat fillImg = img.clone();
	// 填充的掩码。
	Mat mask = Mat::zeros(Size(img.cols + 2, img.rows + 2), CV_8U);
	//漫水填充。
	Point seePoint = Point(5, 5);
	floodFill(fillImg, mask, seePoint, 255);
	//对填充后的结果进行取反,再叠加原图,即可得到填充后的图。
	Mat dst = (255 - fillImg) + img;
	//显示。
	namedWindow("fillImg");
	namedWindow("orgImg");
	namedWindow("dstImg");
	imshow("fillImg", fillImg);
	imshow("orgImg", img);
	imshow("dstImg", dst);
	waitKey(0);
	return 0;
}

三种方法的总结和分析

形态学重建的缺点:

1:如果一个闭合区域内有很多孔洞,该方法会将内部的孔洞全部填充,无法指定填充。
2:当图片的分辨率非常大时,由于该方法采用循环迭代,因此,耗时非常大。
3:处理灰度图像时,会出现一定的边界误差。

轮廓填充的缺点:

1:如果是二值图,效果比形态学重建的一样,并且速度更快。但是如果是RGB图像或者灰度图像的话,则需要进行阈值处理,此时需要设定超参数。
2:图像的精度受轮廓点的精度影像。轮廓找的好,填充的越好。

漫水填充的缺点:

1:需要自己选定初始点。代码中只是简单的示例,实际情况会更复杂。
2:处理灰度图像时,会出现一定的边界误差。

附录 A

RetrievalModes的枚举列表 介绍
RETR_EXTERNAL
Python: cv.RETR_EXTERNAL
只检索极端的外部轮廓。它为所有轮廓设置层次结构[i][2]=层次结构[i][3]=-1。即不包含父轮廓和内嵌轮廓。
RETR_LIST
Python: cv.RETR_LIST
检索所有轮廓,而不建立任何层次关系。
RETR_CCOMP
Python: cv.RETR_CCOMP
检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层
RETR_TREE
Python: cv.RETR_TREE
检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
RETR_FLOODFILL
Python: cv.RETR_FLOODFILL

附录 B

ContourApproximationModes的枚举列表 介绍
CHAIN_APPROX_NONE
Python: cv.CHAIN_APPROX_NONE
存储了所有轮廓点。也就是说,轮廓线中的任意2个点(x1,y1)和(x2,y2)要么是水平的,要么是垂直的,要么是对角邻域,即max(abs(x1-x2),abs(y2-y1))==1。
CHAIN_APPROX_SIMPLE
Python: cv.CHAIN_APPROX_SIMPLE
压缩水平段、垂直段和对角线段,只留下它们的端点,即拐点与拐点之间直线段上的信息点不予保留。例如,一个由上至右的矩形轮廓用4个点编码。
CHAIN_APPROX_TC89_L1
Python: cv.CHAIN_APPROX_TC89_L1
应用了Teh-Chin链近似算法中的一种
CHAIN_APPROX_TC89_KCOS
Python: cv.CHAIN_APPROX_TC89_KCOS
应用了Teh-Chin链近似算法中的一种

附录 C

线段类型 介绍
FILLED
Python: cv.FILLED
填满
LINE_4
Python: cv.LINE_4
4邻接线
LINE_8
Python: cv.LINE_8
8邻接线
LINE_AA
Python: cv.LINE_AA
抗锯齿线

附录 D

FloodFillFlags类型 介绍
FLOODFILL_FIXED_RANGE
Python: cv.FLOODFILL_FIXED_RANGE
如果设置,则考虑当前像素和种子像素之间的差值。否则,将考虑相邻像素之间的差异(即范围是浮动的)。
FLOODFILL_MASK_ONLY
Python: cv.FLOODFILL_MASK_ONLY
如果设置了,函数不会改变图像(忽略newVal),只用前面描述的在flags的第8-16位中指定的值填充掩码。此选项仅在具有mask形参的函数变体中有意义。

猜你喜欢

转载自blog.csdn.net/weixin_43610114/article/details/128034901