作者:大森林 | 來源:計算機視覺工坊
添加微信:dddvisiona,備注:3D點雲,拉你入群。文末附行業細分群。
3D點雲目标跟蹤的評價名額,可以根據跟蹤的目标是單個還是多個,分為單目标跟蹤(SOT)和多目标跟蹤(MOT)兩種。一般來說,SOT的評價名額主要關注跟蹤的準确性和魯棒性,而MOT的評價名額則需要考慮跟蹤的完整性和一緻性。
SOT的常用評價名額有:
- 平均重疊率(Average Overlap Rate, AOR):表示預測的3D邊界框與真實的3D邊界框之間的重疊比例的平均值。
- 平均中心誤差(Average Center Error, ACE):表示預測的3D邊界框與真實的3D邊界框之間的中心點距離的平均值。
- 成功率(Success Rate, SR):表示預測的3D邊界框與真實的3D邊界框之間的重疊比例超過某個門檻值(如0.5)的幀數占總幀數的比例。
- 精确率(Precision Rate, PR):表示預測的3D邊界框與真實的3D邊界框之間的中心點距離小于某個門檻值(如1米)的幀數占總幀數的比例。
MOT的常用評價名額有:
- 多目标跟蹤精度(Multiple Object Tracking Accuracy, MOTA):綜合考慮了漏檢率、誤檢率和ID切換率對跟蹤精度的影響。
- 多目标跟蹤精确度(Multiple Object Tracking Precision, MOTP):表示預測的3D邊界框與真實的3D邊界框之間的重疊比例或中心點距離的平均值。
- 跟蹤長度(Track Length, TL):表示每個目标被成功跟蹤的幀數。
- 跟蹤片段(Track Fragment, TF):表示每個目标被中斷跟蹤的次數。
- ID切換率(ID Switch Rate, ISR):表示每個目标被錯誤地配置設定給另一個ID或從另一個ID切換過來的次數。
評價名額詳細代碼:
首先,我們需要導入一些必要的庫,如numpy, scipy和sklearn。然後,我們需要定義一些輔助函數,如計算兩個3D邊界框之間的重疊比例(IoU),計算兩個3D點之間的歐氏距離,以及使用匈牙利算法進行資料關聯。
這裡也推薦「3D視覺工坊」新課程《面向自動駕駛領域的3D點雲目标檢測全棧學習路線!(單模态+多模态/資料+代碼)》
import numpy as npfrom scipy.spatial.transform import Rotation as Rfrom scipy.optimize import linear_sum_assignmentfrom sklearn.metrics import pairwise_distances# 計算兩個3D邊界框之間的重疊比例(IoU)def iou_3d(box1, box2): # box1和box2都是7維向量,表示(x, y, z, w, l, h, yaw) # 其中(x, y, z)是中心點坐标,(w, l, h)是寬度、長度和高度,yaw是偏航角 # 傳回兩個邊界框之間的IoU值,範圍在[0, 1] # 将邊界框轉換為8個頂點的矩陣 box1_corners = box_to_corners(box1) box2_corners = box_to_corners(box2) # 計算兩個邊界框在每個軸上的投影區間 box1_xmin = np.min(box1_corners[:, 0]) box1_xmax = np.max(box1_corners[:, 0]) box1_ymin = np.min(box1_corners[:, 1]) box1_ymax = np.max(box1_corners[:, 1]) box1_zmin = np.min(box1_corners[:, 2]) box1_zmax = np.max(box1_corners[:, 2]) box2_xmin = np.min(box2_corners[:, 0]) box2_xmax = np.max(box2_corners[:, 0]) box2_ymin = np.min(box2_corners[:, 1]) box2_ymax = np.max(box2_corners[:, 1]) box2_zmin = np.min(box2_corners[:, 2]) box2_zmax = np.max(box2_corners[:, 2]) # 計算兩個邊界框在每個軸上的交集區間 inter_xmin = max(box1_xmin, box2_xmin) inter_xmax = min(box1_xmax, box2_xmax) inter_ymin = max(box1_ymin, box2_ymin) inter_ymax = min(box1_ymax, box2_ymax) inter_zmin = max(box1_zmin, box2_zmin) inter_zmax = min(box1_zmax, box2_zmax) # 如果沒有交集,傳回0 if inter_xmax < inter_xmin or inter_ymax < inter_ymin or inter_zmax < inter_zmin: return 0.0 # 計算交集區域的體積 inter_vol = (inter_xmax - inter_xmin) * (inter_ymax - inter_ymin) * (inter_zmax - inter_zmin) # 計算兩個邊界框的體積 box1_vol = (box1_xmax - box1_xmin) * (box1_ymax - box1_ymin) * (box1_zmax - box1_zmin) box2_vol = (box2_xmax - box2_xmin) * (box2_ymax - box2_ymin) * (box2_zmax - box2_zmin) # 計算并傳回IoU值 iou = inter_vol / (box1_vol + box2_vol - inter_vol) return iou# 将7維向量表示的邊界框轉換為8個頂點的矩陣表示def box_to_corners(box): # 輸入是一個7維向量,表示(x, y, z, w, l, h, yaw) # 輸出是一個8x3的矩陣,表示8個頂點的坐标 # 提取邊界框的參數 x, y, z, w, l, h, yaw = box # 計算旋轉矩陣 rot = R.from_euler('z', yaw).as_matrix() # 計算邊界框的中心點 center = np.array([x, y, z]) # 計算邊界框的8個頂點的相對坐标 x_corners = np.array([w, w, -w, -w, w, w, -w, -w]) / 2 y_corners = np.array([l, -l, -l, l, l, -l, -l, l]) / 2 z_corners = np.array([h, h, h, h, -h, -h, -h, -h]) / 2 corners = np.vstack((x_corners, y_corners, z_corners)) # 通過旋轉和平移,将相對坐标轉換為絕對坐标 corners = np.dot(rot, corners).T + center return corners# 計算兩個3D點之間的歐氏距離def euclidean_distance(point1, point2): # point1和point2都是3維向量,表示(x, y, z) # 傳回兩個點之間的歐氏距離 # 計算兩個點之間的差異向量 diff = point1 - point2 # 計算并傳回歐氏距離 dist = np.sqrt(np.sum(diff ** 2)) return dist# 使用匈牙利算法進行資料關聯def data_association(cost_matrix): # cost_matrix是一個m x n的矩陣,表示m個預測和n個觀測之間的代價(如距離或者負IoU) # 傳回一個長度為m的向量,表示每個預測比對的觀測的索引,如果沒有比對,則為-1 # 使用scipy庫中的linear_sum_assignment函數,求解最小化總代價的比對方案 row_ind, col_ind = linear_sum_assignment(cost_matrix) # 初始化比對結果為-1 matches = np.full(cost_matrix.shape[0], -1) # 将比對方案指派給比對結果 matches[row_ind] = col_ind return matches
接下來,我們需要定義一些評價名額的計算函數,如AOR,ACE,SR,PR,MOTA,MOTP,TL,TF,ISR等。我們已經有了預測的3D邊界框和真實的3D邊界框的清單,以及每個邊界框的置信度得分。我們還需要定義一些門檻值,如重疊比例門檻值(iou_threshold),中心點距離門檻值(dist_threshold),置信度得分門檻值(score_threshold)等,這些門檻值将在代碼中具體給出。
這裡再次推薦「3D視覺工坊」新課程《面向自動駕駛領域的3D點雲目标檢測全棧學習路線!(單模态+多模态/資料+代碼)》
# 計算平均重疊率(AOR)def average_overlap_rate(pred_boxes, gt_boxes):
# pred_boxes是一個p x 7的矩陣,表示p個預測的3D邊界框
# gt_boxes是一個g x 7的矩陣,表示g個真實的3D邊界框
# 傳回平均重疊率(AOR)值
# 如果沒有預測或真實邊界框,傳回0
if pred_boxes.shape[0] == 0 or gt_boxes.shape[0] == 0:
return 0.0 # 計算預測和真實邊界框之間的重疊比例矩陣,大小為p x g
iou_matrix = np.zeros((pred_boxes.shape[0], gt_boxes.shape[0]))
for i in range(pred_boxes.shape[0]):
for j in range(gt_boxes.shape[0]):
iou_matrix[i][j] = iou_3d(pred_boxes[i], gt_boxes[j])
# 使用匈牙利算法進行資料關聯,得到比對結果
matches = data_association(-iou_matrix)
# 計算并傳回平均重疊率(AOR)值
ace = np.mean(dist_matrix[matches != -1])
return ace# 計算成功率(SR)
def success_rate(pred_boxes, gt_boxes, iou_threshold=0.5):
# pred_boxes是一個p x 7的矩陣,表示p個預測的3D邊界框
# gt_boxes是一個g x 7的矩陣,表示g個真實的3D邊界框
# iou_threshold是一個浮點數,表示重疊比例的門檻值,預設為0.5
# 傳回成功率(SR)值
# 如果沒有預測或真實邊界框,傳回0
if pred_boxes.shape[0] == 0 or gt_boxes.shape[0] == 0:
return 0.0
# 計算預測和真實邊界框之間的重疊比例矩陣,大小為p x g iou_matrix = np.zeros((pred_boxes.shape[0], gt_boxes.shape[0])) for i in range(pred_boxes.shape[0]): for j in range(gt_boxes.shape[0]): iou_matrix[i][j] = iou_3d(pred_boxes[i], gt_boxes[j]) # 使用匈牙利算法進行資料關聯,得到比對結果 matches = data_association(-iou_matrix) # 計算并傳回成功率(SR)值 sr = np.sum(iou_matrix[matches != -1] >= iou_threshold) / pred_boxes.shape[0] return sr# 計算精确率(PR)def precision_rate(pred_boxes, gt_boxes, dist_threshold=1.0): # pred_boxes是一個p x 7的矩陣,表示p個預測的3D邊界框 # gt_boxes是一個g x 7的矩陣,表示g個真實的3D邊界框 # dist_threshold是一個浮點數,表示中心點距離的門檻值,預設為1.0 # 傳回精确率(PR)值 # 如果沒有預測或真實邊界框,傳回0 if pred_boxes.shape[0] == 0 or gt_boxes.shape[0] == 0: return 0.0 # 提取預測和真實邊界框的中心點坐标 pred_centers = pred_boxes[:, :3] gt_centers = gt_boxes[:, :3] # 計算預測和真實邊界框之間的中心點距離矩陣,大小為p x g dist_matrix = pairwise_distances(pred_centers, gt_centers) # 使用匈牙利算法進行資料關聯,得到比對結果 matches = data_association(dist_matrix) # 計算并傳回精确率(PR)值 pr = np.sum(dist_matrix[matches != -1] <= dist_threshold) / pred_boxes.shape[0] return pr# 計算多目标跟蹤精度(MOTA)def multiple_object_tracking_accuracy(pred_boxes, gt_boxes, iou_threshold=0.5): # pred_boxes是一個清單,長度為t,表示t個時間步的預測的3D邊界框 # gt_boxes是一個清單,長度為t,表示t個時間步的真實的3D邊界框 # iou_threshold是一個浮點數,表示重疊比例的門檻值,預設為0.5 # 傳回多目标跟蹤精度(MOTA)值 # 如果沒有預測或真實邊界框,傳回0 if len(pred_boxes) == 0 or len(gt_boxes) == 0: return 0.0 # 初始化漏檢數、誤檢數、ID切換數和總真實數為0 miss_count = 0 false_count = 0 switch_count = 0 total_count = 0 # 初始化上一時間步的比對結果為空字典 prev_matches = {} # 周遊每個時間步 for t in range(len(pred_boxes)): # 擷取目前時間步的預測和真實邊界框 pred_box = pred_boxes[t] gt_box = gt_boxes[t] # 計算目前時間步的真實邊界框的數量,并累加到總真實數中 total_count += gt_box.shape[0] # 如果目前時間步沒有預測或真實邊界框,跳過該時間步 if pred_box.shape[0] == 0 or gt_box.shape[0] == 0: continue # 計算目前時間步的預測和真實邊界框之間的重疊比例矩陣,大小為p x g iou_matrix = np.zeros((pred_box.shape[0], gt_box.shape[0])) for i in range(pred_box.shape[0]): for j in range(gt_box.shape[0]): iou_matrix[i][j] = iou_3d(pred_box[i], gt_box[j]) # 使用匈牙利算法進行資料關聯,得到比對結果 matches = data_association(-iou_matrix) # 初始化目前時間步的比對結果為空字典 curr_matches = {} # 周遊每個預測邊界框 for i in range(pred_box.shape[0]): # 如果沒有比對的真實邊界框,或者重疊比例低于門檻值,累加誤檢數,并跳過該預測邊界框 if matches[i] == -1 or iou_matrix[i][matches[i]] < iou_threshold: false_count += 1 continue # 擷取比對的真實邊界框的索引 j = matches[i] # 如果上一時間步有比對的真實邊界框,并且ID不同,累加ID切換數 if j in prev_matches and prev_matches[j] != i: switch_count += 1 # 将目前的比對結果儲存到字典中 curr_matches[j] = i # 周遊每個真實邊界框 for j in range(gt_box.shape[0]): # 如果沒有比對的預測邊界框,累加漏檢數 if j not in curr_matches: miss_count += 1 # 更新上一時間步的比對結果為目前的比對結果 prev_matches = curr_matches # 計算并傳回多目标跟蹤精度(MOTA)值 mota = 1 - (miss_count + false_count + switch_count) / total_count return mota# 計算多目标跟蹤精确度(MOTP)def multiple_object_tracking_precision(pred_boxes, gt_boxes, iou_threshold=0.5): # pred_boxes是一個清單,長度為t,表示t個時間步的預測的3D邊界框 # gt_boxes是一個清單,長度為t,表示t個時間步的真實的3D邊界框 # iou_threshold是一個浮點數,表示重疊比例的門檻值,預設為0.5 # 傳回多目标跟蹤精确度(MOTP)值 # 如果沒有預測或真實邊界框,傳回0 if len(pred_boxes) == 0 or len(gt_boxes) == 0: return 0.0 # 初始化總比對數和總重疊率為0 match_count = 0 sum_iou = 0.0 # 周遊每個時間步 for t in range(len(pred_boxes)): # 擷取目前時間步的預測和真實邊界框 pred_box = pred_boxes[t] gt_box = gt_boxes[t] # 如果目前時間步沒有預測或真實邊界框,跳過該時間步 if pred_box.shape[0] == 0 or gt_box.shape[0] == 0: continue # 計算目前時間步的預測和真實邊界框之間的重疊比例矩陣,大小為p x g iou_matrix = np.zeros((pred_box.shape[0], gt_box.shape[0])) for i in range(pred_box.shape[0]): for j in range(gt_box.shape[0]): iou_matrix[i][j] = iou_3d(pred_box[i], gt_box[j]) # 使用匈牙利算法進行資料關聯,得到比對結果 matches = data_association(-iou_matrix) # 周遊每個預測邊界框 for i in range(pred_box.shape[0]): # 如果沒有比對的真實邊界框,或者重疊比例低于門檻值,跳過該預測邊界框 if matches[i] == -1 or iou_matrix[i][matches[i]] < iou_threshold: continue # 擷取比對的真實邊界框的索引 j = matches[i] # 累加比對數和重疊率 match_count += 1 sum_iou += iou_matrix[i][j] # 計算并傳回多目标跟蹤精确度(MOTP)值 motp = sum_iou / match_count return motp# 計算跟蹤長度(TL)def track_length(pred_boxes, gt_boxes, iou_threshold=0.5): # pred_boxes是一個清單,長度為t,表示t個時間步的預測的3D邊界框 # gt_boxes是一個清單,長度為t,表示t個時間步的真實的3D邊界框 # iou_threshold是一個浮點數,表示重疊比例的門檻值,預設為0.5 # 傳回一個字典,鍵為真實目标的ID,值為對應的跟蹤長度 # 如果沒有預測或真實邊界框,傳回空字典 if len(pred_boxes) == 0 or len(gt_boxes) == 0: return {} # 初始化跟蹤長度字典為空字典 tl_dict = {} # 周遊每個時間步 for t in range(len(pred_boxes)): # 擷取目前時間步的預測和真實邊界框 pred_box = pred_boxes[t] gt_box = gt_boxes[t] # 如果目前時間步沒有預測或真實邊界框,跳過該時間步 if pred_box.shape[0] == 0 or gt_box.shape[0] == 0: continue # 計算目前時間步的預測和真實邊界框之間的重疊比例矩陣,大小為p x g iou_matrix = np.zeros((pred_box.shape[0], gt_box.shape[0])) for i in range(pred_box.shape[0]): for j in range(gt_box.shape[0]): iou_matrix[i][j] = iou_3d(pred_box[i], gt_box[j]) # 使用匈牙利算法進行資料關聯,得到比對結果 matches = data_association(-iou_matrix) # 周遊每個預測邊界框 for i in range(pred_box.shape[0]): # 如果沒有比對的真實邊界框,或者重疊比例低于門檻值,跳過該預測邊界框 if matches[i] == -1 or iou_matrix[i][matches[i]] < iou_threshold: continue # 擷取比對的真實邊界框的索引 j = matches[i] # 如果真實目标的ID已經在跟蹤長度字典中,累加1 if j in tl_dict: tl_dict[j] += 1 # 否則,初始化為1 else: tl_dict[j] = 1 # 傳回跟蹤長度字典 return tl_dict# 計算跟蹤片段(TF)def track_fragment(pred_boxes, gt_boxes, iou_threshold=0.5): # pred_boxes是一個清單,長度為t,表示t個時間步的預測的3D邊界框 # gt_boxes是一個清單,長度為t,表示t個時間步的真實的3D邊界框 # iou_threshold是一個浮點數,表示重疊比例的門檻值,預設為0.5 # 傳回一個字典,鍵為真實目标的ID,值為對應的跟蹤片段數 # 如果沒有預測或真實邊界框,傳回空字典 if len(pred_boxes) == 0 or len(gt_boxes) == 0: return {} # 初始化跟蹤片段字典為空字典 tf_dict = {} # 初始化上一時間步的比對結果為空字典 prev_matches = {} # 周遊每個時間步 for t in range(len(pred_boxes)): # 擷取目前時間步的預測和真實邊界框 pred_box = pred_boxes[t] gt_box = gt_boxes[t] # 如果目前時間步沒有預測或真實邊界框,跳過該時間步 if pred_box.shape[0] == 0 or gt_box.shape[0] == 0: continue # 計算目前時間步的預測和真實邊界框之間的重疊比例矩陣,大小為p x g iou_matrix = np.zeros((pred_box.shape[0], gt_box.shape[0])) for i in range(pred_box.shape[0]): for j in range(gt_box.shape[0]): iou_matrix[i][j] = iou_3d(pred_box[i], gt_box[j]) # 使用匈牙利算法進行資料關聯,得到比對結果 matches = data_association(-iou_matrix) # 初始化目前時間步的比對結果為空字典 curr_matches = {} # 周遊每個預測邊界框 for i in range(pred_box.shape[0]): # 如果沒有比對的真實邊界框,或者重疊比例低于門檻值,跳過該預測邊界框 if matches[i] == -1 or iou_matrix[i][matches[i]] < iou_threshold: continue # 擷取比對的真實邊界框的索引 j = matches[i] # 将目前的比對結果儲存到字典中 curr_matches[j] = i # 如果真實目标的ID已經在跟蹤片段字典中,且上一時間步沒有比對該目标,累加1 if j in tf_dict and j not in prev_matches: tf_dict[j] += 1 # 否則,初始化為1 elif j not in tf_dict: tf_dict[j] = 1 # 更新上一時間步的比對結果為目前的比對結果 prev_matches = curr_matches # 傳回跟蹤片段字典 return tf_dict# 計算ID切換率(ISR)def id_switch_rate(pred_boxes, gt_boxes, iou_threshold=0.5): # pred_boxes是一個清單,長度為t,表示t個時間步的預測的3D邊界框 # gt_boxes是一個清單,長度為t,表示t個時間步的真實的3D邊界框 # iou_threshold是一個浮點數,表示重疊比例的門檻值,預設為0.5 # 傳回一個字典,鍵為真實目标的ID,值為對應的ID切換次數 # 如果沒有預測或真實邊界框,傳回空字典 if len(pred_boxes) == 0 or len(gt_boxes) == 0: return {} # 初始化ID切換率字典為空字典 isr_dict = {} # 初始化上一時間步的比對結果為空字典 prev_matches = {} # 周遊每個時間步 for t in range(len(pred_boxes)): # 擷取目前時間步的預測和真實邊界框 pred_box = pred_boxes[t] gt_box = gt_boxes[t] # 如果目前時間步沒有預測或真實邊界框,跳過該時間步 if pred_box.shape[0] == 0 or gt_box.shape[0] == 0: continue # 計算目前時間步的預測和真實邊界框之間的重疊比例矩陣,大小為p x g iou_matrix = np.zeros((pred_box.shape[0], gt_box.shape[0])) for i in range(pred_box.shape[0]): for j in range(gt_box.shape[0]): iou_matrix[i][j] = iou_3d(pred_box[i], gt_box[j]) # 使用匈牙利算法進行資料關聯,得到比對結果 matches = data_association(-iou_matrix) # 初始化目前時間步的比對結果為空字典 curr_matches = {} # 周遊每個預測邊界框 for i in range(pred_box.shape[0]): # 如果沒有比對的真實邊界框,或者重疊比例低于門檻值,跳過該預測邊界框 if matches[i] == -1 or iou_matrix[i][matches[i]] < iou_threshold: continue # 擷取比對的真實邊界框的索引 j = matches[i] # 将目前的比對結果儲存到字典中 curr_matches[j] = i # 如果上一時間步有比對的真實邊界框,并且ID不同,累加1 if j in prev_matches and prev_matches[j] != i: # 如果真實目标的ID已經在ID切換率字典中,累加1 if j in isr_dict: isr_dict[j] += 1 # 否則,初始化為1 else: isr_dict[j] = 1 # 更新上一時間步的比對結果為目前的比對結果 prev_matches = curr_matches # 傳回ID切換率字典 return isr_dict
以上就是我幫大家總結的3D點雲目标跟蹤中常見的評價名額和代碼詳解,希望可以幫助到大家。
目前工坊已經建立了3D視覺方向多個社群,包括SLAM、工業3D視覺、自動駕駛方向,細分群包括:[工業方向]三維點雲、結構光、機械臂、缺陷檢測、三維測量、TOF、相機标定、綜合群;[SLAM方向]多傳感器融合、ORB-SLAM、雷射SLAM、機器人導航、RTK|GPS|UWB等傳感器交流群、SLAM綜合讨論群;[自動駕駛方向]深度估計、Transformer、毫米波|雷射雷達|視覺攝像頭傳感器讨論群、多傳感器标定、自動駕駛綜合群等。[三維重建方向]NeRF、colmap、OpenMVS等。除了這些,還有求職、硬體選型、視覺産品落地等交流群。大家可以添加小助理微信: dddvisiona,備注:加群+方向+學校|公司, 小助理會拉你入群。