Calculando Precisão, Revocação, Acurácia e Medida F com PCL e C++

Olá!

Neste post descrevo a implementação do cálculo das métricas vistas no post sobre Precisão, Revocação, Acurácia e Medida F.

Como trabalhei com processamento de imagens 3D no meu mestrado, neste post o foco é apresentar a implementação realizada utilizando a biblioteca PCL e a OpenCV.

O objetivo deste post é explicar o código-fonte. Caso você queria fazer o download, vá no repositório no github. É um projeto do Visual Studio 2017, caso você esteja por fora, veja aqui como instalar a PCL no Windows e como criar um projeto com a PCL no Visual Sutio 2017.

Abaixo estão os trechos de código explicados. Veja aqui o código completo.

 

Declarando as Nuvens de Pontos em RGB

Aqui vamos declarar as nuvens de pontos que serão lidas dos arquivos PLY. Note que as nuvens são coloridas:

// Declarando a nuvem do cenário.
pcl::PointCloud ::Ptr cloud_org(new pcl::PointCloud );

// Declarando o padrao ouro.
pcl::PointCloud ::Ptr cloud_pb(new pcl::PointCloud );


// Declarando o padrao ouro com regioes hipoidroticas.
pcl::PointCloud ::Ptr cloud_pb_hipoidroticas(new pcl::PointCloud );

 

Declarando as Nuvens  de Pontos com Intensidade

 

// Declarando o Padrão Ouro (somente anidrose) em Escala de Cinza.
pcl::PointCloud <pcl::PointXYZI>::Ptr cloud_ref;

// Declarando o Padrão Ouro com Hipoidrose em Escala de Cinza.
pcl::PointCloud <pcl::PointXYZI>::Ptr cloud_ref_hipoidrose;

// Declarando a nuvem de pontos do cenario corrente.
pcl::PointCloud <pcl::PointXYZI>::Ptr cloud_pred;


// Declarando a variável que contém os dados do precision and recall das duas comparações.
std::stringstream valores_calculados;

 

 

Leitura das Nuvens de Ponto do Padrão Ouro

Aqui estou fazendo a leitura das nuvens de pontos do pardão ouro. Na minha pesquisa eu usei duas versões do padrão ouro. Dê uma olhada neste post onde utilizo o CloudCompare para segmentar manualmente as imagens 3D.

/**
* Le a nuvem de pontos que simboliza o padrao ouro: Entra em PLY.
*/
void read_cloud_padrao_ouro()
{
  std::cout << "___________________________________________________________" << std::endl;
  std::cout << "                LENDO AS NUVENS DO PADRAO OURO             " << std::endl;

  std::cout << "  - Tentando ler a nuvem do padrao ouro somente das areas de anidrose... ";

  std::string padrao_ouro_anidrose = "PLY_PadraoOuro/padrao_ouro_anidrose.ply";

  pcl::PLYReader reader;

  // Lendo a nuvem do Padrão Ouro considerando apenas as áreas de Anidrose.
  if (reader.read(padrao_ouro_anidrose, *cloud_pb) == -1) { //lucy_pb.ply
    std::cout << std::endl << "  - Erro ao ler a nuvem de pontos do padrao ouro das areas de anidrose." << std::endl;
  }

  std::cout << "OK " << std::endl;


  std::cout << "  - Calculando a intensidade da nuvem de pontos das areas de anidrose... ";
  cloud_ref = cloudRGB2GRAY(cloud_pb);
  std::cout << "OK " << std::endl << std::endl;


  
  std::cout << "  - Tentando ler a nuvem do padrao ouro areas de anidrose com hipoidroticas... ";

  std::string padrao_ouro_com_hipoidrotica = "PLY_PadraoOuro/padrao_ouro_anidrose_com_hipoidrotica.ply";

  // Lendo a nuvem do Padrão Ouro considerando apenas as áreas de Anidrose com Hipoidrose.
  if (reader.read(padrao_ouro_com_hipoidrotica, *cloud_pb_hipoidroticas) == -1) { //lucy_pb.ply
    std::cout << std::endl << "Erro ao ler a nuvem de pontos do padrao ouro das areas de anidrose com hipoidroticas." << std::endl;
  }

  std::cout << "OK " << std::endl;


  std::cout << "  - Calculando a intensidade da nuvem de pontos das areas de anidrose com hipoidroticas... ";
  cloud_ref_hipoidrose = cloudRGB2GRAY(cloud_pb_hipoidroticas);
  std::cout << "OK " << std::endl << std::endl;
}

 

 

Convertendo Tons de Cinza

Nesta função vamos converter as nuvens que estão em RGB para tons de cinza. Vamos utilizar a estrutura específica da PCL para isso, que contém o campo adicional intensity, onde teremos uma coleção XYZI. Repare que utilizei a biblioteca OpenCV para me ajudar na conversão de cada m dos pontos da nuvem.

/**
* Converte as nuvens para tons de cinza, adicionando a intensidade que é usada como critério de
* cálculo entre do TP, TN, FP e FN.
*/
pcl::PointCloud<pcl::PointXYZI>::Ptr cloudRGB2GRAY(pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud) {
  pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_gray(new pcl::PointCloud<pcl::PointXYZI>);
  cloud_gray->height = cloud->height;
  cloud_gray->width = cloud->width;

  for (pcl::PointCloud<pcl::PointXYZRGB>::iterator it = cloud->begin(); it != cloud->end(); it++) {
    // Color conversion
    cv::Mat pixel(1, 1, CV_8UC3, cv::Scalar(it->r, it->g, it->b));
    cv::Mat temp;
    cv::cvtColor(pixel, temp, CV_RGB2GRAY);

    pcl::PointXYZI pointI;
    pointI.x = it->x;
    pointI.y = it->y;
    pointI.z = it->z;
    pointI.intensity = temp.at<uchar>(0, 0);

    cloud_gray->push_back(pointI);

  }
  return cloud_gray;
}

 

 

Gravando o Resultado num Arquivo de Texto

Como pretendia processar várias nuvens de pontos em sequencia decidi gravar os resultados obtidos em arquivos de texto, por exemplo: Dados_Precision_and_Recall_Cenario_22.txt. Veja como fiz:

/**
 * Função que calcula e grava o arquivo txt com o nro do cenario e os dados do Precision and Recall.
 */
void save_txt(int cenario) {

  std::stringstream nome_arquivo;
  nome_arquivo << "Dados_Precision_and_Recall_Cenario_" << cenario << ".txt";

   

  std::stringstream ss;
  ss << "            Dados Precision and Recall do Cenario " << cenario << std::endl << std::endl;
  

  ss << valores_calculados.str();



  std::cout << "  - Gravando os valores calculados no aquivo... ";

  FILE * pFile;
  pFile = fopen(nome_arquivo.str().c_str(), "w");
  if (pFile != NULL)
  {
    fputs(ss.str().c_str(), pFile);
    fclose(pFile);
  }

  std::cout << "OK " << std::endl << std::endl;
  std::cout << ss.str();
}

 

Mais Importante: Comparando as Duas Nuvens!

Nesta função calculamos as métricas

/**
 * Função que computa dos dados de TP, TN, FP, FN entre as duas nuvens.
 */
void cloud_compare(int scenario, pcl::PointCloud <pcl::PointXYZI>::Ptr cloud_ref, pcl::PointCloud <pcl::PointXYZI>::Ptr cloud_pred) {

  
  float tn = 0.f, tp = 0.f, fn = 0.f, fp = 0.f;
  int hidrotica_pontos = 0;

  pcl::KdTreeFLANN<pcl::PointXYZI> tree_ref;
  tree_ref.setInputCloud(cloud_ref);

  std::vector<int> nn_indices(1);
  std::vector<float> nn_dists(1);



  // Comparando as duas nuvens de pontos.
  for (pcl::PointCloud<pcl::PointXYZI>::iterator it = cloud_pred->begin(); it != cloud_pred->end(); it++) {
    tree_ref.nearestKSearch(*it, 1, nn_indices, nn_dists);

    float i_ref = cloud_ref->points[nn_indices[0]].intensity;
    float i_pred = it->intensity;

    if (i_ref == 255 & i_pred == 255) tp++;
    else if (i_ref == 0 & i_pred == 0) tn++;
    else if (i_ref == 0 & i_pred == 255) fp++;
    else if (i_ref == 255 & i_pred == 0) fn++;


    if (i_ref == 255 & i_pred == 255) hidrotica_pontos++;
  }



  // Calculando Precision and Recall.
  float precision = tp / (tp + fp);
  float recall = tp / (tp + fn);
  float accu = (tp + tn) / (tp + tn + fp + fn);
  float tpr = tp / (tp + fn);
  float tnr = tn / (tn + fp);
  float f_m = 2 * ((precision*recall) / (precision + recall));


  // Calculando % das áreas de anidrose e hipoidrose juntas.
  int total_pontos = cloud_org->size();

  float porcentagem_hidrotica = (hidrotica_pontos * 100) / total_pontos;
  float porcentagem_anidrose_hipoidrotica = 100 - porcentagem_hidrotica;


  valores_calculados << "   - Precision: " << precision << std::endl;
  valores_calculados << "   - Recall: " << recall << std::endl;
  valores_calculados << "   - True Positive Rate: " << tpr << std::endl;
  valores_calculados << "   - True Negative Rate: " << tnr << std::endl;
  valores_calculados << "   - Accuracy: " << accu << std::endl;
  valores_calculados << "   - F measure: " << f_m << std::endl << std::endl;

  valores_calculados << "   - Areas Hidroticas: " << porcentagem_hidrotica << "%" << std::endl;
  valores_calculados << "   - Areas Anidrose e Hipoidroticas: " << porcentagem_anidrose_hipoidrotica << "%" << std::endl;
}

 

 

 

Chamando a função de Acordo com o Cenário

aqui..

No Método Prinicipal

Chamando no Método Principal…

int main()
{
  std::cout << "PROGRAMA DE COMPARACAO DE PRECISION AND RECALL INICIADO! " << std::endl << std::endl;


  // Lendo a núvem do padrao ouro.
  read_cloud_padrao_ouro();



  std::cout << "___________________________________________________________" << std::endl;
  std::cout << "           INICIANDO O CALCULO PARA OS CENARIOS            ";



  /**
  * Avaliando a segmentação do Region Growing em LAB com a variação do MinClusterSize.
  */
  make_scenario(10, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_10_pb.ply");
  make_scenario(11, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_11_pb.ply");
  make_scenario(12, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_12_pb.ply");
  make_scenario(13, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_13_pb.ply");
  make_scenario(14, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_14_pb.ply");
  make_scenario(15, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_15_pb.ply");
  make_scenario(16, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_16_pb.ply");
  make_scenario(17, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_17_pb.ply");
  make_scenario(18, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_18_pb.ply");
  make_scenario(19, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_19_pb.ply");
  make_scenario(20, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_20_pb.ply");
  make_scenario(21, "PLY_Comparacao/LAB/minClusterSize/paciente_segmentado_cenario_21_pb.ply");
}

 

 

Conclusão

por fim…