OpenCV 연결 구성요소 라벨링 및 분석

이 자습서에서는 OpenCV를 사용하여 연결된 구성 요소 레이블 지정 및 분석을 수행하는 방법을 배웁니다. 특히 OpenCV에서 가장 일반적으로 사용되는 연결된 구성 요소 레이블 지정 기능인 cv2.connectedComponentsWithStats 에 중점을 둘 것입니다 .
연결된 구성 요소 레이블링(연결된 구성 요소 분석, 블롭 추출 또는 영역 레이블링이라고도 함)은 이진 이미지에서 "블롭"과 같은 영역의 연결성을 결정하기 위한 그래프 이론의 알고리즘 응용 프로그램입니다.

윤곽선을 사용하는 것과 같은 맥락에서 연결된 구성 요소 분석을 자주 사용하지만 연결된 구성 요소 레이블 지정을 통해 이진 이미지에서 블롭에 대한 보다 세분화된 필터링을 수행할 수 있습니다. 등고선 분석 작업을 할 때 우리는 종종 등고선의 계층 구조에 의해 제약을 받습니다(즉, 한 윤곽이 다른 윤곽 안에 포함됨). 연결된 구성 요소 분석을 통해 이러한 구조를 보다 쉽게 ​​분할하고 분석할 수 있습니다.

연결된 구성 요소 분석의 좋은 예는 이진(즉, 임계값) 번호판 이미지의 연결된 구성 요소를 계산하고 해당 속성(예: 너비, 높이, 영역, 견고성 등)을 기반으로 얼룩을 필터링하는 것입니다. 이것이 바로 오늘 우리가 할 일입니다.

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: 입력 이미지 경로
  • –연결성: 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-튜플을 반환합니다.

  • 감지된 고유 레이블의 총 수(즉, 연결된 구성 요소의 총 수)
  • 입력 임계값 이미지와 동일한 공간 차원을 갖는 레이블이라는 마스크입니다. 레이블의 각 위치에 대해 픽셀이 속한 연결된 구성 요소에 해당하는 정수 ID 값이 있습니다. 이 섹션의 뒷부분에서 레이블 행렬을 필터링하는 방법을 배웁니다.
  • 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 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]

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)

먼저 현재 구성 요소 ID와 동일한 레이블의 모든 위치를 찾습니다. 그런 다음 결과를 배경 값이 0이고 전경 값이 255인 부호 없는 8비트 정수로 변환합니다. 마지막으로 원본 이미지와 마스크 이미지가 표시됩니다.

첫 번째 연결된 구성 요소는 실제로 우리의 배경입니다. 일반적으로 배경이 필요하지 않기 때문에 일반적으로 이것을 건너뜁니다. 그런 다음 연결된 나머지 구성요소를 표시합니다. 연결된 각 구성 요소에 대해 경계 상자(녹색 사각형)와 중심/중심(빨간색 원)을 그립니다. 이러한 연결된 구성 요소 중 일부는 자동차 번호판 문자이고 다른 일부는 단지 "노이즈"라는 것을 눈치채셨을 것입니다. 다음 섹션에서 이 문제를 다룰 것입니다.

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 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
    # 显示输出图像和掩码
    cv2.imshow("Output", output)
    cv2.imshow("Connected Component", componentMask)
    cv2.waitKey(0)

2.3 연결된 구성 요소 필터링

우리의 이전 코드 샘플은 OpenCV를 사용하여 연결된 구성 요소를 추출하는 방법을 보여주었지만 이를 필터링하는 방법은 보여주지 않았습니다.

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())

# 加载图像,转为灰度,二值化
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) = 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语句显示每个连接组件的宽度、高度和面积,
    # 同时将它们单独显示在屏幕上。我记录了车牌字符的宽度、高度和面积,并找到了它们的最小/最大值,
    # 对于您自己的应用程序也应该这样做。

    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)

여기에 이미지 설명 삽입

자동 번호판/번호판 인식(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);//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;
}

참조 목록

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

Supongo que te gusta

Origin blog.csdn.net/weixin_43229348/article/details/126047746
Recomendado
Clasificación