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…