跟踪算法性能测试_VOT数据集为例

时间:2022-06-19
本文章向大家介绍跟踪算法性能测试_VOT数据集为例,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

快要开始写毕业论文了,算法性能测试不可避免,今天要写的这些东西大部分是在年前弄完的,趁热记录一下。

网上是有各种测试VOT的代码的,我找到的大部分是matlab的,比如这个:VOC_TOOL_KIT,不过我一直在做的这个算法是用CPP写的,所以还是想写一个CPP的性能测试框架,结合cpp11的一些调试器,这个东西其实是不难的,下面分享一下。

1. 根据list自动读取视频

VOT

每个文件夹里包含图片序列,list里面写的是每个文件夹的名称,是为了读取文件夹下的图片和groundtruth信息用的。 把每一行的信息存储为一个字符串,这样会得到一个字符串列表,我们用vector<string>来存储:

// 读取list列表的信息,输入参数为`list`的路径。
vector<string> read_list(const string &list_name)
{
    vector<string> list_mes;
    ifstream list(list_name);      //ifstream对象。
    string line;
    while(getline(list,line))   //读取list列表信息
    {
        //cout<<line<<endl;
        list_mes.push_back(line);
    }
    list.close();
    return list_mes;
}

2. 读取groundtruth和图片序列

首先来讲groundtruth的读取,groundtruth是标注的跟踪框信息,每一行有8个数字(竟然还是小数?),分别是:

分别是矩形的四个点的坐标,但是值得注意的是,这四个点并没有对应的位置关系(这个问题应该是标注的时候的问题),所以我们就只能根据坐标之间相互的大小关系来得到矩形框的信息,我们希望最后groundtruth提取到vector<cv::Rect>里面。

过程和上面的类似,先拿到每一行,然后把每一行分割出来(这种题目在LeetCode里刷的太多了)转换为数字。而后根据他们之间的大小关系来构建cv::Rect对象,这个过程中把小数转换为整数,我用的是四舍五入,这个不是最重要的。

cv::Rect split_line(string &line)
vector<cv::Rect> read_groundtruth(const string &groundtruth_txt,int &num_of_line)    //const常量才可以由字符串隐式转换
{
    vector<cv::Rect> groundtruth;            // vector<rect>  用来存groundtruth
    ifstream groundtruth_file;               // 文件对象
    groundtruth_file.open(groundtruth_txt);      //打开txt文件
    string line;         //当前行
    Rect rect_tmp;       //每一行搞成一个rect
    while(getline(groundtruth_file,line))
    {
        rect_tmp=split_line(line);    //分解字符串为RECT
        groundtruth.push_back(rect_tmp);    //压入vector
        num_of_line++;     
    }
    groundtruth_file.close();
    return groundtruth;
}



cv::Rect split_line(string &line)
{
    double pos[8];            //八个点
    int index=0;              //点的索引
    string tmp;               //暂存的string,来转换为double
    tmp.clear();              //清零
    for(auto l:line)          //遍历字符串,这里面是一个比较简单的字符串根据特定字符分离的一个算法
    {
        if(l==',')
        {
            pos[index]=stod(tmp);
            index++;
            tmp.clear();     //一定要记得清零
        }
        else
        {
            tmp+=l;
        }
    }
    pos[index]=stod(tmp);    //处理最后一个
    //四个点,对应矩形的四个点

    /* 我后来发现标注的点并不是遵循这样的规律,不一定一开始是左上角的点,这取决于当时标注的
    人先从哪个点开始点的,所以应该来使用坐标之间的大小关系来确定到底是哪个点
    cv::Point2f up_left(pos[0],pos[1]);
    cv::Point2f up_right(pos[2],pos[3]);
    cv::Point2f down_right(pos[4],pos[5]);
    cv::Point2f down_left(pos[6],pos[7]);
    //cout<<up_left<<" "<<up_right<<" "<<down_right<<" "<<down_left<<endl;
    int x=round(up_left.x);
    int y=round(up_left.y);
    int weidth=round(down_right.x-up_left.x);
    int height=round(down_right.y-up_left.y);
    cv::Rect res(x,y,weidth,height);
    */

   double xmin=min(pos[0],min(min(pos[2],pos[4]),pos[6]));
   double ymin=min(pos[1],min(min(pos[3],pos[5]),pos[7]));
   double xmax=max(pos[0],max(max(pos[2],pos[4]),pos[6]));
   double ymax=max(pos[1],max(max(pos[3],pos[5]),pos[7]));

   cv::Rect res(xmin,ymin,xmax-xmin,ymax-ymin);
   //cout<<res<<endl;
    
    return res;
}

3. 初始化跟踪器并运行

我这里用的KCF,大概的框架如下:

void kcf_test()
{
   //读取list信息
    vector<string> list=read_list("vot2015//list.txt");
   //主循环,每一个循环代表一个图片序列
    for(int i=0;i<list.size();i++)
    {

       //打印进度信息
        cout<<"this is the VOT tracking test!!"<<endl;
        cout<<"and this is "<<list[i]<<endl;
        //保存跟踪结果,我这里用了两个跟踪器
        ofstream res_ground("results//" + list[i] + "_res_ground.txt");
        ofstream res_kcf("results//" + list[i] + "_res_kcf.txt");
        ofstream res_kcf_inter("results//" + list[i] + "_res_kcf_interpolation.txt");
        ofstream ave_fps_kcf("results//" + list[i] + "_ave_fps_kcf.txt");
        ofstream ave_fps_kcf_inter("results//" + list[i] + "_ave_fps_kcf_inter.txt");


        //表头
        res_ground<< "frametxtytwidththeightn";
        res_kcf << "frametxtytwidththeightn";
        res_kcf_inter << "frametxtytwidththeightn";
        ave_fps_kcf<< "frametave_fpsn";
        ave_fps_kcf_inter << "frametave_fpsn";

        
    

        string path="vot2015//"+list[i]+"//";      //当前图片路径
        cout<<path<<endl;

        int num_of_line=0;   //图片数量
       
        
        //读取groundtruth信息
        vector<cv::Rect> groundtruth=read_groundtruth(path+"groundtruth.txt",num_of_line);
    
        int index=1;
        for(auto gg:groundtruth)
        {
            
            res_ground<<index++<<"t"<<gg.x<<"t"<<gg.y<<"t"<<gg.width<<"t"<<gg.height<<"n";
        }
        res_ground.close();      //关闭txt文件

        //第一帧的跟踪结果就采用groundtruth里读取的值
        res_kcf << 1 << "t" << groundtruth[0].x << "t" << groundtruth[0].y << "t" << groundtruth[0].width << "t" << groundtruth[0].height << "n";
        res_kcf_inter << 1 << "t" << groundtruth[0].x << "t" << groundtruth[0].y << "t" << groundtruth[0].width << "t" << groundtruth[0].height << "n";
        
        //跟踪结果保存的vector
        vector<cv::Rect> track_res;
        track_res.push_back(groundtruth[0]);
    
        //读取第一张图片并初始化跟踪器
        string zeros8="00000000";
        cv::Mat img=imread(path+"00000001.jpg");
        imshow("img",img);
        double all_time=0;
        KCFTracker tracker(true,true,true,true);    //构造
        KCFTracker tracker_NI(true,true,true,false);    //构造
        tracker.init(groundtruth[0],img);      //初始化
        tracker_NI.init(groundtruth[0],img);
        cv::rectangle(img,groundtruth[0],cv::Scalar(0,0,255));   //第一帧画框
        
        for(int j=2;j<num_of_line;j++)
        {
            string img_name=zeros8+std::to_string(j);
            string img_path=path+string(img_name.end()-8,img_name.end())+".jpg";
            
            cv::Mat frame=imread(img_path);    
            double start=static_cast<double>(getTickCount());
            cv::Rect Rect_kcf_i=tracker.update(frame);
            cv::Rect Rect_kcf=tracker_NI.update(frame);
            double time=((double)getTickCount()-start)/getTickFrequency();

            //主要的参数
            res_kcf << j << "t" << Rect_kcf.x << "t" << Rect_kcf.y << "t" << Rect_kcf.width << "t" << Rect_kcf.height << "n";
            res_kcf_inter << j << "t" << Rect_kcf_i.x << "t" << Rect_kcf_i.y << "t" << Rect_kcf_i.width << "t" << Rect_kcf_i.height << "n";


            all_time+=time;
            //cout<<"fpst"<<1./time<<endl;
            //cout<<"ave_fps:t"<<double(i-1)/all_time<<endl;
            //蓝色groundtruth,红色KCF,绿色KCF_i
            rectangle(frame, groundtruth[j - 1], Scalar(255, 0, 0));
            rectangle(frame, Rect_kcf, Scalar(0, 0, 255));
            rectangle(frame, Rect_kcf_i, Scalar(0, 255, 0));

            imshow("test",frame);
            waitKey(10);
        }
    }
    
}

我会把跟踪器的跟踪结果(cv::Rect)保存到txt里面,然后使用matlab或者python写脚本来解析这些txt文件来画图或者干你想干的任何事情。

4.解析TXT文件并画图(以PrecisionPlot为例)

为了练习使用python,后面的画图之类的脚本都是用python写的,可能用的不熟,代码难免有冗余。

我主要画两个图,第一个是CLE(center location erroe),就是中心位置误差,就是跟踪框的中心和标注的跟踪框的位置之间的欧氏距离,横轴用帧数,纵轴用CLE。

第二个也是跟踪里面常用的,PrecisionPlot,横轴是阈值,从0-100,纵轴是一个百分比,这个百分比的含义为:CLE小于等于当前阈值的帧数在所有帧数中所占的比例。

代码在下面,主要的功夫还是花在了解析字符串和批量处理上面,注释写的比较清楚。

# -*- coding: utf-8 -*-
"""
Created on Mon Jan 28 20:16:05 2019

@author: zhxing
this code can draw position precision of tracking result in vot challenge
"""

import math
import matplotlib.pyplot as plt
import numpy

#存放文件的路径以及各种文件的路径
path="results//"
ave_fps_kcf="_ave_fps_kcf.txt"
ave_fps_kcf_inter="_ave_fps_kcf_inter.txt"
res_ground="_res_ground.txt"
res_kcf="_res_kcf.txt"
res_kcf_interpolation="_res_kcf_interpolation.txt"

file=open(path+"list.txt")
lines=file.readlines()

#calculate the Pre,CLE is CENTOR LOCATION ERROR,and it is a list
def calculatePre(CLE):
    res=[]
    for thresh in range(1,100):
        tmp=numpy.array(CLE)  #get the temporary variable
        tmp[tmp<=thresh]=1
        tmp[tmp>thresh]=0
        num=sum(tmp)
        rate=float(num)/float(tmp.size)
        res.append(rate)
    return res


#定义画中心位置误差图像的函数
def drawCLE(title,ResGroundLines,ResKcfLines,ResKcfILines):
    CleKcf=[]
    CleKcfI=[]
    num_of_frame=len(ResGroundLines)-2        #帧数,去掉表头和最后一帧(主要是我结果好像少写了一帧)
    for index in range(1,(num_of_frame+1)):
        #每一行拿出来,第一列是分别是 frame   x   y   width   height,分离出来并转换成数字
        GroundPos=(ResGroundLines[index]).split('t')
        KcfPos=(ResKcfLines[index]).split('t')
        KcfIPos=(ResKcfILines[index]).split('t')
        GroundPos=list(map(int,GroundPos))
        KcfPos=list(map(int,KcfPos))
        KcfIPos=list(map(int,KcfIPos))
        
        #提取中心位置
        P_G=[GroundPos[1]+GroundPos[3]/2,GroundPos[2]+GroundPos[3]/2]
        P_K=[KcfPos[1]+KcfPos[3]/2,KcfPos[2]+KcfPos[4]/2]
        P_KI=[KcfIPos[1]+KcfIPos[3]/2,KcfIPos[2]+KcfIPos[4]/2]
        
        CLE_KCF=math.sqrt((P_K[0]-P_G[0])**2+(P_K[1]-P_G[1])**2)
        CLE_KCF_I=math.sqrt((P_KI[0]-P_G[0])**2+(P_KI[1]-P_G[1])**2)
        
        CleKcf.append(CLE_KCF)
        CleKcfI.append(CLE_KCF_I)
        
    plt.figure()       #CLE  CENTOR LOCATION ERROR
    plt.title(title+"CLE Plot")
    plt.plot(CleKcf,color='red',label="KCF")
    plt.plot(CleKcfI,color='blue',label="KCF_I")
    plt.legend()
    plt.savefig("results//png//"+title+".png",dpi=600)
    
    PreKcf=calculatePre(CleKcf)
    PreKcfI=calculatePre(CleKcfI)

    plt.figure()       #PRECISION PERCENT
    plt.title(title+"Precision Plot")
    plt.plot(PreKcf,color='red',label='KCF')
    plt.plot(PreKcfI,color='blue',label="KCF_I")
    plt.legend()
    plt.savefig("results//png//"+title+"_Pre.png",dpi=600)
    
#主函数
for target in lines:
    print("this is the:t"+target)
    #target有个回车,这里需要把这个回车给去掉,然后下面把当前target下的文件读取
    AveFpsKcf=open(path+target[:-1]+ave_fps_kcf)
    AveFpsKcfI=open(path+target[:-1]+ave_fps_kcf_inter)
    ResGround=open(path+target[:-1]+res_ground)
    ResKcf=open(path+target[:-1]+res_kcf)
    ResKcfI=open(path+target[:-1]+res_kcf_interpolation)
    AveFpsKcfLines=AveFpsKcf.readlines()
    AveFpsKcfILines=AveFpsKcfI.readlines()
    ResGroundLines=ResGround.readlines()
    ResKcfLines=ResKcf.readlines()
    ResKcfILines=ResKcfI.readlines()
    drawCLE(target,ResGroundLines,ResKcfLines,ResKcfILines)

大概就是这样了。这个代码我不是单独写的,而是写在了darknet里面了,具体在src/image_opencv.cpp里面,可以参考。 github地址:https://github.com/zhxing001/DarkNet