【python】OpenCV—Connected Components

文章目录

  • 1、任务描述
  • 2、代码实现
  • 3、完整代码
  • 4、结果展示
  • 5、涉及到的库函数
  • 6、参考
  • 1、任务描述

    基于 python opencv 的连通分量标记和分析函数,分割车牌中的数字、号码、分隔符

  • cv2.connectedComponents
  • cv2.connectedComponentsWithStats
  • cv2.connectedComponentsWithAlgorithm
  • cv2.connectedComponentsWithStatsWithAlgorithm
  • 2、代码实现

    导入必要的包,加载输入图像,将其转换为灰度,并对其进行二值化处理

    # 导入必要的包
    import argparse
    import cv2
    
    # 解析构建的参数解析器
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", default="1.jpeg", help="path to input image")
    ap.add_argument("-c", "--connectivity", type=int, default=4, help="connectivity for connected analysis")
    args = vars(ap.parse_args())  # 将参数转为字典格式
    
    # 加载输入图像,将其转换为灰度,并对其进行阈值处理
    image = cv2.imread(args["image"])  # (366, 640, 3)
    cv2.imshow("src", image)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    cv2.imwrite("gray.jpg", gray)
    
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    cv2.imshow("threshold", thresh)
    cv2.imwrite("threshold.jpg", thresh)
    

    对阈值化后的图像应用连通分量分析

    # 对阈值化后的图像应用连通分量分析
    output = cv2.connectedComponentsWithStats(thresh, args["connectivity"], cv2.CV_32S)
    (numLabels, labels, stats, centroids) = output
    

    cv2.connectedComponentsWithStats 可以结合后面章节的介绍查看

    输入图片的尺寸假如是 (366, 640, 3),看看 cv2.connectedComponentsWithStats 的返回情况

    """
    [labels] (366, 640)
    
    array([[1, 1, 1, ..., 1, 1, 1],
           [1, 1, 1, ..., 1, 1, 1],
           [1, 1, 1, ..., 1, 1, 1],
           ...,
           [1, 1, 1, ..., 1, 1, 1],
           [1, 1, 1, ..., 1, 1, 1],
           [1, 1, 1, ..., 1, 1, 1]], dtype=int32)
    
    [state]
    array([[    83,     83,    482,    163,  57925],
           [     0,      0,    640,    366, 155776],
           [    96,     96,    456,    138,   2817],
           [   113,    108,     75,    113,   5915],
           [   194,    119,     52,     90,   2746],
           [   270,    120,     62,     90,   2260],
           [   489,    124,     46,     85,   2370],
           [   344,    126,     29,     82,   1398],
           [   394,    126,     29,     82,   1397],
           [   445,    126,     29,     82,   1396],
           [   253,    149,     17,     18,    240]], dtype=int32)
    
    [centroids]
    array([[333.22577471, 163.75948209],
           [317.48520953, 191.81337305],
           [323.41924033, 174.62051828],
           [148.71885038, 163.47658495],
           [219.46686089, 164.00837582],
           [299.82566372, 161.7420354 ],
           [512.84767932, 165.38818565],
           [362.91773963, 161.85479256],
           [412.91481747, 161.956335  ],
           [463.91833811, 161.96919771],
           [261.3125    , 157.22083333]])
    """
    

    注意这里是质心,不是连通区域矩形框的中心

    对于 x 方向的质心,图像在质心左右两边像素和相等,y 同理,上下两边像素和相等

    遍历每个连通分量,忽略 label = 0 背景,提取当前标签的连通分量统计信息和质心,可视化边界框和当前连通分量的质心

    # 遍历每个连通分量
    for i in range(0, numLabels):
        # 0表示的是背景连通分量,忽略
        if i == 0:
            text = "examining component {}/{} (background)".format(
                i + 1, numLabels)
        # otherwise, we are examining an actual connected component
        else:
            text = "examining component {}/{}".format(i + 1, numLabels)
    
        # 打印当前的状态信息
        print("[INFO] {}".format(text))
    
        # 提取当前标签的连通分量统计信息和质心
        x = stats[i, cv2.CC_STAT_LEFT]  # 左上角横坐标
        y = stats[i, cv2.CC_STAT_TOP]  # 左上角纵坐标
        w = stats[i, cv2.CC_STAT_WIDTH]  # 边界框的宽
        h = stats[i, cv2.CC_STAT_HEIGHT]  # 边界框的高
        area = stats[i, cv2.CC_STAT_AREA]  # 边界框的面积
        (cX, cY) = centroids[i]  # 边界框的质心
    
        # 可视化边界框和当前连通分量的质心
        # clone原始图,在图上画当前连通分量的边界框以及质心
        output = image.copy()
        cv2.rectangle(output, (x, y), (x + w, y + h), (0, 255, 0), 3)  # 绿色边界框
        cv2.circle(output, (int(cX), int(cY)), 4, (0, 0, 255), -1)  # 红色质心
    
        # 创建掩码
        componentMask = (labels == i).astype("uint8") * 255  # 绘制 mask,对应label 置为 255,其余为 0
    
        # 显示输出图像和掩码
        cv2.imshow("Output", output)
        cv2.imwrite(f"output-{str(i).zfill(3)}.jpg", output)
        cv2.imshow("Connected Component", componentMask)
        cv2.imwrite(f"componentMask-{str(i).zfill(3)}.jpg", componentMask)
        cv2.waitKey(0)
    

    创建掩码的时候比较巧妙 componentMask = (labels == i).astype("uint8") * 255

    3、完整代码

    # 导入必要的包
    import argparse
    import cv2
    
    # 解析构建的参数解析器
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", default="1.jpeg", help="path to input image")
    ap.add_argument("-c", "--connectivity", type=int, default=4, help="connectivity for connected analysis")
    args = vars(ap.parse_args())  # 将参数转为字典格式
    
    # 加载输入图像,将其转换为灰度,并对其进行阈值处理
    image = cv2.imread(args["image"])  # (366, 640, 3)
    cv2.imshow("src", image)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    cv2.imwrite("gray.jpg", gray)
    
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    cv2.imshow("threshold", thresh)
    cv2.imwrite("threshold.jpg", thresh)
    
    # 对阈值化后的图像应用连通分量分析
    output = cv2.connectedComponentsWithStats(thresh, args["connectivity"], cv2.CV_32S)
    (numLabels, labels, stats, centroids) = output
    
    # 遍历每个连通分量
    for i in range(0, numLabels):
        # 0表示的是背景连通分量,忽略
        if i == 0:
            text = "examining component {}/{} (background)".format(
                i + 1, numLabels)
        # otherwise, we are examining an actual connected component
        else:
            text = "examining component {}/{}".format(i + 1, numLabels)
    
        # 打印当前的状态信息
        print("[INFO] {}".format(text))
    
        # 提取当前标签的连通分量统计信息和质心
        x = stats[i, cv2.CC_STAT_LEFT]  # 左上角横坐标
        y = stats[i, cv2.CC_STAT_TOP]  # 左上角纵坐标
        w = stats[i, cv2.CC_STAT_WIDTH]  # 边界框的宽
        h = stats[i, cv2.CC_STAT_HEIGHT]  # 边界框的高
        area = stats[i, cv2.CC_STAT_AREA]  # 边界框的面积
        (cX, cY) = centroids[i]  # 边界框的质心
    
        # 可视化边界框和当前连通分量的质心
        # clone原始图,在图上画当前连通分量的边界框以及质心
        output = image.copy()
        cv2.rectangle(output, (x, y), (x + w, y + h), (0, 255, 0), 3)  # 绿色边界框
        cv2.circle(output, (int(cX), int(cY)), 4, (0, 0, 255), -1)  # 红色质心
    
        # 创建掩码
        componentMask = (labels == i).astype("uint8") * 255  # 绘制 mask,对应label 置为 255,其余为 0
    
        # 显示输出图像和掩码
        cv2.imshow("Output", output)
        cv2.imwrite(f"output-{str(i).zfill(3)}.jpg", output)
        cv2.imshow("Connected Component", componentMask)
        cv2.imwrite(f"componentMask-{str(i).zfill(3)}.jpg", componentMask)
        cv2.waitKey(0)
    

    4、结果展示

    输入图片


    output

    [INFO] examining component 1/11 (background)
    [INFO] examining component 2/11
    [INFO] examining component 3/11
    [INFO] examining component 4/11
    [INFO] examining component 5/11
    [INFO] examining component 6/11
    [INFO] examining component 7/11
    [INFO] examining component 8/11
    [INFO] examining component 9/11
    [INFO] examining component 10/11
    [INFO] examining component 11/11
    

    灰度图

    二值化后的结果

    遍历每个连通分量

    componentMask0

    output0,车牌外矩形轮廓

    componentMask1

    output1,图像边界的大框

    componentMask2

    output2,车牌内矩形轮廓

    componentMask3


    output3,汉字豫

    componentMask4

    output4,字母 U

    componentMask5

    output5,字母 V

    componentMask6


    output6,数字 9

    componentMask7

    output7,数字 1

    componentMask8

    output8,数字 1

    componentMask9

    output9,数字 1

    componentMask10

    output10,分隔符

    总结,配合车牌检测,和 OCR 就能形成一个简略的车牌识别系统 😊

    5、涉及到的库函数

    cv2.connectedComponentsWithStats 是 OpenCV 库中的一个函数,用于寻找图像中的连通区域,并计算每个连通区域的统计信息。这个函数在处理二值图像时非常有用,可以帮助我们了解图像中不同对象的数量和特征。

    一、函数原型

    retval, labels, stats, centroids = cv2.connectedComponentsWithStats(image, connectivity=8, ltype=CV_32S)
    

    二、参数说明

  • image: 输入图像,应为二值图像(黑白图像),即图像中的每个像素点非黑即白。
  • connectivity: 像素的连通性。4 或 8,表示每个像素点与其上下左右(4连通)或上下左右加对角线方向(8连通)的像素点是否视为连通。默认值为 8。
  • ltype: 输出标签图像的类型,通常为 cv2.CV_32S。
  • 三、返回值

  • retval: 连通区域的数量(包括背景,如果背景被视为一个连通区域的话)。
  • labels: 与输入图像同样大小的标签图像,其中每个连通区域被赋予一个唯一的标签值。
  • stats: 一个矩阵,包含了每个连通区域的统计信息。对于每个连通区域,矩阵中存储了以下信息:(x, y, width, height, area),其中 (x, y) 是连通区域的边界框的左上角坐标,width 和 height 是边界框的宽度和高度,area 是连通区域的面积。
  • centroids: 连通区域的质心坐标矩阵,每个连通区域有一个对应的 (cx, cy) 坐标。
  • 四、示例

    下面是一个简单的使用 cv2.connectedComponentsWithStats 的示例:

    import cv2  
    import numpy as np  
      
    # 读取图像并转换为灰度图像  
    image = cv2.imread('example.png', 0)  
      
    # 二值化处理(例如,阈值分割)  
    _, binary = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)  
      
    # 查找连通区域及统计信息  
    num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary)  
      
    # 打印连通区域的数量  
    print('Number of connected components:', num_labels)  
      
    # 遍历每个连通区域,并打印其统计信息  
    for i in range(1, num_labels):  # 注意:背景区域的标签为0,从1开始遍历  
        x, y, w, h, area = stats[i, 0:5]  
        print(f'Component {i}: (x, y) = ({x}, {y}), Width = {w}, Height = {h}, Area = {area}')
    

    五、注意事项

  • 在处理二值图像时,确保图像已经正确地进行了二值化处理。
  • 连通区域的数量(返回值 retval)包括了背景区域,如果背景被视为一个连通区域的话。
  • 输出的标签图像 labels 中的每个像素值代表了对应像素点所属的连通区域的标签。
  • 通过 cv2.connectedComponentsWithStats,我们可以方便地获取图像中连通区域的数量和统计信息,这对于图像分析和处理中的许多任务都是非常有用的。

    6、参考

  • OpenCV 连通分量标记和分析
  • https://pyimagesearch.com/2021/02/22/opencv-connected-component-labeling-and-analysis/
  • https://docs.opencv.org/4.x/de/d01/samples_2cpp_2connected_components_8cpp-example.html
  • 作者:bryant_meng

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【python】OpenCV—Connected Components

    发表回复