一、前言
最近需要將自己的跟蹤代碼在自己拍攝的數據集上進行測試,這里我選擇使用 UAV123 官方 toolkits 進行配置。首先需要搞清楚這部分代碼是如何運行的,精度圖和成功率圖是如何繪制出來的,然后再將自己的數據集加進去進行測試。
二、UAV123官方toolkits工作流程
本文以 UAV123 數據集為例查看 toolkits 的工作流程,共使用了 10 個跟蹤器。
在繪制結果圖時,首先需要將每個跟蹤器在每個序列上的跟蹤結果保存為一個 .mat 文件,然后運行根目錄下的 perfPlot.m 文件進行結果圖的繪制。直接來看 perfPlot.m 文件。
1. 準備工作
準備工作包括加載數據集中各序列的屬性、繪圖風格、圖像序列、跟蹤器等。
attPath = '...\UAV123\tracker_benchmark_v1.1\anno_uav123\att\'; % UAV123
首先需要設置一個 attpath 加載 att 文件夾。我們可以看到,att 文件夾下也放置著與數據集中圖像序列數目相等的 txt 文件,且每個文件的名稱即為圖像序列的名稱。與標注目標位置的 txt 文件不同,這里的 txt 文件中只含有 12 個非 0 即 1 的數字,分別對應著 UAV123 數據集的 12 種屬性。若該序列包含某種屬性,則該屬性位置對應的值即為 1,否則為 0。12 種屬性分別為:
%UAV123
attName={'Scale Variation' 'Aspect Ratio Change' 'Low Resolution' 'Fast Motion' 'Full Occlusion' 'Partial Occlusion' 'Out-of-View' 'Background Clutter' 'Illumination Variation' 'Viewpoint Change' 'Camera Motion' 'Similar Object'};attFigName={'SV' 'ARC' 'LR' 'FM' 'FOC' 'POC' 'OV' 'BC' 'IV' 'VC' 'CM' 'SOB'};
這里我根據 UAV123.json 文件中的信息對每種屬性包含的圖像序列進行了整理:
尺度變化(Scale Variation) | group1_2、car10、person8_1、person12_2、car3_s、car1_1、group3_1、car11、group1_3、group1_1、person11、car13、person8_2、group3_3、car1_3、car1_2、car1_s、group3_2、wakeboard10、car12、person10、group1_4、person15、car17、car15、person14_1、uav8、group3_4、person16、car14、person3、car3、car6_5、wakeboard1、group2_3、group2_2、car6_4、car2、car2_s、wakeboard2、group2_1、wakeboard3、car5、wakeboard7、car6_3、car6_2、wakeboard6、person17_2、car4、truck4_2、building2、wakeboard4、boat9、car4_s、boat8、car6、wakeboard5、person17_1、person17_1、truck4_1、car7、building3、person19_3、person1_s、wakeboard8、boat5、truck1、car8_2、boat4、wakeboard9、person19_2、car9、boat6、truck2、car8_1、boat7、person3_s、person19_1、bird1_3、boat3、person7_2、person5_1、bike1、boat2、bird1_2、person7_1、bike3、bike2、boat1、bird1_1、uav5、uav4、car18、person18、uav6、uav7、person21、uav3、car16_2、uav1_3、uav1_2、uav2、person4_2、person20、person9、person22、car16_1、uav1_1、person23、person4_1 |
縱橫比變化(Aspect Ratio Change) | person8_1、person12_2、group3_1、group1_3、person11、person8_2、group3_3、car1_3、car1_2、car1_s、group3_2、car12、group1_4、car17、car15、person14_1、group3_4、person16、car14、car6_5、wakeboard1、group2_3、car2、wakeboard2、group2_1、wakeboard3、wakeboard7、car6_3、car6_2、wakeboard6、person17_2、car4、wakeboard4、boat9、car4_s、boat8、wakeboard5、person17_1、car7、person19_3、wakeboard8、boat5、truck1、boat4、person19_2、car9、truck2、car8_1、boat7、person3_s、person19_1、bird1_3、boat3、person7_2、bike1、bird1_2、person7_1、bike2、bird1_1、car18、person18、person21、car16_2、uav1_3、uav1_2、person20、person9、uav1_1 |
低分辨率(Low Resolution) | group3_1、car11、group1_3、car13、car1_2、group3_2、wakeboard10、car12、car17、car15、person14_1、uav8、group3_4、car14、car3、group2_3、car2、wakeboard3、wakeboard6、car4、truck4_2、boat9、boat8、wakeboard5、truck4_1、person19_3、wakeboard8、wakeboard9、car9、truck3、truck2、bird1_3、bird1_2、bike3、bike2、bird1_1、uav5、uav4、uav6、uav7、person21、uav3、uav1_3、uav1_2、uav2、person22、car16_1、uav1_1 |
快速運動(Fast Motion) | person8_1、car3_s、car1_s、car2_s、wakeboard2、wakeboard6、car4_s、wakeboard5、person19_3、person1_s、person19_2、car9、boat7、person3_s、person19_1、bird1_3、person7_2、bike1、bird1_2、person7_1、bird1_1、car18、uav6、uav3、car16_2、uav1_3、car16_1、person23 |
全部遮擋(Full Occlusion) | person8_1、person12_2、car11、car1_3、car1_2、group3_2、car12、person10、person14_1、group3_4、person16、car14、group2_3、group2_2、car2、group2_1、person17_1、car7、person19_3、person19_2、person19_1、bird1_3、bird1_2、person7_1、bike2、bird1_1、uav6、uav7、uav1_3、uav1_2、uav2、person9、uav1_1 |
局部遮擋(Partial Occlusion) | group1_2、car10、person8_1、person12_2、car3_s、group3_1、car11、person13、group1_3、group1_1、person11、person8_2、group3_3、car1_3、car1_2、car1_s、group3_2、car12、person10、group1_4、person14_3、person15、car15、person14_1、group3_4、person16、car14、car3、car6_5、group2_3、group2_2、car2、car2_s、group2_1、car6_2、person17_2、car4、truck4_2、boat9、wakeboard5、person17_1、truck4_1、car7、person19_3、person1_s、truck1、person19_2、car9、truck3、truck2、car8_1、person3_s、person19_1、bird1_3、person7_2、bird1_2、person7_1、bike3、bike2、bird1_1、person2_2、person18、uav6、uav7、person21、uav1_3、uav1_2、uav2、person20、person9、car16_1、uav1_1、person4_1 |
移出視野(Out-of-View) | person8_1、person8_2、car1_3、car1_s、person10、person14_3、car14、car3、car6_5、car2、car6_2、person19_3、person19_2、car8_1、person3_s、person19_1、bird1_3、person7_2、bird1_2、person7_1、bird1_1、person2_2、person18、uav1_3、uav1_2、person20、person9、car16_1、uav1_1、person4_1 |
背景雜波(Background Clutter) | person13、group3_3、person12_1、group3_2、person14_2、person14_3、person14_1、uav8、group3_4、person16、group2_3、group2_1、person17_2、truck3、bike2、uav5、uav7、person21、uav1_3、uav1_2、uav1_1 |
光照變化(Illumination Variation) | person12_2、car3_s、person13、person12_1、car1_s、group3_2、group1_4、uav8、group3_4、person16、wakeboard1、group2_3、car6_4、car2_s、car6_3、wakeboard6、person17_2、car4_s、wakeboard5、person17_1、person1_s、wakeboard8、car8_2、boat4、person3_s、bike1、bike2、person21、uav1_3、uav1_2、uav1_1 |
視角變化(Viewpoint Change) | car3_s、car11、person13、group1_3、person6、person11、person12_1、car1_3、car1_2、car1_s、car12、person10、group1_4、person14_3、car17、car14、car6_5、group2_3、group2_2、car6_4、car2_s、wakeboard2、group2_1、wakeboard3、wakeboard7、car6_3、car6_2、wakeboard6、truck4_2、wakeboard4、boat9、car4_s、boat8、wakeboard5、car7、person19_3、person1_s、wakeboard8、boat5、truck1、boat4、car8_1、boat7、boat3、person7_2、person5_1、bike1、person7_1、person5_2、car18、person18、person21、car16_2、uav1_3、uav1_2、person20、person9、person22、car16_1、uav1_1 |
相機運動(Camera Motion) | person8_1、person12_2、car3_s、car1_1、car11、group1_3、person6、car13、group3_3、person12_1、car1_2、car1_s、group3_2、person10、group1_4、person1、person14_2、person14_3、car17、person14_1、group3_4、person16、car14、person3、car3、car6_5、group2_3、group2_2、car6_4、car2、car2_s、wakeboard2、group2_1、wakeboard3、building5、car6_2、wakeboard6、person17_2、car4、car4_s、wakeboard5、person19_3、person1_s、wakeboard8、wakeboard9、person19_2、car9、car8_1、boat7、person3_s、person19_1、bird1_3、person7_2、bike1、person7_1、person2_2、car18、person18、uav6、person2_1、uav3、car16_2、uav1_3、person4_2、person20、person9、car16_1、uav1_1、person23、person4_1 |
相似目標(Similar Object) | group1_2、car10、car1_1、group3_1、car11、group1_3、group1_1、person11、group3_3、car1_3、car1_2、group3_2、car12、person10、group1_4、person15、car15、group3_4、car3、group2_3、group2_2、car6_4、car2、group2_1、car6_3、car4、car7、building3、person19_3、car8_2、car9、car8_1、bike1、uav4、uav6、person21、person20、person9、person4_1 |
然后是設定繪圖風格、加載圖像序列和跟蹤器等等,其中,加載的圖像序列包括 …/util/configSeqs.m 中設置的所有屬性:
跟蹤則包含我們在 …/util/configTrackers.m 中設置的兩種屬性:
然后,這里設置了一個元胞數組 nameTrkAll,用來存儲所有跟蹤器在圖片中顯示的名稱:
nameTrkAll=cell(numTrk,1);
for idxTrk=1:numTrkt = trackers{idxTrk};nameTrkAll{idxTrk}=t.namePaper;
end
接下來,定義了一個元胞數組 nameSeqAll,用來存儲所有圖像序列的名稱,以及一個 double 數組,用來存儲每個圖像序列的長度,以及一個空數組 att,用來存儲每個序列的屬性。attNum 則用來記錄該數據集共有集中屬性。這里是 12 種。
nameSeqAll=cell(numSeq,1);
numAllSeq=zeros(numSeq,1);att=[];
for idxSeq=1:numSeqs = seqs{idxSeq};nameSeqAll{idxSeq}=s.name;s.len = s.endFrame - s.startFrame + 1;numAllSeq(idxSeq) = s.len;att(idxSeq,:)=load([attPath s.name '.txt']);
endattNum = size(att,2);
然后設置 figPath 和 perfMatPath,分別為存儲結果圖的路徑和存儲 距離精度及重疊精度 mat 文件的路徑。
接下來,設置度量類型、評估類型、排名類型,這里分別設置為距離誤差、重疊率,一次性評估,精度:
metricTypeSet = {'error', 'overlap'};
evalTypeSet = {'OPE'}; %'SRE', 'OPE'rankingType = 'threshold'; %AUC, threshold
然后設置跟蹤器的數量同時選定繪圖風格。
接下來設置記錄重疊率的閾值集 thresholdSetOverlap,以及記錄誤差的閾值集 thresholdSetError。
thresholdSetOverlap = 0:0.05:1;
thresholdSetError = 0:50;
2. 繪制精度曲線
在接下來的循環中,首先計算的是跟蹤精度,以精度圖的形式呈現。
在繪制精度圖的過程中,首先將閾值集設置為誤差閾值集,然后設置排名 rankIdx ,以及 x、y 坐標軸名稱
case 'error'thresholdSet = thresholdSetError;rankIdx = 21;xLabelName = 'Location error threshold';yLabelName = 'Precision';
接下來,利用一次性評估標準計算跟蹤誤差,首先確定保存誤差結果的 mat 文件的名稱及存放地址:
evalType = evalTypeSet{j};%SRE, TRE, OPE
plotType = [metricType '_' evalType];
switch metricTypecase 'overlap'titleName = ['Success plots on UAV123 '];case 'error'titleName = ['Precision plots on UAV123 '];
end
dataName = [perfMatPath 'aveSuccessRatePlot_' num2str(numTrk) 'alg_' plotType '.mat'];
若不存在則創建:
if ~exist(dataName)genPerfMat(seqs, trackers, evalType, nameTrkAll, perfMatPath);
end
我們已經知道后面的跟蹤精度圖都是根據這個 mat 文件中的數據繪制的,那我們先來看一下這個文件是如何生成的:
pathAnno = '.../UAV123/tracker_benchmark_v1.1/anno_uav123/'; % UAV123
numTrk = length(trackers);
thresholdSetOverlap = 0:0.05:1;
thresholdSetError = 0:50;
首先需要明確每個序列中記錄目標真值框文件的位置,然后是跟蹤器的數目,以及重疊率閾值集和誤差閾值集。
然后根據評估標準明確選取哪個位置的跟蹤結果:
switch evalTypecase 'SRE'rpAll=['.\results\results_SRE\'];case {'TRE'}rpAll=['.\results\results_TRE\'];case {'OPE'}rpAll=['.\results\results_OPE\'];
end
然后則按照 configSeqs.m 中的序列順序依次進行計算。首先初始化一些基本信息,包括序列的幀數、名稱等等:
s = seqs{idxSeq};
s.len = s.endFrame - s.startFrame + 1;
s.s_frames = cell(s.len,1);
nz = strcat('%0',num2str(s.nz),'d'); %number of zeros in the name of image
其中 s 包含下列信息:
記錄序列中每張圖片的位置:
for i=1:s.lenimage_no = s.startFrame + (i-1);id = sprintf(nz,image_no);s.s_frames{i} = strcat(s.path,id,'.',s.ext);
end
讀取真值框,設置采樣片段數,然后
rect_anno = dlmread([pathAnno s.name '.txt']);
numSeg = 20;
[subSeqs, subAnno]=splitSeqTRE(s,numSeg,rect_anno);
來看一下這個 splitSeqTRE 函數。
splitSeqTRE 函數將每個序列20個片段,首先,排除所有occ/不可見幀,然后進行采樣:
Idx = 1:seq.len;
for j = 1:size(IdxExclude,1)Idx(IdxExclude(j,1):IdxExclude(j,2))=0;
end
Idx = Idx(find(Idx>0));for i=1:length(Idx)r = rect_anno(Idx(i),:);if r(1)<=0 | r(2)<=0 | r(3)<=0 | r(4)<=0 | isnan(sum(r))Idx(i) = 0;end
endIdx = Idx(find(Idx>0));
上面這段代碼即將所有不可見幀排除了。
設置數組 startFrIdxOne 記錄每組圖片的其實幀號:
for i = length(Idx):-1:1if seq.len - Idx(i) + 1 >= minNumendSeg = Idx(i);endSegIdx = i;break;end
endstartFrIdxOne = [floor(1:endSegIdx/(segNum-1):endSegIdx) endSegIdx] ;
然后將序列分為 20 組圖片,變量 subAnno 記錄每組圖片的的真值框,變量 subSeqs 記錄每組圖片的信息:
for i = 1:length(startFrIdxOne)index = Idx(startFrIdxOne(i));subS.path = seq.path;subS.nz = seq.nz;subS.ext = seq.ext;subS.startFrame = index+seq.startFrame-1;subS.endFrame = seq.endFrame;subS.len = subS.endFrame - subS.startFrame + 1;subS.annoBegin = seq.startFrame;subS.init_rect = rect_anno(index,:);anno = rect_anno(index:end,:);subS.s_frames = seq.s_frames(index:end);subS.name = seq.name;
% subS.nameIdx = [seq.name '_' num2str(i)];subAnno{i} = anno;subSeqs{i}=subS;
end
這里每組圖片是由先前計算的 startFrIdxOne 到最后一幀的信息。
回到 genPerfMat 函數,按照跟蹤器的順序依次計算誤差和重疊率:
t = trackers{idxTrk};load([rpAll s.name '_' t.name '.mat'])
disp([s.name ' ' t.name]);aveCoverageAll=[];
aveErrCenterAll=[];
errCvgAccAvgAll = 0;
errCntAccAvgAll = 0;
errCoverageAll = 0;
errCenterAll = 0;lenALL = 0;
switch evalTypecase 'SRE'idxNum = length(results);anno=subAnno{1};case 'TRE'idxNum = length(results);case 'OPE'idxNum = 1;anno=subAnno{1};
end
運行到這里發現雖然 splitSeqTRE 函數將圖像序列分為了 20 個片段,但使用 OPE 的評估方式只 用到了 subAnno{1},也就是剩余的都沒用到,而 subAnno{1} 就是所有圖片的真值框。
設置數組 successNumOverlap 記錄不同重疊率閾值下的成功率數量,設置數組 successNumErr 記錄不同誤差閾值下的成功率數量:
successNumOverlap = zeros(idxNum,length(thresholdSetOverlap));
successNumErr = zeros(idxNum,length(thresholdSetError));
加載跟蹤結果:
res = results{idx};
計算誤差:
[aveCoverage, aveErrCenter, errCoverage, errCenter] = calcSeqErrRobust(res, anno);
直接來看這個 calcSeqErrRobust 函數。首先對跟蹤結果進行了一個處理,對跟蹤結果為 NaN 或者跟蹤框尺度為負值且真值框不為 NaN 的情況,將跟蹤結果改為前一幀的跟蹤結果:
if strcmp(results.type,'rect')for i = 2:seq_lengthr = results.res(i,:);r_anno = rect_anno(i,:);if (isnan(r) | r(3)<=0 | r(4)<=0)&(~isnan(r_anno))results.res(i,:)=results.res(i-1,:);endend
end
計算真值框的中心坐標:
centerGT = [rect_anno(:,1)+(rect_anno(:,3)-1)/2 rect_anno(:,2)+(rect_anno(:,4)-1)/2];
將第一幀的真值框坐標賦給第一幀的跟蹤結果并計算跟蹤器跟蹤的目標框中心坐標:
rectMat(1,:) = rect_anno(1,:);
center = [rectMat(:,1)+(rectMat(:,3)-1)/2 rectMat(:,2)+(rectMat(:,4)-1)/2];
計算每一幀的中心距離誤差:
errCenter = sqrt(sum(((center(1:seq_length,:) - centerGT(1:seq_length,:)).^2),2));
將真值框大于 0 的幀的跟蹤結果和真值框值送入 calcRectInt 函數計算重疊率 tmp:
index = rect_anno>0;
idx=(sum(index,2)==4);
tmp = calcRectInt(rectMat(idx,:),rect_anno(idx,:));
計算平均重疊率和平均中心距離誤差:
aveErrCoverage = sum(errCoverage(idx))/length(idx);
aveErrCenter = sum(errCenter(idx))/length(idx);
按照之前設置的重疊率閾值分別計算該序列成功的幀數:
for tIdx=1:length(thresholdSetOverlap)successNumOverlap(idx,tIdx) = sum(errCoverage >thresholdSetOverlap(tIdx));
end
按照之前設置的中心距離誤差閾值分別計算該序列成功的幀數:
for tIdx=1:length(thresholdSetError)successNumErr(idx,tIdx) = sum(errCenter <= thresholdSetError(tIdx));
end
計算成功率和跟蹤精度每個閾值處的值:
if strcmp(evalType, 'OPE')aveSuccessRatePlot(idxTrk, idxSeq,:) = successNumOverlap/(lenALL+eps);aveSuccessRatePlotErr(idxTrk, idxSeq,:) = successNumErr/(lenALL+eps);
這里 eps 的目的是什么呢?
到這里第一個序列第一個跟蹤器的成功率和跟蹤精度就計算結束了。依次循環直至在所有序列上計算完所有跟蹤器的結果。
將成功率和跟蹤精度存至 perMat 文件夾下:
dataName1=[perfMatPath 'aveSuccessRatePlot_' num2str(numTrk) 'alg_overlap_' evalType '.mat'];
save(dataName1,'aveSuccessRatePlot','nameTrkAll');dataName2=[perfMatPath 'aveSuccessRatePlot_' num2str(numTrk) 'alg_error_' evalType '.mat'];
aveSuccessRatePlot = aveSuccessRatePlotErr;
save(dataName2,'aveSuccessRatePlot','nameTrkAll');
genPerfMat 函數執行完畢,回到 perfPlot 函數。
加載中心距離誤差值,設置結果圖名稱:
load(dataName);
figName= [figPath 'quality_plot_' plotType '_' rankingType];
由 plotDrawSave 函數繪制曲線圖。
來看一下 plotDrawSave 函數。
首先需要按照跟蹤器的順序依次統計中心距離誤差小于 20 個像素的幀數所占百分比,即距離精度:
for idxTrk=1:numTrk%each row is the sr plot of one sequencetmp=aveSuccessRatePlot(idxTrk, idxSeqSet,:);aa=reshape(tmp,[length(idxSeqSet),size(aveSuccessRatePlot,3)]);aa=aa(sum(aa,2)>eps,:);bb=mean(aa);switch rankingTypecase 'AUC'perf(idxTrk) = mean(bb);case 'threshold'perf(idxTrk) = bb(rankIdx);end
end
這里 bb 即為每個跟蹤器在 123 個序列上每個閾值下的平均中心距離誤差,rankIdx 值為 21,bb 中第 21 個閾值剛好對應 20 個像素,即通用閾值。
對跟蹤器根據 20 個像素的公共閾值進行排名:
[tmp,indexSort]=sort(perf,'descend');
其中 tmp 為排名之后的分數,indexSort 為排名之后各跟蹤器對應的順序。
然后按照排序后的順序從高到低依次繪制精度曲線:
for idxTrk=indexSort(1:rankNum)tmp=aveSuccessRatePlot(idxTrk,idxSeqSet,:);aa=reshape(tmp,[length(idxSeqSet),size(aveSuccessRatePlot,3)]);aa=aa(sum(aa,2)>eps,:);bb=mean(aa);switch rankingTypecase 'AUC'score = mean(bb);tmp=sprintf('%.3f', score);case 'threshold'score = bb(rankIdx);tmp=sprintf('%.3f', score);endh(i) = plot(thresholdSet,bb,'color',plotDrawStyle{i}.color, 'lineStyle', plotDrawStyle{i}.lineStyle,'lineWidth', 4,'Parent',axes1);hold oni=i+1;
end
精度曲線繪制完成。
3. 繪制每種屬性下的精度曲線
回到 perfPlot 函數,依次繪制每種屬性的精度曲線圖。
首先需要找出每種屬性對應的序列:
idxSeqSet=find(att(:,attIdx)>attTrld);
然后同樣進入 plotDrawSave 函數,分別繪制精度曲線圖。
成功率曲線的繪制過程與精度曲線類似。
三、測試自己的數據集
1. 數據集標注
首先需要對數據集進行標注,標注過程就不再贅述了。數據集中的每個序列應該對應一個 .txt 格式的標注文檔,文檔中包含了每張圖片中目標框的左上角和目標框的大小。
2. 配置數據集
仿照 UAV123 數據集將圖像序列和標注數據進行存儲。例如,我的數據集命名為 UAV_seq ,那么首先建立一個名為 UAV_seq 的文件夾,文件夾內容如下:
data_seq 文件夾中為每個圖像序列的名稱,每個文件夾下為對應的圖像序列;anno 文件夾下則放置每個圖像對應的 txt 文件。
3. 在 configSeqs 中添加數據集信息
仿照 seqUAV123 在 …/util/configSeqs.m 文件中添加 UAV_seq 的相關信息,包括數據集中圖像序列的名稱、路徑、開始、結束幀數等等。