Serie práctica de OpenCV - Detección detallada de contornos

0. Prefacio

En el campo de la visión por computadora , un contorno generalmente se refiere a una serie de puntos en el límite de un objeto en una imagen. Por lo tanto, el contorno generalmente describe la información clave del límite del objeto y contiene la información principal sobre la forma del objeto, que se puede utilizar para el análisis de la forma y la detección y el reconocimiento de objetos. En esta sección, primero presentamos cómo extraer los contornos de la imagen y luego explicamos cómo calcular el descriptor de forma de los contornos.

1. Extraiga el contorno de la región

1.1 Extracción de contorno

Las imágenes a menudo contienen representaciones de objetos de interés, y uno de los objetivos del análisis de imágenes es identificar y extraer estos objetos. En las aplicaciones de detección/reconocimiento de objetos , normalmente es necesario generar una imagen binaria, mostrar la posición del objeto de destino y extraer los objetos contenidos en la imagen binaria. Por ejemplo, usando la siguiente imagen binaria:

imagen binaria

Podemos obtener esta imagen con una simple operación de umbralización y luego aplicar un filtro morfológico abierto/cerrado. Esta sección describe cómo extraer objetos de interés en una imagen y, más específicamente, extraeremos partes conectadas en una imagen, es decir, formas que consisten en un conjunto de píxeles conectados en una imagen binaria . OpenCVSe proporciona una función simple para extraer los contornos de partes conectadas de una imagen, cv::findContoursla función .

(1) Para usar cv::findContoursla función, necesitamos un vector de puntos para almacenar todos los contornos de salida:

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

(2) Utilice cv::findContoursla función para detectar todos los contornos de la imagen y guardarlos en un vector de contorno:

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

cv::findContoursLa entrada de la función es una imagen binaria y la salida es un vector de contornos, cada contorno está cv::Pointrepresentado por un vector de objetos, por lo que el parámetro de salida se define como std::vectorun objeto. Además, se especifican dos banderas, la primera significa que solo se requiere el contorno exterior, es decir, se ignoran los agujeros en el objeto; la segunda bandera se usa para especificar el formato del contorno, use la opción, el vector se enumere todos los puntos en el contorno, CV_CHAIN_APPROX_NONEuse CV_CHAIN_APPROX_SIMPLEuna bandera que solo incluirá los puntos finales de los contornos horizontales, verticales o diagonales, también se pueden usar otras banderas para obtener aproximaciones más complejas de las cadenas de contorno. Usando la imagen que se muestra arriba, puede obtener 10componentes conectados.

(3)OpenCV Es muy conveniente dibujar el contorno de la parte conectada en una imagen usando :

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

Si el 3parámetro th de esta función es negativo, se dibujan todos los contornos, o se puede usar un valor positivo para especificar el índice del contorno a dibujar, como se muestra en la siguiente figura:

imagen de perfil

El contorneado se logra escaneando sistemáticamente la imagen hasta que se detecten todas las partes objetivo, comenzando desde el punto de inicio en la parte conectada, siguiendo su contorno y marcando píxeles en su borde; después de marcar, continúe escaneando en la última posición hasta una nueva parte de conexión .

(4) Los restos conectados identificados pueden entonces analizarse individualmente. Por ejemplo, podemos eliminar algunas partes no válidas estimando el tamaño esperado del objeto de destino, y podemos usar los valores mínimo y máximo del perímetro de la parte conectada para eliminar las conexiones no válidas:

// 消除所有过短或过长的轮廓
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;
        }
    }
}

Dado que std::vectorla complejidad temporal de cada operación de eliminación es O ( N ) O(N) O(N), este ciclo se puede optimizar aún más. Dibujar el contorno sobre la imagen original, el resultado se muestra en la siguiente figura:

dibujo de contorno

1.2 Análisis de contorno complejo

El uso de criterios simples puede ayudarnos a identificar todos los objetos de interés en una imagen y, en casos más complejos, necesitamos un análisis más detallado de las propiedades de las partes conectadas.
Con la función, también es posible detectar todos los contornos cerrados en una imagen binaria (incluidos los contornos con forma de agujero en los objetos) cv::findContoursespecificando el indicador en la llamada a la función :CV_RETR_LIST

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

Usando las llamadas de función anteriores, se pueden obtener los siguientes contornos:

contorno

Puede ver la adición de contornos adicionales en la imagen de arriba. También es posible organizar estos contornos en una jerarquía, la parte principal es el componente principal, los agujeros en él son sus hijos, si hay componentes dentro de esos agujeros, se convierten en hijos de los hijos anteriores, y así sucesivamente, la jerarquía se puede obtener usando CV_RETR_TREEla bandera:

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

En este caso, cada contorno tiene un elemento de jerarquía correspondiente en el mismo índice, que consta de cuatro números enteros. Los dos primeros enteros proporcionan los índices del contorno anterior y siguiente al mismo nivel, los dos últimos enteros proporcionan los índices del primer hijo y padre de ese contorno, los índices negativos indican el final de la lista de contornos. CV_RETR_CCOMPLas banderas son similares, pero la jerarquía consta de solo dos niveles.

2. Calcular el descriptor de forma de la región

La parte conectada generalmente corresponde a un objeto de destino en la escena de la imagen, y para identificar este objeto o compararlo con otros elementos de la imagen, es posible que necesitemos tomar medidas para extraer las características deseadas. En esta sección, presentamos OpenCVlos descriptores de forma disponibles en , para describir formas de contorno.
Hay varias OpenCVfunciones disponibles como descriptores de forma, cuya aplicación puede extraer partes conectadas. Usamos el vector de contorno correspondiente al objeto de destino, calculamos el descriptor de forma en ( contours[0]a ) el contorno y trazamos el resultado contours[3](ancho de línea) en la imagen de contorno (ancho de línea ).12

(1) boundingRect La función se utiliza para calcular el borde del rectángulo:

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

(2) minEnclosingCircle La función se utiliza para aproximar el círculo envolvente mínimo:

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

(3) La aproximación poligonal del contorno del área utiliza approxPolyDPla función:

// 近似多边形
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;

La función de dibujo de polígonos cv::polylineses similar a otras funciones de dibujo, el primer 3parámetro es de tipo booleano que se utiliza para indicar si el contorno está cerrado, si es verdadero, conecta el último punto con el primer punto.

(4) La función de casco convexo convexHulles otra forma de aproximación de polígonos:

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

(5) Los momentos son otro descriptor poderoso que permite el cálculo del centroide dentro de una región:

// 矩
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);
}

La imagen resultante es la siguiente:

descriptor de forma

En la mayoría de los casos, un cuadro delimitador es la forma más compacta de representar y ubicar un objeto de interés en una imagen, definido como el rectángulo de tamaño más pequeño que encierra completamente la forma del objeto. La altura y el ancho del cuadro delimitador pueden indicar el tamaño vertical u horizontal del objeto; por ejemplo, la relación de aspecto se puede usar para distinguir los automóviles de los peatones; cuando solo se requiere el tamaño y la ubicación aproximados del objeto, el límite más pequeño generalmente se usa el círculo.
Cuando desee una representación compacta similar a la forma del objeto de destino, puede usar la aproximación poligonal, especifique la distancia máxima aceptable entre la forma del objeto de destino y el polígono aproximado especificando el parámetro de precisión ( cv::approxPolyDPel primer parámetro en la función ) , y la función devuelve Los vectores corresponden a los vértices del polígono. Para dibujar este polígono, necesitamos iterar sobre los vectores y conectar puntos adyacentes con segmentos de línea entre ellos. El casco convexo, o envoltura convexa, de una forma es el polígono convexo más pequeño que contiene la forma, que se puede considerar como la forma de una banda de goma elástica alrededor de un objeto objetivo. El perfil del casco convexo se desviará del perfil original en las ubicaciones que son cóncavas en el perfil de forma del objeto, que normalmente se denominan defectos de convexidad, y se pueden identificar mediante la función , llamada de la siguiente manera:4cv::Point
OpenCVcv::convexityDefects

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

contourLos parámetros y hullson el contorno original y el contorno del casco convexo (ambos std::vector<cv::Point>casos), respectivamente. La salida es un vector de cuatro elementos enteros, los dos primeros enteros son índices de puntos en el contorno que limita el defecto; el tercer entero corresponde al punto más lejano dentro de la superficie cóncava, y el último entero corresponde a este punto más lejano a la convexa distancia del casco.
El momento es una herramienta matemática de uso común en el análisis de la estructura de la forma. OpenCVDefine una estructura de datos que encapsula todos los momentos calculados de la forma. cv::momentsEl valor de retorno de la función utiliza esta estructura de datos. Estos momentos constituyen una descripción concisa de la forma del objeto. . Podemos usar los primeros 3 momentos espaciales en esta estructura para obtener el centroide de la forma.
También puede usar OpenCVla función para calcular las propiedades estructurales, cv::minAreaRectla función calcula el rectángulo de rotación cerrado más pequeño; cv::contourAreala función estima el área del contorno (el número de píxeles internos); cv::pointPolygonTestla función se usa para determinar si un punto está dentro o fuera del contorno, y cv::matchShapesla similitud entre dos contornos se puede medir por sexo. Podemos realizar un análisis de estructura de imagen más avanzado combinando todas estas propiedades.

2.1 Detección de cuadrilátero

Podemos usar la imagen transformada obtenida por operaciones morfológicas para extraer la forma de la imagen, asumiendo que usamos el resultado obtenido al transformar la imagen usando operaciones morfológicas MSER, y luego construimos un algoritmo para detectar el componente cuadrilátero en la imagen. Supongamos que detectamos la siguiente MSERimagen binaria usando el algoritmo. Detectar el componente cuadrilátero puede ayudarnos a identificar ventanas en edificios, etc. Para reducir el ruido en la imagen, usamos algunos filtros morfológicos para preprocesar la imagen:

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

A continuación, obtenga los contornos:

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

Finalmente, itere sobre todos los contornos y aproxímelos con polígonos:

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;
}

Los resultados de la prueba son los siguientes:

Descriptor de cuadrilátero

Si queremos detectar rectángulos, podemos medir los ángulos entre lados adyacentes y eliminar 90los cuadriláteros que se desvían demasiado (en comparación con los grados).

3. Código completo

El archivo de código completo blobs.cppes el siguiente:

#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();
        // 使用多边形近似轮廓
        cv::approxPolyDP(*it,poly,5,true);
        // 检测轮廓是否为四边形
        if (poly.size()==4) {
    
    
            cv::polylines(quadri, poly, true, 0, 2);
        }

        ++it;
    }
    cv::namedWindow("MSER quadrilateral");
    cv::imshow("MSER quadrilateral", quadri);
    cv::waitKey();
    return 0;
}

resumen

En este artículo, primero presentamos los conceptos relacionados con los contornos y luego comprendemos cómo utilizar para cv::findContours()detectar contornos y cv::drawContours()dibujar contornos.Después de obtener los contornos, podemos calcular el descriptor de forma de los contornos.

Transferencia: https://blog.csdn.net/LOVEmy134611/article/details/128833287?spm=1001.2014.3001.5501

Supongo que te gusta

Origin blog.csdn.net/m0_58523831/article/details/129701969
Recomendado
Clasificación