在本教程中,您将学习如何使用 OpenCV 执行连通分量标记和分析。具体来说,我们将重点介绍 OpenCV 最常用的连通分量标记函数:cv2.connectedComponentsWithStats
连通分量标记(也称为连通分量分析、斑点提取或区域标记)是图论的一种算法应用,用于确定二进制图像中“斑点”状区域的连通性。

我们经常在与使用轮廓相同的情况下使用连通分量分析;然而,连通分量标记通常可以让我们对二值图像中的斑点进行更细粒度的过滤。在使用轮廓分析时,我们经常受到轮廓层次结构的限制(即一个轮廓包含在另一个轮廓中)。通过连通分量分析,我们可以更轻松地分割和分析这些结构。

连通分量分析的一个很好的例子是计算二值(即阈值后的)车牌图像的连通分量,并根据它们的属性(例如宽度、高度、面积、solidity等)过滤斑点。这正是我们今天在这里要做的。

1.OpenCV 连通分量标记和分析

在本教程的第一部分,我们将回顾 OpenCV 提供的用于执行连通分量标记和分析的四个函数。这些函数中最受欢迎的是cv2.connectedComponentsWithStats
首先,我们将配置我们的开发环境并查看我们的项目目录结构。
接下来,我们将实现两种形式的连通分量分析:

  • 一种方法将演示如何使用 OpenCV 的连通分量标记和分析函数,计算每个连通分量的统计数据,然后单独提取/可视化每个连通分量。
  • 第二种方法显示了连接分量分析的实际示例。我们对车牌进行阈值化,然后使用连通分量分析仅提取车牌字符。

1.1 OpenCV 连通分量标记和分析函数

OpenCV 提供了四种连通分量分析函数:

  • cv2.connectedComponents
  • cv2.connectedComponentsWithStats
  • cv2.connectedComponentsWithAlgorithm
  • cv2.connectedComponentsWithStatsWithAlgorithm

最流行的方法是 cv2.connectedComponentsWithStats,它返回以下信息:

  • 连通分量的边界框
  • 连通分量的面积(以像素为单位)
  • 连通分量的质心/中心 (x, y) 坐标

第一种方法,cv2.connectedComponents,和第二种方法一样,只是不返回上面的统计信息。在绝大多数情况下,您将需要统计信息,因此简单地使用 cv2.connectedComponentsWithStats 即可。
第三种方法 cv2.connectedComponentsWithAlgorithm 实现了更快、更有效的连通分量分析算法。

如果您使用并行处理支持编译 OpenCV,则 cv2.connectedComponentsWithAlgorithmcv2.connectedComponentsWithStatsWithAlgorithm 将比前两个运行得更快。
但一般来说,坚持使用 cv2.connectedComponentsWithStats 直到您熟悉连通分量标记。

1.2 项目结构

在我们使用 OpenCV 实现连通分量标记和分析之前,让我们先来看看我们的项目目录结构。

我们将应用连通分量分析来自动过滤车牌 (license_plate.png) 中的字符。
为了完成这项任务并了解有关连通分量分析的更多信息,我们将实现两个 Python 脚本:

  • basic_connected_components.py:演示如何应用连通分量标记,提取每个组件及其统计数据,并在我们的屏幕上可视化它们。
  • filtering_connected_components.py:应用连通分量标记,通过检查每个连通分量的宽度、高度和面积(以像素为单位)过滤掉非牌照字符。

2.案例实现

2.1 使用 OpenCV 实现基本的连通分量标记

让我们开始使用 OpenCV 实现连通分量分析。
打开项目文件夹中的 basic_connected_components.py 文件,让我们开始工作:

# 导入相关包
# 导入必要的包
import argparse
import cv2# 解析构建的参数解析器
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, 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:输入图像路径
  • –connectivity:4连通或者8连通

接下来,进行图像预处理操作

# 加载输入图像,将其转换为灰度,并对其进行阈值处理
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]

阈值处理以后,将得到如下图像:


请注意车牌字符在黑色背景上显示为白色。但是,输入图像中也有一堆噪声也显示为前景(白色)。我们的目标是应用连通分量分析来过滤掉这些噪声区域,只留下车牌字符。

但在我们开始之前,让我们先学习如何使用 cv2.connectedComponentsWithStats 函数:

output = cv2.connectedComponentsWithStats(thresh, args["connectivity"], cv2.CV_32S)
(numLabels, labels, stats, centroids) = output

使用OpenCV的cv2.connectedComponentsWithStats 执行连通分量分析。我们在这里传入三个参数:

  • 阈值化后的图像
  • 4连通还是8连通
  • 数据类型(应该使用cv2.CV_32S)

然后 cv2.connectedComponentsWithStats 返回一个 4 元组:

  • 检测到的唯一标签总数(即总连通分量数)
  • 一个名为labels的掩码, 掩码与我们的输入阈值图像具有相同的空间维度。对于labels中的每个位置,我们都有一个整数 ID 值,该值对应于像素所属的连通分量。您将在本节后面学习如何过滤labels矩阵。
  • stats:每个连通分量的统计信息,包括边界框坐标和面积(以像素为单位)。
  • 每个连通分量的质心(即中心)(x,y)坐标。

让我们开始解析这些数值:

# 遍历每个连通分量
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 componentelse: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]

if/else语句说明:

  • 第一个连通分量,即ID 为 0,始终是背景。我们通常会忽略背景,但如果您需要它,请记住 ID=0 包含它。
  • 否则,如果 i > 0,那么我们知道该连通分量值得进一步探索。

解析我们的统计数据和质心列表:

  • 连通分量的起始x坐标
  • 连通分量的起始y坐标
  • 连通分量的宽(w)
  • 连通分量的高(h)
  • 连通分量的质心坐标(x,y)
 # 可视化边界框和当前连通分量的质心# 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# 显示输出图像和掩码cv2.imshow("Output", output)cv2.imshow("Connected Component", componentMask)cv2.waitKey(0)

首先在labels中找到与当前组件 ID 相等的所有位置。然后我们将结果转换为一个无符号的 8 位整数,其中背景值为 0,前景值为 255。最后显示原始图以及掩码图。

第一个连通分量实际上是我们的背景。我们通常会跳过,因为通常不需要背景。 然后显示其余连通分量。对于每个连通分量,我们绘制边界框(绿色矩形)和质心/中心(红色圆圈)。 您可能已经注意到,其中一些连接的组件是车牌字符,而另一些则只是“噪音”。我们将在下一部分解决这个问题。

2.2 完整代码

# 导入必要的包
import argparse
import cv2# 解析构建的参数解析器
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", default="plate.jpg", 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"])
cv2.imshow("src", image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv2.imshow("threshold", 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 componentelse: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# 显示输出图像和掩码cv2.imshow("Output", output)cv2.imshow("Connected Component", componentMask)cv2.waitKey(0)

2.3 过滤连通分量

我们之前的代码示例演示了如何使用 OpenCV 提取连接的组件,但没有演示如何过滤它们。

import numpy as np
import argparse
import cv2ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", default="plate.jpg", help="path to image")
ap.add_argument("-c", "--connectivity", type=int, default=4, help="connectivity for connected component analysis")args = vars(ap.parse_args())# 加载图像,转为灰度,二值化
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY)# 应用连通分量分析
output = cv2.connectedComponentsWithStats(thresh, connectivity=args["connectivity"], ltype=cv2.CV_32S)
(numLabels, labels, stats, centriods) = outputmask = np.zeros(gray.shape, dtype="uint8")for i in range(1, numLabels):  # 忽略背景x = stats[i, cv2.CC_STAT_LEFT]  # [i, 0]y = stats[i, cv2.CC_STAT_TOP]  # [i, 1]w = stats[i, cv2.CC_STAT_WIDTH]  # [i, 2]h = stats[i, cv2.CC_STAT_HEIGHT]  # [i, 3]area = stats[i, cv2.CC_STAT_AREA]  # [i, 4]# 确保宽高以及面积既不太大也不太小keepWidth = w > 50 and w < 500keepHeight = h > 150 and h < 650keepArea = area > 500 and area < 25000# 我使用print语句显示每个连接组件的宽度、高度和面积,# 同时将它们单独显示在屏幕上。我记录了车牌字符的宽度、高度和面积,并找到了它们的最小/最大值,# 对于您自己的应用程序也应该这样做。if all((keepWidth, keepHeight, keepArea)):print("[INFO] keep connected component '{}'".format(i))componentMask = (labels == i).astype("uint8") * 255mask = cv2.bitwise_or(mask, componentMask)cv2.imshow("Image", image)
cv2.imshow("Chracters", mask)
cv2.waitKey(0)

如果我们正在构建一个自动牌照/车牌识别(ALPR/ANPR)系统,我们将获取这些字符,然后将它们传递给光学字符识别(OCR)算法进行识别。但这一切都取决于我们是否能够将字符二值化并提取它们,连通分量分析使我们能够做到这一点!

2.4 C++代码案例

#include <opencv2/core/utility.hpp>
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
Mat img;
int threshval = 100;
static void on_trackbar(int, void*)
{Mat bw = threshval < 128 ? (img < threshval) : (img > threshval);Mat labelImage(img.size(), CV_32S);int nLabels = connectedComponents(bw, labelImage, 8);std::vector<Vec3b> colors(nLabels);colors[0] = Vec3b(0, 0, 0);//backgroundfor(int label = 1; label < nLabels; ++label){colors[label] = Vec3b( (rand()&255), (rand()&255), (rand()&255) );}Mat dst(img.size(), CV_8UC3);for(int r = 0; r < dst.rows; ++r){for(int c = 0; c < dst.cols; ++c){int label = labelImage.at<int>(r, c);Vec3b &pixel = dst.at<Vec3b>(r, c);pixel = colors[label];}}imshow( "Connected Components", dst );
}
int main( int argc, const char** argv )
{CommandLineParser parser(argc, argv, "{@image|stuff.jpg|image for converting to a grayscale}");parser.about("\nThis program demonstrates connected components and use of the trackbar\n");parser.printMessage();cout << "\nThe image is converted to grayscale and displayed, another image has a trackbar\n""that controls thresholding and thereby the extracted contours which are drawn in color\n";String inputImage = parser.get<string>(0);img = imread(samples::findFile(inputImage), IMREAD_GRAYSCALE);if(img.empty()){cout << "Could not read input image file: " << inputImage << endl;return EXIT_FAILURE;}imshow( "Image", img );namedWindow( "Connected Components", WINDOW_AUTOSIZE);createTrackbar( "Threshold", "Connected Components", &threshval, 255, on_trackbar );on_trackbar(threshval, 0);waitKey(0);return EXIT_SUCCESS;
}

参考目录

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

OpenCV 连通分量标记和分析相关推荐

  1. opencv 图像与视频分析教程③

    opencv 图像与视频分析教程 代码: https://github.com/bai1231/opencv-learn_and_pratice 二值图像分析 图像二值化 二值图像轮廓分析 霍夫检测 ...

  2. VS2008中V表结束标记的分析

    VS2008中V表结束标记的分析 在逆向C++目标时,我们有时候可能会关注一个V表中到底有多少个虚函数. 这种细节大概多半是与编译器实现相关. 为了弄清楚这个问题,我在VS2008下写了一个简单的测试 ...

  3. Java OpenCV 图像处理30 视频分析和对象跟踪 视频读取

    Java OpenCV 图像处理30 视频分析和对象跟踪 视频读取 Java OpenCV-4.0.0 图像处理 视频分析和对象跟踪 视频读取 package com.xu.opencv.video; ...

  4. 【学习OpenCV4】OpenCV绘制标记/线/矩形/文字/圆等

    本文分享内容来自图书<学习OpenCV 4:基于Python的算法实战>,该书内容如下: 第1章 OpenCV快速入门: 第2章 图像读写模块imgcodecs: 第3章 核心库模块cor ...

  5. C#运行opencv,示例代码分析

    示例代码下载位置: https://github.com/shimat/opencvsharp_samples/ 错误  CS1617  /langversion  的选项"8.0" ...

  6. OpenCV多目标跟踪与视频分析

    点击我爱计算机视觉标星,更快获取CVML新技术 在视频监控与分析中,视频前后景分析.多目标检测.目标跟踪等算法需要协同工作,今天跟大家分享的开源库,给出了一个基于OpenCV的开源实现.供大家学习参考 ...

  7. opencv 的norm_OpenCV视频分析-Meanshift、Camshiftamp;运动轨迹绘制

    基于均值迁移的对象移动分析(Meanshift) ✏️ ⛳️ 概述 本质: ✔️ Mean Shift均值漂移算法是无参密度估计理论的一种,无参密度估计不需要事先知道对象的任何先验知识,完全依靠训练数 ...

  8. 【OpenCV】56 二值图像分析–直线拟合与极值点寻找

    56 二值图像分析–直线拟合与极值点寻找 代码 import cv2 as cv import numpy as npdef canny_demo(image):t = 80canny_output ...

  9. python opencv findcontours_OpenCV之视频分析 – 背景消除与前景ROI提取

    python代码: import C++代码: #include OpenCV学习笔记代码,欢迎follow: MachineLP/OpenCV-​github.com

最新文章

  1. spring 托管bean_在非托管对象中使用Spring托管Bean
  2. c# dynamic 无法创建 泛型变量的问题
  3. 项目中缺少maven dependencis,或者pom文件报红
  4. redis日志_面试题之Redis如何保证系统宕机数据不会丢失?
  5. 如何用VB编程实现关闭WINDOWS窗口?
  6. 威纶触摸屏与电脑连接_威纶通TK6070IP触摸屏下载线MT6071IE触摸屏编程线连接电脑USB线...
  7. 《代码整洁之道姐妹篇》
  8. 树莓派如何安装 Python 环境
  9. c语言计算标准体重作业,c语言/* 已知成人标准体重粗算公式:
  10. WIFI 认证 测试
  11. 海胆状金纳米颗粒,粒径:150-200nm|银包金纳米颗粒 粒径:5-200nm|碳包金纳米颗粒 粒径:可定制
  12. 65nm粒径量子点MMSNs-HRP-CDs/CP-CDs结合酶/蛋白/荧光的制备过程
  13. 宁强天津中学2021高考成绩查询,宁强县天津高级中学教务信息网
  14. 树莓派4通过华为ME909S 4G模块连接蜂窝网(非PPP)
  15. 停车还能360全方位影像_汽车新技术:360全景环视系统技术
  16. Couldnt check the working tree for unmerged files because of an error. bad signature index file cor
  17. python基础===【字符串】所有相关操作
  18. js过滤出对象中想要的数据
  19. ​戴尔科技集团+微软Azure Stack HCI:引领混合云上云新范式
  20. 免费好用的软件资源网站合集

热门文章

  1. 个人网站备案之如何取网站名称那点事儿?
  2. php采集从零,PHP从零单排(十八)图像处理
  3. Python基础之进程(Process)
  4. 无法启动此程序 因为计算机中丢失xlive,xlive.dll没有被指定在windows运行
  5. Linux下使用gnome-terminal命令一键开启工作环境
  6. CORDOVA+VUE打包APK文件无法访问数据接口
  7. 利用KindEditor漏洞上传网马
  8. php如何撮合交易,撮合成交的原则以及过程
  9. 2020年车工(中级)考试及车工(中级)考试试卷
  10. Thinkphp command使用