"Skeletonize" figures

Asked

Viewed 100 times

1

I wrote code to "skeletonize" figures, that is, to reduce their thicknesses to just one pixel. I had already written the code in Python that worked correctly. However, the same code rewritten to C++, although Compile and run error-free, does not produce the same result as with Python. Follow an excerpt of the code in C++ to see if anyone will help me, thank you.

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

using namespace cv;
using namespace std;

int vizinhos(int x, int y, int img[][180]);
int zero_um(int x, int y, int img[][180]);
int multiplica_r1(int x, int y, int img[][180]);
int multiplica_r2(int x, int y, int img[][180]);
int conta_pixels(int img[][180]);

// INÍCIO DA FUNÇÃO PRINCIPAL ******************************************
int main(){

    // Lê a imagem original
    Mat img;
    img = imread("/home/dexter/Público/xis.png", CV_LOAD_IMAGE_GRAYSCALE);

    // Binariza a imagem
    Mat img_bin;
    threshold(img, img_bin, 50, 255, THRESH_BINARY);

    // Obtém o negativo da imagem binarizada
    int img_bin_neg[180][180];
    for (int i=0; i<180; i++) {
        for(int j=0; j<180; j++) {
            img_bin_neg[i][j] = (255 - img_bin.at<uchar>(i,j)) / 255;
        }
    }

    // EFETUA A ESQUELETONIZAÇÃO **************

    int brancos0, brancos1, dif;
    brancos0 = conta_pixels(img_bin_neg);
    dif = 10;
    int flag[180][180];

    while (dif > 0) {

        // Aplica conjunto de regras 1
        for (int i=1; i<179; i++) {
            for (int j=1; j<179; j++) {

                int B = vizinhos(i,j,img_bin_neg);
                bool teste1 = (B >= 2) && (B <= 6);

                int A = zero_um(i,j,img_bin_neg);
                bool teste2 = (A == 1);

                int p2p4p6, p4p6p8 = multiplica_r1(i,j,img_bin_neg);
                bool teste3 = (p2p4p6 == 0);
                bool teste4 = (p4p6p8 == 0);

                if (teste1 * teste2 * teste3 * teste4 == 1) {
                    flag[i][j] = 1;
                }
            }
        }
        for (int i=0; i<180; i++) {
            for (int j=0; j<180; j++) {
                if (flag[i][j] == 1) { img_bin_neg[i][j] = 0; }
            }
        }

        // Aplica conjunto de regras 2
        for (int i=1; i<179; i++) {
            for (int j=1; j<179; j++) {

                int B = vizinhos(i,j,img_bin_neg);
                bool teste1A = (B >= 3) && (B <= 6);

                int A = zero_um(i,j,img_bin_neg);
                bool teste2A = (A == 1);

                int p2p4p8, p2p6p8 = multiplica_r2(i,j,img_bin_neg);
                bool teste3A = (p2p4p8 == 0);
                bool teste4A = (p2p6p8 == 0);

                if (teste1A * teste2A * teste3A * teste4A == 1) {
                    flag[i][j] = 1;
                }
            }
        }

        for (int i=0; i<180; i++) {
            for (int j=0; j<180; j++) {
                if (flag[i][j] == 1) { img_bin_neg[i][j] = 0; }
            }
        }

        brancos1 = conta_pixels(img_bin_neg);
        dif = brancos0 - brancos1;
        brancos0 = brancos1;

    } // Fim do laço while


    // IMAGEM ESQUELETONIZADA
    int m_esqueleto[180][180];
    for (int i=0; i<180; i++) {
        for(int j=0; j<180; j++) {
            m_esqueleto[i][j] = 1 - img_bin_neg[i][j];
        }
    }

    Mat esqueletonizada(180, 180, CV_8UC1);
    for (int i=0; i<180; i++) {
        for (int j=0; j<180; j++) {
            esqueletonizada.at<uchar>(i,j) = m_esqueleto[i][j] * 255;
        }
    }

    namedWindow("img esqueletonizada", WINDOW_NORMAL);
    imshow("img esqueletonizada", esqueletonizada);
    waitKey();

return 0;
}   

// FIM DA FUNÇÃO PRINCIPAL *************************

// FUNÇÕES PARA ESQUELETONIZAÇÃO
int vizinhos(int x, int y, int img[][180]){
    int B = 0;
    for (int j=0; j<3; j++) {
        int v = j+y-1;
        B += img[x-1][v] + img[x+1][v];
    }
    B += img[x][y-1] + img[x][y+1];
    return B;
}

int zero_um(int x, int y, int img[][180]){
    int A = 0;
    for (int j=0; j<2; j++) {
        int v = j+y-1;
        if (img[x-1][v]==0 && img[x-1][v+1]==1) { A += 1; }
        if (img[x+1][v]==1 && img[x+1][v+1]==0) { A += 1; }
    }

    for (int i=0; i<2; i++){
        int u = i+x-1;
        if (img[u][y+1]==0 && img[u+1][y+1]==1) { A += 1; }
        if (img[u][y-1]==1 && img[u+1][y-1]==0) { A += 1; }
    }
    return A;
}

int multiplica_r1(int x, int y, int img[][180]){
    int p2p4p6 = img[x-1][y] * img[x][y+1] * img[x+1][y];
    int p4p6p8 = img[x][y+1] * img[x+1][y] * img[x][y-1];
    return p2p4p6, p4p6p8;
}

int multiplica_r2(int x, int y, int img[][180]){
    int p2p4p8 = img[x-1][y] * img[x][y+1] * img[x][y-1];
    int p2p6p8 = img[x-1][y] * img[x+1][y] * img[x][y-1];
    return p2p4p8, p2p6p8;
}

int conta_pixels(int img[][180]){
    int pixels_brancos = 0;
    for (int i=0; i<180; i++){
        for (int j=0; j<180; j++){
            if (img[i][j] == 1) { pixels_brancos += 1; }
        }
    }
    return pixels_brancos;
}
  • Hello @Dexter, are you wanting to use the functions of opencv even to do this, or are you trying to do the "skeletonization" in your own way?

  • I used opencv only to read and manipulate matrices. By the way, I don’t even know if I needed to import all these libraries.

1 answer

1


I don’t know if this is exactly what you wanted, but it might help you get what you expect, that was the result:

imagem descrevendo o processo de preparação da imagem para a aplicação do thinning

The approach that I followed was to each step accomplished, generate an image so that I could understand what was happening, after the image read in gray scale I "separated" what for me is "information" in the case of this image, only the logo without gray background, and so I applied two types of "skeletonization" (in the documentation is referred to as thinning), the first is the ZHANGSUEN fourth image from left to right, and the second GUOHALL the fifth image, follows the documentation of the função and the types of thinning.

Below the code to demonstrate how the whole process was done:

//Compila no linux com: g++ `pkg-config --cflags --libs opencv4` main.cpp -o thinning

#include <opencv2/opencv.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/ximgproc.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

void gray_to_black( Mat& img );

int main(){

    Mat stack;
    Mat img_thinning_a;
    Mat img_thinning_b;
    vector<int> compression_params;
    compression_params.push_back( cv::ImwriteFlags::IMWRITE_PNG_COMPRESSION );
    compression_params.push_back( 9 );

    //Obtém a imagem em escala de cinza
    stack = imread("/home/SOpt/Q376812/resources/SO_icon.png", cv::ImreadModes::IMREAD_GRAYSCALE );        
    imwrite( "/home/SOpt/Q376812/resources/SO_gray.png", stack, compression_params );

    //identifica os pixels "acinzentados" e os define como preto (para o caso do logo)
    gray_to_black( stack );
    imwrite( "/home/SOpt/Q376812/resources/SO_gray_removed.png", stack, compression_params );

    //Aplica o Thinning de dois tipos diferentes
    ximgproc::thinning( stack, img_thinning_a, ximgproc::ThinningTypes::THINNING_ZHANGSUEN );
    ximgproc::thinning( stack, img_thinning_b, ximgproc::ThinningTypes::THINNING_GUOHALL );

    imwrite( "/home/SOpt/Q376812/resources/SO_thinning_a.png", img_thinning_a, compression_params );
    imwrite( "/home/SOpt/Q376812/resources/SO_thinning_b.png", img_thinning_b, compression_params );

    return 0;
}   

void gray_to_black( Mat& img )
{
    auto color = img.at<uchar>( 0, 0 );
    for( auto x = 0; x < img.rows; x++ )
    {
        for( int y = 0; y < img.cols; y++ )
        {
            color = img.at<uchar>( x, y );
            if( color != 0 && color != 255 )
            { 
                img.at<uchar>( x, y ) = 0;
            }
        }
    }
}
  • Wonderful, worked with THINNING_ZHANGSUEN, using my figure!!

  • Good, I am happy to have helped, but see that this does not work in all cases, as I said in the reply leave in white only what is necessary for thinning.

  • But this THINNING function has to be worked on the negative image, so I used THRESH_BINARY_INV in the original image. After applying the THINNING function, I reversed the image again using THRESH_BINARY_INV. Thanks for the tip, saved me a ton of code!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.