OpenCV实战(15)——轮廓检测详解

Posted 盼小辉丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV实战(15)——轮廓检测详解相关的知识,希望对你有一定的参考价值。

OpenCV实战(15)——轮廓检测详解

0. 前言

在计算机视觉领域,轮廓通常指图像中对象边界的一系列点。因此,轮廓通常描述了对象边界的关键信息,包含了有关对象形状的主要信息,该信息可用于形状分析与对象检测和识别。本节中,我们首先介绍如何提取图像中轮廓,然后讲解如何计算轮廓的形状描述符。

1. 提取区域轮廓

1.1 轮廓提取

图像通常包含目标对象的表示,图像分析的目标之一是识别和提取这些对象。在目标检测/识别应用中,通常需要生成一个二值图像,显示目标物体的位置,提取包含在二值图像中的对象。例如,使用如下二值图像:

我们可以通过简单的阈值操作获得此图像,然后应用开/闭形态滤波器。本节将介绍如何提取图像中的目标对象,更具体地说,我们将提取图像中的连接部分,即由二值图像中的一组连接像素组成的形状。OpenCV 提供了一个简单的函数来提取图像的连接部分的轮廓,即 cv::findContours 函数。

(1) 要使用 cv::findContours 函数,我们需要一个点向量存储所有输出轮廓:

std::vector<std::vector<cv::Point> > contours;

(2) 使用 cv::findContours 函数检测图像的所有轮廓并将它们保存在轮廓向量中:

cv::findContours(image,
        contours,               // 轮廓向量
        cv::RETR_EXTERNAL,      // 检索外部轮廓
        cv::CHAIN_APPROX_NONE); // 检索每个轮廓的所有像素

cv::findContours 函数的输入是二值图像,输出是一个轮廓向量,每个轮廓由一个 cv::Point 对象向量表示,因此输出参数定义为 std::vector 对象。此外,还指定了两个标志,第一个表示只需要外部轮廓,即忽略对象中的孔;第二个标志用于指定轮廓的格式,使用 CV_CHAIN_APPROX_NONE 选项,向量将列出轮廓中的所有点,使用 CV_CHAIN_APPROX_SIMPLE 标志,将仅包含水平、垂直或对角线轮廓的端点,也可以使用其他标志获取更复杂的轮廓链近似表示。使用上示图像,可以得到 10 个连通分量。

(3) 使用 OpenCV 可以非常方便地在一张图片上绘制出连接部分的轮廓:

cv::Mat result(image.size(), CV_8U, cv::Scalar(255));
cv::drawContours(result, contours,
                -1,             // 绘制所有轮廓
                cv::Scalar(0),  // 颜色
                2);             // 线宽为2

如果此函数的第 3 个参数为负值,则绘制所有轮廓,也可以使用正值指定要绘制的轮廓的索引,如下图所示:

轮廓是通过系统地扫描图像直到检测出所有的目标部分,从连接部分上的起点开始,沿着它的轮廓,在其边框上标记像素;完成标记后,在最后一个位置继续扫描,直到找到新的连接部分。

(4) 然后可以单独分析识别的连接部分。例如,我们可以通过预估目标对象的预期大小消除一些无效部分,可以使用连接部分周长的最小值和最大值消除无效连接:

// 消除所有过短或过长的轮廓
int cmin = 50;
int cmax = 500;
std::vector<std::vector<cv::Point> >::iterator itc = contours.begin();
while (itc!=contours.end()) 
    if (itc!=contours.end()) 
        if (itc->size()<cmin || itc->size()>cmax) 
            itc = contours.erase(itc);
         else 
            ++itc;
        
    

由于 std::vector 中的每个消除操作的时间复杂度都是 O ( N ) O(N) O(N),因此该循环可以进一步进行优化。在原图上绘制轮廓,结果如下图所示:

1.2 复杂轮廓分析

使用简单的标准就能够帮助我们识别图像中所有感兴趣的对象,在更复杂情况下,我们需要对连接部分的属性进行更精细的分析。
使用 cv::findContours 函数,还可以通过在函数调用中指定 CV_RETR_LIST 标志检测二值图中所有闭合轮廓(包括对象中的孔形轮廓):

cv::findContours(image, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);

使用以上函数调用,可以得到以下轮廓:

可以看到上图中增加了额外轮廓。也可以将这些轮廓组织成层次结构,主要部分是父组件,其中的孔是其子组件,如果这些孔内还有组件,它们将成为之前子组件的子组件,依此类推,该层次结构可以通过使用 CV_RETR_TREE 标志获得:

std::vector<cv::Vec4i> hierarchy;
cv::findContours(image,
                contours, // 轮廓向量
                hierarchy, // 分层表示
                CV_RETR_TREE, // 使用树结构检索所有轮廓 
                CV_CHAIN_APPROX_NONE); // 每一轮廓的所有像素

在这种情况下,每个轮廓在相同的索引处都有一个对应的层次元素,由四个整数组成。前两个整数提供了同一级别的下一个和前一个轮廓的索引,后两个整数提供该轮廓的第一个子项和父项的索引,负索引表示轮廓列表的结尾。CV_RETR_CCOMP 标志类似,但层次结构仅包括两个级别。

2. 计算区域形状描述符

连接部分通常对应于图片场景的中某个目标对象,为了识别此对象,或将其与其他图像元素进行比较,我们可能需要进行测量以提取所需特征。在本节中,我们介绍 OpenCV 中可用的形状描述符,用于描述轮廓形状。
有多个 OpenCV 函数可用作形状描述符,应用这些函数可以提取连接部分。我们使用目标对象相对应的轮廓向量,计算轮廓上( contours[0]contours[3] )的形状描述符并在轮廓图像(线宽为 1 )上绘制结果(线宽为 2)。

(1) boundingRect 函数用于计算矩形边框:

// 矩形
cv::Rect r0 = cv::boundingRect(contours[0]);
cv::rectangle(result, r0, 0, 2);

(2) minEnclosingCircle 函数用于近似最小包围圆:

// 圆形
float radius;
cv::Point2f center;
cv::minEnclosingCircle(contours[1], center, radius);
cv::circle(result, center, static_cast<int>(radius), 0, 2);

(3) 区域轮廓的多边形近似使用 approxPolyDP 函数:

// 近似多边形
std::vector<cv::Point> poly;
cv::approxPolyDP(contours[2], poly, 5, true);
cv::polylines(result, poly, true, 0, 2);
std::cout << "Polygon size: " << poly.size() << std::endl;

多边形绘制函数 cv::polylines 与其他绘图函数类似,第 3 个参数为布尔类型用于指示轮廓是否闭合,如果为true,则将最后一个点连接到第一个点。

(4) 凸包函数 convexHull 是多边形近似的另一种形式:

// 凸包
std::vector<cv::Point> hull;
cv::convexHull(contours[3], hull);
cv::polylines(result, hull, true, 0, 2);

(5) 矩是另一个强大的描述符,可以计算区域内的质心:

// 矩
itc = contours.begin();
while (itc!=contours.end()) 
    cv::Moments mom = cv::moments(*itc++);
    cv::circle(result,
                cv::Point(mom.m10/mom.m00, mom.m01/mom.m00),
                2, cv::Scalar(0), 2);

结果图像如下:

边界框大多数情况下是表示和定位图像中目标对象的最紧凑的方式,其定义为完全包含对象形状的最小尺寸的矩形。边界框的高度和宽度可以指示对象的垂直或水平尺寸,例如,可以使用高宽比来区分汽车和行人;当只需要目标的近似尺寸和位置时,通常使用最小包围圆。
当想要与目标对象形状相似的紧凑的表示时,可以采用多边形近似,通过指定精度参数( cv::approxPolyDP 函数中的第 4 个参数)指定目标对象形状与近似多边形之间的最大可接受距离,函数返回的 cv::Point 的向量对应于多边形的顶点。为了绘制这个多边形,我们需要遍历向量并在它们之间线段将相邻点连接起来。
形状的凸包或凸包络是包含形状的最小凸多边形,可以将其想象为弹性皮筋围在目标对象周围时的形状,凸包轮廓将在对象形状轮廓的凹面位置偏离原始轮廓,这些位置通常称为凸面缺陷,并且可以使用 OpenCV 函数 cv::convexityDefects 识别这些缺陷,调用方式如下所示:

std::vector<cv::Vec4i> defects;
cv::convexityDefects(contours[3], hull, defects);

contourhull 参数分别是原始轮廓和凸包轮廓(均为 std::vector<cv::Point> 实例)。输出是由四个整数元素组成的向量,前两个整数是轮廓上的点索引,用于界定缺陷;第三个整数对应凹面内最远的点,最后一个整数对应这个最远点到凸包的距离。
矩是形状结构分析中常用的数学工具,OpenCV 定义了一个封装了形状所有计算矩的数据结构,cv::moments 函数的返回值就使用这种数据结构,这些矩构成了对物体形状的简洁描述。我们可以使用这个结构中前3个空间矩来获得形状的质心。
也可以使用 OpenCV 函数计算结构属性,cv::minAreaRect 函数计算最小的封闭旋转矩形;cv::contourArea 函数估计轮廓(内部像素数)的面积;cv::pointPolygonTest 函数用于确定一个点是在轮廓内部还是外部,而 cv::matchShapes 可以测量两个轮廓之间的相似性。我们可以通过组合所有这些属性进行更高级的图像结构分析。

2.1 四边形检测

我们可以利用形态学操作转换后获得的图像提取图像形状,假设,我们使用形态学操作转换图像获得的 MSER 结果,然后构建算法检测图像中的四边形分量。假设我们检测以下使用 MSER 算法得到的二值图像,检测四边形分量能够帮助我们识别建筑物上的窗户等,为了减少图像中的噪音,我们使用了一些形态滤波器对图像进行预处理:

// 创建二值图像
components = components==255;
// 图像开操作
cv::morphologyEx(components, components, cv::MORPH_OPEN, cv::Mat(), cv::Point(-1, -1), 3);

接下来,获取轮廓:

// 反转图像
cv::Mat componentsInv = 255 - components;
// 获取轮廓和连接部分
cv::findContours(componentsInv, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);

最后,遍历所有轮廓并用多边形近似:

cv::Mat quadri(components.size(), CV_8U, 255);
std::vector<std::vector<cv::Point> >::iterator it = contours.begin();
while (it!= contours.end()) 
    poly.clear();
    // 使用多边形近似轮廓
    cv::approxPolyDP(*it,poly,5,true);
    // 检测轮廓是否为四边形
    if (poly.size()==4) 
        cv::polylines(quadri, poly, true, 0, 2);
    

    ++it;

检测结果如下所示:

如果想要检测矩形,我们可以测量相邻边之间的角度并消除掉偏差过大(与 90 度相比)的四边形。

3. 完整代码

完整代码文件 blobs.cpp 如下所示:

#include <iostream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int main() 
    // 读取二进制图像
    cv::Mat image = cv::imread("binary.png", 0);
    if (!image.data) return 0;
    cv::namedWindow("Binary Image");
    cv::imshow("Binary Image", image);
    // 获取轮廓和连接部分
    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(image,
            contours,               // 轮廓向量
            cv::RETR_EXTERNAL,      // 检索外部轮廓
            cv::CHAIN_APPROX_NONE); // 检索每个轮廓的所有像素
    std::cout << "Contours: " << contours.size() << std::endl;
    std::vector<std::vector<cv::Point> >::const_iterator itContours = contours.begin();
    for (; itContours!=contours.end(); ++itContours) 
        std::cout << "Size: " << itContours->size() << std::endl;
    
    // 绘制轮廓
    cv::Mat result(image.size(), CV_8U, cv::Scalar(255));
    cv::drawContours(result, contours,
                    -1,             // 绘制所有轮廓
                    cv::Scalar(0),  // 颜色
                    2);             // 线宽为2
    cv::namedWindow("Contours");
    cv::imshow("Contours", result);
    // 消除所有过短或过长的轮廓
    int cmin = 50;
    int cmax = 500;
    std::vector<std::vector<cv::Point> >::iterator itc = contours.begin();
    while (itc!=contours.end()) 
        if (itc!=contours.end()) 
            if (itc->size()<cmin || itc->size()>cmax) 
                itc = contours.erase(itc);
             else 
                ++itc;
            
        
    
    // 绘制轮廓
    cv::Mat original = cv::imread("2.png");
    cv::drawContours(original, contours, -1, cv::Scalar(0, 0, 255), 2);
    cv::namedWindow("Contours on Animals");
    cv::imshow("Contours on Animals",original);
    result.setTo(cv::Scalar(255));
    cv::drawContours(result, contours, -1, 0, 1);
    image = cv::imread("binary.png", 0);
    // 矩形
    cv::Rect r0 = cv::boundingRect(contours[0]);
    cv::rectangle(result, r0, 0, 2);
    // 圆形
    float radius;
    cv::Point2f center;
    cv::minEnclosingCircle(contours[1], center, radius);
    cv::circle(result, center, static_cast<int>(radius), 0, 2);
    // 近似多边形
    std::vector<cv::Point> poly;
    cv::approxPolyDP(contours[2], poly, 5, true);
    cv::polylines(result, poly, true, 0, 2);
    std::cout << "Polygon size: " << poly.size() << std::endl;
    // 凸包
    std::vector<cv::Point> hull;
    cv::convexHull(contours[3], hull);
    cv::polylines(result, hull, true, 0, 2);
    // std::vector<cv::Vec4i> defects;
    // cv::convexityDefects(contours[3], hull, defects);
    // 矩
    itc = contours.begin();
    while (itc!=contours.end()) 
        cv::Moments mom = cv::moments(*itc++);
        cv::circle(result,
                    cv::Point(mom.m10/mom.m00, mom.m01/mom.m00),
                    2, cv::Scalar(0), 2);
    
    cv::namedWindow("Some Shape descriptors");
    cv::imshow("Some Shape descriptors", result);
    image = cv::imread("binary.png", 0);
    cv::findContours(image, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);
    result.setTo(255);
    cv::drawContours(result, contours, -1, 0, 2);
    cv::namedWindow("All Contours");
    cv::imshow("All Contours", result);
    // MSER 图像
    cv::Mat components;
    components = cv::imread("mser.png",0);
    // 创建二值图像
    components = components==255;
    // 图像开操作
    cv::morphologyEx(components, components, cv::MORPH_OPEN, cv::Mat(), cv::Point(-1, -1), 3);
    cv::namedWindow("MSER image");
    cv::imshow("MSER image", components);
    contours.clear();
    // 反转图像
    cv::Mat componentsInv = 255 - components;
    // 获取轮廓和连接部分
    cv::findContours(componentsInv, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);
    cv::Mat quadri(components.size(), CV_8U, 255);
    std::vector<std::vector<cv::Point> >::iterator it = contours.begin();
    while (it!= contours.end()) 
        poly.clear(
        
                

OpenCV-Python实战(11)——OpenCV轮廓检测相关应用

0. 前言

在计算机视觉领域,轮廓通常指图像中对象边界的一系列点。因此,轮廓通常描述了对象边界的关键信息,包含了有关对象形状的主要信息,该信息可用于形状分析与对象检测和识别。我们已经在《OpenCV轮廓检测》中介绍了如何检测和绘制轮廓,在本文中,我们将继续学习如何利用获取到的轮廓,进行形状分析以及对象检测和识别。

1. 轮廓绘制

《OpenCV轮廓检测》中,我们介绍了如何从图像矩计算获取轮廓属性(例如,质心,面积,圆度或偏心等)。除此之外,OpenCV 还提供了一些其他用于进一步描述轮廓的函数。
cv2.boundingRect() 返回包含轮廓所有点的最小边界矩形:

x, y, w, h = cv2.boundingRect(contours[0])

cv2.minAreaRect() 返回包含轮廓所有点的最小旋转(如有必要)矩形:

rotated_rect = cv2.minAreaRect(contours[0])

为了提取旋转矩形的四个点,可以使用 cv2.boxPoints() 函数,返回旋转矩形的四个顶点:

box = cv2.boxPoints(rotated_rect)

cv2.minEnclosingCircle() 返回包含轮廓所有点的最小圆(该函数返回圆心和半径):

(x, y), radius = cv2.minEnclosingCircle(contours[0])

cv2.fitEllipse() 返回包含轮廓所有点的椭圆(具有最小平方误差):

ellipse = cv2.fitEllipse(contours[0])

cv2.approxPolyDP() 基于给定精度返回给定轮廓的轮廓近似,此函数使用 Douglas-Peucker 算法:

approx = cv2.approxPolyDP(contours[0], epsilon, True)

其中,epsilon 参数用于确定精度,确定原始曲线之间的最大距离及其近似。因此,由此产生的轮廓是类似于给定的轮廓的压缩轮廓。
接下来,我们使用与轮廓相关的 OpenCV 函数计算给定轮廓的外端点,在具体讲解代码时,首先看下结果图像,以更好的理解上述函数:


首先编写 extreme_points() 用于计算定义给定轮廓的四个外端点:

def extreme_points(contour):
    """检测轮廓的极值点"""

    extreme_left = tuple(contour[contour[:, :, 0].argmin()][0])
    extreme_right = tuple(contour[contour[:, :, 0].argmax()][0])
    extreme_top = tuple(contour[contour[:, :, 1].argmin()][0])
    extreme_bottom = tuple(contour[contour[:, :, 1].argmax()][0])

    return extreme_left, extreme_right, extreme_top, extreme_bottom

np.argmin() 沿轴返回最小值的索引,在多个出现最小值的情况下返回第一次出现的索引;而 np.argmax() 返回最大值的索引。一旦索引 index 计算完毕,就可以利用索引获取阵列的相应元素(例如,contour[index]——[[40 320]] ),如果要访问第一个元素,则使用 contour[index][0]——[40 320];最后,我们将其转换为元组:tuple(contour[index][0]——(40,320),用以绘制轮廓点。

def array_to_tuple(arr):
    """将列表转换为元组"""
    return tuple(arr.reshape(1, -1)[0])

def draw_contour_points(img, cnts, color):
    """绘制所有检测到的轮廓点"""
    for cnt in cnts:
        squeeze = np.squeeze(cnt)
        for p in squeeze:
            pp = array_to_tuple(p)
            cv2.circle(img, pp, 10, color, -1)
    return img

def draw_contour_outline(img, cnts, color, thickness=1):
    """绘制所有轮廓"""
    for cnt in cnts:
        cv2.drawContours(img, [cnt], 0, color, thickness)

def show_img_with_matplotlib(color_img, title, pos):
    """图像可视化"""
    img_RGB = color_img[:, :, ::-1]

    ax = plt.subplot(2, 3, pos)
    plt.imshow(img_RGB)
    plt.title(title, fontsize=8)
    plt.axis('off')

# 加载图像并转换为灰度图像
image = cv2.imread("example.png")
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 阈值处理转化为二值图像
ret, thresh = cv2.threshold(gray_image, 60, 255, cv2.THRESH_BINARY)

# 利用二值图像检测图像中的轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

# 显示检测到的轮廓数
print("detected contours: '{}' ".format(len(contours)))
# 创建原始图像的副本以执行可视化
boundingRect_image = image.copy()
minAreaRect_image = image.copy()
fitEllipse_image = image.copy()
minEnclosingCircle_image = image.copy()
approxPolyDP_image = image.copy()

# 1. cv2.boundingRect()
x, y, w, h = cv2.boundingRect(contours[0])
cv2.rectangle(boundingRect_image, (x, y), (x + w, y + h), (0, 255, 0), 5)

# 2. cv2.minAreaRect()
rotated_rect = cv2.minAreaRect(contours[0])
box = cv2.boxPoints(rotated_rect)
box = np.int0(box)
cv2.polylines(minAreaRect_image, [box], True, (0, 0, 255), 5)

# 3. cv2.minEnclosingCircle()
(x, y), radius = cv2.minEnclosingCircle(contours[0])
center = (int(x), int(y))
radius = int(radius)
cv2.circle(minEnclosingCircle_image, center, radius, (255, 0, 0), 5)

# 4. cv2.fitEllipse()
ellipse = cv2.fitEllipse(contours[0])
cv2.ellipse(fitEllipse_image, ellipse, (0, 255, 255), 5)

# 5. cv2.approxPolyDP()
epsilon = 0.01 * cv2.arcLength(contours[0], True)
approx = cv2.approxPolyDP(contours[0], epsilon, True)
draw_contour_outline(approxPolyDP_image, [approx], (255, 255, 0), 5)
draw_contour_points(approxPolyDP_image, [approx], (255, 0, 255))

# 检测轮廓的极值点
left, right, top, bottom = extreme_points(contours[0])
cv2.circle(image, left, 20, (255, 0, 0), -1)
cv2.circle(image, right, 20, (0, 255, 0), -1)
cv2.circle(image, top, 20, (0, 255, 255), -1)
cv2.circle(image, bottom, 20, (0, 0, 255), -1)

# 可视化
show_img_with_matplotlib(image, "image and extreme points", 1)
show_img_with_matplotlib(boundingRect_image, "cv2.boundingRect()", 2)
show_img_with_matplotlib(minAreaRect_image, "cv2.minAreaRect()", 3)
show_img_with_matplotlib(minEnclosingCircle_image, "cv2.minEnclosingCircle()", 4)
show_img_with_matplotlib(fitEllipse_image, "cv2.ellipse()", 5)
show_img_with_matplotlib(approxPolyDP_image, "cv2.approxPolyDP()", 6)
plt.show()

我们还可以测试在其他图像上的效果:

2. 轮廓筛选

如果想要计算检测到的轮廓的大小,可以使用基于图像矩的方法或使用 OpenCV 函数 cv2.contourArea() 来计算检测到的轮廓的大小,接下来,让我们将根据每个检测到的轮廓大小对其进行排序,在实践中,某些小的轮廓可能是噪声导致的,可能需要对轮廓进行筛选。
我们首先在画布上绘制不同半径的圆,用于后续检测:

# 画布
image = np.ones((300,700,3), dtype='uint8')
# 绘制不同半径的圆
cv2.circle(image, (20, 20), 8, (64, 128, 0), -1)
cv2.circle(image, (60, 80), 25, (128, 255, 64), -1)
cv2.circle(image, (100, 180), 50, (64, 255, 64), -1)
cv2.circle(image, (200, 250), 45, (255, 128, 64), -1)
cv2.circle(image, (300, 250), 30, (35, 128, 35), -1)
cv2.circle(image, (380, 100), 15, (125, 255, 125), -1)
cv2.circle(image, (600, 210), 55, (125, 125, 255), -1)
cv2.circle(image, (450, 150), 60, (0, 255, 125), -1)
cv2.circle(image, (330, 180), 20, (255, 125, 0), -1)
cv2.circle(image, (500, 60), 35, (125, 255, 0), -1)
cv2.circle(image, (200, 80), 65, (125, 64, 125), -1)
cv2.circle(image, (620, 80), 48, (255, 200, 128), -1)
cv2.circle(image, (400, 260), 28, (255, 255, 0), -1)

接下来,检测图中轮廓:

gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 阈值处理
ret, thresh = cv2.threshold(gray_image, 50, 255, cv2.THRESH_BINARY)
# 检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# 打印检测到的轮廓数
print("detected contours: '{}' ".format(len(contours)))

根据每个检测到的轮廓大小进行排序:

def sort_contours_size(cnts):
    """根据大小对轮廓进行排序"""

    cnts_sizes = [cv2.contourArea(contour) for contour in cnts]
    (cnts_sizes, cnts) = zip(*sorted(zip(cnts_sizes, cnts)))
    return cnts_sizes, cnts
    
(contour_sizes, contours) = sort_contours_size(contours)

最后进行可视化:

for i, (size, contour) in enumerate(zip(contour_sizes, contours)):
    # 计算轮廓的矩
    M = cv2.moments(contour)
    # 质心
    cX = int(M['m10'] / M['m00'])
    cY = int(M['m01'] / M['m00'])
    # get_position_to_draw() 函数与上例相同
    (x, y) = get_position_to_draw(str(i + 1), (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 2, 5)

    # 将排序结果置于形状的质心
    cv2.putText(image, str(i + 1), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 5)
# show_img_with_matplotlib() 函数与上例相同
show_img_with_matplotlib(image, 'image', 1)
show_img_with_matplotlib(image, "result", 2)

plt.show()

程序运行结果如下所示:

3. 轮廓识别

我们之前已经介绍了 cv2.approxPolyDP(),它可以使用 Douglas Peucker 算法用较少的点来使一个轮廓逼近检测的轮廓。此函数中的一个关键参数是 epsilon,其用于设置近似精度。我们使用 cv2.approxPolyDP(),以便根据被抽取的轮廓中的检测到顶点的数量识别轮廓(例如,三角形,方形,矩形,五角形或六角形)。为了减少点数,给定某个轮廓,我们首先计算轮廓的边( perimeter )。基于边,建立 epsilon 参数, epsilon 参数计算如下:

epsilon = 0.03 * perimeter

如果该常数变大(例如,从 0.03 变为 0.1 ),则 epsilon 参数也会更大,近似精度将减小,这导致具有较少点的轮廓,并且导致顶点的缺失,对轮廓的识别也将不正确,因为它基于检测到的顶点的数量;另一方面,如果该常数较小(例如,从0.03 变为 0.001),则 epsilon 参数也将变小,因此,近似精度将增加,将产生具有更多点的近似轮廓,对轮廓的识别同样会出现错误,因为获得了虚假顶点。

# 构建测试图像
image = np.ones((300,700,3), dtype='uint8')
cv2.circle(image, (100, 80), 65, (64, 128, 0), -1)
pts = np.array([[300, 10], [400, 150], [200, 150]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.fillPoly(image, [pts], (64, 255, 64))
cv2.rectangle(image, (450, 20),(650, 150),(125, 125, 255),-1)
cv2.rectangle(image, (50, 180),(150, 280),(255, 125, 0),-1)
pts = np.array([[365, 220], [320, 282], [247, 258], [247, 182], [320, 158]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.fillPoly(image, [pts], (125, 64, 125))
pts = np.array([[645, 220], [613, 276], [548, 276], [515, 220], [547, 164],[612, 164]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.fillPoly(image,以上是关于OpenCV实战(15)——轮廓检测详解的主要内容,如果未能解决你的问题,请参考以下文章

Opencv项目实战目标检测:自动检测出现的所有动态目标

OpenCV与EmguCV中的图像轮廓提取

OpenCV-Python实战(11)——OpenCV轮廓检测相关应用

OpenCV-Python实战(15)——面部特征点检测详解(仅需5行代码学会3种面部特征点检测方法)

OpenCV实战(14)——图像线条提取

详解用OpenCV的轮廓检测函数findContours()得到的轮廓拓扑结构(hiararchy)矩阵的意义以及怎样用轮廓拓扑结构矩阵绘制轮廓拓扑结构图

(c)2006-2024 SYSTEM All Rights Reserved IT常识