Pregunta Carta interior letra, reconocimiento de patrones.


Me gustaría detectar este patrón

https://www.dropbox.com/s/ghuaywtfdsb249j/test.jpg

Como pueden ver, es básicamente la letra C, dentro de otra, con diferentes orientaciones. Mi patrón puede tener varias C dentro de la otra, la que estoy publicando con 2 C es solo una muestra. Me gustaría detectar cuántas C hay y la orientación de cada una. Por ahora, he logrado detectar el centro de tal patrón, básicamente, he podido detectar el centro de la C más interna. ¿Me podría dar alguna idea sobre los diferentes algoritmos que podría usar?


5
2017-12-18 23:36


origen


Respuestas:


¡Y aquí vamos! UN descripción general de alto nivel de este enfoque se puede describir como la ejecución secuencial de los siguientes pasos:

No quiero entrar en demasiados detalles ya que estoy compartiendo el código fuente, así que siéntase libre de probar y cambiarlo de la forma que desee. Empecemos, Viene el invierno:

#include <iostream>
#include <vector>
#include <cmath>

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

cv::RNG rng(12345);
float PI = std::atan(1) * 4;

void isolate_object(const cv::Mat& input, cv::Mat& output)
{    
    if (input.channels() != 1)
    {
        std::cout << "isolate_object: !!! input must be grayscale" << std::endl;
        return;
    }

    // Store the set of points in the image before assembling the bounding box
    std::vector<cv::Point> points;
    cv::Mat_<uchar>::const_iterator it = input.begin<uchar>();
    cv::Mat_<uchar>::const_iterator end = input.end<uchar>();
    for (; it != end; ++it)
    {
        if (*it) points.push_back(it.pos());
    }

    // Compute minimal bounding box
    cv::RotatedRect box = cv::minAreaRect(cv::Mat(points));

    // Set Region of Interest to the area defined by the box
    cv::Rect roi;
    roi.x = box.center.x - (box.size.width / 2);
    roi.y = box.center.y - (box.size.height / 2);
    roi.width = box.size.width;
    roi.height = box.size.height;

    // Crop the original image to the defined ROI
    output = input(roi);
}

Para más detalles sobre la implementación de isolate_object() Por favor revisa este hilo. cv::RNG se usa más adelante para llena cada contorno con un color diferentey PI, Bueno, ya sabes Pi.

int main(int argc, char* argv[])
{   
    // Load input (colored, 3-channel, BGR)
    cv::Mat input = cv::imread("test.jpg");
    if (input.empty())
    {
        std::cout << "!!! Failed imread() #1" << std::endl;
        return -1;
    }

    // Convert colored image to grayscale
    cv::Mat gray;
    cv::cvtColor(input, gray, CV_BGR2GRAY);

    // Execute a threshold operation to get a binary image from the grayscale
    cv::Mat binary;
    cv::threshold(gray, binary, 128, 255, cv::THRESH_BINARY); 

los binario la imagen se ve exactamente como la entrada porque solo tenía 2 colores (B y N):

    // Find the contours of the C's in the thresholded image
    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(binary, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);

    // Fill the contours found with unique colors to isolate them later
    cv::Mat colored_contours = input.clone();  
    std::vector<cv::Scalar> fill_colors;   
    for (size_t i = 0; i < contours.size(); i++)
    {
        std::vector<cv::Point> cnt = contours[i];
        double area = cv::contourArea(cv::Mat(cnt));        
        //std::cout << "* Area: " << area << std::endl;

        // Fill each C found with a different color. 
        // If the area is larger than 100k it's probably the white background, so we ignore it.
        if (area > 10000 && area < 100000)
        {
            cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255));            
            cv::drawContours(colored_contours, contours, i, color, 
                             CV_FILLED, 8, std::vector<cv::Vec4i>(), 0, cv::Point());
            fill_colors.push_back(color);
            //cv::imwrite("test_contours.jpg", colored_contours);
        }           
    }

Qué contornos de color parece:

    // Create a mask for each C found to isolate them from each other
    for (int i = 0; i < fill_colors.size(); i++)
    {
        // After inRange() single_color_mask stores a single C letter
        cv::Mat single_color_mask = cv::Mat::zeros(input.size(), CV_8UC1);
        cv::inRange(colored_contours, fill_colors[i], fill_colors[i], single_color_mask);
        //cv::imwrite("test_mask.jpg", single_color_mask);

Desde esto for el bucle se ejecuta dos veces, uno para cada color que se utilizó para rellenar los contornos. Quiero que vea todas las imágenes generadas por esta etapa. Entonces las siguientes imágenes son las que fueron almacenadas por single_color_mask (uno para cada iteración del ciclo):

 

        // Crop image to the area of the object
        cv::Mat cropped;
        isolate_object(single_color_mask, cropped);        
        //cv::imwrite("test_cropped.jpg", cropped);
        cv::Mat orig_cropped = cropped.clone();

Estos son los que fueron almacenados por recortada (por cierto, la C más pequeña se ve gorda porque la imagen es redimensionada por esta página para que tenga el mismo tamaño que la C más grande, no te preocupes):

 

        // Figure out the center of the image
        cv::Point obj_center(cropped.cols/2, cropped.rows/2);
        //cv::circle(cropped, obj_center, 3, cv::Scalar(128, 128, 128));
        //cv::imwrite("test_cropped_center.jpg", cropped);

Para que sea más claro entender por qué obj_center es para, pinté un pequeño círculo gris con fines educativos en ese lugar:

   

        // Figure out the exact center location of the border
        std::vector<cv::Point> border_points;
        for (int y = 0; y < cropped.cols; y++) 
        {
            if (cropped.at<uchar>(obj_center.x, y) != 0)
                border_points.push_back(cv::Point(obj_center.x, y));

            if (border_points.size() > 0 && cropped.at<uchar>(obj_center.x, y) == 0)
                break;
        }

        if (border_points.size() == 0)
        {
            std::cout << "!!! Oops! No border detected." << std::endl;
            return 0;
        }

        // Figure out the exact center location of the border
        cv::Point border_center = border_points[border_points.size() / 2];
        //cv::circle(cropped, border_center, 3, cv::Scalar(128, 128, 128));
        //cv::imwrite("test_border_center.jpg", cropped);

El procedimiento anterior escanea una sola línea vertical desde la parte superior / media de la imagen para encontrar los bordes del círculo para poder calcular su ancho. Una vez más, con fines educativos, pinté un pequeño círculo gris en el medio del borde. Esto es lo que recortada parece:

   

        // Scan the border of the circle for discontinuities 
        int radius = obj_center.y - border_center.y;
        if (radius < 0) 
            radius *= -1;  
        std::vector<cv::Point> discontinuity_points;   
        std::vector<int> discontinuity_angles;
        for (int angle = 0; angle <= 360; angle++)
        {
            int x = obj_center.x + (radius * cos((angle+90) * (PI / 180.f))); 
            int y = obj_center.y + (radius * sin((angle+90) * (PI / 180.f)));                

            if (cropped.at<uchar>(x, y) < 128)
            {
                discontinuity_points.push_back(cv::Point(y, x));
                discontinuity_angles.push_back(angle); 
                //cv::circle(cropped, cv::Point(y, x), 1, cv::Scalar(128, 128, 128));                           
            }
        }

        //std::cout << "Discontinuity size: " << discontinuity_points.size() << std::endl;
        if (discontinuity_points.size() == 0 && discontinuity_angles.size() == 0)
        {
            std::cout << "!!! Oops! No discontinuity detected. It's a perfect circle, dang!" << std::endl;
            return 0;
        }

Genial, por lo que el código anterior escanea a lo largo de la mitad del borde del círculo en busca de discontinuidad. Estoy compartiendo una imagen de muestra para ilustrar lo que quiero decir. Cada punto gris en la imagen representa un píxel que se prueba. Cuando el píxel es negro significa que encontramos una discontinuidad:

enter image description here

        // Figure out the approximate angle of the discontinuity: 
        // the first angle found will suffice for this demo.
        int approx_angle = discontinuity_angles[0];        
        std::cout << "#" << i << " letter C is rotated approximately at: " << approx_angle << " degrees" << std::endl;    

        // Figure out the central point of the discontinuity
        cv::Point discontinuity_center;
        for (int a = 0; a < discontinuity_points.size(); a++)
            discontinuity_center += discontinuity_points[a];
        discontinuity_center.x /= discontinuity_points.size(); 
        discontinuity_center.y /= discontinuity_points.size(); 
        cv::circle(orig_cropped, discontinuity_center, 2, cv::Scalar(128, 128, 128));

        cv::imshow("Original crop", orig_cropped);
        cv::waitKey(0);
    }

    return 0;
}

Muy bien ... Esta última parte del código es responsable de determinar el ángulo aproximado de la discontinuidad, así como indicar el punto central de la discontinuidad. Las siguientes imágenes son almacenadas por orig_cropped. Una vez más, agregué un punto gris para mostrar las posiciones exactas detectadas como el centro de las brechas:

   

Cuando se ejecuta, esta aplicación imprime la siguiente información en la pantalla:

#0 letter C is rotated approximately at: 49 degrees
#1 letter C is rotated approximately at: 0 degrees 

Espero que ayude.


14
2017-12-21 23:29



Para empezar podrías usar la transformación de Hough. Este algoritmo no es muy rápido, pero es bastante robusto. Especialmente si tienes imágenes tan claras.

El enfoque general sería:

1) preprocessing - suppress noise, convert to grayscale / binary
2) run edge detector
3) run Hough transform - IIRC it's `cv::HoughCircles` in OpenCV
4) do some postprocessing - remove surplus circles, decide which ones correspond to shape of letter C, and so on

Mi enfoque le dará 2 círculos por letra C. Uno en el límite interno, uno en la letra C externa. Si desea solo un círculo por letra, puede usar el algoritmo de esqueletización. Más información aquí http://homepages.inf.ed.ac.uk/rbf/HIPR2/skeleton.htm


0
2017-12-19 07:13



Dado que hemos anidado estructuras de C y usted conoce los centros de las C y desea evaluar las orientaciones, simplemente debe observar la distribución de píxeles a lo largo de la C radio de los C concéntricos en todas las direcciones.

Esto se puede hacer realizando un simple dilatación morfológica Operación desde el centro. A medida que alcancemos el radio correcto para la C más interna, alcanzaremos un número máximo de píxeles cubiertos para la C más interna. La diferencia entre el disco y la C nos dará la ubicación de la brecha en el conjunto y se puede realizar una erosión final para obtener el centroide de la brecha en la C. El ángulo entre el centro y este punto es la orientación de la C. Este paso se itera hasta que se cubran todas las Cs.

Esto también se puede hacer rápidamente usando el Función de distancia desde el punto central de la Cs.


0
2017-12-22 00:30