詳解OpenCV執(zhí)行連通分量標(biāo)記的方法和分析
在本教程中,您將學(xué)習(xí)如何使用 OpenCV 執(zhí)行連通分量標(biāo)記和分析。具體來(lái)說(shuō),我們將重點(diǎn)介紹 OpenCV 最常用的連通分量標(biāo)記函數(shù):cv2.connectedComponentsWithStats。
連通分量標(biāo)記(也稱為連通分量分析、斑點(diǎn)提取或區(qū)域標(biāo)記)是圖論的一種算法應(yīng)用,用于確定二進(jìn)制圖像中“斑點(diǎn)”狀區(qū)域的連通性。
我們經(jīng)常在與使用輪廓相同的情況下使用連通分量分析;然而,連通分量標(biāo)記通??梢宰屛覀儗?duì)二值圖像中的斑點(diǎn)進(jìn)行更細(xì)粒度的過(guò)濾。在使用輪廓分析時(shí),我們經(jīng)常受到輪廓層次結(jié)構(gòu)的限制(即一個(gè)輪廓包含在另一個(gè)輪廓中)。通過(guò)連通分量分析,我們可以更輕松地分割和分析這些結(jié)構(gòu)。
連通分量分析的一個(gè)很好的例子是計(jì)算二值(即閾值后的)車(chē)牌圖像的連通分量,并根據(jù)它們的屬性(例如寬度、高度、面積、solidity等)過(guò)濾斑點(diǎn)。這正是我們今天在這里要做的。
1.OpenCV 連通分量標(biāo)記和分析
在本教程的第一部分,我們將回顧 OpenCV 提供的用于執(zhí)行連通分量標(biāo)記和分析的四個(gè)函數(shù)。這些函數(shù)中最受歡迎的是cv2.connectedComponentsWithStats。
首先,我們將配置我們的開(kāi)發(fā)環(huán)境并查看我們的項(xiàng)目目錄結(jié)構(gòu)。
接下來(lái),我們將實(shí)現(xiàn)兩種形式的連通分量分析:
一種方法將演示如何使用 OpenCV 的連通分量標(biāo)記和分析函數(shù),計(jì)算每個(gè)連通分量的統(tǒng)計(jì)數(shù)據(jù),然后單獨(dú)提取/可視化每個(gè)連通分量。
第二種方法顯示了連接分量分析的實(shí)際示例。我們對(duì)車(chē)牌進(jìn)行閾值化,然后使用連通分量分析僅提取車(chē)牌字符。
1.1 OpenCV 連通分量標(biāo)記和分析函數(shù)
OpenCV 提供了四種連通分量分析函數(shù):
- cv2.connectedComponents
- cv2.connectedComponentsWithStats
- cv2.connectedComponentsWithAlgorithm
- cv2.connectedComponentsWithStatsWithAlgorithm
最流行的方法是 cv2.connectedComponentsWithStats,它返回以下信息:
- 連通分量的邊界框
- 連通分量的面積(以像素為單位)
- 連通分量的質(zhì)心/中心 (x, y) 坐標(biāo)
第一種方法,cv2.connectedComponents,和第二種方法一樣,只是不返回上面的統(tǒng)計(jì)信息。在絕大多數(shù)情況下,您將需要統(tǒng)計(jì)信息,因此簡(jiǎn)單地使用 cv2.connectedComponentsWithStats 即可。
第三種方法 cv2.connectedComponentsWithAlgorithm 實(shí)現(xiàn)了更快、更有效的連通分量分析算法。
如果您使用并行處理支持編譯 OpenCV,則 cv2.connectedComponentsWithAlgorithm 和 cv2.connectedComponentsWithStatsWithAlgorithm 將比前兩個(gè)運(yùn)行得更快。
但一般來(lái)說(shuō),堅(jiān)持使用 cv2.connectedComponentsWithStats 直到您熟悉連通分量標(biāo)記。
1.2 項(xiàng)目結(jié)構(gòu)
在我們使用 OpenCV 實(shí)現(xiàn)連通分量標(biāo)記和分析之前,讓我們先來(lái)看看我們的項(xiàng)目目錄結(jié)構(gòu)。

我們將應(yīng)用連通分量分析來(lái)自動(dòng)過(guò)濾車(chē)牌 (license_plate.png) 中的字符。
為了完成這項(xiàng)任務(wù)并了解有關(guān)連通分量分析的更多信息,我們將實(shí)現(xiàn)兩個(gè) Python 腳本:
basic_connected_components.py:演示如何應(yīng)用連通分量標(biāo)記,提取每個(gè)組件及其統(tǒng)計(jì)數(shù)據(jù),并在我們的屏幕上可視化它們。
filtering_connected_components.py:應(yīng)用連通分量標(biāo)記,通過(guò)檢查每個(gè)連通分量的寬度、高度和面積(以像素為單位)過(guò)濾掉非牌照字符。
2.案例實(shí)現(xiàn)
2.1 使用 OpenCV 實(shí)現(xiàn)基本的連通分量標(biāo)記
讓我們開(kāi)始使用 OpenCV 實(shí)現(xiàn)連通分量分析。
打開(kāi)項(xiàng)目文件夾中的 basic_connected_components.py 文件,讓我們開(kāi)始工作:
# 導(dǎo)入相關(guān)包
# 導(dǎo)入必要的包
import argparse
import cv2
# 解析構(gòu)建的參數(shù)解析器
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()) # 將參數(shù)轉(zhuǎn)為字典格式
我們有兩個(gè)命令行參數(shù)
–image:輸入圖像路徑
–connectivity:4連通或者8連通
接下來(lái),進(jìn)行圖像預(yù)處理操作
# 加載輸入圖像,將其轉(zhuǎn)換為灰度,并對(duì)其進(jìn)行閾值處理 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]
閾值處理以后,將得到如下圖像:

請(qǐng)注意車(chē)牌字符在黑色背景上顯示為白色。但是,輸入圖像中也有一堆噪聲也顯示為前景(白色)。我們的目標(biāo)是應(yīng)用連通分量分析來(lái)過(guò)濾掉這些噪聲區(qū)域,只留下車(chē)牌字符。
但在我們開(kāi)始之前,讓我們先學(xué)習(xí)如何使用 cv2.connectedComponentsWithStats 函數(shù):
output = cv2.connectedComponentsWithStats(thresh, args["connectivity"], cv2.CV_32S) (numLabels, labels, stats, centroids) = output
使用OpenCV的cv2.connectedComponentsWithStats 執(zhí)行連通分量分析。我們?cè)谶@里傳入三個(gè)參數(shù):
- 閾值化后的圖像
- 4連通還是8連通
- 數(shù)據(jù)類型(應(yīng)該使用cv2.CV_32S)
然后 cv2.connectedComponentsWithStats 返回一個(gè) 4 元組:
- 檢測(cè)到的唯一標(biāo)簽總數(shù)(即總連通分量數(shù))
- 一個(gè)名為labels的掩碼, 掩碼與我們的輸入閾值圖像具有相同的空間維度。對(duì)于labels中的每個(gè)位置,我們都有一個(gè)整數(shù) ID 值,該值對(duì)應(yīng)于像素所屬的連通分量。您將在本節(jié)后面學(xué)習(xí)如何過(guò)濾labels矩陣。
- stats:每個(gè)連通分量的統(tǒng)計(jì)信息,包括邊界框坐標(biāo)和面積(以像素為單位)。
- 每個(gè)連通分量的質(zhì)心(即中心)(x,y)坐標(biāo)。
讓我們開(kāi)始解析這些數(shù)值:
# 遍歷每個(gè)連通分量
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)
# 打印當(dāng)前的狀態(tài)信息
print("[INFO] {}".format(text))
# 提取當(dāng)前標(biāo)簽的連通分量統(tǒng)計(jì)信息和質(zhì)心
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語(yǔ)句說(shuō)明:
- 第一個(gè)連通分量,即ID 為 0,始終是背景。我們通常會(huì)忽略背景,但如果您需要它,請(qǐng)記住 ID=0 包含它。
- 否則,如果 i > 0,那么我們知道該連通分量值得進(jìn)一步探索。
解析我們的統(tǒng)計(jì)數(shù)據(jù)和質(zhì)心列表:
- 連通分量的起始x坐標(biāo)
- 連通分量的起始y坐標(biāo)
- 連通分量的寬(w)
- 連通分量的高(h)
- 連通分量的質(zhì)心坐標(biāo)(x,y)
# 可視化邊界框和當(dāng)前連通分量的質(zhì)心
# clone原始圖,在圖上畫(huà)當(dāng)前連通分量的邊界框以及質(zhì)心
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)
創(chuàng)建一個(gè)我們可以繪制的輸出圖像。然后我們將當(dāng)前的連通分量的邊界框繪制為綠色矩形,將質(zhì)心繪制為紅色圓圈。
我們的最終代碼塊演示了如何為當(dāng)前連通分量創(chuàng)建掩碼:
# 創(chuàng)建掩碼
componentMask = (labels == i).astype("uint8") * 255
# 顯示輸出圖像和掩碼
cv2.imshow("Output", output)
cv2.imshow("Connected Component", componentMask)
cv2.waitKey(0)
首先在labels中找到與當(dāng)前組件 ID 相等的所有位置。然后我們將結(jié)果轉(zhuǎn)換為一個(gè)無(wú)符號(hào)的 8 位整數(shù),其中背景值為 0,前景值為 255。最后顯示原始圖以及掩碼圖。
第一個(gè)連通分量實(shí)際上是我們的背景。我們通常會(huì)跳過(guò),因?yàn)橥ǔ2恍枰尘啊?然后顯示其余連通分量。對(duì)于每個(gè)連通分量,我們繪制邊界框(綠色矩形)和質(zhì)心/中心(紅色圓圈)。 您可能已經(jīng)注意到,其中一些連接的組件是車(chē)牌字符,而另一些則只是“噪音”。我們將在下一部分解決這個(gè)問(wèn)題。
2.2 完整代碼
# 導(dǎo)入必要的包
import argparse
import cv2
# 解析構(gòu)建的參數(shù)解析器
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()) # 將參數(shù)轉(zhuǎn)為字典格式
# 加載輸入圖像,將其轉(zhuǎn)換為灰度,并對(duì)其進(jìn)行閾值處理
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)
# 對(duì)閾值化后的圖像應(yīng)用連通分量分析
output = cv2.connectedComponentsWithStats(thresh, args["connectivity"], cv2.CV_32S)
(numLabels, labels, stats, centroids) = output
# 遍歷每個(gè)連通分量
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)
# 打印當(dāng)前的狀態(tài)信息
print("[INFO] {}".format(text))
# 提取當(dāng)前標(biāo)簽的連通分量統(tǒng)計(jì)信息和質(zhì)心
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]
# 可視化邊界框和當(dāng)前連通分量的質(zhì)心
# clone原始圖,在圖上畫(huà)當(dāng)前連通分量的邊界框以及質(zhì)心
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)
# 創(chuàng)建掩碼
componentMask = (labels == i).astype("uint8") * 255
# 顯示輸出圖像和掩碼
cv2.imshow("Output", output)
cv2.imshow("Connected Component", componentMask)
cv2.waitKey(0)
2.3 過(guò)濾連通分量
我們之前的代碼示例演示了如何使用 OpenCV 提取連接的組件,但沒(méi)有演示如何過(guò)濾它們。
import numpy as np
import argparse
import cv2
ap = 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())
# 加載圖像,轉(zhuǎn)為灰度,二值化
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY)
# 應(yīng)用連通分量分析
output = cv2.connectedComponentsWithStats(thresh, connectivity=args["connectivity"], ltype=cv2.CV_32S)
(numLabels, labels, stats, centriods) = output
mask = 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 < 500
keepHeight = h > 150 and h < 650
keepArea = area > 500 and area < 25000
# 我使用print語(yǔ)句顯示每個(gè)連接組件的寬度、高度和面積,
# 同時(shí)將它們單獨(dú)顯示在屏幕上。我記錄了車(chē)牌字符的寬度、高度和面積,并找到了它們的最小/最大值,
# 對(duì)于您自己的應(yīng)用程序也應(yīng)該這樣做。
if all((keepWidth, keepHeight, keepArea)):
print("[INFO] keep connected component '{}'".format(i))
componentMask = (labels == i).astype("uint8") * 255
mask = cv2.bitwise_or(mask, componentMask)
cv2.imshow("Image", image)
cv2.imshow("Chracters", mask)
cv2.waitKey(0)

如果我們正在構(gòu)建一個(gè)自動(dòng)牌照/車(chē)牌識(shí)別(ALPR/ANPR)系統(tǒng),我們將獲取這些字符,然后將它們傳遞給光學(xué)字符識(shí)別(OCR)算法進(jìn)行識(shí)別。但這一切都取決于我們是否能夠?qū)⒆址祷⑻崛∷鼈?,連通分量分析使我們能夠做到這一點(diǎn)!
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);//background
for(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;
}到此這篇關(guān)于詳解OpenCV執(zhí)行連通分量標(biāo)記的方法和分析的文章就介紹到這了,更多相關(guān)OpenCV連通分量標(biāo)記內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python 從csv讀數(shù)據(jù)到mysql的實(shí)例
今天小編就為大家分享一篇python 從csv讀數(shù)據(jù)到mysql的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
Python3實(shí)現(xiàn)從指定路徑查找文件的方法
這篇文章主要介紹了Python3實(shí)現(xiàn)從指定路徑查找文件的方法,涉及Python目錄與文件的相關(guān)操作技巧,需要的朋友可以參考下2015-05-05
爬蟲(chóng)使用IP來(lái)隱藏真實(shí)地址的過(guò)程(python示例)
這篇文章主要為大家介紹了爬蟲(chóng)使用IP來(lái)隱藏真實(shí)地址的過(guò)程(python示例)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Python selenium模塊實(shí)現(xiàn)定位過(guò)程解析
這篇文章主要介紹了python selenium模塊實(shí)現(xiàn)定位過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07

