1. 程式人生 > >KITTI 3D目標檢測離線評估工具包說明

KITTI 3D目標檢測離線評估工具包說明

KITTI 3D目標檢測離線評估工具包說明

本文是KITTI 3D目標檢測離線評估工具包的使用說明和相關程式碼學習檔案,從這裡可以下載。更新於2018.09.20。

文章目錄

工具包README檔案

這個工具包是離線執行的,可以在使用者的電腦上評估驗證集(從KITTI訓練集中選出來的)。評估的指標包括:

  • 重疊率:overlap on image (AP)
  • 旋轉重疊率:oriented overlap on image (AOS)
  • 地面重疊率(鳥瞰視角):overlap on ground-plane (AP)
  • 3D重疊率:overlap in 3D (AP)

首先在終端編譯evaluate_object_3d_offline.cpp

檔案,之後執行評估命令:

./evaluate_object_3d_offline groundtruth_dir result_dir

需要注意的是,使用者並不需要評估整個KITTI訓練集。Evaluator只評估有結果存在的那些樣本。

程式碼學習

這一部分主要是希望通過學習程式碼理解所得到的結果和影象,並不深究其中的語法。

總結:

  • 這個函式主要用於評估實驗結果,但是評估過程中並未評估所有的結果,而是挑選了置信概率最大的前幾個結果(程式中預設取前41個),函式計算了precision和recall並畫出二者的關係曲線(關於這兩個演算法評估概念可以參看這裡的說明)。
  • 評估演算法是按照類別判斷的,對於KITTI庫分為3類(人、車、自行車),每個類別中有不同難度(簡單、中等、困難),曲線是每個類別對應一個曲線圖,圖中包括三種難度下演算法的評估結果曲線。
  • 演算法中還將評估分為2D評估、鳥瞰評估、3D評估三種不同角度,其中2D評估可以有帶轉角的評估AOS,另外兩種則不評估此項。
  • 結果或真值資料的儲存格式應當遵從這個順序:目標型別(人、車、自行車對應的字串),是否截斷(失效值-1),是否遮擋(無遮擋、部分遮擋、全部遮擋)(失效值-1),旋轉角度(失效值-10),左上角座標x1,左上角座標y1,右下角座標x2,右下角座標y2,高,寬,長,box中心座標t1,box中心座標t2,box中心座標t3,box朝向ry,閾值(score)。其中,真值資料不具有最後一項,結果資料是否截斷、是否遮擋對應資料無效。
  • t*在函式toPolygon中用到了,但是含義不清楚;ry的含義也不清楚。

evaluate_object_3d_offline.cpp

這一部分記錄了evaluate_object_3d_offline.cpp檔案的學習筆記,包括其中的主函式、用於評估總過程的eval函式、定義儲存資訊含義的結構體tBox\tGroundtruth\tDetection、具體實施按類評估的eval_class、和用於儲存結果並畫出影象的saveAndPlotPlots。

主函式

主導整個評估過程。

int32_t main (int32_t argc,char *argv[]) {

  // 需要2或4個輸入
  //如果輸入個數為3,顯示用法並返回
  if (argc!=3) { 
    cout << "Usage: ./eval_detection_3d_offline gt_dir result_dir" << endl;
    return 1;  return 1;
  }

  //讀取輸入
  string gt_dir = argv[1];			//第一個輸入是真值路徑
  string result_dir = argv[2];  	//第二個輸入是結果路徑

  //定義用於提示的郵件地址
  Mail *mail;
  mail = new Mail();
  mail->msg("Thank you for participating in our evaluation!");

  //執行評估過程
  //如果評估過程成功,有郵箱地址就將結果連結傳送到郵箱,沒有就儲存在本地plot下
  //否則,返回錯誤資訊並刪除結果目錄
  if (eval(gt_dir, result_dir, mail)) {
    mail->msg("Your evaluation results are available at:");
    mail->msg(result_dir.c_str());
  } else {
    system(("rm -r " + result_dir + "/plot").c_str());
    mail->msg("An error occured while processing your results.");
  } 

  //傳送郵件並退出
  delete mail;

  return 0;
}

eval

從主函式中可以看到,起評估作用的是eval函式,下面貼出eval函式和學習說明:

bool eval(string gt_dir, string result_dir, Mail* mail){

  //設定全域性變數CLASS_NAMES,其中包括car, pedestrain, cyclist
  initGlobals();

  // 真值和結果路徑:
  // string gt_dir         = "data/object/label_2";  真值路徑
  // string result_dir     = "results/" + result_sha; 結果路徑

  //儲存eval結果圖的路徑
  string plot_dir       = result_dir + "/plot";

  // 按照上面定義的plot路徑建立輸出目錄
  system(("mkdir " + plot_dir).c_str()); 

  //定義了兩個二維陣列groundtruth和detections,用於儲存真值和檢測結果
  //定義了一個名為groundtruth的二維陣列,其中每個位置上的資料型別是tGroundtruth,其中存有box的型別、兩組對角座標(左上、右下)、影象轉角等資訊。具體見tBox和tGroundtruth說明。
  vector< vector<tGroundtruth> > groundtruth; 
  //參考上面真值定義
  vector< vector<tDetection> >   detections;

  //儲存是否計算旋轉重疊率AOS(在載入檢測結果的時候可能被設成false),並記錄本次提交都包含哪些labels
  //預設計算AOS(僅對於2D評估)
  bool compute_aos=true; 
  //定義eval_image,儲存bool變數,預設值為false,長度為3
  vector<bool> eval_image(NUM_CLASS, false);
  vector<bool> eval_ground(NUM_CLASS, false);
  vector<bool> eval_3d(NUM_CLASS, false);

  // 讀取所有影象的真值和檢測結果
  mail->msg("Loading detections...");
  //儲存所有有結果的影象編號
  std::vector<int32_t> indices = getEvalIndices(result_dir + "/data/");
  printf("number of files for evaluation: %d\n", (int)indices.size()); 

  //對於所有影象,讀取真值並檢查是否都資料讀取成功
  for (int32_t i=0; i<indices.size(); i++) { 

    // 生成檔名 
    //定義一個長為256的字串,叫file_name
    char file_name[256];
    sprintf(file_name,"%06d.txt",indices.at(i));

    //讀取真值和結果(result poses)
    bool gt_success,det_success;		//定義變數用於儲存是否讀取成功
    //讀取所有圖片的真值,每個圖片15個值(具體參見tGroundtruth),存入gt(用push_back一組接一組)
    vector<tGroundtruth> gt   = loadGroundtruth(gt_dir + "/" + file_name,gt_success); 
    //讀取檢測結果,共16個值(最後一個為score),如果轉角的值(第4個)為-10,則不計算AOS
    vector<tDetection>   det  = loadDetections(result_dir + "/data/" + file_name,   
            compute_aos, eval_image, eval_ground, eval_3d, det_success); 
    groundtruth.push_back(gt);			//將gt存入groundtruth,也就是直到此時才給之前定義的goundtruth賦值
    detections.push_back(det); 			//將det存入detections

    //檢查是否有讀取失敗,如果有,輸出提示並返回
    if (!gt_success) { 
      mail->msg("ERROR: Couldn't read: %s of ground truth. Please write me an email!", file_name);
      return false;
    }
    if (!det_success) {
      mail->msg("ERROR: Couldn't read: %s", file_name);
      return false;
    }
  } 
  mail->msg("  done.");

  // 定義指向結果檔案的指標
  FILE *fp_det=0, *fp_ori=0; 		//FILE是定義在C++標準庫中的一個結構體,以指標的方式儲存與記憶體中,其內容描述了一個檔案

  //對於所有類別評估2D視窗
  for (int c = 0; c < NUM_CLASS; c++) {
    CLASSES cls = (CLASSES)c; 		//找到序號對應的類別(此時cls的值為CAR、PEDESTRAIN或CYCLIST)
    if (eval_image[c]) { 			//如果存在這一類別的影象(在loadDetections裡面判斷了)才計算
      fp_det = fopen((result_dir + "/stats_" + CLASS_NAMES[c] + "_detection.txt").c_str(), "w"); 			//讓fp_det指標指向用於儲存結果的檔案
      if(compute_aos) 				//如果需要計算AOS,就讓fp_ori指向用於儲存AOS的檔案(這裡預設不計算)
        fp_ori = fopen((result_dir + "/stats_" + CLASS_NAMES[c] + "_orientation.txt").c_str(),"w");
      vector<double> precision[3], aos[3];			 //定義兩個長度為3的容器(對應簡單、中等、困難三個級別),分別用於儲存準確率和AOS
      //如果有任意一個難度計算失敗,則返回提示並退出(具體計算過程見eval_class說明)
      if(   !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, imageBoxOverlap, precision[0], aos[0], EASY, IMAGE)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, imageBoxOverlap, precision[1], aos[1], MODERATE, IMAGE)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, imageBoxOverlap, precision[2], aos[2], HARD, IMAGE)) {
        mail->msg("%s evaluation failed.", CLASS_NAMES[c].c_str());
        return false;
      }
      fclose(fp_det); 			//關閉detection的儲存檔案
      saveAndPlotPlots(plot_dir, CLASS_NAMES[c] + "_detection", CLASS_NAMES[c], precision, 0); 			//畫出曲線圖(具體見saveAndPlotPlots說明)
      if(compute_aos){ 			//如果需要計算AOS
        saveAndPlotPlots(plot_dir, CLASS_NAMES[c] + "_orientation", CLASS_NAMES[c], aos, 1); //畫出AOS曲線
        fclose(fp_ori);
      }
    }
  }
  printf("Finished 2D bounding box eval.\n"); 			//結束2D評估
  
  //對於鳥瞰圖和3D box不要計算AOS
  compute_aos = false;

  //對於所有類別評估鳥瞰角度的bounding box
  for (int c = 0; c < NUM_CLASS; c++) {
    CLASSES cls = (CLASSES)c;
    if (eval_ground[c]) { 			//如果存在該型別的圖片才計算
      fp_det = fopen((result_dir + "/stats_" + CLASS_NAMES[c] + "_detection_ground.txt").c_str(), "w");  			//將指標指向用於儲存鳥瞰結果的檔案
      vector<double> precision[3], aos[3];  			//同2D,分別用於儲存簡單、中等和困難的情況
      printf("Going to eval ground for class: %s\n", CLASS_NAMES[c].c_str());
      //如果任意一個難度評估出錯,提示並返回
      if(   !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, groundBoxOverlap, precision[0], aos[0], EASY, GROUND)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, groundBoxOverlap, precision[1], aos[1], MODERATE, GROUND)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, groundBoxOverlap, precision[2], aos[2], HARD, GROUND)) {
        mail->msg("%s evaluation failed.", CLASS_NAMES[c].c_str());
        return false;
      }
      fclose(fp_det);
      saveAndPlotPlots(plot_dir, CLASS_NAMES[c] + "_detection_ground", CLASS_NAMES[c], precision, 0); 			 //畫出評估影象(具體參見saveAndPlotPlots說明)
    }
  }
  printf("Finished Birdeye eval.\n");			//結束鳥瞰評估

  //對於所有類別評估3D bounding boxes
  for (int c = 0; c < NUM_CLASS; c++) { 
    CLASSES cls = (CLASSES)c;
    if (eval_3d[c]) {			 //如果評估3D結果
      fp_det = fopen((result_dir + "/stats_" + CLASS_NAMES[c] + "_detection_3d.txt").c_str(), "w"); 			//指標指向儲存3D評估結果的檔案
      vector<double> precision[3], aos[3];
      //如果任意一個難度評估出錯,則提示並返回
      printf("Going to eval 3D box for class: %s\n", CLASS_NAMES[c].c_str());
      if(   !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, box3DOverlap, precision[0], aos[0], EASY, BOX3D)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, box3DOverlap, precision[1], aos[1], MODERATE, BOX3D)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, box3DOverlap, precision[2], aos[2], HARD, BOX3D)) {
        mail->msg("%s evaluation failed.", CLASS_NAMES[c].c_str());
        return false;
      }
      fclose(fp_det);
      saveAndPlotPlots(plot_dir, CLASS_NAMES[c] + "_detection_3d", CLASS_NAMES[c], precision, 0); 
      CLASS_NAMES[c], precision, 0);
    }
  }
  printf("Finished 3D bounding box eval.\n");

  // 成功完成評估,返回true
  return true;
}

tBox\tGroundtruth\tDetection

tBox、tGroundtruth和tDetection結構體定義(用於儲存真值和檢測結果,eval程式碼中用到):

// holding bounding boxes for ground truth and detections
struct tBox {
  string  type;     // 儲存目標型別
  double   x1;     
  double   y1;      //與x1共同定位左上角座標
  double   x2;      
  double   y2;      //與x2共同定位右下角座標
  double   alpha;   //影象轉角
  tBox (string type, double x1,double y1,double x2,double y2,double alpha) : //定義與結構體同名的建構函式,在呼叫時賦值
    type(type),x1(x1),y1(y1),x2(x2),y2(y2),alpha(alpha) {}    
};

//儲存真值
struct tGroundtruth {
  tBox    box;        //儲存目標型別、box、朝向
  double  truncation; // truncation 0..1 這個目前還不理解幹什麼用的
  int32_t occlusion;  // 是否遮擋,0代表無遮擋,1代表部分遮擋,2代表完全遮擋
  double ry;  //目前未知含義
  double  t1, t2, t3; //目前未知含義
  double h, w, l; //高、寬、長
  tGroundtruth () : //這是這個結構體內所包含的同名建構函式,在呼叫時賦值
    box(tBox("invalild",-1,-1,-1,-1,-10)),truncation(-1),occlusion(-1) {}  
  tGroundtruth (tBox box,double truncation,int32_t occlusion) :
    box(box),truncation(truncation),occlusion(occlusion) {}   
  tGroundtruth (string type,double x1,double y1,double x2,double y2,double alpha,double truncation,int32_t occlusion) :
    box(tBox(type,x1,y1,x2,y2,alpha)),truncation(truncation),occlusion(occlusion) {}   
};

//儲存檢測結果
struct tDetection {
  tBox    box;    //儲存目標型別、box、朝向
  double  thresh; //檢測概率(detection score)
  double  ry;  //目前未知含義
  double  t1, t2, t3;  //目前未知含義
  double  h, w, l;  //高、寬、長
  tDetection (): //定義與結構體同名的建構函式,在呼叫時賦值
    box(tBox("invalid",-1,-1,-1,-1,-10)),thresh(-1000) {}    
  tDetection (tBox box,double thresh) :
    box(box),thresh(thresh) {}
  tDetection (string type,double x1,double y1,double x2,double y2,double alpha,double thresh) :
    box(tBox(type,x1,y1,x2,y2,alpha)),thresh(thresh) {}  
};

總結:
基本儲存內容和順序為:型別、左上角x、左上角y、右下角x、右下角y、朝向(角度);如果是真值那麼後面會加上:截斷資訊(truncation)、遮擋情況;如果是檢測結果後面會加上:檢測概率(score)。


eval_class

eval_class程式碼部分:

bool eval_class (FILE *fp_det, FILE *fp_ori, CLASSES current_class,
    const vector< vector<tGroundtruth> > &groundtruth,
    const vector< vector<tDetection> > &detections, bool compute_aos,
    double (*boxoverlap)(tDetection, tGroundtruth, int32_t),
    vector<double> &precision, vector<double> &aos,
    DIFFICULTY difficulty, METRIC metric) {
assert(groundtruth.size() == detections.size());

  // 初始化
  int32_t n_gt=0;                                     // 真值影象總數(recall的分母)
  vector<double> v, thresholds;                       //用於儲存檢測得到的概率detection scores,對其評估的結果用於recall的離散化
  
  vector< vector<int32_t> > ignored_gt, ignored_det;  //用於儲存對於當前類別/難度忽略的影象標號
  vector< vector<tGroundtruth> > dontcare;            //用於儲存在真值中包含的不關心區域的編號

  //對於所有待測試影象進行:
  for (int32_t i=0; i<groundtruth.size(); i++){

    //用於儲存當前幀中的忽略的真值、檢測結果和不關心區域
    vector<int32_t> i_gt, i_det;
    vector<tGroundtruth> dc;

    //只評估當前類別下的目標(忽略遮擋、截斷的目標)
    cleanData(current_class, groundtruth[i], detections[i], i_gt, dc, i_det, n_gt, difficulty);
    ignored_gt.push_back(i_gt);
    ignored_det.push_back(i_det);
    dontcare.push_back(dc);

    //計算資料以得到recall的值 
    tPrData pr_tmp = tPrData();			//用於儲存相似度、true positives、false positives和false negatives
    pr_tmp = computeStatistics(current_class, groundtruth[i], detections[i], dc, i_gt, i_det, false, boxoverlap, metric);				//具體分析見ComputeStatistics說明,輸出為tPrData型別

    //將所有圖片的detection scores存入向量
    for(int32_t j=0; j<pr_tmp.v.size(); j++)
      v.push_back(pr_tmp.v[j]);
  }
  //獲取對於recall離散化必須要評估的scores(當不再滿足(r_recall-current_recall) < (current_recall-l_recall)時就退出,在退出前,每有一個滿足的current_recall就加1/40(數值認人為規定)),返回的是排名前40的滿足條件的scores。
  thresholds = getThresholds(v, n_gt);

  //計算相關scores的TP、FP和FN
  vector<tPrData> pr;
  pr.assign(thresholds.size(),tPrData());
  for (int32_t i=0; i<groundtruth.size(); i++){
    //對於所有的scores/recall thresholds做如下操作:
    for(int32_t t=0; t<thresholds.size(); t++){
      tPrData tmp = tPrData();
      tmp = computeStatistics(current_class, groundtruth[i], detections[i], dontcare[i],
                             ignored_gt[i], ignored_det[i], true, boxoverlap, metric,
                              compute_aos, thresholds[t], t==38);			//具體分析見ComputeStatistics說明,輸出為tPrData型別

      //將當前幀下TP、FP、FN和AOS的數值加到當前閾值下的總評估中
      pr[t].tp += tmp.tp;
      pr[t].fp += tmp.fp;
      pr[t].fn += tmp.fn;
      if(tmp.similarity!=-1)			//如果判斷AOS
        pr[t].similarity += tmp.similarity;
    }
  }

  //計算recall、precision和AOS
  vector<double> recall;
  precision.assign(N_SAMPLE_PTS, 0);
  if(compute_aos)
    aos.assign(N_SAMPLE_PTS, 0);
  double r=0;
  for (int32_t i=0; i<thresholds.size(); i++)
    r = pr[i].tp/(double)(pr[i].tp + pr[i].fn);			//計算recall
    recall.push_back(r);
    precision[i] = pr[i].tp/(double)(pr[i].tp + pr[i].fp);			//計算precision
    if(compute_aos)					//如果需要,計算AOS
      aos[i] = pr[i].similarity/(double)(pr[i].tp + pr[i].fp);
  }

  //用最大值濾取precision和AOS(對i)
  for (int32_t i=0; i<thresholds.size(); i++){
    precision[i] = *max_element(precision.begin()+i, precision.end());
    if(compute_aos)
      aos[i] = *max_element(aos.begin()+i, aos.end());
  }

  //儲存資料並返回計算成功
  saveStats(precision, aos, fp_det, fp_ori);			//在指標fp_det和fp_ori指向的檔案中寫入資料
    return true;
}

saveAndPlotPlots

saveAndPlotPlots說明。

void saveAndPlotPlots(string dir_name,string file_name,string obj_type,vector<double> vals[],bool is_aos){

  char command[1024];
  //儲存結果影象到指定路徑
  FILE *fp = fopen((dir_name + "/" + file_name + ".txt").c_str(),"w");			//儲存在plot資料夾下對應類別的檔案中
  printf("save %s\n", (dir_name + "/" + file_name + ".txt").c_str());
  
  //對於擷取數量的樣本,按正確率從高到低輸出結果(格式:佔40個樣本的前百分之多少、簡單類別、中等類別、困難類別分別對應的精度)
  for (int32_t i=0; i<(int)N_SAMPLE_PTS; i++)
    fprintf(fp,"%f %f %f %f\n",(double)i/(N_SAMPLE_PTS-1.0),vals[0][i],vals[1][i],vals[2][i]); 
  fclose(fp);

  //求解三種難度下的精度之和,計算AP並顯示
  float sum[3] = {0, 0, 0};
  for (int v = 0; v < 3; ++v)
      for (int i = 0; i < vals[v].size(); i = i + 4)
          sum[v] += vals[v][i];
  printf("%s AP: %f %f %f\n", file_name.c_str(), sum[0] / 11 * 100, sum[1] / 11 * 100, sum[2] / 11 * 100);


  //建立png + eps
  for (int32_t j=0; j<2; j++) {

    //開啟檔案
    FILE *fp = fopen((dir_name + "/" + file_name + ".gp").c_str(),"w");

    //儲存gnuplot指令
    if (j==0) {
      fprintf(fp,"set term png size 450,315 font \"Helvetica\" 11\n");
      fprintf(fp,"set output \"%s.png\"\n",file_name.c_str());
    } else {
      fprintf(fp,"set term postscript eps enhanced color font \"Helvetica\" 20\n");
      fprintf(fp,"set output \"%s.eps\"\n",file_name.c_str());
    }

    //設定labels和範圍
    fprintf(fp,"set size ratio 0.7\n");
    fprintf(fp,"set xrange [0:1]\n");
    fprintf(fp,"set yrange [0:1]\n");
    fprintf(fp,"set xlabel \"Recall\"\n");
    if (!is_aos) fprintf(fp,"set ylabel \"Precision\"\n");
    else         fprintf(fp,"set ylabel \"Orientation Similarity\"\n");
    obj_type[0] = toupper(obj_type[0]);
    fprintf(fp,"set title \"%s\"\n",obj_type.c_str());

    //線寬
    int32_t   lw = 5;
    if (j==0) lw = 3;

    //畫error曲線
    fprintf(fp,"plot ");
    fprintf(fp,"\"%s.txt\" using 1:2 title 'Easy' with lines ls 1 lw %d,",file_name.c_str(),lw); 
    fprintf(fp,"\"%s.txt\" using 1:3 title 'Moderate' with lines ls 2 lw %d,",file_name.c_str(),lw); 
    fprintf(fp,"\"%s.txt\" using 1:4 title 'Hard' with lines ls 3 lw %d",file_name.c_str(),lw);

    //關閉檔案
    fclose(fp);

    //執行gnuplot以生成png + eps
    sprintf(command,"cd %s; gnuplot %s",dir_name.c_str(),(file_name + ".gp").c_str());
    system(command);
  }

  //生成pdf並擷取
  sprintf(command,"cd %s; ps2pdf %s.eps %s_large.pdf",dir_name.c_str(),file_name.c_str(),file_name.c_str());
  system(command);
  sprintf(command,"cd %s; pdfcrop %s_large.pdf %s.pdf",dir_name.c_str(),file_name.c_str(),file_name.c_str());
  system(command);
  sprintf(command,"cd %s; rm %s_large.pdf",dir_name.c_str(),file_name.c_str()); 
  system(command);
}

computeStatistics

用於計算必要的資料,為recall的計算做準備:

tPrData computeStatistics(CLASSES current_class, const vector<tGroundtruth> &gt,
    const vector<tDetection> &det, const vector<tGroundtruth> &dc,
    const vector<int32_t> &ignored_gt, const vector<int32_t>  &ignored_det,
    bool compute_fp, double (*boxoverlap)(tDetection, tGroundtruth, int32_t),
    METRIC metric, bool compute_aos=false, double thresh=0, bool debug=false){

  tPrData stat = tPrData();
  const double NO_DETECTION = -10000000;
  vector<double> delta;            //用於儲存TP需要的角度的不同(AOS計算需要)
  vector<bool> assigned_detection; //用於儲存一個檢測結果是被標註有效還是忽略
  assigned_detection.assign(det.size(), false);
  vector<bool> ignored_threshold;
  ignored_threshold.assign(det.size(), false); //如果計算FP,用於儲存低於閾值的檢測結果

  //在計算precision時,忽略低score的檢測結果(需要FP)
  if(compute_fp)
    for(int32_t i=0; i<det.size(); i++)
      if(det[i].thresh<thresh)
        ignored_threshold[i] = true;

  //評估所有真值boxes
  for(int32_t i=0; i<gt.size(); i++){

    //如果這個真值不屬於當前或相似類別,則忽略
    if(ignored_gt[i]==-1)
      continue;
   	   /*=======================================================================
    find candidates (overlap with ground truth > 0.5) (logical len(det)) 
    =======================================================================*/
    int32_t det_idx          = -1;
    double valid_detection = NO_DETECTION;
    double max_overlap     = 0;

    //尋找可能的檢測結果
    bool assigned_ignored_det = false; 
    for(int32_t j=0; j<det.size(); j++){

      //如果這個檢測結果不屬於當前類別或已經存在或低於閾值,都被忽略 
      if(ignored_det[j]==-1) 
        continue;
      if(assigned_detection[j])
        continue;
      if(ignored_threshold[j])
        continue;

      //找到候選目標對應的score最大值並獲取響應的檢測結果編號
      double overlap = boxoverlap(det[j], gt[i], -1); 

      //為計算recall閾值,需要考慮擁有最高score的候選
      if(!compute_fp && overlap>MIN_OVERLAP[metric][current_class] && det[j].thresh>valid_detection){
        det_idx         = j;
        valid_detection = det[j].thresh;
      }

      //為計算precision曲線值,需要考慮擁有最大重疊率的候選  
      //如果該候選是一個被忽略的檢測(min_height),啟用重疊率檢測
      else if(compute_fp && overlap>MIN_OVERLAP[metric][current_class] && (overlap>max_overlap || assigned_ignored_det) && ignored_det[j]==0){ 
        max_overlap     = overlap;
        det_idx         = j;
        valid_detection = 1;
        assigned_ignored_det = false;;
      }
      else if(compute_fp && overlap>MIN_OVERLAP[metric][current_class] && valid_detection==NO_DETECTION && ignored_det[j]==1){
        det_idx              = j; 
        valid_detection      = 1;
        assigned_ignored_det = true;
      }
    }
    /*=======================================================================
    compute TP, FP and FN  compute TP, FP and FN
    =======================================================================

    //如果沒有給當前有效的真值分配任何東西
    if(valid_detection==NO_DETECTION && ignored_gt[i]==0) {  
      stat.fn++;
    }

    //只評估有效真值等同於 detection assignments (considering difficulty level)
    else if(valid_detection!=NO_DETECTION && (ignored_gt[i]==1 || ignored_det[det_idx]==1))
      assigned_detection[det_idx] = true;

    //找到一個有效的true positive
    else if(valid_detection!=NO_DETECTION){

      //向閾值向量寫入最高的
      stat.tp++;
      stat.v.push_back(det[det_idx].thresh);

      //真值和檢測結果之間的計算角度差異(如果提供了有效的角度檢測)
      if(compute_aos) 
        delta.push_back(gt[i].box.alpha - det[det_idx].box.alpha);  

      //清空
      assigned_detection[det_idx] = true;
    }
  }

  //如果需要計算FP are requested,則考慮stuff area 
  if(compute_fp){

    //計數fp
    for(int32_t i=0; i<det.size(); i++){

      //如果需要,對所有的false positives計數(高度小於規定的被忽略(ignored_det==1))
      if(!(assigned_detection[i] || ignored_det[i]==-1 || ignored_det[i]==1 || ignored_threshold[i]))
        stat.fp++;
    }

    //不考慮與 stuff area重疊的檢測結果
    int32_t nstuff = 0; 
    for(int32_t i=0; i<dc.size(); i++){
      for(int32_t j=0; j<det.size(); j++){

        //忽略不屬於當前類別的檢測結果、已經處理過的檢測結果、閾值或最小高度很低的檢測結果
        if(assigned_detection[j])
          continue;
        if(ignored_det[j]==-1 || ignored_det[j]==1)
          continue;
        if(ignored_threshold[j]) 
          continue;

        //計算重疊率,如果重疊率超過給定數值就分配給stuff area 
        double overlap = boxoverlap(det[j], dc[i], 0);
        if(overlap>MIN_OVERLAP[metric][current_class]){
          assigned_detection[j] = true;
          nstuff++;
        }
      }
    }

    // FP = 所有未分配真值的點的個數(no. of all not to ground truth assigned detections) - 分配到stuff area的點的個數(detections assigned to stuff areas)
    stat.fp -= nstuff;

    //如果所有角度值有效則計算AOS
    if(compute_aos){
      vector<double> tmp;

      // FP have a similarity of 0, for all TP compute AOS
      tmp.assign(stat.fp, 0);
      for(int32_t i=0; i<delta.size(); i++)
        tmp.push_back((1.0+cos(delta[i]))/2.0);

      // be sure, that all orientation deltas are computed
      assert(tmp.size()==stat.fp+stat.tp);
      assert(delta.size()==stat.tp);

      // get the mean orientation similarity for this image
      if(stat.tp>0 || stat.fp>0)
        stat.similarity = accumulate(tmp.begin(), tmp.end(), 0.0);

      // there was neither a FP nor a TP, so the similarity is ignored in the evaluation
      else
        stat.similarity = -1;
    }
  }
  return stat;
}