回传数据解析,兼容v5和v10

This commit is contained in:
jiajie555
2025-04-18 14:41:53 +08:00
commit 010f5c445a
888 changed files with 93632 additions and 0 deletions

0
tracking/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
文件夹 trackdicts_20240608 和 trackdicts_1 下的数据为和手部关联前的跟踪结果数据

View File

@ -0,0 +1,337 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Aug 30 17:53:03 2024
have Deprecated!
1. 确认在相同CamerType下track.data 中 CamerID 项数量 = 图像数 = 帧ID数 = 最大帧ID
2. 读取0/1_tracking_output.data 中数据boxes、featslen(boxes)=len(feats)
帧ID约束
3. 优先选择前摄
4. 保存图像数据
5. 一次购物事件类型
shopEvent: {barcode:
type: getout, input
front_traj:[{imgpath: str,
box: arrar(1, 9),
feat: array(1, 256)
}]
back_traj: [{imgpath: str,
box: arrar(1, 9),
feat: array(1, 256)
}]
}
@author: ym
"""
import numpy as np
import cv2
import os
import sys
import json
import pickle
sys.path.append(r"D:\DetectTracking")
from tracking.utils.read_data import extract_data, read_tracking_output, read_deletedBarcode_file
IMG_FORMAT = ['.bmp', '.jpg', '.jpeg', '.png']
def creat_shopping_event(basepath):
eventList = []
'''一、构造放入商品事件列表'''
k = 0
for filename in os.listdir(basepath):
# filename = "20240723-155413_6904406215720"
'''filename下为一次购物事件'''
filepath = os.path.join(basepath, filename)
'''================ 0. 检查 filename 及 filepath 正确性和有效性 ================'''
nmlist = filename.split('_')
if filename.find('2024')<0 or len(nmlist)!=2 or len(nmlist[0])!=15 or len(nmlist[1])<11:
continue
if not os.path.isdir(filepath): continue
print(f"Event name: {filename}")
'''================ 1. 构造事件描述字典,暂定 9 items ==============='''
event = {}
event['barcode'] = nmlist[1]
event['type'] = 'input'
event['filepath'] = filepath
event['back_imgpaths'] = []
event['front_imgpaths'] = []
event['back_boxes'] = np.empty((0, 9), dtype=np.float64)
event['front_boxes'] = np.empty((0, 9), dtype=np.float64)
event['back_feats'] = np.empty((0, 256), dtype=np.float64)
event['front_feats'] = np.empty((0, 256), dtype=np.float64)
# event['feats_compose'] = np.empty((0, 256), dtype=np.float64)
# event['feats_select'] = np.empty((0, 256), dtype=np.float64)
'''================= 1. 读取 data 文件 ============================='''
for dataname in os.listdir(filepath):
# filename = '1_track.data'
datapath = os.path.join(filepath, dataname)
if not os.path.isfile(datapath): continue
CamerType = dataname.split('_')[0]
''' 3.1 读取 0/1_track.data 中数据,暂不考虑'''
# if dataname.find("_track.data")>0:
# bboxes, ffeats, trackerboxes, tracker_feat_dict, trackingboxes, tracking_feat_dict = extract_data(datapath)
''' 3.2 读取 0/1_tracking_output.data 中数据'''
if dataname.find("_tracking_output.data")>0:
tracking_output_boxes, tracking_output_feats = read_tracking_output(datapath)
if len(tracking_output_boxes) != len(tracking_output_feats): continue
if CamerType == '0':
event['back_boxes'] = tracking_output_boxes
event['back_feats'] = tracking_output_feats
elif CamerType == '1':
event['front_boxes'] = tracking_output_boxes
event['front_feats'] = tracking_output_feats
# '''1.1 事件的特征表征方式选择'''
# bk_feats = event['back_feats']
# ft_feats = event['front_feats']
# feats_compose = np.empty((0, 256), dtype=np.float64)
# if len(ft_feats):
# feats_compose = np.concatenate((feats_compose, ft_feats), axis=0)
# if len(bk_feats):
# feats_compose = np.concatenate((feats_compose, bk_feats), axis=0)
# event['feats_compose'] = feats_compose
# '''3. 构造前摄特征'''
# if len(ft_feats):
# event['feats_select'] = ft_feats
'''================ 2. 读取图像文件地址并按照帧ID排序 ============='''
frontImgs, frontFid = [], []
backImgs, backFid = [], []
for imgname in os.listdir(filepath):
name, ext = os.path.splitext(imgname)
if ext not in IMG_FORMAT or name.find('frameId')<0: continue
CamerType = name.split('_')[0]
frameId = int(name.split('_')[3])
imgpath = os.path.join(filepath, imgname)
if CamerType == '0':
backImgs.append(imgpath)
backFid.append(frameId)
if CamerType == '1':
frontImgs.append(imgpath)
frontFid.append(frameId)
frontIdx = np.argsort(np.array(frontFid))
backIdx = np.argsort(np.array(backFid))
'''2.1 生成依据帧 ID 排序的前后摄图像地址列表'''
frontImgs = [frontImgs[i] for i in frontIdx]
backImgs = [backImgs[i] for i in backIdx]
'''2.2 将前、后摄图像路径添加至事件字典'''
bfid = event['back_boxes'][:, 7].astype(np.int64)
ffid = event['front_boxes'][:, 7].astype(np.int64)
if len(bfid) and max(bfid) <= len(backImgs):
event['back_imgpaths'] = [backImgs[i-1] for i in bfid]
if len(ffid) and max(ffid) <= len(frontImgs):
event['front_imgpaths'] = [frontImgs[i-1] for i in ffid]
'''================ 3. 判断当前事件有效性,并添加至事件列表 =========='''
condt1 = len(event['back_imgpaths'])==0 or len(event['front_imgpaths'])==0
condt2 = len(event['front_feats'])==0 and len(event['back_feats'])==0
if condt1 or condt2:
print(f" Error, condt1: {condt1}, condt2: {condt2}")
continue
eventList.append(event)
# k += 1
# if k==1:
# continue
'''一、构造放入商品事件列表,暂不处理'''
# delepath = os.path.join(basepath, 'deletedBarcode.txt')
# bcdList = read_deletedBarcode_file(delepath)
# for slist in bcdList:
# getoutFold = slist['SeqDir'].strip()
# getoutPath = os.path.join(basepath, getoutFold)
# '''取出事件文件夹不存在,跳出循环'''
# if not os.path.exists(getoutPath) and not os.path.isdir(getoutPath):
# continue
# ''' 生成取出事件字典 '''
# event = {}
# event['barcode'] = slist['Deleted'].strip()
# event['type'] = 'getout'
# event['basepath'] = getoutPath
return eventList
def get_std_barcodeDict(bcdpath):
stdBlist = []
for filename in os.listdir(bcdpath):
filepath = os.path.join(bcdpath, filename)
if not os.path.isdir(filepath) or not filename.isdigit(): continue
stdBlist.append(filename)
bcdpaths = [(barcode, os.path.join(bcdpath, barcode)) for barcode in stdBlist]
stdBarcodeDict = {}
for barcode, bpath in bcdpaths:
stdBarcodeDict[barcode] = []
for root, dirs, files in os.walk(bpath):
imgpaths = []
if "base" in dirs:
broot = os.path.join(root, "base")
for imgname in os.listdir(broot):
imgpath = os.path.join(broot, imgname)
_, ext = os.path.splitext(imgpath)
if ext not in IMG_FORMAT: continue
imgpaths.append(imgpath)
stdBarcodeDict[barcode].extend(imgpaths)
break
else:
for imgname in files:
imgpath = os.path.join(root, imgname)
_, ext = os.path.splitext(imgpath)
if ext not in IMG_FORMAT: continue
imgpaths.append(imgpath)
stdBarcodeDict[barcode].extend(imgpaths)
jsonpath = os.path.join(r'\\192.168.1.28\share\测试_202406\contrast\barcodes', f"{barcode}.pickle")
with open(jsonpath, 'wb') as f:
pickle.dump(stdBarcodeDict, f)
print(f"Barcode: {barcode}")
return stdBarcodeDict
def one2one_test(filepath):
savepath = r'\\192.168.1.28\share\测试_202406\contrast'
'''获得 Barcode 列表'''
bcdpath = r'\\192.168.1.28\share\已标注数据备份\对比数据\barcode\barcode_1771'
stdBarcodeDict = get_std_barcodeDict(bcdpath)
eventList = creat_shopping_event(filepath)
print("=========== eventList have generated! ===========")
barcodeDict = {}
for event in eventList:
'''9 items: barcode, type, filepath, back_imgpaths, front_imgpaths,
back_boxes, front_boxes, back_feats, front_feats
'''
barcode = event['barcode']
if barcode not in stdBarcodeDict.keys():
continue
if len(event['feats_select']):
event_feats = event['feats_select']
elif len(event['back_feats']):
event_feats = event['back_feats']
else:
continue
std_bcdpath = os.path.join(bcdpath, barcode)
for root, dirs, files in os.walk(std_bcdpath):
if "base" in files:
std_bcdpath = os.path.join(root, "base")
break
'''保存一次购物事件的轨迹子图'''
basename = os.path.basename(event['filepath'])
spath = os.path.join(savepath, basename)
if not os.path.exists(spath):
os.makedirs(spath)
cameras = ('front', 'back')
for camera in cameras:
if camera == 'front':
boxes = event['front_boxes']
imgpaths = event['front_imgpaths']
else:
boxes = event['back_boxes']
imgpaths = event['back_imgpaths']
for i, box in enumerate(boxes):
x1, y1, x2, y2, tid, score, cls, fid, bid = box
imgpath = imgpaths[i]
image = cv2.imread(imgpath)
subimg = image[int(y1/2):int(y2/2), int(x1/2):int(x2/2), :]
camerType, timeTamp, _, frameID = os.path.basename(imgpath).split('.')[0].split('_')
subimgName = f"{camerType}_{tid}_fid({fid}, {frameID}).png"
subimgPath = os.path.join(spath, subimgName)
cv2.imwrite(subimgPath, subimg)
print(f"Image saved: {basename}")
def main():
fplist = [r'\\192.168.1.28\share\测试_202406\0723\0723_1',
r'\\192.168.1.28\share\测试_202406\0723\0723_2',
# r'\\192.168.1.28\share\测试_202406\0723\0723_3',
r'\\192.168.1.28\share\测试_202406\0722\0722_01',
r'\\192.168.1.28\share\测试_202406\0722\0722_02'
]
for filepath in fplist:
one2one_test(filepath)
# for filepath in fplist:
# try:
# one2one_test(filepath)
# except Exception as e:
# print(f'{filepath}, Error: {e}')
if __name__ == '__main__':
main()

View File

@ -0,0 +1,806 @@
# -*- coding: utf-8 -*-
"""
Created on Tue Apr 16 11:51:07 2024
@author: ym
"""
import cv2
import os
import numpy as np
# import time
import pickle
import json
# import matplotlib.pyplot as plt
import pandas as pd
import shutil
import random
import math
import sys
from scipy.spatial.distance import cdist
import matplotlib.pyplot as plt
from pathlib import Path
from utils.gen import Profile
sys.path.append(r"D:\DetectTracking\tracking")
from dotrack.dotracks_back import doBackTracks
from dotrack.dotracks_front import doFrontTracks
from utils.drawtracks import plot_frameID_y2, draw_all_trajectories
# from utils.drawtracks import draw5points, drawTrack, drawtracefeat, drawFeatures
# from datetime import datetime
from utils.mergetrack import readDict
import csv
def read_csv_file():
file_path = r'D:\DeepLearning\yolov5_track\tracking\matching\featdata\Similarity.csv'
with open(file_path, mode='r', newline='') as file:
data = list(csv.reader(file))
matrix = []
for i in range(1, len(data)):
matrix.append(data[i][1:])
matrix = np.array(matrix, dtype = np.float32)
simil = 1 + (matrix-1)/2
print("done!!!")
def get_img_filename(imgpath = r'./matching/images/' ):
imgexts = ['.png', '.jpg', '.jpeg']
ImgFileList = []
for root, dirs, files in os.walk(imgpath):
ImgList = []
for file in files:
_, ext = os.path.splitext(file)
if ext in imgexts:
ImgFileList.append(os.path.join(root, file))
return ImgFileList
def calculate_similarity_track(similarmode = 'other'):
'''
similarmode:
'mean'
'other'
'''
ContrastDict = np.load('./matching/featdata/imgs_feats_data_refined.pkl', allow_pickle=True)
print(f"The Num of imgsample: {len(ContrastDict)}")
''''================= 构造空字典 MatchObjDict ================='''
def splitkey(key):
videoname = key.split('_')
BarCode = videoname[0]
SampleTime = videoname[1].split('-')[1]
CameraType = videoname[2]
ActionType = videoname[3]
TrackID = '_'.join(key.split('_')[7:])
return BarCode, SampleTime, CameraType, ActionType, TrackID
MatchObjList = []
CameraList = []
for key in ContrastDict.keys():
BarCode, SampleTime, CameraType, ActionType, FeatureID = splitkey(key)
MatchObjList.append('_'.join([BarCode, SampleTime, ActionType]))
CameraList.append(CameraType)
# MatchObjSet = set(MatchObjList)
# CameraSet = set(CameraList)
objects = list(set(MatchObjList))
cameras = list(set(CameraList))
assert len(cameras) == 2, "The num of cameras is error!"
MatchObjDict = {}
for obj in objects:
CameraDict = {}
for camera in cameras:
CameraDict[camera] = {}
MatchObjDict[obj] = CameraDict
for key, value in ContrastDict.items():
BarCode, SampleTime, CameraType, ActionType, FeatureID = splitkey(key)
MatchObj = '_'.join([BarCode, SampleTime, ActionType])
vdict = {}
if FeatureID not in MatchObjDict[MatchObj][CameraType]:
vdict[FeatureID] = value['feature']
MatchObjDict[MatchObj][CameraType].update(vdict)
print(f"The Num of MatchObjDict: {len(MatchObjDict)}")
# MatchKeys = [key for key in MatchObjDict.keys()]
num = len(objects)
GtMatrix = np.zeros((num, num), dtype=np.float32)
Similarity = np.zeros((num, num), dtype=np.float32)
InterMatrix = np.zeros((num, num), dtype=np.float32) # 类间
IntraMatrix = np.zeros((num, num), dtype=np.float32) # 类内
'''生成GT矩阵: GtMatrix, IntraMatrix, InterMatrix'''
for i, obi in enumerate(objects):
barcode_i = obi.split('_')[0]
for j, obj in enumerate(objects):
barcode_j = obj.split('_')[0]
if barcode_i == barcode_j:
GtMatrix[i, j] = 1
if i!=j: IntraMatrix[i, j] = 1
else:
GtMatrix[i, j] = 0
InterMatrix[i, j] = 1
'''生成相似度矩阵: Similarity '''
ObjFeatList = []
for i, obi in enumerate(objects):
obidict = MatchObjDict[obi]
camlist = []
for camera in obidict.keys():
featlist = []
for fid in obidict[camera].keys():
featlist.append(MatchObjDict[obi][camera][fid])
camlist.append(featlist)
ObjFeatList.append(camlist)
Similarity_1 = Similarity.copy()
for i in range(len(objects)):
obi = ObjFeatList[i]
for j in range(len(objects)):
obj = ObjFeatList[j]
simival = []
for ii in range(len(obi)):
if len(obi[ii])==0: continue
feat_ii = np.asarray(obi[ii])
for jj in range(len(obj)):
if len(obj[jj])==0: continue
feat_jj = np.asarray(obj[jj])
if similarmode == 'mean':
featii = np.mean(feat_ii, axis=0)
featjj = np.mean(feat_jj, axis=0)
try:
matrix = 1- np.maximum(0.0, cdist(featii[None, :], featjj[None, :], 'cosine'))
except Exception as e:
print(f'error is {e.__class__.__name__}')
else:
matrix = 1- np.maximum(0.0, cdist(feat_ii, feat_jj, 'cosine'))
simival.append(np.max(matrix))
if len(simival)==0: continue
Similarity[i, j] = max(simival)
# feat_i = np.empty((0, 256), dtype = np.float32)
# feat_j = np.empty((0, 256), dtype = np.float32)
# for ii in range(len(obi)):
# feat_ii = np.asarray(obi[ii])
# feat_i = np.concatenate((feat_i, feat_ii), axis=0)
# for jj in range(len(obj)):
# feat_jj = np.asarray(obi[jj])
# feat_j = np.concatenate((feat_j, feat_jj), axis=0)
# if similarmode == 'mean':
# feati = np.mean(feat_i, axis=0)
# featj = np.mean(feat_j, axis=0)
# matrix = 1- np.maximum(0.0, cdist(feati[None, :], featj[None, :], 'cosine'))
# else:
# matrix = 1- np.maximum(0.0, cdist(feat_i, feat_j, 'cosine'))
# Similarity_1[i, j] = np.max(matrix)
SimiDict = {'keys': objects, 'GtMatrix': GtMatrix, 'Similarity': Similarity,
'IntraMatrix':IntraMatrix, 'InterMatrix':InterMatrix}
with open(r"./matching/featdata/MatchDict_track.pkl", "wb") as f:
pickle.dump(SimiDict, f)
# SimiDict_1 = {'keys': objects, 'GtMatrix': GtMatrix, 'Similarity':Similarity_1,
# 'IntraMatrix':IntraMatrix, 'InterMatrix':InterMatrix}
# with open(r"./matching/featdata/MatchDict_track_1.pkl", "wb") as f:
# pickle.dump(SimiDict_1, f)
df_GtMatrix = pd.DataFrame(data=GtMatrix, columns = objects, index = objects)
df_GtMatrix.to_csv('./matching/featdata/GtMatrix_track.csv',index=True)
df_similarity = pd.DataFrame(data=Similarity, columns = objects, index = objects)
df_similarity.to_csv('./matching/featdata/Similarity_track.csv',index=True)
# df_similarity_1 = pd.DataFrame(data=Similarity_1, columns = objects, index = objects)
# df_similarity_1.to_csv('./matching/featdata/Similarity_track_1.csv',index=True)
print("Done!!!!")
# SimilarMode = ['mean', 'max']
def calculate_similarity(similarmode = 'mean'):
ContrastDict = np.load('./matching/featdata/imgs_feats_data_noplane.pkl', allow_pickle=True)
print(f"The Num of imgsample: {len(ContrastDict)}")
FrontBackMerged = True
TracKeys = {}
for key, value in ContrastDict.items():
feature = value['feature']
videoname = key.split('_')[:7]
BarCode = videoname[0]
SampleTime = videoname[1].split('-')[1]
CameraType = videoname[2]
ActionType = videoname[3]
TrackID = key.split('_')[7]
if FrontBackMerged:
TracKey = '_'.join([BarCode, SampleTime, ActionType])
else:
TracKey = '_'.join([BarCode, SampleTime, CameraType, ActionType])
if TracKey in TracKeys:
TracKeys[TracKey].append(feature)
else:
TracKeys[TracKey] = []
TracKeys[TracKey].append(feature)
'''===== 生成GT矩阵: Similarity、GtMatrix、IntraMatrix、InterMatrix ====='''
num = len(TracKeys)
keys = [key for key in TracKeys.keys()]
GtMatrix = np.zeros((num, num), dtype=np.float32)
Similarity = np.zeros((num, num), dtype=np.float32)
InterMatrix = np.zeros((num, num), dtype=np.float32) # 类间
IntraMatrix = np.zeros((num, num), dtype=np.float32) # 类内
for i, key_i in enumerate(keys):
barcode_i = key_i.split('_')[0]
feat_i = np.asarray(TracKeys[key_i], dtype=np.float32)
for j, key_j in enumerate(keys):
barcode_j = key_j.split('_')[0]
feat_j = np.asarray(TracKeys[key_j], dtype=np.float32)
if similarmode == 'mean':
feati = np.mean(feat_i, axis=0)
featj = np.mean(feat_j, axis=0)
matrix = 1- np.maximum(0.0, cdist(feati[None, :], featj[None, :], 'cosine'))
else:
matrix = 1- np.maximum(0.0, cdist(feat_i, feat_j, 'cosine'))
Similarity[i, j] = np.max(matrix)
if barcode_i == barcode_j:
GtMatrix[i, j] = 1
if i!=j: IntraMatrix[i, j] = 1
else:
GtMatrix[i, j] = 0
InterMatrix[i, j] = 1
# =============================================================================
# '''生成相似度矩阵: Similarity '''
# for i, key_i in enumerate(keys):
# feat_i = np.asarray(TracKeys[key_i], dtype=np.float32)
# for j, key_j in enumerate(keys):
# feat_j = np.asarray(TracKeys[key_j], dtype=np.float32)
#
# if similarmode == 'mean':
# feati = np.mean(feat_i, axis=0)
# featj = np.mean(feat_j, axis=0)
# matrix = 1- np.maximum(0.0, cdist(feati[None, :], featj[None, :], 'cosine'))
# else:
# matrix = 1- np.maximum(0.0, cdist(feat_i, feat_j, 'cosine'))
# Similarity[i, j] = np.max(matrix)
# =============================================================================
MatchDict = {'keys': keys, 'GtMatrix':GtMatrix, 'Similarity':Similarity,
'IntraMatrix':IntraMatrix, 'InterMatrix':InterMatrix}
with open(r"./matching/featdata/MatchDict_noplane.pkl", "wb") as f:
pickle.dump(MatchDict, f)
df_GtMatrix = pd.DataFrame(data=GtMatrix, columns = keys, index = keys)
df_GtMatrix.to_csv('./matching/featdata/GtMatrix_noplane.csv',index=True)
df_similarity = pd.DataFrame(data=Similarity, columns = keys, index = keys)
df_similarity.to_csv('./matching/featdata/Similarity_noplane.csv',index=True)
def sortN_matching(filename = r'./matching/featdata/MatchDict.pkl'):
SimilarDict = np.load(filename, allow_pickle=True)
'''********** keys的顺序与Similarity中行列值索引一一对应 **********'''
keys = SimilarDict['keys']
Similarity = SimilarDict['Similarity']
'''1. 将时间根据 Barcode 归并,并确保每个 Barcode 下至少两个事件'''
BarcodeDict1 = {}
for i, key in enumerate(keys):
barcode = key.split('_')[0]
if barcode not in BarcodeDict1.keys():
BarcodeDict1[barcode] = []
BarcodeDict1[barcode].append(i)
BarcodeDict = {}
BarcodeList = []
for barcode, value in BarcodeDict1.items():
if len(value) < 2: continue
BarcodeDict[barcode] = value
BarcodeList.append(barcode)
BarcodeList = list(set(BarcodeList))
'''实验参数设定
N: 任意选取的 Barcode 数
R重复实验次数每次从同一 Barcode 下随机选取2个事件分别归入加购、退购集合
Thresh相似度阈值
'''
N = 10
if N > len(BarcodeList):
N = math.ceil(len(BarcodeList)/2)
R = 20
Thresh = np.linspace(0.1, 1, 100)
# Thresh = np.linspace(0.601, 0.7, 100)
Recall, Precision = [], []
for th in Thresh:
recall = np.zeros((1, R), dtype=np.float32)
precision = np.zeros((1, R), dtype=np.float32)
for rep in range(R):
BarcodeSelect = random.sample(BarcodeList, N)
AddDict = {}
TakeoutDict = {}
for barcode in BarcodeSelect:
barlist = BarcodeDict[barcode]
if len(barlist) < 2:continue
selected = random.sample(barlist, 2)
AddDict[barcode] = selected[0]
TakeoutDict[barcode] = selected[1]
OrderMatrix = np.zeros((N, N), dtype=np.float32)
GTMatrix = np.zeros((N, N), dtype=np.float32)
MatchMatrix_1 = np.zeros((N, N), dtype=np.float32)
MatchMatrix_2 = np.zeros((N, N), dtype=np.float32)
i = 0
for keyi in BarcodeSelect:
ii = TakeoutDict[keyi]
j = 0
for keyj in BarcodeSelect:
jj = AddDict[keyj]
OrderMatrix[i, j] = Similarity[int(ii), int(jj)]
if keyi == keyj:
GTMatrix[i, j] = 1
j += 1
i += 1
max_indices = np.argmax(OrderMatrix, axis = 1)
for i in range(N):
MatchMatrix_1[i, max_indices[i]] = 1
similar = OrderMatrix[i, max_indices[i]]
if similar > th:
MatchMatrix_2[i, max_indices[i]] = 1
GT_indices = np.where(GTMatrix == 1)
FNTP = MatchMatrix_2[GT_indices]
pred_indices = np.where(MatchMatrix_2 == 1)
TP = np.sum(FNTP==1)
FN = np.sum(FNTP==0)
FPTP = GTMatrix[pred_indices]
FP = np.sum(FPTP == 0)
# assert TP == np.sum(FPTP == 0), "Please Check Errors!!!"
recall[0, rep] = TP/(TP+FN)
precision[0, rep] = TP/(TP+FP+1e-3) # 阈值太大时可能TP、FP都为0
Recall.append(recall)
Precision.append(precision)
Recall = np.asarray(Recall).reshape([len(Thresh),-1])
Precision = np.asarray(Precision).reshape([len(Thresh),-1])
reclmean = np.sum(Recall, axis=1) / (np.count_nonzero(Recall, axis=1) + 1e-3)
precmean = np.sum(Precision, axis=1) / (np.count_nonzero(Precision, axis=1) + 1e-3)
print("Done!!!!!")
# th1, recl = [c[0] for c in Recall], [c[1] for c in Recall]
# th2, prep = [c[0] for c in Precision], [c[1] for c in Precision]
recl = [r for r in reclmean]
prep = [p for p in precmean]
'''================= Precision & Recall ================='''
fig, ax = plt.subplots()
ax.plot(Thresh, recl, 'g', label='Recall = TP/(TP+FN)')
ax.plot(Thresh, prep, 'r', label='PrecisePos = TP/(TP+FP)')
# ax.set_xlim([0, 1])
# ax.set_ylim([0, 1])
ax.grid(True)
ax.set_title('Precision & Recall')
ax.legend()
plt.show()
def match_evaluate(filename = r'./matching/featdata/MatchDict.pkl'):
SimiDict = np.load(filename, allow_pickle=True)
keys = SimiDict['keys']
GtMatrix = SimiDict['GtMatrix']
Similarity = SimiDict['Similarity']
IntraMatrix = SimiDict['IntraMatrix']
InterMatrix = SimiDict['InterMatrix']
BarcodeList = []
for key in keys:
BarcodeList.append(key.split('_')[0])
BarcodeList = list(set(BarcodeList))
IntraRows, IntraCols = np.nonzero(IntraMatrix)
InterRows, InterCols = np.nonzero(InterMatrix)
IntraN, InterN = len(IntraRows), len(InterRows)
assert IntraN <= InterN, "类内大于类间数,样本不平衡"
InterNSelect = IntraN
Thresh = np.linspace(0.1, 1, 100)
# Thresh = np.linspace(0.2, 0.4, 11)
Correct = []
PrecisePos = []
PreciseNeg = []
Recall = []
CorrectMatries = []
for th in Thresh:
MatchMatrix = Similarity > th
CorrectMatrix = MatchMatrix == GtMatrix
CorrectMatries.append(CorrectMatrix)
nn = np.random.permutation(np.arange(InterN))[:InterNSelect]
InterRowsSelect, InterColsSelect = InterRows[nn], InterCols[nn]
IntraCorrMatrix = CorrectMatrix[IntraRows, IntraCols]
InterCorrMatrix = CorrectMatrix[InterRowsSelect, InterColsSelect]
TP = np.sum(IntraCorrMatrix)
TN = np.sum(InterCorrMatrix)
FN = IntraN - TP
FP = InterNSelect - TN
if TP+FP > 0:
PrecisePos.append((th, TP/(TP+FP)))
if TN+FN > 0:
PreciseNeg.append((th, TN/(TN+FN)))
if TP+FN > 0:
Recall.append((th, TP/(TP+FN)))
if TP+TN+FP+FN > 0:
Correct.append((th, (TP+TN)/(TP+TN+FP+FN)))
# print(f'Th: {th}')
# print(f'TP:{TP}, FP:{FP}, TN:{TN}, FN:{FN}')
CorrectMatries = np.asarray(CorrectMatries)
'''====================== 分析错误原因 =========================='''
'''
keys两种构成方式其中的元素来自于MatchingDict
BarCode, SampleTime, ActionType #不考虑摄像头类型(前后摄)
BarCode, SampleTime, CameraType, ActionType# 考虑摄像头类型(前后摄)
为了便于显示,在图像文件名中,将 ActionType 进行了缩写,匹配时取 [:3]
"addGood" --------> "add"
"returnGood" --------> "return"
'''
##============= 获取图像存储位置,可以通过 keys 检索到对应的图像文件
imgpath = r'./matching/images/'
ImgFileList = get_img_filename(imgpath)
rowx, colx = np.where(CorrectMatries[66,:,:] == False)
rows, cols = [], []
for i in range(len(rowx)):
ri, ci = rowx[i], colx[i]
if ci > ri:
rows.append(ri)
cols.append(ci)
KeysError = [(keys[rows[i]], keys[cols[i]]) for i in range(len(rows))]
SimiScore = [Similarity[rows[i], cols[i]] for i in range(len(rows))]
for i, keykey in enumerate(KeysError):
key1, key2 = keykey
sscore = SimiScore[i]
kt1, kt2 = key1.split('_'), key2.split('_')
if len(kt1)==3 and len(kt2)==3:
file1 = [f for f in ImgFileList if kt1[0] in f and kt1[1] in f and kt1[2][:3] in f]
file2 = [f for f in ImgFileList if kt2[0] in f and kt2[1] in f and kt2[2][:3] in f]
elif len(kt1)==4 and len(kt1)==4:
file1 = [f for f in ImgFileList if kt1[0] in f and kt1[1] in f and kt1[2] in f and kt1[3][:3] in f]
file2 = [f for f in ImgFileList if kt2[0] in f and kt2[1] in f and kt2[2] in f and kt2[3][:3] in f]
else:
pass
if len(file1)==0 or len(file2)==0:
continue
if kt1[0] == kt2[0]:
gt = "same"
else:
gt = "diff"
path = Path(f'./matching/results/{i}_{gt}_{sscore:.2f}')
if path.exists() and path.is_dir():
shutil.rmtree(path)
path1, path2 = path.joinpath(key1), path.joinpath(key2)
path1.mkdir(parents=True, exist_ok=True)
path2.mkdir(parents=True, exist_ok=True)
for file in file1:
shutil.copy2(file, path1)
for file in file2:
shutil.copy2(file, path2)
if i==99:
break
th1, corr = [c[0] for c in Correct], [c[1] for c in Correct]
th2, recl = [c[0] for c in Recall], [c[1] for c in Recall]
th3, prep = [c[0] for c in PrecisePos], [c[1] for c in PrecisePos]
th4, pren = [c[0] for c in PreciseNeg], [c[1] for c in PreciseNeg]
'''================= Correct ==================='''
fig, ax = plt.subplots()
ax.plot(th1, corr, 'b', label='Correct = (TP+TN)/(TP+TN+FP+FN)')
max_corr = max(corr)
max_index = corr.index(max_corr)
max_thresh = th1[max_index]
ax.plot([0, max_thresh], [max_corr, max_corr], 'r--')
ax.plot([max_thresh, max_thresh], [0, max_corr], 'r--')
ax.set_xlim([0, 1])
ax.set_ylim([0, 1])
ax.grid(True)
ax.set_title('Correct')
ax.legend()
plt.show()
'''================= PrecisePos & PreciseNeg & Recall ================='''
fig, ax = plt.subplots()
ax.plot(th2, recl, 'g', label='Recall = TP/(TP+FN)')
ax.plot(th3, prep, 'c', label='PrecisePos = TP/(TP+FP)')
ax.plot(th4, pren, 'm', label='PreciseNeg = TN/(TN+FN)')
ax.set_xlim([0, 1])
ax.set_ylim([0, 1])
ax.grid(True)
ax.set_title('PrecisePos & PreciseNeg')
ax.legend()
plt.show()
def have_tracked():
trackdir = r"./data/tracks"
# =============================================================================
# FileList = []
# with open(r'./matching/视频分类/单.txt', 'r') as file:
# lines = file.readlines()
# for line in lines:
# file = line.split('.')[0]
# FileList.append(file)
# FileList = list(set(FileList))
# =============================================================================
MatchingDict = {}
k, gt = 0, Profile()
for filename in os.listdir(trackdir):
file, ext = os.path.splitext(filename)
# if file not in FileList: continue
if file.find('20240508')<0: continue
filepath = os.path.join(trackdir, filename)
tracksDict = np.load(filepath, allow_pickle=True)
bboxes = tracksDict['TrackBoxes']
with gt:
if filename.find("front") >= 0:
vts = doFrontTracks(bboxes, tracksDict)
vts.classify()
elif filename.find("back") >= 0:
vts = doBackTracks(bboxes, tracksDict)
vts.classify()
print(file+f" need time: {gt.dt:.2f}s")
elements = file.split('_')
assert len(elements) == 7, f"{filename} fields num: {len(elements)}"
BarCode = elements[0]
## ====================================== 只用于在images文件夹下保存图片
SampleTime = elements[1].split('-')[1]
CameraType = elements[2]
if elements[3]=="addGood":
ActionType = "add"
elif elements[3]=="returnGood":
ActionType = "return"
else:
ActionType = "x"
subimg_dir = Path(f'./matching/images/{BarCode}_{SampleTime}_{ActionType}/')
if not subimg_dir.exists():
subimg_dir.mkdir(parents=True, exist_ok=True)
# boxes: [x1, y1, x2, y2, track_id, score, cls, frame_index, box_index]
# 0, 1, 2, 3, 4, 5, 6, 7, 8
for track in vts.Residual:
boxes = track.boxes
for i in range(boxes.shape[0]):
box = boxes[i, :]
tid, fid, bid = int(box[4]), int(box[7]), int(box[8])
feat_dict = tracksDict[fid]
feature = feat_dict[bid]
img = feat_dict[f'{bid}_img']
sub_img_file = subimg_dir.joinpath(f"{BarCode}_{SampleTime}_{CameraType}_{ActionType}_{tid}_{fid}_{bid}.png")
cv2.imwrite(str(sub_img_file), img)
condict = {f"{file}_{tid}_{fid}_{bid}": {'img': img, 'feature': feature}}
MatchingDict.update(condict)
# k += 1
# if k == 100:
# break
featpath = Path('./matching/featdata/')
if not featpath.exists():
featpath.mkdir(parents=True, exist_ok=True)
featdata = featpath.joinpath('imgs_feats_data_noplane.pkl')
with open(featdata, 'wb') as file:
pickle.dump(MatchingDict, file)
def imgsample_cleaning():
ContrastDict = np.load('./matching/featdata/imgs_feats_data.pkl', allow_pickle=True)
print(f"The Num of imgsample: {len(ContrastDict)}")
MatchingDict_refined = {}
for filename, value in ContrastDict.items():
elements = filename.split('_')
tid = elements[7]
fid = elements[8]
bid = elements[9]
BarCode = elements[0]
SampleTime = elements[1].split('-')[1]
CameraType = elements[2]
if elements[3]=="addGood":
ActionType = "add"
elif elements[3]=="returnGood":
ActionType = "return"
else:
ActionType = "x"
refimgdir = f'.\matching\images_refined\{BarCode}_{SampleTime}_{ActionType}'
file = '_'.join(elements[0:7])
if os.path.exists(refimgdir) and os.path.isdir(refimgdir):
imgpath = os.path.join(refimgdir, f"{BarCode}_{SampleTime}_{CameraType}_{ActionType}_{tid}_{fid}_{bid}.png")
if os.path.isfile(imgpath):
condict = {f"{file}_{tid}_{fid}_{bid}": value}
MatchingDict_refined.update(condict)
featpath = Path('./matching/featdata/')
if not featpath.exists():
featpath.mkdir(parents=True, exist_ok=True)
featdata = featpath.joinpath('imgs_feats_data_refined.pkl')
with open(featdata, 'wb') as file:
pickle.dump(MatchingDict_refined, file)
print(f"The Num of ContrastDict: {len(ContrastDict)}")
print(f"The Num of MatchingDict_refined: {len(MatchingDict_refined)}")
print(f"The Num of cleaned img: {len(ContrastDict)} - {len(MatchingDict_refined)}")
def main():
'''1. 提取运动商品轨迹'''
# have_tracked()
'''2. 清除一次事件中包含多件商品的事件'''
# imgsample_cleaning()
'''3.1 计算事件间相似度: 将 front、back 的所有 track 特征合并'''
calculate_similarity()
'''3.2 计算事件间相似度: 考虑前后摄的不同组合,或 track 间的不同组合'''
# calculate_similarity_track()
'''4.1 事件间匹配的总体性能评估'''
filename = r'./matching/featdata/MatchDict_plane.pkl'
match_evaluate(filename)
filename = r'./matching/featdata/MatchDict_noplane.pkl'
match_evaluate(filename)
'''4.2 模拟实际场景任选N件作为一组作为加购取出其中一件时的性能评估'''
# filename = r'./matching/featdata/MatchDict_refined.pkl'
# sortN_matching(filename)
if __name__ == "__main__":
# save_dir = Path(f'./result/')
# read_csv_file()
main()

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,721 @@
# -*- coding: utf-8 -*-
"""
Created on Mon Mar 4 18:16:01 2024
@author: ym
"""
import numpy as np
import cv2
from pathlib import Path
from scipy.spatial.distance import cdist
from tracking.utils.mergetrack import track_equal_track, readDict
curpath = Path(__file__).resolve().parents[0]
curpath = Path(curpath)
parpath = curpath.parent
class MoveState:
"""商品运动状态标志"""
Static = 0
DownWard = 1
UpWard = 2
FreeMove = 3
Unknown = -1
def bbox_ioa(box1, box2, iou=False, eps=1e-7):
"""
Calculate the intersection over box2 area given box1 and box2. Boxes are in x1y1x2y2 format.
Args:
box1 (np.array): A numpy array of shape (n, 4) representing n bounding boxes.
box2 (np.array): A numpy array of shape (m, 4) representing m bounding boxes.
iou (bool): Calculate the standard iou if True else return inter_area/box2_area.
eps (float, optional): A small value to avoid division by zero. Defaults to 1e-7.
Returns:
(np.array): A numpy array of shape (n, m) representing the intersection over box2 area.
"""
# Get the coordinates of bounding boxes
b1_x1, b1_y1, b1_x2, b1_y2 = box1.T
b2_x1, b2_y1, b2_x2, b2_y2 = box2.T
# Intersection area
inter_area = (np.minimum(b1_x2[:, None], b2_x2) - np.maximum(b1_x1[:, None], b2_x1)).clip(0) * \
(np.minimum(b1_y2[:, None], b2_y2) - np.maximum(b1_y1[:, None], b2_y1)).clip(0)
# box2 area
area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1)
if iou:
box1_area = (b1_x2 - b1_x1) * (b1_y2 - b1_y1)
area = area + box1_area[:, None] - inter_area
# Intersection over box2 area
return inter_area / (area + eps)
class ShoppingCart:
def __init__(self, bboxes):
self.bboxes = bboxes
self.loadrate = self.load_rate()
def load_rate(self):
bboxes = self.bboxes
fid = min(bboxes[:, 7])
idx = bboxes[:, 7] == fid
boxes = bboxes[idx]
temp = np.zeros(self.incart.shape, np.uint8)
for i in range(boxes.shape[0]):
x1, y1, x2, y2, tid = boxes[i, 0:5]
cv2.rectangle(temp, (int(x1), int(y1)), (int(x2), int(y2)), 255, cv2.FILLED)
'''1. and 滤除购物车边框外的干扰'''
loadstate = cv2.bitwise_and(self.incart, temp)
'''2. xor 得到购物车内内被填充的区域'''
# loadstate = cv2.bitwise_xor(self.incart, temp1)
num_loadstate = cv2.countNonZero(loadstate)
num_incart = cv2.countNonZero(self.incart)
loadrate = num_loadstate / (num_incart+0.01)
# edgeline = cv2.imread("./shopcart/cart_tempt/edgeline.png", cv2.IMREAD_GRAYSCALE)
# cv2.imwrite(f"./test/temp.png", cv2.add(temp, edgeline))
# cv2.imwrite(f"./test/incart.png", cv2.add(self.incart, edgeline))
# cv2.imwrite(f"./test/loadstate.png", cv2.add(loadstate, edgeline))
return loadrate
@property
def incart(self):
img = cv2.imread(str(parpath/'shopcart/cart_tempt/incart.png'), cv2.IMREAD_GRAYSCALE)
ret, binary = cv2.threshold(img, 250, 255, cv2.THRESH_BINARY)
return binary
@property
def outcart(self):
img = cv2.imread(str(parpath/'shopcart/cart_tempt/outcart.png'), cv2.IMREAD_GRAYSCALE)
ret, binary = cv2.threshold(img, 250, 255, cv2.THRESH_BINARY)
return binary
@property
def cartedge(self):
img = cv2.imread(str(parpath/'shopcart/cart_tempt/cartedge.png'), cv2.IMREAD_GRAYSCALE)
ret, binary = cv2.threshold(img, 250, 255, cv2.THRESH_BINARY)
return binary
class Track:
'''抽象基类,不能实例化对象'''
def __init__(self, boxes, features=None, imgshape=(1024, 1280)):
'''
boxes: [x1, y1, x2, y2, track_id, score, cls, frame_index, box_index]
0 1 2 3 4 5 6 7 8
'''
# assert len(set(boxes[:, 4].astype(int))) == 1, "For a Track, track_id more than 1"
# assert len(set(boxes[:, 6].astype(int))) == 1, "For a Track, class number more than 1"
self.boxes = boxes
self.features = features
self.slt_boxes = self.select_boxes()
self.tid = int(boxes[0, 4])
self.cls = int(boxes[0, 6])
self.frnum = boxes.shape[0]
self.isCornpoint = False
self.imgshape = imgshape
# self.isBorder = False
# self.state = MoveState.Unknown
'''轨迹开始帧、结束帧 ID'''
# self.start_fid = int(np.min(boxes[:, 7]))
# self.end_fid = int(np.max(boxes[:, 7]))
''''''
self.Hands = []
self.HandsIou = []
self.Goods = []
self.GoodsIou = []
'''5个关键点中心点、左上点、右上点、左下点、右下点 )坐标'''
self.compute_cornpoints()
'''5个关键点轨迹特征可以在子类中实现降低顺序处理时的计算量
(中心点、左上点、右上点、左下点、右下点 )轨迹特征'''
self.compute_cornpts_feats()
'''应计算各个角点面积、平均面积'''
mw, mh = np.mean(boxes[:, 2]-boxes[:, 0]), np.mean((boxes[:, 3]-boxes[:, 1]))
self.mwh = np.mean((mw, mh))
self.Area = mw * mh
'''
最后一帧与第一帧间的位移:
vshift: 正值为向下,负值为向上
hshift: 负值为向购物车边框两边移动,正值为物品向中心移动
'''
self.vshift = self.cornpoints[-1, 1] - self.cornpoints[0, 1] # 纵向位移
self.hshift = abs(self.cornpoints[0, 0] - self.imgshape[0]/2) - \
abs(self.cornpoints[-1, 0] - self.imgshape[0]/2)
'''手部状态分析'''
self.HAND_STATIC_THRESH = 100
if self.cls == 0:
self.extract_hand_features()
def select_boxes(self):
slt_boxes = []
idx = np.argsort(self.boxes[:, 7])
boxes = self.boxes[idx]
features = self.features[idx]
for i in range(len(boxes)):
simi = None
box, tid, fid, bid = boxes[i, :4], int(boxes[i, 4]), int(boxes[i, 7]), int(boxes[i, 8])
if i == 0:
slt_boxes.append(boxes[i, :])
continue
if len(boxes)!=len(features):
print("check!")
continue
box0, tid0, fid0, bid0 = boxes[i-1, :4], int(boxes[i-1, 4]), int(boxes[i-1, 7]), int(boxes[i-1, 8])
# 当前 box 和轨迹上一个 box 的iou
iou = bbox_ioa(box[None, :], box0[None, :])
# 当前 box 和轨迹上一个 box 的 feat similarity
feat0 = features[i, :][None, :]
feat1 = features[i-1, :][None, :]
simi = 1 - np.maximum(0.0, cdist(feat0, feat1, "cosine"))[0][0]
if iou > 0.85 and simi>0.85:
continue
slt_boxes.append(boxes[i, :])
return np.array(slt_boxes)
def compute_cornpoints(self):
'''
cornpoints 共10项分别是个点的坐标值x, y
(center, top_left, top_right, bottom_left, bottom_right)
'''
boxes = self.boxes
cornpoints = np.zeros((self.frnum, 10))
cornpoints[:,0] = (boxes[:, 0] + boxes[:, 2]) / 2
cornpoints[:,1] = (boxes[:, 1] + boxes[:, 3]) / 2
cornpoints[:,2], cornpoints[:,3] = boxes[:, 0], boxes[:, 1]
cornpoints[:,4], cornpoints[:,5] = boxes[:, 2], boxes[:, 1]
cornpoints[:,6], cornpoints[:,7] = boxes[:, 0], boxes[:, 3]
cornpoints[:,8], cornpoints[:,9] = boxes[:, 2], boxes[:, 3]
self.cornpoints = cornpoints
def compute_cornpts_feats(self):
'''
'''
# print(f"TrackID: {self.tid}")
trajectory = []
trajlens = []
trajdist = []
trajrects = []
trajrects_wh = []
for k in range(5):
# diff_xy2 = np.power(np.diff(self.cornpoints[:, 2*k:2*(k+1)], axis = 0), 2)
# trajlen = np.sum(np.sqrt(np.sum(diff_xy2, axis = 1)))
X = self.cornpoints[:, 2*k:2*(k+1)]
traj = np.linalg.norm(np.diff(X, axis=0), axis=1)
trajectory.append(traj)
trajlen = np.sum(traj)
trajlens.append(trajlen)
ptdist = np.max(cdist(X, X))
trajdist.append(ptdist)
'''最小外接矩形:
rect[0]: 中心(x, y)
rect[1]: (w, h)
rect[0]: 旋转角度 (-90°, 0]
'''
rect = cv2.minAreaRect(X.astype(np.int64))
rect_wh = max(rect[1])
trajrects_wh.append(rect_wh)
trajrects.append(rect)
self.trajectory = trajectory
self.trajlens = trajlens
self.trajdist = trajdist
self.trajrects = trajrects
self.trajrects_wh = trajrects_wh
def trajfeature(self):
'''
分两种情况计算轨迹特征(检测框边界不在图像边界范围内,在图像边界范围内):
-最小长度轨迹trajmin
-最小轨迹长度trajlen_min
-最小轨迹欧氏距离trajdist_max
'''
# idx1 = self.trajlens.index(max(self.trajlens))
idx1 = self.trajrects_wh.index(max(self.trajrects_wh))
trajmax = self.trajectory[idx1]
trajlen_max = self.trajlens[idx1]
trajdist_max = self.trajdist[idx1]
if not self.isCornpoint:
# idx2 = self.trajlens.index(min(self.trajlens))
idx2 = self.trajrects_wh.index(min(self.trajrects_wh))
trajmin = self.trajectory[idx2]
trajlen_min = self.trajlens[idx2]
trajdist_min = self.trajdist[idx2]
else:
trajmin = self.trajectory[0]
trajlen_min = self.trajlens[0]
trajdist_min = self.trajdist[0]
'''最小轨迹长度/最大轨迹长度,越小,代表运动幅度越小'''
trajlen_rate = trajlen_min/(trajlen_max+0.0001)
'''最小轨迹欧氏距离/目标框尺度均值'''
trajdist_rate = trajdist_min/(self.mwh+0.0001)
self.trajmin = trajmin
self.trajmax = trajmax
self.TrajFeat = [trajlen_min, trajlen_max,
trajdist_min, trajdist_max,
trajlen_rate, trajdist_rate]
def pt_state_fids(self, det_y, STATIC_THRESH = 8):
'''
前摄时y一般选择为 box 的 y1 坐标,且需限定商品在购物车内。
inputs
y1D array
parameters
STATIC_THRESH轨迹处于静止状态的阈值。
outputs
输出为差分值小于 STATIC_THRESH 的y中元素的start, end索引
ranges = [(x1, y1),
(x1, y1),
...]
'''
# print(f"The ID is: {self.tid}")
# det_y = np.diff(y, axis=0)
ranges, rangex = [], []
static_indices = np.where(np.abs(det_y) < STATIC_THRESH)[0]
if len(static_indices) == 0:
rangex.append((0, len(det_y)))
return ranges, rangex
start_index = static_indices[0]
for i in range(1, len(static_indices)):
if static_indices[i] != static_indices[i-1] + 1:
ranges.append((start_index, static_indices[i-1] + 1))
start_index = static_indices[i]
ranges.append((start_index, static_indices[-1] + 1))
if len(ranges) == 0:
rangex.append((0, len(det_y)))
return ranges, rangex
idx1, idx2 = ranges[0][0], ranges[-1][1]
if idx1 != 0:
rangex.append((0, idx1))
# 轨迹的最后阶段是运动状态
for k in range(1, len(ranges)):
index1 = ranges[k-1][1]
index2 = ranges[k][0]
rangex.append((index1, index2))
if idx2 != len(det_y):
rangex.append((idx2, len(det_y)))
return ranges, rangex
def PositionState(self, camerType="back"):
'''
camerType: back, 后置摄像头
front, 前置摄像头
'''
if camerType=="back":
incart = cv2.imread(str(parpath/'shopcart/cart_tempt/incart.png'), cv2.IMREAD_GRAYSCALE)
outcart = cv2.imread(str(parpath/'shopcart/cart_tempt/outcart.png'), cv2.IMREAD_GRAYSCALE)
else:
incart = cv2.imread(str(parpath/'shopcart/cart_tempt/incart_ftmp.png'), cv2.IMREAD_GRAYSCALE)
outcart = cv2.imread(str(parpath/'shopcart/cart_tempt/outcart_ftmp.png'), cv2.IMREAD_GRAYSCALE)
# incart = cv2.imread('./cart_tempt/incart_ftmp.png', cv2.IMREAD_GRAYSCALE)
# outcart = cv2.imread('./cart_tempt/outcart_ftmp.png', cv2.IMREAD_GRAYSCALE)
xc, yc = self.cornpoints[:,0].clip(0,self.imgshape[0]-1).astype(np.int64), self.cornpoints[:,1].clip(0,self.imgshape[1]-1).astype(np.int64)
x1, y1 = self.cornpoints[:,6].clip(0,self.imgshape[0]-1).astype(np.int64), self.cornpoints[:,7].clip(0,self.imgshape[1]-1).astype(np.int64)
x2, y2 = self.cornpoints[:,8].clip(0,self.imgshape[0]-1).astype(np.int64), self.cornpoints[:,9].clip(0,self.imgshape[1]-1).astype(np.int64)
# print(self.tid)
Cent_inCartnum = np.count_nonzero(incart[(yc, xc)])
LB_inCartnum = np.count_nonzero(incart[(y1, x1)])
RB_inCartnum = np.count_nonzero(incart[(y2, x2)])
Cent_outCartnum = np.count_nonzero(outcart[(yc, xc)])
LB_outCartnum = np.count_nonzero(outcart[(y1, x1)])
RB_outCartnum = np.count_nonzero(outcart[(y2, x2)])
'''Track完全在车内左下角点、右下角点与 outcart 的交集为 0'''
self.isWholeInCart = False
if LB_outCartnum + RB_outCartnum == 0:
self.isWholeInCart = True
'''Track完全在车外左下角点、中心点与 incart 的交集为 0
右下角点、中心点与 incart 的交集为 0
'''
self.isWholeOutCart = False
if Cent_inCartnum + LB_inCartnum == 0 or Cent_inCartnum + RB_inCartnum == 0:
self.isWholeOutCart = True
self.Cent_isIncart = False
self.LB_isIncart = False
self.RB_isIncart = False
if Cent_inCartnum: self.Cent_isIncart = True
if LB_inCartnum: self.LB_isIncart = True
if RB_inCartnum: self.RB_isIncart = True
self.posState = self.Cent_isIncart+self.LB_isIncart+self.RB_isIncart
def is_freemove(self):
# if self.tid==4:
# print(f"track ID: {self.tid}")
# boxes = self.boxes
# features = self.features
# similars = 1 - np.maximum(0.0, cdist(self.features, self.features, metric = 'cosine'))
box1 = self.boxes[0, :4]
box2 = self.boxes[-1, :4]
''' 第1帧、最后一帧subimg的相似度 '''
feat1 = self.features[0, :][None, :]
feat2 = self.features[-1, :][None, :]
similar = 1 - np.maximum(0.0, cdist(feat1, feat2, metric = 'cosine'))
condta = similar > 0.8
''' 第1帧、最后一帧 boxes 四个角点间的距离 '''
ptd = box2 - box1
ptd1 = np.linalg.norm((ptd[0], ptd[1]))
ptd2 = np.linalg.norm((ptd[2], ptd[1]))
ptd3 = np.linalg.norm((ptd[0], ptd[3]))
ptd4 = np.linalg.norm((ptd[2], ptd[3]))
condtb = ptd1<50 and ptd2<50 and ptd3<50 and ptd4<50
condt = condta and condtb
return condt
def extract_hand_features(self):
assert self.cls == 0, "The class of traj must be HAND!"
self.isHandStatic = False
x0 = (self.boxes[:, 0] + self.boxes[:, 2]) / 2
y0 = (self.boxes[:, 1] + self.boxes[:, 3]) / 2
handXY = np.stack((x0, y0), axis=-1)
# handMaxY0 = np.max(y0)
handCenter = np.array([(max(x0)+min(x0))/2, (max(y0)+min(y0))/2])
handMaxDist = np.max(np.linalg.norm(handXY - handCenter))
if handMaxDist < self.HAND_STATIC_THRESH:
self.isHandStatic = True
return
class doTracks:
def __init__(self, bboxes, trackefeats):
'''fundamental property
trackefeats: dict, key 格式 "fid_bid"
'''
self.bboxes = bboxes
# self.TracksDict = TracksDict
self.frameID = np.unique(bboxes[:, 7].astype(int))
self.trackID = np.unique(bboxes[:, 4].astype(int))
self.lboxes = self.array2list()
self.lfeats = self.getfeats(trackefeats)
'''对 self.tracks 中的元素进行分类,将 track 归入相应列表中'''
self.Hands = []
self.Kids = []
self.Static = []
self.Residual = []
self.Confirmed = []
self.DownWard = [] # subset of self.Residual
self.UpWard = [] # subset of self.Residual
self.FreeMove = [] # subset of self.Residual
def array2list(self):
'''
将 bboxes 变换为 track 列表
bboxes: [x1, y1, x2, y2, track_id, score, cls, frame_index, box_index]
Return
lboxes列表列表中元素具有同一 track_idx1y1x2y2 格式
[x1, y1, x2, y2, track_id, score, cls, frame_index, box_index]
'''
track_ids = self.bboxes[:, 4].astype(int)
lboxes = []
for t_id in self.trackID:
# print(f"The ID is: {t_id}")
idx = np.where(track_ids == t_id)[0]
box = self.bboxes[idx, :]
assert len(set(box[:, 7])) == len(box), "Please check!!!"
lboxes.append(box)
return lboxes
def getfeats(self, trackefeats):
lboxes = self.lboxes
lfeats = []
for boxes in lboxes:
feats = []
for i in range(boxes.shape[0]):
fid, bid = int(boxes[i, 7]), int(boxes[i, 8])
key = f"{int(fid)}_{int(bid)}"
if key in trackefeats:
feats.append(trackefeats[key])
feats = np.asarray(feats, dtype=np.float32)
lfeats.append(feats)
return lfeats
def similarity(self):
nt = len(self.tracks)
similar_dict = {}
if nt >= 2:
for i in range(nt):
for j in range(i, nt):
tracka = self.tracks[i]
trackb = self.tracks[j]
similar = self.feat_similarity(tracka, trackb)
similar_dict.update({(tracka.tid, trackb.tid): similar})
return similar_dict
def feat_similarity(self, tracka, trackb, metric='cosine'):
boxes_a, boxes_b = tracka.boxes, trackb.boxes
na, nb = tracka.boxes.shape[0], trackb.boxes.shape[0]
feata, featb = [], []
for i in range(na):
fid, bid = tracka.boxes[i, 7:9]
feata.append(self.features_dict[fid][bid])
for i in range(nb):
fid, bid = trackb.boxes[i, 7:9]
featb.append(self.features_dict[fid][bid])
feata = np.asarray(feata, dtype=np.float32)
featb = np.asarray(featb, dtype=np.float32)
similarity_matrix = 1-np.maximum(0.0, cdist(feata, featb, metric))
feata_m = np.mean(feata, axis =0)[None,:]
featb_m = np.mean(featb, axis =0)[None,:]
simi_ab = 1 - cdist(feata_m, featb_m, metric)
print(f'tid {int(boxes_a[0, 4])} vs {int(boxes_b[0, 4])}: {simi_ab[0][0]}')
# return np.max(similarity_matrix)
return simi_ab
def merge_tracks_loop(self, alist):
na, nb = len(alist), 0
while na!=nb:
na = len(alist)
alist = self.merge_tracks(alist) #func is from subclass
nb = len(alist)
return alist
def base_merge_tracks(self, Residual):
"""
对不同id但可能是同一商品的目标进行归并
"""
mergedTracks = []
alist = [t for t in Residual]
while alist:
atrack = alist[0]
cur_list = []
cur_list.append(atrack)
alist.pop(0)
blist = [b for b in alist]
alist = []
for btrack in blist:
# afids = []
# for track in cur_list:
# afids.extend(list(track.boxes[:, 7].astype(np.int_)))
# bfids = btrack.boxes[:, 7].astype(np.int_)
# interfid = set(afids).intersection(set(bfids))
# if len(interfid):
# print("wait!!!")
# if track_equal_track(atrack, btrack) and len(interfid)==0:
if track_equal_track(atrack, btrack):
cur_list.append(btrack)
else:
alist.append(btrack)
mergedTracks.append(cur_list)
return mergedTracks
@staticmethod
def join_tracks(tlista, tlistb):
"""Combine two lists of stracks into a single one."""
exists = {}
res = []
for t in tlista:
exists[t.tid] = 1
res.append(t)
for t in tlistb:
tid = t.tid
if not exists.get(tid, 0):
exists[tid] = 1
res.append(t)
return res
@staticmethod
def sub_tracks(tlista, tlistb):
track_ids_b = {t.tid for t in tlistb}
return [t for t in tlista if t.tid not in track_ids_b]
def array2frame(self, bboxes):
frameID = np.sort(np.unique(bboxes[:, 7].astype(int)))
fboxes = []
for fid in frameID:
idx = np.where(bboxes[:, 7] == fid)[0]
box = bboxes[idx, :]
fboxes.append(box)
return fboxes
def isintrude(self):
'''
boxes: [x1, y1, x2, y2, track_id, score, cls, frame_index, box_index]
0 1 2 3 4 5 6 7 8
'''
OverlapNum = 3
bboxes = self.bboxes.astype(np.int64)
fboxes = self.array2frame(bboxes)
incart = cv2.bitwise_not(self.incart)
sum_incart = np.zeros(incart.shape, dtype=np.int64)
for fid, boxes in enumerate(fboxes):
for i in range(len(boxes)):
x1, y1, x2, y2 = boxes[i, 0:4]
sum_incart[y1:y2, x1:x2] += 1
sumincart = np.zeros(sum_incart.shape, dtype=np.uint8)
idx255 = np.where(sum_incart >= OverlapNum)
sumincart[idx255] = 255
idxnzr = np.where(sum_incart!=0)
base = np.zeros(sum_incart.shape, dtype=np.uint8)
base[idxnzr] = 255
contours_sum, _ = cv2.findContours(sumincart, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours_base, _ = cv2.findContours(base, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
have_existed, invasion = [], []
for k, ct_temp in enumerate(contours_base):
tmp1 = np.zeros(sum_incart.shape, dtype=np.uint8)
cv2.drawContours(tmp1, [ct_temp], -1, 255, cv2.FILLED)
# 确定轮廓的包含关系
for ct_sum in contours_sum:
tmp2 = np.zeros(sum_incart.shape, dtype=np.uint8)
cv2.drawContours(tmp2, [ct_sum], -1, 255, cv2.FILLED)
tmp = cv2.bitwise_and(tmp1, tmp2)
if np.count_nonzero(tmp) == np.count_nonzero(tmp2):
have_existed.append(k)
inIdx = [i for i in range(len(contours_base)) if i not in have_existed]
invasion = np.zeros(sum_incart.shape, dtype=np.uint8)
for i in inIdx:
cv2.drawContours(invasion, [contours_base[i]], -1, 255, cv2.FILLED)
cv2.imwrite("./result/intrude/invasion.png", invasion)
Intrude = True if len(inIdx)>=1 else False
print(f"is intruded: {Intrude}")
return Intrude

View File

@ -0,0 +1,277 @@
# -*- coding: utf-8 -*-
"""
Created on Mon Mar 4 18:36:31 2024
@author: ym
"""
import numpy as np
import cv2
import copy
import sys
from pathlib import Path
FILE = Path(__file__).resolve()
ROOT = FILE.parents[2] # YOLOv5 root directory
if str(ROOT) not in sys.path:
sys.path.append(str(ROOT))
from tracking.utils.mergetrack import track_equal_track
from scipy.spatial.distance import cdist
curpath = Path(__file__).resolve().parents[0]
curpath = Path(curpath)
parpath = curpath.parent
from .dotracks import doTracks, ShoppingCart
from .track_back import backTrack
class doBackTracks(doTracks):
def __init__(self, bboxes, trackefeats):
super().__init__(bboxes, trackefeats)
self.tracks = [backTrack(b, f) for b, f in zip(self.lboxes, self.lfeats)]
# self.similar_dict = self.similarity()
# self.shopcart = ShoppingCart(bboxes)
self.incart = self.getincart()
def getincart(self):
img1 = cv2.imread(str(parpath/'shopcart/cart_tempt/incart.png'), cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread(str(parpath/'shopcart/cart_tempt/cartedge.png'), cv2.IMREAD_GRAYSCALE)
ret, binary1 = cv2.threshold(img1, 250, 255, cv2.THRESH_BINARY)
ret, binary2 = cv2.threshold(img2, 250, 255, cv2.THRESH_BINARY)
binary = cv2.bitwise_or(binary1, binary2)
return binary
def classify(self):
'''功能:对 tracks 中元素分类 '''
tracks = self.tracks
# 提取手的frame_id并和动目标的frame_id 进行关联
hand_tracks = [t for t in tracks if t.cls==0]
self.Hands.extend(hand_tracks)
tracks = self.sub_tracks(tracks, hand_tracks)
# 提取小孩的track并计算状态left, right, incart
kid_tracks = [t for t in tracks if t.cls==9]
kid_states = [self.kid_state(t) for t in kid_tracks]
self.Kids = [x for x in zip(kid_tracks, kid_states)]
tracks = self.sub_tracks(tracks, kid_tracks)
out_trcak = [t for t in tracks if t.isWholeOutCart]
tracks = self.sub_tracks(tracks, out_trcak)
static_tracks = [t for t in tracks if t.frnum>1 and t.is_static()]
self.Static.extend(static_tracks)
'''剔除静止目标后的 tracks'''
tracks = self.sub_tracks(tracks, static_tracks)
tracks_free = [t for t in tracks if t.frnum>1 and t.is_freemove()]
self.FreeMove.extend(tracks_free)
tracks = self.sub_tracks(tracks, tracks_free)
# '''购物框边界外具有运动状态的干扰目标'''
# out_trcak = [t for t in tracks if t.is_OutTrack()]
# tracks = self.sub_tracks(tracks, out_trcak)
'''轨迹循环归并'''
# merged_tracks = self.merge_tracks(tracks)
merged_tracks = self.merge_tracks_loop(tracks)
[self.associate_with_hand(htrack, gtrack) for htrack in hand_tracks for gtrack in tracks]
tracks = [t for t in merged_tracks if t.frnum > 1]
self.merged_tracks = merged_tracks
static_tracks = [t for t in tracks if t.frnum>1 and t.is_static()]
self.Static.extend(static_tracks)
tracks = self.sub_tracks(tracks, static_tracks)
# for gtrack in tracks:
# for htrack in hand_tracks:
# hand_ious = self.associate_with_hand(htrack, gtrack)
# if len(hand_ious):
# gtrack.Hands.append(htrack)
# gtrack.HandsIou.append(hand_ious)
# htrack.Goods.append((gtrack, hand_ious))
# for htrack in hand_tracks:
# self.merge_based_hands(htrack)
self.Residual = tracks
self.Confirmed = self.confirm_track()
def confirm_track(self):
Confirmed = None
mindist = 0
for track in self.Residual:
md = min(track.trajrects_wh)
if md > mindist:
mindist = copy.deepcopy(md)
Confirmed = copy.deepcopy(track)
if Confirmed is not None:
return [Confirmed]
return []
# def merge_based_hands(self, htrack):
# gtracks = htrack.Goods
# if len(gtracks) >= 2:
# atrack, afious = gtracks[0]
# btrack, bfious = gtracks[1]
def associate_with_hand(self, htrack, gtrack):
'''
迁移至基类:
手部 Track、商品 Track 建立关联的依据:
a. 运动帧的帧索引有交集
b. 帧索引交集部分iou均大于0
'''
assert htrack.cls==0 and gtrack.cls!=0 and gtrack.cls!=9, 'Track cls is Error!'
hand_ious = []
hboxes = np.empty(shape=(0, 9), dtype = np.float64)
gboxes = np.empty(shape=(0, 9), dtype = np.float64)
# start, end 为索引值,需要 start:(end+1)
for start, end in htrack.moving_index:
hboxes = np.concatenate((hboxes, htrack.boxes[start:end+1, :]), axis=0)
for start, end in gtrack.moving_index:
gboxes = np.concatenate((gboxes, gtrack.boxes[start:end+1, :]), axis=0)
hfids, gfids = hboxes[:, 7], gboxes[:, 7]
fids = sorted(set(hfids).intersection(set(gfids)))
if len(fids)==0:
return None
# print(f"Goods ID: {gtrack.tid}, Hand ID: {htrack.tid}")
for f in fids:
h = np.where(hboxes[:,7] == f)[0][0]
g = np.where(gboxes[:,7] == f)[0][0]
x11, y11, x12, y12 = hboxes[h, 0:4]
x21, y21, x22, y22 = gboxes[g, 0:4]
x1, y1 = max((x11, x21)), max((y11, y21))
x2, y2 = min((x12, x22)), min((y12, y22))
union = (x2 - x1).clip(0) * (y2 - y1).clip(0)
area1 = (x12 - x11) * (y12 - y11)
area2 = (x22 - x21) * (y22 - y21)
iou = union / (area1 + area2 - union + 1e-6)
if iou >= 0.01:
gtrack.Hands.append((htrack.tid, f, iou))
return gtrack.Hands
def merge_tracks(self, Residual):
"""
对不同id但可能是同一商品的目标进行归并
和 dotrack_front.py中函数相同可以合并可以合并至基类
"""
mergedTracks = self.base_merge_tracks(Residual)
oldtracks, newtracks = [], []
for tracklist in mergedTracks:
if len(tracklist) > 1:
boxes = np.empty((0, 9), dtype=np.float32)
feats = np.empty((0, 256), dtype=np.float32)
for i, track in enumerate(tracklist):
if i==0: ntid, ncls=track.boxes[0, 4], track.boxes[0, 6]
iboxes = track.boxes.copy()
ifeats = track.features.copy()
# iboxes[:, 4], iboxes[:, 6] = ntid, ncls
boxes = np.concatenate((boxes, iboxes), axis=0)
feats = np.concatenate((feats, ifeats), axis=0)
oldtracks.append(track)
fid_indices = np.argsort(boxes[:, 7])
boxes_fid = boxes[fid_indices]
feats_fid = feats[fid_indices]
newtracks.append(backTrack(boxes_fid, feats_fid))
elif len(tracklist) == 1:
oldtracks.append(tracklist[0])
newtracks.append(tracklist[0])
redu = self.sub_tracks(Residual, oldtracks)
merged = self.join_tracks(redu, newtracks)
return merged
def kid_state(self, track):
left_dist = track.cornpoints[:, 2]
right_dist = 1024 - track.cornpoints[:, 4]
if np.sum(left_dist<30)/track.frnum>0.8 and np.sum(right_dist>512)/track.frnum>0.7:
kidstate = "left"
elif np.sum(left_dist>512)/track.frnum>0.7 and np.sum(right_dist<30)/track.frnum>0.8:
kidstate = "right"
else:
kidstate = "incart"
return kidstate
def isuptrack(self, track):
Flag = False
return Flag
def isdowntrack(self, track):
Flag = False
return Flag
def isfreetrack(self, track):
Flag = False
return Flag

View File

@ -0,0 +1,193 @@
# -*- coding: utf-8 -*-
"""
Created on Mon Mar 4 18:38:20 2024
@author: ym
"""
import cv2
import copy
import numpy as np
from pathlib import Path
curpath = Path(__file__).resolve().parents[0]
curpath = Path(curpath)
parpath = curpath.parent
# from tracking.utils.mergetrack import track_equal_track
from .dotracks import doTracks
from .track_front import frontTrack
class doFrontTracks(doTracks):
def __init__(self, bboxes, frameDictList):
super().__init__(bboxes, frameDictList)
# self.tracks = [frontTrack(b) for b in self.lboxes]
self.tracks = [frontTrack(b, f) for b, f in zip(self.lboxes, self.lfeats)]
self.incart = self.getincart()
def getincart(self):
img = cv2.imread(str(parpath/'shopcart/cart_tempt/incart_ftmp.png'), cv2.IMREAD_GRAYSCALE)
ret, binary = cv2.threshold(img, 250, 255, cv2.THRESH_BINARY)
return binary
def classify(self):
'''功能:对 tracks 中元素分类 '''
tracks = self.tracks
'''提取手的 tracks'''
hand_tracks = [t for t in tracks if t.cls==0]
self.Hands.extend(hand_tracks)
tracks = self.sub_tracks(tracks, hand_tracks)
'''提取小孩的 tracks'''
kid_tracks = [t for t in tracks if t.cls==9]
tracks = self.sub_tracks(tracks, kid_tracks)
out_trcak = [t for t in tracks if t.isWholeOutCart]
tracks = self.sub_tracks(tracks, out_trcak)
'''静态 tracks'''
static_tracks = [t for t in tracks if t.frnum>1 and t.is_static()]
'''剔除静止目标后的 tracks'''
tracks = self.sub_tracks(tracks, static_tracks)
tracks_free = [t for t in tracks if t.frnum>1 and t.is_freemove()]
self.FreeMove.extend(tracks_free)
tracks = self.sub_tracks(tracks, tracks_free)
# [self.associate_with_hand(htrack, gtrack) for htrack in hand_tracks for gtrack in tracks]
'''轨迹循环归并'''
merged_tracks = self.merge_tracks_loop(tracks)
[self.associate_with_hand(htrack, gtrack) for htrack in hand_tracks for gtrack in merged_tracks]
tracks = [t for t in merged_tracks if t.frnum > 1]
# for gtrack in tracks:
# # print(f"Goods ID:{gtrack.tid}")
# for htrack in hand_tracks:
# hand_ious = self.associate_with_hand(htrack, gtrack)
# if len(hand_ious):
# gtrack.Hands.append(htrack)
# gtrack.HandsIou.append(hand_ious)
'''静止 tracks 判断与剔除静止 tracks'''
static_tracks = [t for t in tracks if t.frnum>1 and t.is_static()]
tracks = self.sub_tracks(tracks, static_tracks)
freemoved_tracks = [t for t in tracks if t.is_free_move()]
tracks = self.sub_tracks(tracks, freemoved_tracks)
self.Residual = tracks
self.Confirmed = self.confirm_track()
def confirm_track(self):
Confirmed = None
mindist = 0
for track in self.Residual:
md = min(track.trajrects_wh)
if md > mindist:
mindist = copy.deepcopy(md)
Confirmed = copy.deepcopy(track)
if Confirmed is not None:
return [Confirmed]
return []
def associate_with_hand(self, htrack, gtrack):
'''
迁移至基类:
手部 Track、商品 Track 建立关联的依据:
a. 运动帧的帧索引有交集
b. 帧索引交集部分iou均大于0
'''
assert htrack.cls==0 and gtrack.cls!=0 and gtrack.cls!=9, 'Track cls is Error!'
hboxes = np.empty(shape=(0, 9), dtype = np.float64)
gboxes = np.empty(shape=(0, 9), dtype = np.float64)
# start, end 为索引值,需要 start:(end+1)
for start, end in htrack.dynamic_y2:
hboxes = np.concatenate((hboxes, htrack.boxes[start:end+1, :]), axis=0)
for start, end in gtrack.dynamic_y1:
gboxes = np.concatenate((gboxes, gtrack.boxes[start:end+1, :]), axis=0)
hfids, gfids = hboxes[:, 7], gboxes[:, 7]
fids = sorted(set(hfids).intersection(set(gfids)))
if len(fids)==0:
return None
# print(f"Goods ID: {gtrack.tid}, Hand ID: {htrack.tid}")
for f in fids:
h = np.where(hfids==f)[0][0]
g = np.where(gfids==f)[0][0]
x11, y11, x12, y12 = hboxes[h, 0:4]
x21, y21, x22, y22 = gboxes[g, 0:4]
x1, y1 = max((x11, x21)), max((y11, y21))
x2, y2 = min((x12, x22)), min((y12, y22))
union = (x2 - x1).clip(0) * (y2 - y1).clip(0)
area1 = (x12 - x11) * (y12 - y11)
area2 = (x22 - x21) * (y22 - y21)
iou = union / (area1 + area2 - union + 1e-6)
if iou >= 0.01:
gtrack.Hands.append((htrack.tid, f, iou))
return gtrack.Hands
def merge_tracks(self, Residual):
"""
对不同id但可能是同一商品的目标进行归并
和 dotrack_back.py中函数相同可以合并至基类
"""
mergedTracks = self.base_merge_tracks(Residual)
oldtracks, newtracks = [], []
for tracklist in mergedTracks:
if len(tracklist) > 1:
boxes = np.empty((0, 9), dtype=np.float32)
feats = np.empty((0, 256), dtype=np.float32)
for i, track in enumerate(tracklist):
if i==0: ntid, ncls=track.boxes[0, 4], track.boxes[0, 6]
iboxes = track.boxes.copy()
ifeats = track.features.copy()
# iboxes[:, 4], iboxes[:, 6] = ntid, ncls
boxes = np.concatenate((boxes, iboxes), axis=0)
feats = np.concatenate((feats, ifeats), axis=0)
oldtracks.append(track)
fid_indices = np.argsort(boxes[:, 7])
boxes_fid = boxes[fid_indices]
feats_fid = feats[fid_indices]
newtracks.append(frontTrack(boxes_fid, feats_fid))
elif len(tracklist) == 1:
oldtracks.append(tracklist[0])
newtracks.append(tracklist[0])
redu = self.sub_tracks(Residual, oldtracks)
merged = self.join_tracks(redu, newtracks)
return merged

View File

@ -0,0 +1,241 @@
# -*- coding: utf-8 -*-
"""
Created on Mon Mar 4 18:28:47 2024
@author: ym
"""
import cv2
import numpy as np
from scipy.spatial.distance import cdist
from sklearn.decomposition import PCA
from .dotracks import MoveState, Track
from pathlib import Path
curpath = Path(__file__).resolve().parents[0]
curpath = Path(curpath)
parpath = curpath.parent
class backTrack(Track):
# boxes: [x1, y1, x2, y2, track_id, score, cls, frame_index, box_index]
# 0, 1, 2, 3, 4, 5, 6, 7, 8
def __init__(self, boxes, features, imgshape=(1024, 1280)):
super().__init__(boxes, features, imgshape)
'''该函数依赖项: self.cornpoints
MarginState: list, seven elements, 表示轨迹中boxes出现在图像的
[左上,右上,左中,右中,左下,右下底部]
'''
self.isCornpoint, self.MarginState = self.isimgborder()
'''该函数依赖项: self.isCornpoint不能在父类中初始化'''
self.trajfeature()
'''静止点帧索引'''
# self.static_index = self.compute_static_fids()
'''运动点帧索引(运动帧两端的静止帧索引)'''
# self.moving_index = self.compute_dynamic_fids()
self.static_index, self.moving_index = self.compute_static_dynamic_fids()
'''该函数依赖项: self.cornpoints定义 4 个商品位置变量:
self.Cent_isIncart, self.LB_isIncart, self.RB_isIncart
self.posState = self.Cent_isIncart+self.LB_isIncart+self.RB_isIncart'''
self.PositionState(camerType="back")
'''self.feature_ious = (incart_iou, outcart_iou, cartboarder_iou, maxbox_iou, minbox_iou)
self.incartrates = incartrates'''
self.compute_ious_feat()
def isimgborder(self, BoundPixel=10, BoundThresh=0.3):
x1, y1 = self.cornpoints[:,2], self.cornpoints[:,3],
x2, y2 = self.cornpoints[:,8], self.cornpoints[:,9]
condt1 = sum(abs(x1)<BoundPixel) / self.frnum > BoundThresh
condt2 = sum(abs(y1)<BoundPixel) / self.frnum > BoundThresh
condt3 = sum(abs(x2-self.imgshape[0])<BoundPixel) / self.frnum > BoundThresh
condt4 = sum(abs(y2-self.imgshape[1])<BoundPixel) / self.frnum > BoundThresh
condt = condt1 or condt2 or condt3 or condt4
isCornpoint = False
if condt:
isCornpoint = True
condtA = condt1 and condt2
condtB = condt3 and condt2
condtC = condt1 and not condt2 and not condt4
condtD = condt3 and not condt2 and not condt4
condtE = condt1 and condt4
condtF = condt3 and condt4
condtG = condt4 and not condt1 and not condt3
MarginState = [condtA, condtB, condtC, condtD, condtE, condtF, condtG]
return isCornpoint, MarginState
def PCA(self):
self.pca = PCA()
X = self.cornpoints[:, 0:2]
self.pca.fit(X)
def compute_ious_feat(self):
'''输出:
self.feature_ious = (incart_iou, outcart_iou, cartboarder_iou, maxbox_iou, minbox_iou)
self.incartrates = incartrates
其中:
boxes流track中所有boxes形成的轨迹图可分为三部分incart, outcart, cartboarder
incart_iou, outcart_iou, cartboarder_iou各部分和 boxes流的 iou。
incart_iou = 0track在购物车外
outcart_iou = 0track在购物车内也可能是通过左下角、右下角置入购物车
maxbox_iou, minbox_ioutrack中最大、最小 box 和boxes流的iou二者差值越小越接近 1表明track的运动型越小。
incartrates: 各box和incart的iou时序由小变大反应的是置入过程由大变小反应的是取出过程
'''
incart = cv2.imread(str(parpath/"shopcart/cart_tempt/incart.png"), cv2.IMREAD_GRAYSCALE)
outcart = cv2.imread(str(parpath/"shopcart/cart_tempt/outcart.png"), cv2.IMREAD_GRAYSCALE)
cartboarder = cv2.imread(str(parpath/"shopcart/cart_tempt/cartboarder.png"), cv2.IMREAD_GRAYSCALE)
incartrates = []
temp = np.zeros(incart.shape, np.uint8)
maxarea, minarea = 0, self.imgshape[0]*self.imgshape[1]
for i in range(self.frnum):
# x, y, w, h = self.boxes[i, 0:4]
x = (self.boxes[i, 2] + self.boxes[i, 0]) / 2
w = (self.boxes[i, 2] - self.boxes[i, 0]) / 2
y = (self.boxes[i, 3] + self.boxes[i, 1]) / 2
h = (self.boxes[i, 3] - self.boxes[i, 1]) / 2
if w*h > maxarea: maxarea = w*h
if w*h < minarea: minarea = w*h
cv2.rectangle(temp, (int(x-w/2), int(y-h/2)), (int(x+w/2), int(y+h/2)), 255, cv2.FILLED)
temp1 = np.zeros(incart.shape, np.uint8)
cv2.rectangle(temp1, (int(x-w/2), int(y-h/2)), (int(x+w/2), int(y+h/2)), 255, cv2.FILLED)
temp2 = cv2.bitwise_and(incart, temp1)
inrate = cv2.countNonZero(temp1)/(w*h)
incartrates.append(inrate)
isincart = cv2.bitwise_and(incart, temp)
isoutcart = cv2.bitwise_and(outcart, temp)
iscartboarder = cv2.bitwise_and(cartboarder, temp)
num_temp = cv2.countNonZero(temp)
num_incart = cv2.countNonZero(isincart)
num_outcart = cv2.countNonZero(isoutcart)
num_cartboarder = cv2.countNonZero(iscartboarder)
incart_iou = num_incart/(num_temp+1e-6)
outcart_iou = num_outcart/(num_temp+1e-6)
cartboarder_iou = num_cartboarder/(num_temp+1e-6)
maxbox_iou = maxarea/(num_temp+1e-6)
minbox_iou = minarea/(num_temp+1e-6)
self.feature_ious = (incart_iou, outcart_iou, cartboarder_iou, maxbox_iou, minbox_iou)
self.incartrates = incartrates
def compute_static_dynamic_fids(self):
if self.MarginState[0] or self.MarginState[2]:
idx1 = 4
elif self.MarginState[1] or self.MarginState[3]:
idx1 = 3
elif self.MarginState[4]:
idx1 = 2
elif self.MarginState[5]:
idx1 = 1
elif self.MarginState[6]:
if self.trajlens[1] < self.trajlens[2]:
idx1 = 1
else:
idx1 = 2
else:
idx1 = self.trajlens.index(min(self.trajlens))
# idx1 = self.trajlens.index(min(self.trajlens))
trajmin = self.trajectory[idx1]
static, dynamic = self.pt_state_fids(trajmin)
static = np.array(static)
dynamic = np.array(dynamic)
if static.size:
indx = np.argsort(static[:, 0])
static = static[indx]
if dynamic.size:
indx = np.argsort(dynamic[:, 0])
dynamic = dynamic[indx]
return static, dynamic
def is_static(self):
'''静态情况 1: 目标关键点最小相对运动轨迹 < 0.2, 指标值偏大
TrajFeat = [trajlen_min, trajlen_max,
trajdist_min, trajdist_max,
trajlen_rate, trajdist_rate]
'''
# print(f"TrackID: {self.tid}")
boxes = self.boxes
'''静态情况 1: '''
condt1 = self.TrajFeat[5] < 0.2 or self.TrajFeat[3] < 120
'''静态情况 2: 目标初始状态为静止,适当放宽关键点最小相对运动轨迹 < 0.5'''
condt2 = self.static_index.size > 0 \
and self.static_index[0, 0] <= 2 \
and self.static_index[0, 1] >= 5 \
and self.TrajFeat[5] < 0.5 \
and self.TrajFeat[1] < 240 \
and self.isWholeInCart
# and self.posState >= 2
# and self.TrajFeat[0] < 240 \
'''静态情况 3: 目标初始状态和最终状态均为静止'''
condt3 = self.static_index.shape[0] >= 2 \
and self.static_index[0, 0] <= 2 \
and self.static_index[0, 1] >= 5 \
and self.static_index[-1, 1] >= self.frnum-3 \
and self.TrajFeat[1] < 240 \
and self.isWholeInCart
# and self.posState >= 2
# and self.TrajFeat[0] < 240 \
condt4 = self.static_index.shape[0] >= 2 \
and self.static_index[0, 0] <= 2 \
and self.static_index[0, 1] >= 6 \
and self.static_index[-1, 0] <= self.frnum-5 \
and self.static_index[-1, 1] >= self.frnum-2
condt = condt1 or condt2 or condt3 or condt4
return condt
def is_OutTrack(self):
if self.posState <= 1:
isout = True
else:
isout = False
return isout
def compute_distance(self):
pass
def move_start_fid(self):
pass
def move_end_fid(self):
pass

View File

@ -0,0 +1,194 @@
# -*- coding: utf-8 -*-
"""
Created on Mon Mar 4 18:33:01 2024
@author: ym
"""
import numpy as np
import cv2
# from sklearn.cluster import KMeans
from .dotracks import MoveState, Track
from pathlib import Path
curpath = Path(__file__).resolve().parents[0]
curpath = Path(curpath)
parpath = curpath.parent
class frontTrack(Track):
# boxes: [x1, y1, x2, y2, track_id, score, cls, frame_index, box_index]
# 0, 1, 2, 3, 4, 5, 6, 7, 8
def __init__(self, boxes, features, imgshape=(1024, 1280)):
super().__init__(boxes, features, imgshape)
self.CART_HIGH_THRESH1 = imgshape[1]/2.98
'''y1、y2静止状态区间值是 boxes 中对 axis=0 的索引,不是帧索引'''
det_y1 = np.diff(boxes[:, 1], axis=0)
det_y2 = np.diff(boxes[:, 3], axis=0)
self.static_y1, self.dynamic_y1 = self.pt_state_fids(det_y1)
self.static_y2, self.dynamic_y2 = self.pt_state_fids(det_y2)
self.isCornpoint = self.is_left_or_right_cornpoint()
self.isBotmpoint = self.is_bottom_cornpoint()
'''该函数依赖项: self.isCornpoint不能在父类中初始化'''
self.trajfeature()
self.PositionState(camerType="front")
'''手部状态分析'''
self.HAND_STATIC_THRESH = 100
self.CART_POSIT_0 = 430
self.CART_POSIT_1 = 620
def is_left_or_right_cornpoint(self):
''' 基于 all(boxes)
boxes左下角点和图像左下角点重叠 或
boxes右下角点和图像左下角点重叠
'''
x1, y1 = self.boxes[:, 0], self.boxes[:, 1]
x2, y2 = self.boxes[:, 2], self.boxes[:, 3]
# Left-Bottom cornpoint
condt1 = all(x1 < 5) and all(y2 > self.imgshape[1]-5)
# Right-Bottom cornpoint
condt2 = all(x2 > self.imgshape[0]-5) and all(y2 > self.imgshape[1]-5)
condt = condt1 or condt2
return condt
def is_edge_cornpoint(self):
'''基于 all(boxes)boxes是否和图像左右边缘重叠'''
x1, x2 = self.boxes[:, 0], self.boxes[:, 2]
condt = all(x1 < 3) or all(x2 > self.imgshape[0]-3)
return condt
def is_bottom_cornpoint(self):
'''基于 all(boxes)boxes是否和图像下边缘重叠'''
condt = all(self.boxes[:, 3] > self.imgshape[1]-20)
return condt
def is_static(self):
assert self.frnum > 1, "boxes number must greater than 1"
# print(f"The ID is: {self.tid}")
# 手部和小孩目标不考虑
if self.cls == 0 or self.cls == 9:
return False
# boxes 全部 y2=1280
if self.isBotmpoint:
return True
boxes = self.boxes
y0 = (boxes[:, 1]+boxes[:, 3])/2
## 纵轴矢量和
sum_y0 = y0[-1] - y0[0]
sum_y1 = boxes[-1, 1]-boxes[0, 1]
sum_y2 = boxes[-1, 3]-boxes[0, 3]
# 一些需要考虑的特殊情况
isbottom = max(boxes[:, 3]) > 1280-3
istop = min(boxes[:, 1]) < 3
isincart = min(y0) > self.CART_HIGH_THRESH1
uncert = abs(sum_y1)<100 and abs(sum_y2)<100
'''初始条件:商品中心点始终在购物车内、'''
condt0 = max((boxes[:, 1]+boxes[:, 3])/2) > self.CART_HIGH_THRESH1
'''条件1轨迹运动纵向和y1 或 y2描述商品轨迹长度存在情况
(1). 检测框可能与图像上下边缘重合,
(2). 上边或下边存在跳动
'''
if isbottom and istop:
condt1 = abs(sum_y0) < 300
elif isbottom: # y2在底部用y1表征运动
condt1 = sum_y1 > -120 and abs(sum_y0)<80 # 有底部点方向向上阈值小于100
elif istop: # y1在顶部用y2表征运动
condt1 = abs(sum_y2) < 100
else:
condt1 = (abs(sum_y1) < 30 or abs(sum_y2)<30)
'''条件2轨迹的开始和结束阶段均处于静止状态, 利用静止状态区间判断,用 y1
a. 商品在购物车内,
b. 检测框的起始阶段和结束阶段均为静止状态
c. 静止帧长度 > 3'''
condt2 = False
if len(self.static_y1)>=2:
condt_s0 = self.static_y1[0][0]==0 and self.static_y1[0][1] - self.static_y1[0][0] >= 3
condt_s1 = self.static_y1[-1][1]==self.frnum-1 and self.static_y1[-1][1] - self.static_y1[-1][0] >= 3
condt2 = condt_s0 and condt_s1 and isincart
condt = condt0 and (condt1 or condt2)
return condt
def is_upward(self):
'''判断商品是否取出,'''
print(f"The ID is: {self.tid}")
def is_free_move(self):
if self.frnum == 1:
return True
# print(f"The ID is: {self.tid}")
y0 = (self.boxes[:, 1] + self.boxes[:, 3]) / 2
det_y0 = np.diff(y0, axis=0)
sum_y0 = y0[-1] - y0[0]
'''情况1中心点向下 '''
## 初始条件:商品第一次检测到在购物车内
condt0 = y0[0] > self.CART_HIGH_THRESH1
condt_a = False
## 条件1商品初始为静止状态静止条件应严格一些
condt11, condt12 = False, False
if len(self.static_y1)>0:
condt11 = self.static_y1[0][0]==0 and self.static_y1[0][1] - self.static_y1[0][0] >= 5
if len(self.static_y2)>0:
condt12 = self.static_y2[0][0]==0 and self.static_y2[0][1] - self.static_y2[0][0] >= 5
# 条件2商品中心发生向下移动
condt2 = y0[-1] > y0[0]
# 综合判断a
condt_a = condt0 and (condt11 or condt12) and condt2
'''情况2中心点向上 '''
## 商品中心点向上移动但没有关联的Hand轨迹也不是左右边界点
condt_b = condt0 and len(self.Hands)==0 and y0[-1] < y0[0] and (not self.is_edge_cornpoint()) and min(y0)>self.CART_HIGH_THRESH1
'''情况3: 商品在购物车内,但运动方向无序'''
## 中心点在购物车内,纵向轨迹和小于轨迹差中绝对值最大的两个值的和,说明运动没有主方向
condt_c = False
if self.frnum > 3:
condt_c = all(y0>self.CART_HIGH_THRESH1) and \
(abs(sum_y0) < sum(np.sort(np.abs(det_y0))[::-1][:2])-1)
condt = (condt_a or condt_b or condt_c) and self.cls!=0
return condt

View File

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
"""
Created on Mon Jul 29 10:28:21 2024
未来需将这一部分和轨迹分析代码集成
@author: ym
"""
import numpy as np
import cv2
from scipy.spatial.distance import cdist
class TProp:
def __init__(self, boxes):
self.boxes = boxes
class TProp:
'''抽象基类,不能实例化对象'''
def __init__(self, boxes):
'''
boxes: [x1, y1, x2, y2, track_id, score, cls, frame_index, box_index]
0 1 2 3 4 5 6 7 8
'''
# assert len(set(boxes[:, 4].astype(int))) == 1, "For a Track, track_id more than 1"
# assert len(set(boxes[:, 6].astype(int))) == 1, "For a Track, class number more than 1"
self.boxes = boxes
'''5个关键点中心点、左上点、右上点、左下点、右下点 )坐标'''
self.compute_cornpoints()
'''5个关键点轨迹特征可以在子类中实现降低顺序处理时的计算量
(中心点、左上点、右上点、左下点、右下点 )轨迹特征'''
self.compute_cornpts_feats()
self.distmax = max(self.trajdist)
def compute_cornpoints(self):
'''
cornpoints 共10项分别是个点的坐标值x, y
(center, top_left, top_right, bottom_left, bottom_right)
'''
boxes = self.boxes
cornpoints = np.zeros((self.frnum, 10))
cornpoints[:,0] = (boxes[:, 0] + boxes[:, 2]) / 2
cornpoints[:,1] = (boxes[:, 1] + boxes[:, 3]) / 2
cornpoints[:,2], cornpoints[:,3] = boxes[:, 0], boxes[:, 1]
cornpoints[:,4], cornpoints[:,5] = boxes[:, 2], boxes[:, 1]
cornpoints[:,6], cornpoints[:,7] = boxes[:, 0], boxes[:, 3]
cornpoints[:,8], cornpoints[:,9] = boxes[:, 2], boxes[:, 3]
self.cornpoints = cornpoints
def compute_cornpts_feats(self):
'''
'''
trajectory = []
trajlens = []
trajdist = []
trajrects = []
for k in range(5):
# diff_xy2 = np.power(np.diff(self.cornpoints[:, 2*k:2*(k+1)], axis = 0), 2)
# trajlen = np.sum(np.sqrt(np.sum(diff_xy2, axis = 1)))
X = self.cornpoints[:, 2*k:2*(k+1)]
traj = np.linalg.norm(np.diff(X, axis=0), axis=1)
trajectory.append(traj)
trajlen = np.sum(traj)
trajlens.append(trajlen)
ptdist = np.max(cdist(X, X))
trajdist.append(ptdist)
'''最小外接矩形:
rect[0]: 中心(x, y)
rect[1]: (w, h)
rect[0]: 旋转角度 (-90°, 0]
'''
rect = cv2.minAreaRect(X.astype(np.int64))
trajrects.append(rect)
self.trajectory = trajectory
self.trajlens = trajlens
self.trajdist = trajdist
self.trajrects = trajrects

View File

@ -0,0 +1,807 @@
230537101280010007_20240411-144918_back_addGood_70f75407b7ae_570_17788571404.mp4
230537101280010007_20240411-144918_front_addGood_70f75407b7ae_570_17788571404.mp4
230537101280010007_20240411-144945_back_returnGood_70f75407b7ae_565_17788571404.mp4
230537101280010007_20240411-144945_front_returnGood_70f75407b7ae_565_17788571404.mp4
230538001280010009_20240411-144924_back_addGood_70f754088050_550_17327712807.mp4
230538001280010009_20240411-144924_front_addGood_70f754088050_550_17327712807.mp4
230538001280010009_20240411-144934_back_returnGood_70f754088050_550_17327712807.mp4
230538001280010009_20240411-144934_front_returnGood_70f754088050_550_17327712807.mp4
2500456001326_20240411-145321_back_addGood_70f75407b7ae_155_17788571404.mp4
2500456001326_20240411-145321_front_addGood_70f75407b7ae_155_17788571404.mp4
2500456001326_20240411-145327_back_returnGood_70f75407b7ae_155_17788571404.mp4
2500456001326_20240411-145327_front_returnGood_70f75407b7ae_155_17788571404.mp4
2500456001326_20240411-145330_back_addGood_70f754088050_155_17327712807.mp4
2500456001326_20240411-145330_front_addGood_70f754088050_155_17327712807.mp4
2500456001326_20240411-145338_back_returnGood_70f754088050_155_17327712807.mp4
2500456001326_20240411-145338_front_returnGood_70f754088050_155_17327712807.mp4
2500458675341_20240411-144658_back_addGood_70f75407b7ae_140_17788571404.mp4
2500458675341_20240411-144658_front_addGood_70f75407b7ae_140_17788571404.mp4
2500458675341_20240411-144707_back_returnGood_70f75407b7ae_140_17788571404.mp4
2500458675341_20240411-144707_front_returnGood_70f75407b7ae_140_17788571404.mp4
2500458675341_20240411-144711_back_addGood_70f754088050_135_17327712807.mp4
2500458675341_20240411-144711_front_addGood_70f754088050_135_17327712807.mp4
2500458675341_20240411-144718_back_returnGood_70f754088050_135_17327712807.mp4
2500458675341_20240411-144718_front_returnGood_70f754088050_135_17327712807.mp4
2500463464671_20240411-145041_back_addGood_70f75407b7ae_805_17788571404.mp4
2500463464671_20240411-145041_front_addGood_70f75407b7ae_805_17788571404.mp4
2500463464671_20240411-145042_back_addGood_70f754088050_815_17327712807.mp4
2500463464671_20240411-145042_front_addGood_70f754088050_815_17327712807.mp4
2500463464671_20240411-145049_back_returnGood_70f754088050_815_17327712807.mp4
2500463464671_20240411-145049_front_returnGood_70f754088050_815_17327712807.mp4
2500463464671_20240411-145053_back_returnGood_70f75407b7ae_810_17788571404.mp4
2500463464671_20240411-145053_front_returnGood_70f75407b7ae_810_17788571404.mp4
6901070613142_20240411-142722_back_addGood_70f754088050_240_17327712807.mp4
6901070613142_20240411-142722_front_addGood_70f754088050_240_17327712807.mp4
6901070613142_20240411-142725_back_addGood_70f75407b7ae_240_17788571404.mp4
6901070613142_20240411-142725_front_addGood_70f75407b7ae_240_17788571404.mp4
6901070613142_20240411-142730_back_returnGood_70f754088050_240_17327712807.mp4
6901070613142_20240411-142730_front_returnGood_70f754088050_240_17327712807.mp4
6901070613142_20240411-142734_back_returnGood_70f75407b7ae_240_17788571404.mp4
6901070613142_20240411-142734_front_returnGood_70f75407b7ae_240_17788571404.mp4
6901668053893_20240411-143608_back_addGood_70f75407b7ae_70_17788571404.mp4
6901668053893_20240411-143608_back_addGood_70f754088050_70_17327712807.mp4
6901668053893_20240411-143608_front_addGood_70f75407b7ae_70_17788571404.mp4
6901668053893_20240411-143608_front_addGood_70f754088050_70_17327712807.mp4
6901668053893_20240411-143616_back_returnGood_70f754088050_70_17327712807.mp4
6901668053893_20240411-143616_front_returnGood_70f754088050_70_17327712807.mp4
6901668053893_20240411-143617_back_returnGood_70f75407b7ae_70_17788571404.mp4
6901668053893_20240411-143617_front_returnGood_70f75407b7ae_70_17788571404.mp4
6902007010249_20240411-142528_back_addGood_70f75407b7ae_755_17788571404.mp4
6902007010249_20240411-142528_back_addGood_70f754088050_755_17327712807.mp4
6902007010249_20240411-142528_front_addGood_70f75407b7ae_755_17788571404.mp4
6902007010249_20240411-142528_front_addGood_70f754088050_755_17327712807.mp4
6902007010249_20240411-142535_back_returnGood_70f75407b7ae_755_17788571404.mp4
6902007010249_20240411-142535_front_returnGood_70f75407b7ae_755_17788571404.mp4
6902007010249_20240411-142541_back_returnGood_70f754088050_755_17327712807.mp4
6902007010249_20240411-142541_front_returnGood_70f754088050_755_17327712807.mp4
6902022135514_20240411-142819_back_addGood_70f75407b7ae_3180_17788571404.mp4
6902022135514_20240411-142819_front_addGood_70f75407b7ae_3180_17788571404.mp4
6902022135514_20240411-142828_back_addGood_70f754088050_3185_17327712807.mp4
6902022135514_20240411-142828_front_addGood_70f754088050_3185_17327712807.mp4
6902022135514_20240411-142830_back_returnGood_70f75407b7ae_3180_17788571404.mp4
6902022135514_20240411-142830_front_returnGood_70f75407b7ae_3180_17788571404.mp4
6902022135514_20240411-142840_back_returnGood_70f754088050_3185_17327712807.mp4
6902022135514_20240411-142840_front_returnGood_70f754088050_3185_17327712807.mp4
6902265114369_20240411-142331_back_addGood_70f75407b7ae_715_17788571404.mp4
6902265114369_20240411-142331_front_addGood_70f75407b7ae_715_17788571404.mp4
6902265114369_20240411-142338_back_returnGood_70f75407b7ae_720_17788571404.mp4
6902265114369_20240411-142338_front_returnGood_70f75407b7ae_720_17788571404.mp4
6902265114369_20240411-142355_back_addGood_70f754088050_720_17327712807.mp4
6902265114369_20240411-142355_front_addGood_70f754088050_720_17327712807.mp4
6902265114369_20240411-142403_back_returnGood_70f754088050_715_17327712807.mp4
6902265114369_20240411-142403_front_returnGood_70f754088050_715_17327712807.mp4
6902265908012_20240411-142446_back_addGood_70f75407b7ae_1150_17788571404.mp4
6902265908012_20240411-142446_front_addGood_70f75407b7ae_1150_17788571404.mp4
6902265908012_20240411-142447_back_addGood_70f754088050_1150_17327712807.mp4
6902265908012_20240411-142447_front_addGood_70f754088050_1150_17327712807.mp4
6902265908012_20240411-142456_back_returnGood_70f75407b7ae_1150_17788571404.mp4
6902265908012_20240411-142456_front_returnGood_70f75407b7ae_1150_17788571404.mp4
6902265908012_20240411-142459_back_returnGood_70f754088050_1150_17327712807.mp4
6902265908012_20240411-142459_front_returnGood_70f754088050_1150_17327712807.mp4
69025143_20240411-163325_back_addGood_70f75407b7ae_3385_17788571404.mp4
69025143_20240411-163325_front_addGood_70f75407b7ae_3385_17788571404.mp4
69025143_20240411-163352_back_returnGood_70f75407b7ae_3390_17788571404.mp4
69025143_20240411-163352_front_returnGood_70f75407b7ae_3390_17788571404.mp4
69025143_20240411-163417_back_addGood_70f754088050_3380_17327712807.mp4
69025143_20240411-163417_front_addGood_70f754088050_3380_17327712807.mp4
69025143_20240411-163428_back_returnGood_70f754088050_3380_17327712807.mp4
69025143_20240411-163428_front_returnGood_70f754088050_3380_17327712807.mp4
6902538007367_20240411-144030_back_addGood_70f754088050_660_17327712807.mp4
6902538007367_20240411-144030_front_addGood_70f754088050_660_17327712807.mp4
6902538007367_20240411-144031_back_addGood_70f75407b7ae_660_17788571404.mp4
6902538007367_20240411-144031_front_addGood_70f75407b7ae_660_17788571404.mp4
6902538007367_20240411-144037_back_returnGood_70f75407b7ae_660_17788571404.mp4
6902538007367_20240411-144037_front_returnGood_70f75407b7ae_660_17788571404.mp4
6902538007367_20240411-144040_back_returnGood_70f754088050_660_17327712807.mp4
6902538007367_20240411-144040_front_returnGood_70f754088050_660_17327712807.mp4
69028571_20240411-163728_back_addGood_70f75407b7ae_1845_17788571404.mp4
69028571_20240411-163728_front_addGood_70f75407b7ae_1845_17788571404.mp4
69028571_20240411-163739_back_returnGood_70f75407b7ae_1845_17788571404.mp4
69028571_20240411-163739_front_returnGood_70f75407b7ae_1845_17788571404.mp4
69028571_20240411-163853_back_addGood_70f754088050_1840_17327712807.mp4
69028571_20240411-163853_front_addGood_70f754088050_1840_17327712807.mp4
69028571_20240411-163904_back_returnGood_70f754088050_1840_17327712807.mp4
69028571_20240411-163904_front_returnGood_70f754088050_1840_17327712807.mp4
6907992103952_20240411-142013_back_addGood_70f75407b7ae_190_17788571404.mp4
6907992103952_20240411-142013_front_addGood_70f75407b7ae_190_17788571404.mp4
6907992103952_20240411-142021_back_returnGood_70f75407b7ae_190_17788571404.mp4
6907992103952_20240411-142021_front_returnGood_70f75407b7ae_190_17788571404.mp4
6907992103952_20240411-142029_back_addGood_70f754088050_190_17327712807.mp4
6907992103952_20240411-142029_front_addGood_70f754088050_190_17327712807.mp4
6907992103952_20240411-142036_back_returnGood_70f754088050_190_17327712807.mp4
6907992103952_20240411-142036_front_returnGood_70f754088050_190_17327712807.mp4
6907992104157_20240411-141633_back_addGood_70f754088050_1120_17327712807.mp4
6907992104157_20240411-141633_front_addGood_70f754088050_1120_17327712807.mp4
6907992104157_20240411-141637_back_addGood_70f75407b7ae_1120_17788571404.mp4
6907992104157_20240411-141637_front_addGood_70f75407b7ae_1120_17788571404.mp4
6907992104157_20240411-141646_back_returnGood_70f75407b7ae_1120_17788571404.mp4
6907992104157_20240411-141646_back_returnGood_70f754088050_1120_17327712807.mp4
6907992104157_20240411-141646_front_returnGood_70f75407b7ae_1120_17788571404.mp4
6907992104157_20240411-141646_front_returnGood_70f754088050_1120_17327712807.mp4
6907992105765_20240411-142214_back_addGood_70f754088050_1860_17327712807.mp4
6907992105765_20240411-142214_front_addGood_70f754088050_1860_17327712807.mp4
6907992105765_20240411-142220_back_addGood_70f75407b7ae_1855_17788571404.mp4
6907992105765_20240411-142220_front_addGood_70f75407b7ae_1855_17788571404.mp4
6907992105765_20240411-142228_back_returnGood_70f754088050_1860_17327712807.mp4
6907992105765_20240411-142228_front_returnGood_70f754088050_1860_17327712807.mp4
6907992105765_20240411-142230_back_returnGood_70f75407b7ae_1855_17788571404.mp4
6907992105765_20240411-142230_front_returnGood_70f75407b7ae_1855_17788571404.mp4
6907992106113_20240411-142100_back_addGood_70f754088050_3085_17327712807.mp4
6907992106113_20240411-142100_front_addGood_70f754088050_3085_17327712807.mp4
6907992106113_20240411-142133_back_returnGood_70f754088050_3085_17327712807.mp4
6907992106113_20240411-142133_front_returnGood_70f754088050_3085_17327712807.mp4
6907992106113_20240411-142149_back_addGood_70f75407b7ae_3085_17788571404.mp4
6907992106113_20240411-142149_front_addGood_70f75407b7ae_3085_17788571404.mp4
6907992106113_20240411-142157_back_returnGood_70f75407b7ae_3080_17788571404.mp4
6907992106113_20240411-142157_front_returnGood_70f75407b7ae_3080_17788571404.mp4
6907992106205_20240411-141741_back_addGood_70f75407b7ae_795_17788571404.mp4
6907992106205_20240411-141741_front_addGood_70f75407b7ae_795_17788571404.mp4
6907992106205_20240411-141750_back_returnGood_70f75407b7ae_795_17788571404.mp4
6907992106205_20240411-141750_front_returnGood_70f75407b7ae_795_17788571404.mp4
6907992106205_20240411-141806_back_addGood_70f754088050_795_17327712807.mp4
6907992106205_20240411-141806_front_addGood_70f754088050_795_17327712807.mp4
6907992106205_20240411-141815_back_returnGood_70f754088050_795_17327712807.mp4
6907992106205_20240411-141815_front_returnGood_70f754088050_795_17327712807.mp4
6907992106311_20240411-141711_back_addGood_70f754088050_880_17327712807.mp4
6907992106311_20240411-141711_front_addGood_70f754088050_880_17327712807.mp4
6907992106311_20240411-141713_back_addGood_70f75407b7ae_885_17788571404.mp4
6907992106311_20240411-141713_front_addGood_70f75407b7ae_885_17788571404.mp4
6907992106311_20240411-141721_back_returnGood_70f75407b7ae_885_17788571404.mp4
6907992106311_20240411-141721_front_returnGood_70f75407b7ae_885_17788571404.mp4
6907992106311_20240411-141726_back_returnGood_70f754088050_880_17327712807.mp4
6907992106311_20240411-141726_front_returnGood_70f754088050_880_17327712807.mp4
6914973602908_20240411-162105_back_returnGood_70f75407b7ae_5_17788571404.mp4
6914973602908_20240411-162105_front_returnGood_70f75407b7ae_5_17788571404.mp4
6914973602908_20240411-162113_back_returnGood_70f75407b7ae_740_17788571404.mp4
6914973602908_20240411-162113_front_returnGood_70f75407b7ae_740_17788571404.mp4
6914973602908_20240411-162420_back_addGood_70f754088050_740_17327712807.mp4
6914973602908_20240411-162420_front_addGood_70f754088050_740_17327712807.mp4
6914973602908_20240411-162434_back_returnGood_70f754088050_740_17327712807.mp4
6914973602908_20240411-162434_front_returnGood_70f754088050_740_17327712807.mp4
6914973606340_20240411-161927_back_addGood_70f75407b7ae_350_17788571404.mp4
6914973606340_20240411-161927_front_addGood_70f75407b7ae_350_17788571404.mp4
6914973606340_20240411-161936_back_returnGood_70f75407b7ae_355_17788571404.mp4
6914973606340_20240411-161936_front_returnGood_70f75407b7ae_355_17788571404.mp4
6914973606340_20240411-162001_back_addGood_70f754088050_5_17327712807.mp4
6914973606340_20240411-162001_front_addGood_70f754088050_5_17327712807.mp4
6914973606340_20240411-162009_back_returnGood_70f754088050_355_17327712807.mp4
6914973606340_20240411-162009_front_returnGood_70f754088050_355_17327712807.mp4
6920152400630_20240411-144222_back_addGood_70f75407b7ae_580_17788571404.mp4
6920152400630_20240411-144222_front_addGood_70f75407b7ae_580_17788571404.mp4
6920152400630_20240411-144223_back_addGood_70f754088050_570_17327712807.mp4
6920152400630_20240411-144223_front_addGood_70f754088050_570_17327712807.mp4
6920152400630_20240411-144228_back_returnGood_70f75407b7ae_575_17788571404.mp4
6920152400630_20240411-144228_front_returnGood_70f75407b7ae_575_17788571404.mp4
6920152400630_20240411-144236_back_returnGood_70f754088050_570_17327712807.mp4
6920152400630_20240411-144236_front_returnGood_70f754088050_570_17327712807.mp4
6920174757101_20240411-143201_back_addGood_70f75407b7ae_1305_17788571404.mp4
6920174757101_20240411-143201_front_addGood_70f75407b7ae_1305_17788571404.mp4
6920174757101_20240411-143202_back_addGood_70f754088050_1305_17327712807.mp4
6920174757101_20240411-143202_front_addGood_70f754088050_1305_17327712807.mp4
6920174757101_20240411-143208_back_returnGood_70f75407b7ae_1305_17788571404.mp4
6920174757101_20240411-143208_front_returnGood_70f75407b7ae_1305_17788571404.mp4
6920174757101_20240411-143211_back_returnGood_70f754088050_1305_17327712807.mp4
6920174757101_20240411-143211_front_returnGood_70f754088050_1305_17327712807.mp4
6920459905012_20240411-143922_back_addGood_70f75407b7ae_550_17788571404.mp4
6920459905012_20240411-143922_front_addGood_70f75407b7ae_550_17788571404.mp4
6920459905012_20240411-143930_back_returnGood_70f75407b7ae_555_17788571404.mp4
6920459905012_20240411-143930_front_returnGood_70f75407b7ae_555_17788571404.mp4
6920459905012_20240411-143947_back_addGood_70f754088050_550_17327712807.mp4
6920459905012_20240411-143947_front_addGood_70f754088050_550_17327712807.mp4
6920459905012_20240411-143956_back_returnGood_70f754088050_550_17327712807.mp4
6920459905012_20240411-143956_front_returnGood_70f754088050_550_17327712807.mp4
6920907810707_20240411-143338_back_addGood_70f754088050_75_17327712807.mp4
6920907810707_20240411-143338_front_addGood_70f754088050_75_17327712807.mp4
6920907810707_20240411-143339_back_addGood_70f75407b7ae_80_17788571404.mp4
6920907810707_20240411-143339_front_addGood_70f75407b7ae_80_17788571404.mp4
6920907810707_20240411-143347_back_returnGood_70f754088050_80_17327712807.mp4
6920907810707_20240411-143347_front_returnGood_70f754088050_80_17327712807.mp4
6920907810707_20240411-143356_back_returnGood_70f75407b7ae_80_17788571404.mp4
6920907810707_20240411-143356_front_returnGood_70f75407b7ae_80_17788571404.mp4
6922130119213_20240411-142631_back_addGood_70f754088050_1020_17327712807.mp4
6922130119213_20240411-142631_front_addGood_70f754088050_1020_17327712807.mp4
6922130119213_20240411-142636_back_addGood_70f75407b7ae_1020_17788571404.mp4
6922130119213_20240411-142636_front_addGood_70f75407b7ae_1020_17788571404.mp4
6922130119213_20240411-142641_back_returnGood_70f754088050_1020_17327712807.mp4
6922130119213_20240411-142641_front_returnGood_70f754088050_1020_17327712807.mp4
6922130119213_20240411-142647_back_returnGood_70f75407b7ae_1020_17788571404.mp4
6922130119213_20240411-142647_front_returnGood_70f75407b7ae_1020_17788571404.mp4
6922577700968_20240411-141822_back_addGood_70f75407b7ae_1040_17788571404.mp4
6922577700968_20240411-141822_front_addGood_70f75407b7ae_1040_17788571404.mp4
6922577700968_20240411-141834_back_addGood_70f754088050_1045_17327712807.mp4
6922577700968_20240411-141834_front_addGood_70f754088050_1045_17327712807.mp4
6922577700968_20240411-141839_back_returnGood_70f75407b7ae_1045_17788571404.mp4
6922577700968_20240411-141839_front_returnGood_70f75407b7ae_1045_17788571404.mp4
6922577700968_20240411-141844_back_returnGood_70f754088050_1045_17327712807.mp4
6922577700968_20240411-141844_front_returnGood_70f754088050_1045_17327712807.mp4
6922868291168_20240411-142913_back_addGood_70f754088050_1160_17327712807.mp4
6922868291168_20240411-142913_front_addGood_70f754088050_1160_17327712807.mp4
6922868291168_20240411-142921_back_addGood_70f75407b7ae_1150_17788571404.mp4
6922868291168_20240411-142921_front_addGood_70f75407b7ae_1150_17788571404.mp4
6922868291168_20240411-142929_back_returnGood_70f754088050_1160_17327712807.mp4
6922868291168_20240411-142929_front_returnGood_70f754088050_1160_17327712807.mp4
6922868291168_20240411-142933_back_returnGood_70f75407b7ae_1155_17788571404.mp4
6922868291168_20240411-142933_front_returnGood_70f75407b7ae_1155_17788571404.mp4
6923450601549_20240411-162014_back_addGood_70f75407b7ae_600_17788571404.mp4
6923450601549_20240411-162014_front_addGood_70f75407b7ae_600_17788571404.mp4
6923450601549_20240411-162024_back_returnGood_70f75407b7ae_600_17788571404.mp4
6923450601549_20240411-162024_front_returnGood_70f75407b7ae_600_17788571404.mp4
6923450601549_20240411-162216_back_addGood_70f754088050_595_17327712807.mp4
6923450601549_20240411-162216_front_addGood_70f754088050_595_17327712807.mp4
6923450601549_20240411-162227_back_returnGood_70f754088050_595_17327712807.mp4
6923450601549_20240411-162227_front_returnGood_70f754088050_595_17327712807.mp4
6923450603574_20240411-163042_back_addGood_70f75407b7ae_870_17788571404.mp4
6923450603574_20240411-163042_front_addGood_70f75407b7ae_870_17788571404.mp4
6923450603574_20240411-163049_back_returnGood_70f75407b7ae_870_17788571404.mp4
6923450603574_20240411-163049_front_returnGood_70f75407b7ae_870_17788571404.mp4
6923450603574_20240411-163104_back_addGood_70f754088050_865_17327712807.mp4
6923450603574_20240411-163104_front_addGood_70f754088050_865_17327712807.mp4
6923450603574_20240411-163114_back_returnGood_70f754088050_865_17327712807.mp4
6923450603574_20240411-163114_front_returnGood_70f754088050_865_17327712807.mp4
6923450605288_20240411-161704_back_addGood_70f75407b7ae_250_17788571404.mp4
6923450605288_20240411-161704_front_addGood_70f75407b7ae_250_17788571404.mp4
6923450605288_20240411-161715_back_returnGood_70f75407b7ae_250_17788571404.mp4
6923450605288_20240411-161715_front_returnGood_70f75407b7ae_250_17788571404.mp4
6923450605288_20240411-161748_back_addGood_70f754088050_245_17327712807.mp4
6923450605288_20240411-161748_front_addGood_70f754088050_245_17327712807.mp4
6923450605288_20240411-161800_back_returnGood_70f754088050_245_17327712807.mp4
6923450605288_20240411-161800_front_returnGood_70f754088050_245_17327712807.mp4
6923450610428_20240411-161209_back_addGood_70f75407b7ae_275_17788571404.mp4
6923450610428_20240411-161209_front_addGood_70f75407b7ae_275_17788571404.mp4
6923450610428_20240411-161217_back_returnGood_70f75407b7ae_275_17788571404.mp4
6923450610428_20240411-161217_front_returnGood_70f75407b7ae_275_17788571404.mp4
6923450610428_20240411-161235_back_addGood_70f754088050_270_17327712807.mp4
6923450610428_20240411-161235_front_addGood_70f754088050_270_17327712807.mp4
6923450610428_20240411-161249_back_returnGood_70f754088050_275_17327712807.mp4
6923450610428_20240411-161249_front_returnGood_70f754088050_275_17327712807.mp4
6923450610459_20240411-162814_back_addGood_70f75407b7ae_445_17788571404.mp4
6923450610459_20240411-162814_front_addGood_70f75407b7ae_445_17788571404.mp4
6923450610459_20240411-162822_back_returnGood_70f75407b7ae_450_17788571404.mp4
6923450610459_20240411-162822_front_returnGood_70f75407b7ae_450_17788571404.mp4
6923450610459_20240411-162854_back_addGood_70f754088050_445_17327712807.mp4
6923450610459_20240411-162854_front_addGood_70f754088050_445_17327712807.mp4
6923450610459_20240411-162906_back_returnGood_70f754088050_445_17327712807.mp4
6923450610459_20240411-162906_front_returnGood_70f754088050_445_17327712807.mp4
6923450611067_20240411-162639_back_addGood_70f75407b7ae_625_17788571404.mp4
6923450611067_20240411-162639_front_addGood_70f75407b7ae_625_17788571404.mp4
6923450611067_20240411-162648_back_returnGood_70f75407b7ae_625_17788571404.mp4
6923450611067_20240411-162648_front_returnGood_70f75407b7ae_625_17788571404.mp4
6923450611067_20240411-162752_back_addGood_70f754088050_620_17327712807.mp4
6923450611067_20240411-162752_front_addGood_70f754088050_620_17327712807.mp4
6923450611067_20240411-162808_back_returnGood_70f754088050_625_17327712807.mp4
6923450611067_20240411-162808_front_returnGood_70f754088050_625_17327712807.mp4
6923450612415_20240411-160636_back_addGood_70f75407b7ae_870_17788571404.mp4
6923450612415_20240411-160636_front_addGood_70f75407b7ae_870_17788571404.mp4
6923450612415_20240411-160724_back_addGood_70f754088050_865_17327712807.mp4
6923450612415_20240411-160724_front_addGood_70f754088050_865_17327712807.mp4
6923450612415_20240411-160735_back_returnGood_70f754088050_865_17327712807.mp4
6923450612415_20240411-160735_front_returnGood_70f754088050_865_17327712807.mp4
6923450612415_20240411-161013_back_returnGood_70f75407b7ae_865_17788571404.mp4
6923450612415_20240411-161013_front_returnGood_70f75407b7ae_865_17788571404.mp4
6923450612484_20240411-161509_back_addGood_70f75407b7ae_445_17788571404.mp4
6923450612484_20240411-161509_front_addGood_70f75407b7ae_445_17788571404.mp4
6923450612484_20240411-161517_back_returnGood_70f75407b7ae_445_17788571404.mp4
6923450612484_20240411-161517_front_returnGood_70f75407b7ae_445_17788571404.mp4
6923450612484_20240411-161535_back_addGood_70f754088050_450_17327712807.mp4
6923450612484_20240411-161535_front_addGood_70f754088050_450_17327712807.mp4
6923450612484_20240411-161546_back_returnGood_70f754088050_450_17327712807.mp4
6923450612484_20240411-161546_front_returnGood_70f754088050_450_17327712807.mp4
6923450657829_20240411-163522_back_addGood_70f75407b7ae_985_17788571404.mp4
6923450657829_20240411-163522_front_addGood_70f75407b7ae_985_17788571404.mp4
6923450657829_20240411-163532_back_returnGood_70f75407b7ae_985_17788571404.mp4
6923450657829_20240411-163532_front_returnGood_70f75407b7ae_985_17788571404.mp4
6923450657829_20240411-163554_back_addGood_70f754088050_990_17327712807.mp4
6923450657829_20240411-163554_front_addGood_70f754088050_990_17327712807.mp4
6923450657829_20240411-163606_back_returnGood_70f754088050_990_17327712807.mp4
6923450657829_20240411-163606_front_returnGood_70f754088050_990_17327712807.mp4
6923450659441_20240411-163152_back_addGood_70f75407b7ae_1695_17788571404.mp4
6923450659441_20240411-163152_front_addGood_70f75407b7ae_1695_17788571404.mp4
6923450659441_20240411-163204_back_returnGood_70f75407b7ae_1695_17788571404.mp4
6923450659441_20240411-163204_front_returnGood_70f75407b7ae_1695_17788571404.mp4
6923450659441_20240411-163217_back_addGood_70f754088050_1690_17327712807.mp4
6923450659441_20240411-163217_front_addGood_70f754088050_1690_17327712807.mp4
6923450659441_20240411-163231_back_returnGood_70f754088050_1690_17327712807.mp4
6923450659441_20240411-163231_front_returnGood_70f754088050_1690_17327712807.mp4
6923450666838_20240411-162541_back_addGood_70f75407b7ae_285_17788571404.mp4
6923450666838_20240411-162541_front_addGood_70f75407b7ae_285_17788571404.mp4
6923450666838_20240411-162549_back_returnGood_70f75407b7ae_285_17788571404.mp4
6923450666838_20240411-162549_front_returnGood_70f75407b7ae_285_17788571404.mp4
6923450666838_20240411-162637_back_addGood_70f754088050_285_17327712807.mp4
6923450666838_20240411-162637_front_addGood_70f754088050_285_17327712807.mp4
6923450666838_20240411-162704_back_returnGood_70f754088050_285_17327712807.mp4
6923450666838_20240411-162704_front_returnGood_70f754088050_285_17327712807.mp4
6923450668207_20240411-161832_back_addGood_70f75407b7ae_350_17788571404.mp4
6923450668207_20240411-161832_front_addGood_70f75407b7ae_350_17788571404.mp4
6923450668207_20240411-161839_back_returnGood_70f75407b7ae_350_17788571404.mp4
6923450668207_20240411-161839_front_returnGood_70f75407b7ae_350_17788571404.mp4
6923450668207_20240411-161904_back_addGood_70f754088050_350_17327712807.mp4
6923450668207_20240411-161904_front_addGood_70f754088050_350_17327712807.mp4
6923450668207_20240411-161913_back_returnGood_70f754088050_355_17327712807.mp4
6923450668207_20240411-161913_front_returnGood_70f754088050_355_17327712807.mp4
6923450677858_20240411-163609_back_addGood_70f75407b7ae_5_17788571404.mp4
6923450677858_20240411-163609_front_addGood_70f75407b7ae_5_17788571404.mp4
6923450677858_20240411-163618_back_returnGood_70f75407b7ae_1020_17788571404.mp4
6923450677858_20240411-163618_front_returnGood_70f75407b7ae_1020_17788571404.mp4
6923450677858_20240411-163726_back_addGood_70f754088050_1025_17327712807.mp4
6923450677858_20240411-163726_front_addGood_70f754088050_1025_17327712807.mp4
6923450677858_20240411-163737_back_returnGood_70f754088050_1025_17327712807.mp4
6923450677858_20240411-163737_front_returnGood_70f754088050_1025_17327712807.mp4
6923644286293_20240411-141557_back_addGood_70f754088050_795_17327712807.mp4
6923644286293_20240411-141557_front_addGood_70f754088050_795_17327712807.mp4
6923644286293_20240411-141601_back_addGood_70f75407b7ae_800_17788571404.mp4
6923644286293_20240411-141601_front_addGood_70f75407b7ae_800_17788571404.mp4
6923644286293_20240411-141605_back_returnGood_70f754088050_795_17327712807.mp4
6923644286293_20240411-141605_front_returnGood_70f754088050_795_17327712807.mp4
6923644286293_20240411-141609_back_returnGood_70f75407b7ae_800_17788571404.mp4
6923644286293_20240411-141609_front_returnGood_70f75407b7ae_800_17788571404.mp4
6923644298760_20240411-141459_back_addGood_70f75407b7ae_1020_17788571404.mp4
6923644298760_20240411-141459_front_addGood_70f75407b7ae_1020_17788571404.mp4
6923644298760_20240411-141500_back_addGood_70f754088050_1020_17327712807.mp4
6923644298760_20240411-141500_front_addGood_70f754088050_1020_17327712807.mp4
6923644298760_20240411-141511_back_returnGood_70f754088050_1020_17327712807.mp4
6923644298760_20240411-141511_front_returnGood_70f754088050_1020_17327712807.mp4
6923644298760_20240411-141516_back_returnGood_70f75407b7ae_1020_17788571404.mp4
6923644298760_20240411-141516_front_returnGood_70f75407b7ae_1020_17788571404.mp4
6924743915824_20240411-143302_back_addGood_70f75407b7ae_150_17788571404.mp4
6924743915824_20240411-143302_back_addGood_70f754088050_150_17327712807.mp4
6924743915824_20240411-143302_front_addGood_70f75407b7ae_150_17788571404.mp4
6924743915824_20240411-143302_front_addGood_70f754088050_150_17327712807.mp4
6924743915824_20240411-143310_back_returnGood_70f75407b7ae_155_17788571404.mp4
6924743915824_20240411-143310_back_returnGood_70f754088050_150_17327712807.mp4
6924743915824_20240411-143310_front_returnGood_70f75407b7ae_155_17788571404.mp4
6924743915824_20240411-143310_front_returnGood_70f754088050_150_17327712807.mp4
6924882497106_20240411-143858_back_addGood_70f75407b7ae_360_17788571404.mp4
6924882497106_20240411-143858_front_addGood_70f75407b7ae_360_17788571404.mp4
6924882497106_20240411-143904_back_returnGood_70f75407b7ae_360_17788571404.mp4
6924882497106_20240411-143904_front_returnGood_70f75407b7ae_360_17788571404.mp4
6924882497106_20240411-143915_back_addGood_70f754088050_355_17327712807.mp4
6924882497106_20240411-143915_front_addGood_70f754088050_355_17327712807.mp4
6924882497106_20240411-143924_back_returnGood_70f754088050_355_17327712807.mp4
6924882497106_20240411-143924_front_returnGood_70f754088050_355_17327712807.mp4
6925307305525_20240411-141034_back_addGood_70f75407b7ae_1670_17788571404.mp4
6925307305525_20240411-141034_front_addGood_70f75407b7ae_1670_17788571404.mp4
6925307305525_20240411-141044_back_returnGood_70f75407b7ae_1670_17788571404.mp4
6925307305525_20240411-141044_front_returnGood_70f75407b7ae_1670_17788571404.mp4
6925307305525_20240411-141055_back_addGood_70f754088050_1670_17327712807.mp4
6925307305525_20240411-141055_front_addGood_70f754088050_1670_17327712807.mp4
6925307305525_20240411-141106_back_returnGood_70f754088050_1670_17327712807.mp4
6925307305525_20240411-141106_front_returnGood_70f754088050_1670_17327712807.mp4
6928804011173_20240411-143817_back_addGood_70f75407b7ae_555_17788571404.mp4
6928804011173_20240411-143817_front_addGood_70f75407b7ae_555_17788571404.mp4
6928804011173_20240411-143825_back_returnGood_70f75407b7ae_550_17788571404.mp4
6928804011173_20240411-143825_front_returnGood_70f75407b7ae_550_17788571404.mp4
6928804011173_20240411-143833_back_addGood_70f754088050_545_17327712807.mp4
6928804011173_20240411-143833_front_addGood_70f754088050_545_17327712807.mp4
6928804011173_20240411-143841_back_returnGood_70f754088050_550_17327712807.mp4
6928804011173_20240411-143841_front_returnGood_70f754088050_550_17327712807.mp4
6931925828032_20240411-162501_back_addGood_70f75407b7ae_405_17788571404.mp4
6931925828032_20240411-162501_front_addGood_70f75407b7ae_405_17788571404.mp4
6931925828032_20240411-162511_back_returnGood_70f75407b7ae_405_17788571404.mp4
6931925828032_20240411-162511_front_returnGood_70f75407b7ae_405_17788571404.mp4
6931925828032_20240411-162532_back_addGood_70f754088050_405_17327712807.mp4
6931925828032_20240411-162532_front_addGood_70f754088050_405_17327712807.mp4
6931925828032_20240411-162540_back_returnGood_70f754088050_405_17327712807.mp4
6931925828032_20240411-162540_front_returnGood_70f754088050_405_17327712807.mp4
6933620900051_20240411-150018_back_addGood_70f75407b7ae_5_17788571404.mp4
6933620900051_20240411-150018_front_addGood_70f75407b7ae_5_17788571404.mp4
6933620900051_20240411-150035_back_returnGood_70f75407b7ae_380_17788571404.mp4
6933620900051_20240411-150035_front_returnGood_70f75407b7ae_380_17788571404.mp4
6933620900051_20240411-150103_back_addGood_70f754088050_455_17327712807.mp4
6933620900051_20240411-150103_front_addGood_70f754088050_455_17327712807.mp4
6933620900051_20240411-150116_back_returnGood_70f754088050_455_17327712807.mp4
6933620900051_20240411-150116_front_returnGood_70f754088050_455_17327712807.mp4
6934665095108_20240411-141931_back_addGood_70f754088050_360_17327712807.mp4
6934665095108_20240411-141931_front_addGood_70f754088050_360_17327712807.mp4
6934665095108_20240411-141936_back_addGood_70f75407b7ae_360_17788571404.mp4
6934665095108_20240411-141936_front_addGood_70f75407b7ae_360_17788571404.mp4
6934665095108_20240411-141940_back_returnGood_70f754088050_365_17327712807.mp4
6934665095108_20240411-141940_front_returnGood_70f754088050_365_17327712807.mp4
6934665095108_20240411-141945_back_returnGood_70f75407b7ae_360_17788571404.mp4
6934665095108_20240411-141945_front_returnGood_70f75407b7ae_360_17788571404.mp4
6935270642121_20240411-144253_back_addGood_70f75407b7ae_155_17788571404.mp4
6935270642121_20240411-144253_front_addGood_70f75407b7ae_155_17788571404.mp4
6935270642121_20240411-144255_back_addGood_70f754088050_155_17327712807.mp4
6935270642121_20240411-144255_front_addGood_70f754088050_155_17327712807.mp4
6935270642121_20240411-144300_back_returnGood_70f75407b7ae_155_17788571404.mp4
6935270642121_20240411-144300_front_returnGood_70f75407b7ae_155_17788571404.mp4
6935270642121_20240411-144304_back_returnGood_70f754088050_155_17327712807.mp4
6935270642121_20240411-144304_front_returnGood_70f754088050_155_17327712807.mp4
6935284417326_20240411-143504_back_addGood_70f754088050_420_17327712807.mp4
6935284417326_20240411-143504_front_addGood_70f754088050_420_17327712807.mp4
6935284417326_20240411-143508_back_addGood_70f75407b7ae_415_17788571404.mp4
6935284417326_20240411-143508_front_addGood_70f75407b7ae_415_17788571404.mp4
6935284417326_20240411-143517_back_returnGood_70f754088050_420_17327712807.mp4
6935284417326_20240411-143517_front_returnGood_70f754088050_420_17327712807.mp4
6935284417326_20240411-143521_back_returnGood_70f75407b7ae_415_17788571404.mp4
6935284417326_20240411-143521_front_returnGood_70f75407b7ae_415_17788571404.mp4
6941025140798_20240411-135647_back_addGood_70f75407b7ae_1760_17788571404.mp4
6941025140798_20240411-135647_front_addGood_70f75407b7ae_1760_17788571404.mp4
6941025140798_20240411-135655_back_returnGood_70f75407b7ae_1760_17788571404.mp4
6941025140798_20240411-135655_front_returnGood_70f75407b7ae_1760_17788571404.mp4
6941025140798_20240411-140026_back_addGood_70f754088050_1765_17327712807.mp4
6941025140798_20240411-140026_front_addGood_70f754088050_1765_17327712807.mp4
6941025140798_20240411-140105_back_returnGood_70f754088050_1760_17327712807.mp4
6941025140798_20240411-140105_front_returnGood_70f754088050_1760_17327712807.mp4
6952074634794_20240411-143701_back_addGood_70f75407b7ae_275_17788571404.mp4
6952074634794_20240411-143701_front_addGood_70f75407b7ae_275_17788571404.mp4
6952074634794_20240411-143707_back_returnGood_70f75407b7ae_275_17788571404.mp4
6952074634794_20240411-143709_back_returnGood_70f754088050_5_17327712807.mp4
6952074634794_20240411-143709_front_returnGood_70f754088050_5_17327712807.mp4
6952074634794_20240411-143720_back_returnGood_70f754088050_265_17327712807.mp4
6952074634794_20240411-143720_front_returnGood_70f754088050_265_17327712807.mp4
6954432711307_20240411-161612_back_addGood_70f75407b7ae_460_17788571404.mp4
6954432711307_20240411-161612_front_addGood_70f75407b7ae_460_17788571404.mp4
6954432711307_20240411-161622_back_returnGood_70f75407b7ae_460_17788571404.mp4
6954432711307_20240411-161622_front_returnGood_70f75407b7ae_460_17788571404.mp4
6954432711307_20240411-161647_back_addGood_70f754088050_460_17327712807.mp4
6954432711307_20240411-161647_front_addGood_70f754088050_460_17327712807.mp4
6954432711307_20240411-161700_back_returnGood_70f754088050_460_17327712807.mp4
6954432711307_20240411-161700_front_returnGood_70f754088050_460_17327712807.mp4
6959546100993_20240411-135257_back_addGood_70f75407b7ae_295_17788571404.mp4
6959546100993_20240411-135257_front_addGood_70f75407b7ae_295_17788571404.mp4
6959546100993_20240411-135339_back_returnGood_70f75407b7ae_295_17788571404.mp4
6959546100993_20240411-135339_front_returnGood_70f75407b7ae_295_17788571404.mp4
6959546100993_20240411-141255_back_addGood_70f754088050_295_17327712807.mp4
6959546100993_20240411-141255_front_addGood_70f754088050_295_17327712807.mp4
6959546100993_20240411-141309_back_returnGood_70f754088050_295_17327712807.mp4
6959546100993_20240411-141309_front_returnGood_70f754088050_295_17327712807.mp4
6971075127463_20240411-135002_back_addGood_70f75407b7ae_210_17788571404.mp4
6971075127463_20240411-135002_front_addGood_70f75407b7ae_210_17788571404.mp4
6971075127463_20240411-135058_back_returnGood_70f75407b7ae_210_17788571404.mp4
6971075127463_20240411-135058_front_returnGood_70f75407b7ae_210_17788571404.mp4
6971075127463_20240411-141154_back_addGood_70f754088050_215_17327712807.mp4
6971075127463_20240411-141154_front_addGood_70f754088050_215_17327712807.mp4
6971075127463_20240411-141214_back_returnGood_70f754088050_210_17327712807.mp4
6971075127463_20240411-141214_front_returnGood_70f754088050_210_17327712807.mp4
6971328580533_20240411-135748_back_addGood_70f75407b7ae_405_17788571404.mp4
6971328580533_20240411-135748_front_addGood_70f75407b7ae_405_17788571404.mp4
6971328580533_20240411-135757_back_returnGood_70f75407b7ae_405_17788571404.mp4
6971328580533_20240411-135757_front_returnGood_70f75407b7ae_405_17788571404.mp4
6971328580533_20240411-140231_back_addGood_70f754088050_405_17327712807.mp4
6971328580533_20240411-140231_front_addGood_70f754088050_405_17327712807.mp4
6971328580533_20240411-140529_back_addGood_70f754088050_410_17327712807.mp4
6971328580533_20240411-140529_front_addGood_70f754088050_410_17327712807.mp4
6971328580533_20240411-140745_back_returnGood_70f754088050_405_17327712807.mp4
6971328580533_20240411-140745_front_returnGood_70f754088050_405_17327712807.mp4
6971738655333_20240411-144616_back_addGood_70f75407b7ae_270_17788571404.mp4
6971738655333_20240411-144616_back_addGood_70f754088050_260_17327712807.mp4
6971738655333_20240411-144616_front_addGood_70f75407b7ae_270_17788571404.mp4
6971738655333_20240411-144616_front_addGood_70f754088050_260_17327712807.mp4
6971738655333_20240411-144633_back_returnGood_70f75407b7ae_270_17788571404.mp4
6971738655333_20240411-144633_front_returnGood_70f75407b7ae_270_17788571404.mp4
6971738655333_20240411-144635_back_returnGood_70f754088050_260_17327712807.mp4
6971738655333_20240411-144635_front_returnGood_70f754088050_260_17327712807.mp4
6972378998200_20240411-142603_back_addGood_70f754088050_410_17327712807.mp4
6972378998200_20240411-142603_front_addGood_70f754088050_410_17327712807.mp4
6972378998200_20240411-142604_back_addGood_70f75407b7ae_410_17788571404.mp4
6972378998200_20240411-142604_front_addGood_70f75407b7ae_410_17788571404.mp4
6972378998200_20240411-142613_back_returnGood_70f75407b7ae_410_17788571404.mp4
6972378998200_20240411-142613_back_returnGood_70f754088050_410_17327712807.mp4
6972378998200_20240411-142613_front_returnGood_70f75407b7ae_410_17788571404.mp4
6972378998200_20240411-142613_front_returnGood_70f754088050_410_17327712807.mp4
6972790052733_20240411-162926_back_addGood_70f75407b7ae_690_17788571404.mp4
6972790052733_20240411-162926_front_addGood_70f75407b7ae_690_17788571404.mp4
6972790052733_20240411-162933_back_returnGood_70f75407b7ae_690_17788571404.mp4
6972790052733_20240411-162933_front_returnGood_70f75407b7ae_690_17788571404.mp4
6972790052733_20240411-162948_back_addGood_70f754088050_690_17327712807.mp4
6972790052733_20240411-162948_front_addGood_70f754088050_690_17327712807.mp4
6972790052733_20240411-163001_back_returnGood_70f754088050_690_17327712807.mp4
6972790052733_20240411-163001_front_returnGood_70f754088050_690_17327712807.mp4
6974627182033_20240411-150747_back_addGood_70f75407b7ae_485_17788571404.mp4
6974627182033_20240411-150747_front_addGood_70f75407b7ae_485_17788571404.mp4
6974627182033_20240411-150756_back_returnGood_70f75407b7ae_480_17788571404.mp4
6974627182033_20240411-150756_front_returnGood_70f75407b7ae_480_17788571404.mp4
6974627182033_20240411-150825_back_addGood_70f754088050_495_17327712807.mp4
6974627182033_20240411-150825_front_addGood_70f754088050_495_17327712807.mp4
6974627182033_20240411-150835_back_returnGood_70f754088050_495_17327712807.mp4
6974627182033_20240411-150835_front_returnGood_70f754088050_495_17327712807.mp4
6974913231612_20240411-135543_back_addGood_70f75407b7ae_870_17788571404.mp4
6974913231612_20240411-135543_front_addGood_70f75407b7ae_870_17788571404.mp4
6974913231612_20240411-135552_back_returnGood_70f75407b7ae_865_17788571404.mp4
6974913231612_20240411-135552_front_returnGood_70f75407b7ae_865_17788571404.mp4
6974913231612_20240411-140906_back_addGood_70f754088050_755_17327712807.mp4
6974913231612_20240411-140906_front_addGood_70f754088050_755_17327712807.mp4
6974913231612_20240411-140917_back_returnGood_70f754088050_755_17327712807.mp4
6974913231612_20240411-140917_front_returnGood_70f754088050_755_17327712807.mp4
6974995172711_20240411-135456_back_addGood_70f75407b7ae_1455_17788571404.mp4
6974995172711_20240411-135456_front_addGood_70f75407b7ae_1455_17788571404.mp4
6974995172711_20240411-135506_back_returnGood_70f75407b7ae_1460_17788571404.mp4
6974995172711_20240411-135506_front_returnGood_70f75407b7ae_1460_17788571404.mp4
6974995172711_20240411-140831_back_addGood_70f754088050_1460_17327712807.mp4
6974995172711_20240411-140831_front_addGood_70f754088050_1460_17327712807.mp4
6974995172711_20240411-140844_back_returnGood_70f754088050_1460_17327712807.mp4
6974995172711_20240411-140844_front_returnGood_70f754088050_1460_17327712807.mp4
6976090230303_20240411-144457_back_addGood_70f75407b7ae_390_17788571404.mp4
6976090230303_20240411-144457_front_addGood_70f75407b7ae_390_17788571404.mp4
6976090230303_20240411-144458_back_addGood_70f754088050_390_17327712807.mp4
6976090230303_20240411-144458_front_addGood_70f754088050_390_17327712807.mp4
6976090230303_20240411-144508_back_returnGood_70f754088050_390_17327712807.mp4
6976090230303_20240411-144508_front_returnGood_70f754088050_390_17327712807.mp4
6976090230303_20240411-144509_back_returnGood_70f75407b7ae_390_17788571404.mp4
6976090230303_20240411-144509_front_returnGood_70f75407b7ae_390_17788571404.mp4
6976371220276_20240411-150456_back_addGood_70f75407b7ae_390_17788571404.mp4
6976371220276_20240411-150456_front_addGood_70f75407b7ae_390_17788571404.mp4
6976371220276_20240411-150505_back_returnGood_70f75407b7ae_390_17788571404.mp4
6976371220276_20240411-150505_front_returnGood_70f75407b7ae_390_17788571404.mp4
6976371220276_20240411-150559_back_addGood_70f754088050_405_17327712807.mp4
6976371220276_20240411-150559_front_addGood_70f754088050_405_17327712807.mp4
6976371220276_20240411-150610_back_returnGood_70f754088050_405_17327712807.mp4
6976371220276_20240411-150610_front_returnGood_70f754088050_405_17327712807.mp4
230537101280010007_20240412-140824_back_addGood_70f754088050_565_13725988807.mp4
230537101280010007_20240412-140824_front_addGood_70f754088050_565_13725988807.mp4
230537101280010007_20240412-140835_back_returnGood_70f754088050_565_13725988807.mp4
230537101280010007_20240412-140835_front_returnGood_70f754088050_565_13725988807.mp4
2500456001326_20240412-140949_back_addGood_70f754088050_150_13725988807.mp4
2500456001326_20240412-140949_front_addGood_70f754088050_150_13725988807.mp4
2500456001326_20240412-140958_back_returnGood_70f754088050_155_13725988807.mp4
2500456001326_20240412-140958_front_returnGood_70f754088050_155_13725988807.mp4
2500458675341_20240412-140915_back_addGood_70f754088050_130_13725988807.mp4
2500458675341_20240412-140915_front_addGood_70f754088050_130_13725988807.mp4
2500458675341_20240412-140924_back_returnGood_70f754088050_130_13725988807.mp4
2500458675341_20240412-140924_front_returnGood_70f754088050_130_13725988807.mp4
2500463464671_20240412-140718_back_addGood_70f754088050_810_13725988807.mp4
2500463464671_20240412-140718_front_addGood_70f754088050_810_13725988807.mp4
2500463464671_20240412-140731_back_returnGood_70f754088050_810_13725988807.mp4
2500463464671_20240412-140731_front_returnGood_70f754088050_810_13725988807.mp4
6901070613142_20240412-144631_back_addGood_70f754088050_240_13725988807.mp4
6901070613142_20240412-144631_front_addGood_70f754088050_240_13725988807.mp4
6901070613142_20240412-144643_back_returnGood_70f754088050_240_13725988807.mp4
6901070613142_20240412-144643_front_returnGood_70f754088050_240_13725988807.mp4
6901586000993_20240412-152404_back_addGood_70f754088050_890_13725988807.mp4
6901586000993_20240412-152404_front_addGood_70f754088050_890_13725988807.mp4
6901586000993_20240412-152440_back_returnGood_70f754088050_885_13725988807.mp4
6901586000993_20240412-152440_front_returnGood_70f754088050_885_13725988807.mp4
6901668053893_20240412-150115_back_addGood_70f754088050_70_13725988807.mp4
6901668053893_20240412-150115_front_addGood_70f754088050_70_13725988807.mp4
6901668053893_20240412-150125_back_returnGood_70f754088050_70_13725988807.mp4
6901668053893_20240412-150125_front_returnGood_70f754088050_70_13725988807.mp4
6902007010249_20240412-144518_back_addGood_70f754088050_755_13725988807.mp4
6902007010249_20240412-144518_front_addGood_70f754088050_755_13725988807.mp4
6902007010249_20240412-144529_back_returnGood_70f754088050_755_13725988807.mp4
6902007010249_20240412-144529_front_returnGood_70f754088050_755_13725988807.mp4
6902022135514_20240412-144740_back_addGood_70f754088050_3180_13725988807.mp4
6902022135514_20240412-144740_front_addGood_70f754088050_3180_13725988807.mp4
6902022135514_20240412-144750_back_returnGood_70f754088050_3180_13725988807.mp4
6902022135514_20240412-144750_front_returnGood_70f754088050_3180_13725988807.mp4
6902265114369_20240412-144022_back_addGood_70f754088050_720_13725988807.mp4
6902265114369_20240412-144022_front_addGood_70f754088050_720_13725988807.mp4
6902265114369_20240412-144034_back_returnGood_70f754088050_720_13725988807.mp4
6902265114369_20240412-144034_front_returnGood_70f754088050_720_13725988807.mp4
6902265908012_20240412-144141_back_addGood_70f754088050_1145_13725988807.mp4
6902265908012_20240412-144141_front_addGood_70f754088050_1145_13725988807.mp4
6902265908012_20240412-144149_back_returnGood_70f754088050_1145_13725988807.mp4
6902265908012_20240412-144149_front_returnGood_70f754088050_1145_13725988807.mp4
69025143_20240412-135423_back_addGood_70f754088050_3015_13725988807.mp4
69025143_20240412-135423_front_addGood_70f754088050_3015_13725988807.mp4
69025143_20240412-135439_back_returnGood_70f754088050_3015_13725988807.mp4
69025143_20240412-135439_front_returnGood_70f754088050_3015_13725988807.mp4
6907992103952_20240412-141542_back_addGood_70f754088050_190_13725988807.mp4
6907992103952_20240412-141542_front_addGood_70f754088050_190_13725988807.mp4
6907992103952_20240412-141554_back_returnGood_70f754088050_190_13725988807.mp4
6907992103952_20240412-141554_front_returnGood_70f754088050_190_13725988807.mp4
6907992104157_20240412-141255_back_addGood_70f754088050_1120_13725988807.mp4
6907992104157_20240412-141255_front_addGood_70f754088050_1120_13725988807.mp4
6907992104157_20240412-141307_back_returnGood_70f754088050_1120_13725988807.mp4
6907992104157_20240412-141307_front_returnGood_70f754088050_1120_13725988807.mp4
6907992105260_20240412-142144_back_addGood_70f754088050_2775_13725988807.mp4
6907992105260_20240412-142144_front_addGood_70f754088050_2775_13725988807.mp4
6907992105260_20240412-142156_back_returnGood_70f754088050_2775_13725988807.mp4
6907992105260_20240412-142156_front_returnGood_70f754088050_2775_13725988807.mp4
6907992105260_20240412-143214_back_addGood_70f754088050_2770_13725988807.mp4
6907992105260_20240412-143214_front_addGood_70f754088050_2770_13725988807.mp4
6907992105260_20240412-143225_back_returnGood_70f754088050_2775_13725988807.mp4
6907992105260_20240412-143225_front_returnGood_70f754088050_2775_13725988807.mp4
6907992105765_20240412-141505_back_addGood_70f754088050_2085_13725988807.mp4
6907992105765_20240412-141505_front_addGood_70f754088050_2085_13725988807.mp4
6907992105765_20240412-141523_back_returnGood_70f754088050_2085_13725988807.mp4
6907992105765_20240412-141523_front_returnGood_70f754088050_2085_13725988807.mp4
6907992106205_20240412-141729_back_addGood_70f754088050_790_13725988807.mp4
6907992106205_20240412-141729_front_addGood_70f754088050_790_13725988807.mp4
6907992106205_20240412-141741_back_returnGood_70f754088050_790_13725988807.mp4
6907992106205_20240412-141741_front_returnGood_70f754088050_790_13725988807.mp4
6907992106311_20240412-141326_back_addGood_70f754088050_890_13725988807.mp4
6907992106311_20240412-141326_front_addGood_70f754088050_890_13725988807.mp4
6907992106311_20240412-141341_back_returnGood_70f754088050_890_13725988807.mp4
6907992106311_20240412-141341_front_returnGood_70f754088050_890_13725988807.mp4
6914973602908_20240412-134547_back_addGood_70f754088050_505_13725988807.mp4
6914973602908_20240412-134547_front_addGood_70f754088050_505_13725988807.mp4
6914973602908_20240412-134600_back_returnGood_70f754088050_505_13725988807.mp4
6914973602908_20240412-134600_front_returnGood_70f754088050_505_13725988807.mp4
6914973604223_20240412-143609_back_addGood_70f754088050_230_13725988807.mp4
6914973604223_20240412-143609_front_addGood_70f754088050_230_13725988807.mp4
6914973604223_20240412-143619_back_returnGood_70f754088050_230_13725988807.mp4
6914973604223_20240412-143619_front_returnGood_70f754088050_230_13725988807.mp4
6914973606340_20240412-134352_back_addGood_70f754088050_325_13725988807.mp4
6914973606340_20240412-134352_front_addGood_70f754088050_325_13725988807.mp4
6914973606340_20240412-134406_back_returnGood_70f754088050_330_13725988807.mp4
6914973606340_20240412-134406_front_returnGood_70f754088050_330_13725988807.mp4
6919188092377_20240412-141841_back_addGood_70f754088050_515_13725988807.mp4
6919188092377_20240412-141841_front_addGood_70f754088050_515_13725988807.mp4
6919188092377_20240412-141850_back_returnGood_70f754088050_515_13725988807.mp4
6919188092377_20240412-141850_front_returnGood_70f754088050_515_13725988807.mp4
6920152400630_20240412-151036_back_returnGood_70f754088050_580_13725988807.mp4
6920174757101_20240412-144934_back_addGood_70f754088050_1300_13725988807.mp4
6920174757101_20240412-144934_front_addGood_70f754088050_1300_13725988807.mp4
6920174757101_20240412-144946_back_returnGood_70f754088050_1300_13725988807.mp4
6920174757101_20240412-144946_front_returnGood_70f754088050_1300_13725988807.mp4
6920459905012_20240412-150826_back_returnGood_70f754088050_550_13725988807.mp4
6920907810707_20240412-145330_back_addGood_70f754088050_75_13725988807.mp4
6920907810707_20240412-145330_front_addGood_70f754088050_75_13725988807.mp4
6920907810707_20240412-145340_back_returnGood_70f754088050_75_13725988807.mp4
6920907810707_20240412-145340_front_returnGood_70f754088050_75_13725988807.mp4
6922130119213_20240412-144416_back_addGood_70f754088050_1020_13725988807.mp4
6922130119213_20240412-144416_front_addGood_70f754088050_1020_13725988807.mp4
6922130119213_20240412-144426_back_returnGood_70f754088050_1020_13725988807.mp4
6922130119213_20240412-144426_front_returnGood_70f754088050_1020_13725988807.mp4
6922577700968_20240412-141700_back_addGood_70f754088050_1040_13725988807.mp4
6922577700968_20240412-141700_front_addGood_70f754088050_1040_13725988807.mp4
6922577700968_20240412-141710_back_returnGood_70f754088050_1040_13725988807.mp4
6922577700968_20240412-141710_front_returnGood_70f754088050_1040_13725988807.mp4
6922868291168_20240412-145057_back_addGood_70f754088050_1160_13725988807.mp4
6922868291168_20240412-145057_front_addGood_70f754088050_1160_13725988807.mp4
6922868291168_20240412-145113_back_returnGood_70f754088050_1160_13725988807.mp4
6922868291168_20240412-145113_front_returnGood_70f754088050_1160_13725988807.mp4
6923450601549_20240412-134454_back_addGood_70f754088050_640_13725988807.mp4
6923450601549_20240412-134454_front_addGood_70f754088050_640_13725988807.mp4
6923450601549_20240412-134504_back_returnGood_70f754088050_635_13725988807.mp4
6923450601549_20240412-134504_front_returnGood_70f754088050_635_13725988807.mp4
6923450603574_20240412-135226_back_addGood_70f754088050_970_13725988807.mp4
6923450603574_20240412-135226_front_addGood_70f754088050_970_13725988807.mp4
6923450603574_20240412-135240_back_returnGood_70f754088050_965_13725988807.mp4
6923450603574_20240412-135240_front_returnGood_70f754088050_965_13725988807.mp4
6923450605288_20240412-134133_back_addGood_70f754088050_470_13725988807.mp4
6923450605288_20240412-134133_front_addGood_70f754088050_470_13725988807.mp4
6923450605288_20240412-134143_back_returnGood_70f754088050_470_13725988807.mp4
6923450605288_20240412-134143_front_returnGood_70f754088050_470_13725988807.mp4
6923450605332_20240412-135742_back_addGood_70f754088050_1785_13725988807.mp4
6923450605332_20240412-135742_front_addGood_70f754088050_1785_13725988807.mp4
6923450605332_20240412-135756_back_returnGood_70f754088050_1785_13725988807.mp4
6923450605332_20240412-135756_front_returnGood_70f754088050_1785_13725988807.mp4
6923450610428_20240412-133618_back_addGood_70f754088050_320_13725988807.mp4
6923450610428_20240412-133618_front_addGood_70f754088050_320_13725988807.mp4
6923450610428_20240412-133632_back_returnGood_70f754088050_320_13725988807.mp4
6923450610428_20240412-133632_front_returnGood_70f754088050_320_13725988807.mp4
6923450610459_20240412-135040_back_addGood_70f754088050_555_13725988807.mp4
6923450610459_20240412-135040_front_addGood_70f754088050_555_13725988807.mp4
6923450610459_20240412-135057_back_returnGood_70f754088050_555_13725988807.mp4
6923450610459_20240412-135057_front_returnGood_70f754088050_555_13725988807.mp4
6923450611067_20240412-134949_back_addGood_70f754088050_800_13725988807.mp4
6923450611067_20240412-134949_front_addGood_70f754088050_800_13725988807.mp4
6923450611067_20240412-135002_back_returnGood_70f754088050_800_13725988807.mp4
6923450611067_20240412-135002_front_returnGood_70f754088050_800_13725988807.mp4
6923450612415_20240412-133515_back_addGood_70f754088050_820_13725988807.mp4
6923450612415_20240412-133515_front_addGood_70f754088050_820_13725988807.mp4
6923450612415_20240412-133532_back_returnGood_70f754088050_820_13725988807.mp4
6923450612415_20240412-133532_front_returnGood_70f754088050_820_13725988807.mp4
6923450612484_20240412-133746_back_addGood_70f754088050_255_13725988807.mp4
6923450612484_20240412-133746_front_addGood_70f754088050_255_13725988807.mp4
6923450612484_20240412-133758_back_returnGood_70f754088050_255_13725988807.mp4
6923450612484_20240412-133758_front_returnGood_70f754088050_255_13725988807.mp4
6923450657829_20240412-135628_back_addGood_70f754088050_965_13725988807.mp4
6923450657829_20240412-135628_front_addGood_70f754088050_965_13725988807.mp4
6923450657829_20240412-135640_back_returnGood_70f754088050_965_13725988807.mp4
6923450657829_20240412-135640_front_returnGood_70f754088050_965_13725988807.mp4
6923450659441_20240412-135319_back_addGood_70f754088050_1825_13725988807.mp4
6923450659441_20240412-135319_front_addGood_70f754088050_1825_13725988807.mp4
6923450659441_20240412-135334_back_returnGood_70f754088050_1825_13725988807.mp4
6923450659441_20240412-135334_front_returnGood_70f754088050_1825_13725988807.mp4
6923450666838_20240412-134807_back_addGood_70f754088050_760_13725988807.mp4
6923450666838_20240412-134807_front_addGood_70f754088050_760_13725988807.mp4
6923450666838_20240412-134818_back_returnGood_70f754088050_760_13725988807.mp4
6923450666838_20240412-134818_front_returnGood_70f754088050_760_13725988807.mp4
6923450668207_20240412-134250_back_addGood_70f754088050_700_13725988807.mp4
6923450668207_20240412-134250_front_addGood_70f754088050_700_13725988807.mp4
6923450668207_20240412-134302_back_returnGood_70f754088050_700_13725988807.mp4
6923450668207_20240412-134302_front_returnGood_70f754088050_700_13725988807.mp4
6923450677858_20240412-135523_back_addGood_70f754088050_910_13725988807.mp4
6923450677858_20240412-135523_front_addGood_70f754088050_910_13725988807.mp4
6923450677858_20240412-135537_back_returnGood_70f754088050_910_13725988807.mp4
6923450677858_20240412-135537_front_returnGood_70f754088050_910_13725988807.mp4
6923644286293_20240412-141148_back_addGood_70f754088050_810_13725988807.mp4
6923644286293_20240412-141148_front_addGood_70f754088050_810_13725988807.mp4
6923644286293_20240412-141157_back_returnGood_70f754088050_805_13725988807.mp4
6923644286293_20240412-141157_front_returnGood_70f754088050_805_13725988807.mp4
6923644298760_20240412-141219_back_addGood_70f754088050_1020_13725988807.mp4
6923644298760_20240412-141219_front_addGood_70f754088050_1020_13725988807.mp4
6923644298760_20240412-141227_back_returnGood_70f754088050_1020_13725988807.mp4
6923644298760_20240412-141227_front_returnGood_70f754088050_1020_13725988807.mp4
6924743915824_20240412-145245_back_addGood_70f754088050_155_13725988807.mp4
6924743915824_20240412-145245_front_addGood_70f754088050_155_13725988807.mp4
6924743915824_20240412-145254_back_returnGood_70f754088050_155_13725988807.mp4
6924743915824_20240412-145254_front_returnGood_70f754088050_155_13725988807.mp4
6924882497106_20240412-150553_back_addGood_70f754088050_355_13725988807.mp4
6924882497106_20240412-150553_front_addGood_70f754088050_355_13725988807.mp4
6924882497106_20240412-150604_back_returnGood_70f754088050_355_13725988807.mp4
6924882497106_20240412-150604_front_returnGood_70f754088050_355_13725988807.mp4
6925307305525_20240412-152608_back_addGood_70f754088050_590_13725988807.mp4
6925307305525_20240412-152608_front_addGood_70f754088050_590_13725988807.mp4
6925307305525_20240412-152627_back_returnGood_70f754088050_590_13725988807.mp4
6925307305525_20240412-152627_front_returnGood_70f754088050_590_13725988807.mp4
6928033404968_20240412-143452_back_addGood_70f754088050_400_13725988807.mp4
6928033404968_20240412-143452_front_addGood_70f754088050_400_13725988807.mp4
6928033404968_20240412-143502_back_returnGood_70f754088050_400_13725988807.mp4
6928033404968_20240412-143502_front_returnGood_70f754088050_400_13725988807.mp4
6928804011173_20240412-152735_back_addGood_70f754088050_545_13725988807.mp4
6928804011173_20240412-152735_front_addGood_70f754088050_545_13725988807.mp4
6928804011173_20240412-152746_back_returnGood_70f754088050_545_13725988807.mp4
6928804011173_20240412-152746_front_returnGood_70f754088050_545_13725988807.mp4
6931925828032_20240412-134659_back_addGood_70f754088050_415_13725988807.mp4
6931925828032_20240412-134659_front_addGood_70f754088050_415_13725988807.mp4
6931925828032_20240412-134711_back_returnGood_70f754088050_415_13725988807.mp4
6931925828032_20240412-134711_front_returnGood_70f754088050_415_13725988807.mp4
6933620900051_20240412-140306_back_addGood_70f754088050_385_13725988807.mp4
6933620900051_20240412-140306_front_addGood_70f754088050_385_13725988807.mp4
6933620900051_20240412-140326_back_returnGood_70f754088050_390_13725988807.mp4
6933620900051_20240412-140326_front_returnGood_70f754088050_390_13725988807.mp4
6934665095108_20240412-141758_back_addGood_70f754088050_355_13725988807.mp4
6934665095108_20240412-141758_front_addGood_70f754088050_355_13725988807.mp4
6934665095108_20240412-141807_back_returnGood_70f754088050_355_13725988807.mp4
6934665095108_20240412-141807_front_returnGood_70f754088050_355_13725988807.mp4
6935270642121_20240412-151112_back_addGood_70f754088050_165_13725988807.mp4
6935270642121_20240412-151112_front_addGood_70f754088050_165_13725988807.mp4
6935270642121_20240412-151124_back_returnGood_70f754088050_165_13725988807.mp4
6935270642121_20240412-151124_front_returnGood_70f754088050_165_13725988807.mp4
6935284417326_20240412-145558_back_addGood_70f754088050_410_13725988807.mp4
6935284417326_20240412-145558_front_addGood_70f754088050_410_13725988807.mp4
6935284417326_20240412-145610_back_returnGood_70f754088050_410_13725988807.mp4
6935284417326_20240412-145610_front_returnGood_70f754088050_410_13725988807.mp4
6941025140798_20240412-152222_back_addGood_70f754088050_1160_13725988807.mp4
6941025140798_20240412-152222_front_addGood_70f754088050_1160_13725988807.mp4
6941025140798_20240412-152235_back_returnGood_70f754088050_1155_13725988807.mp4
6941025140798_20240412-152235_front_returnGood_70f754088050_1155_13725988807.mp4
6952074634794_20240412-153052_back_addGood_70f754088050_265_13725988807.mp4
6952074634794_20240412-153052_front_addGood_70f754088050_265_13725988807.mp4
6952074634794_20240412-153105_back_returnGood_70f754088050_265_13725988807.mp4
6952074634794_20240412-153105_front_returnGood_70f754088050_265_13725988807.mp4
6952074634794_20240412-153233_back_addGood_70f754088050_265_13725988807.mp4
6952074634794_20240412-153233_front_addGood_70f754088050_265_13725988807.mp4
6952074634794_20240412-153245_back_returnGood_70f754088050_265_13725988807.mp4
6952074634794_20240412-153245_front_returnGood_70f754088050_265_13725988807.mp4
6954432711307_20240412-134028_back_addGood_70f754088050_355_13725988807.mp4
6954432711307_20240412-134028_front_addGood_70f754088050_355_13725988807.mp4
6954432711307_20240412-134040_back_returnGood_70f754088050_350_13725988807.mp4
6954432711307_20240412-134040_front_returnGood_70f754088050_350_13725988807.mp4
6959546100993_20240412-142438_back_addGood_70f754088050_295_13725988807.mp4
6959546100993_20240412-142438_front_addGood_70f754088050_295_13725988807.mp4
6959546100993_20240412-142455_back_returnGood_70f754088050_295_13725988807.mp4
6959546100993_20240412-142455_front_returnGood_70f754088050_295_13725988807.mp4
6971075127463_20240412-142330_back_addGood_70f754088050_210_13725988807.mp4
6971075127463_20240412-142330_front_addGood_70f754088050_210_13725988807.mp4
6971075127463_20240412-142347_back_returnGood_70f754088050_215_13725988807.mp4
6971075127463_20240412-142347_front_returnGood_70f754088050_215_13725988807.mp4
6971075127470_20240412-142650_back_addGood_70f754088050_215_13725988807.mp4
6971075127470_20240412-142650_front_addGood_70f754088050_215_13725988807.mp4
6971075127470_20240412-142700_back_returnGood_70f754088050_215_13725988807.mp4
6971075127470_20240412-142700_front_returnGood_70f754088050_215_13725988807.mp4
6971328580533_20240412-152303_back_addGood_70f754088050_655_13725988807.mp4
6971328580533_20240412-152303_front_addGood_70f754088050_655_13725988807.mp4
6971328580533_20240412-152322_back_returnGood_70f754088050_650_13725988807.mp4
6971328580533_20240412-152322_front_returnGood_70f754088050_650_13725988807.mp4
6971738655333_20240412-141033_back_addGood_70f754088050_270_13725988807.mp4
6971738655333_20240412-141033_front_addGood_70f754088050_270_13725988807.mp4
6971738655333_20240412-141042_back_returnGood_70f754088050_270_13725988807.mp4
6971738655333_20240412-141042_front_returnGood_70f754088050_270_13725988807.mp4
6972378998200_20240412-144314_back_addGood_70f754088050_410_13725988807.mp4
6972378998200_20240412-144314_front_addGood_70f754088050_410_13725988807.mp4
6972378998200_20240412-144326_back_returnGood_70f754088050_410_13725988807.mp4
6972378998200_20240412-144326_front_returnGood_70f754088050_410_13725988807.mp4
6972790052733_20240412-135134_back_addGood_70f754088050_525_13725988807.mp4
6972790052733_20240412-135134_front_addGood_70f754088050_525_13725988807.mp4
6972790052733_20240412-135145_back_returnGood_70f754088050_525_13725988807.mp4
6972790052733_20240412-135145_front_returnGood_70f754088050_525_13725988807.mp4
6974913231612_20240412-142848_back_addGood_70f754088050_500_13725988807.mp4
6974913231612_20240412-142848_front_addGood_70f754088050_500_13725988807.mp4
6974913231612_20240412-142900_back_returnGood_70f754088050_495_13725988807.mp4
6974913231612_20240412-142900_front_returnGood_70f754088050_495_13725988807.mp4
6974995172711_20240412-152143_back_addGood_70f754088050_455_13725988807.mp4
6974995172711_20240412-152143_front_addGood_70f754088050_455_13725988807.mp4
6974995172711_20240412-152158_back_returnGood_70f754088050_455_13725988807.mp4
6974995172711_20240412-152158_front_returnGood_70f754088050_455_13725988807.mp4
6976371220276_20240412-140448_back_addGood_70f754088050_295_13725988807.mp4
6976371220276_20240412-140448_front_addGood_70f754088050_295_13725988807.mp4
6976371220276_20240412-140459_back_returnGood_70f754088050_295_13725988807.mp4
6976371220276_20240412-140459_front_returnGood_70f754088050_295_13725988807.mp4
850009021632_20240412-152522_back_addGood_70f754088050_470_13725988807.mp4
850009021632_20240412-152522_front_addGood_70f754088050_470_13725988807.mp4
850009021632_20240412-152543_back_returnGood_70f754088050_470_13725988807.mp4
850009021632_20240412-152543_front_returnGood_70f754088050_470_13725988807.mp4

View File

@ -0,0 +1,208 @@
6901070613142_20240411-170415_back_addGood_70f75407b7ae_430_17788571404.mp4
6901070613142_20240411-170415_front_addGood_70f75407b7ae_430_17788571404.mp4
6901070613142_20240411-170424_back_returnGood_70f75407b7ae_430_17788571404.mp4
6901070613142_20240411-170424_front_returnGood_70f75407b7ae_430_17788571404.mp4
6901070613142_20240411-170441_back_addGood_70f754088050_430_17327712807.mp4
6901070613142_20240411-170441_front_addGood_70f754088050_430_17327712807.mp4
6901070613142_20240411-170450_back_returnGood_70f754088050_430_17327712807.mp4
6901070613142_20240411-170450_front_returnGood_70f754088050_430_17327712807.mp4
6902538007367_20240411-165931_back_addGood_70f75407b7ae_995_17788571404.mp4
6902538007367_20240411-165931_front_addGood_70f75407b7ae_995_17788571404.mp4
6902538007367_20240411-165942_back_returnGood_70f75407b7ae_995_17788571404.mp4
6902538007367_20240411-165942_front_returnGood_70f75407b7ae_995_17788571404.mp4
6902538007367_20240411-165954_back_addGood_70f754088050_1000_17327712807.mp4
6902538007367_20240411-165954_front_addGood_70f754088050_1000_17327712807.mp4
6902538007367_20240411-170005_back_returnGood_70f754088050_1000_17327712807.mp4
6902538007367_20240411-170005_front_returnGood_70f754088050_1000_17327712807.mp4
6920152400630_20240411-165136_back_addGood_70f75407b7ae_720_17788571404.mp4
6920152400630_20240411-165136_front_addGood_70f75407b7ae_720_17788571404.mp4
6920152400630_20240411-165151_back_returnGood_70f75407b7ae_715_17788571404.mp4
6920152400630_20240411-165151_front_returnGood_70f75407b7ae_715_17788571404.mp4
6920152400630_20240411-165213_back_addGood_70f754088050_720_17327712807.mp4
6920152400630_20240411-165213_front_addGood_70f754088050_720_17327712807.mp4
6920152400630_20240411-165224_back_returnGood_70f754088050_720_17327712807.mp4
6920152400630_20240411-165224_front_returnGood_70f754088050_720_17327712807.mp4
6920907810707_20240411-165615_back_addGood_70f75407b7ae_225_17788571404.mp4
6920907810707_20240411-165615_front_addGood_70f75407b7ae_225_17788571404.mp4
6920907810707_20240411-165625_back_returnGood_70f75407b7ae_225_17788571404.mp4
6920907810707_20240411-165625_front_returnGood_70f75407b7ae_225_17788571404.mp4
6920907810707_20240411-165635_back_addGood_70f754088050_225_17327712807.mp4
6920907810707_20240411-165635_front_addGood_70f754088050_225_17327712807.mp4
6920907810707_20240411-165646_back_returnGood_70f754088050_225_17327712807.mp4
6920907810707_20240411-165646_front_returnGood_70f754088050_225_17327712807.mp4
6923450605288_20240411-172045_back_addGood_70f75407b7ae_750_17788571404.mp4
6923450605288_20240411-172045_front_addGood_70f75407b7ae_750_17788571404.mp4
6923450605288_20240411-172058_back_returnGood_70f75407b7ae_750_17788571404.mp4
6923450605288_20240411-172058_front_returnGood_70f75407b7ae_750_17788571404.mp4
6923450605288_20240411-172134_back_addGood_70f754088050_750_17327712807.mp4
6923450605288_20240411-172134_front_addGood_70f754088050_750_17327712807.mp4
6923450605288_20240411-172147_back_returnGood_70f754088050_750_17327712807.mp4
6923450605288_20240411-172147_front_returnGood_70f754088050_750_17327712807.mp4
6923450610428_20240411-171842_back_addGood_70f75407b7ae_270_17788571404.mp4
6923450610428_20240411-171842_front_addGood_70f75407b7ae_270_17788571404.mp4
6923450610428_20240411-171900_back_returnGood_70f75407b7ae_755_17788571404.mp4
6923450610428_20240411-171900_front_returnGood_70f75407b7ae_755_17788571404.mp4
6923450610428_20240411-171918_back_addGood_70f754088050_755_17327712807.mp4
6923450610428_20240411-171918_front_addGood_70f754088050_755_17327712807.mp4
6923450610428_20240411-172000_back_returnGood_70f754088050_755_17327712807.mp4
6923450610428_20240411-172000_front_returnGood_70f754088050_755_17327712807.mp4
6923450659441_20240411-171417_back_addGood_70f75407b7ae_2340_17788571404.mp4
6923450659441_20240411-171417_front_addGood_70f75407b7ae_2340_17788571404.mp4
6923450659441_20240411-171430_back_returnGood_70f75407b7ae_2335_17788571404.mp4
6923450659441_20240411-171430_front_returnGood_70f75407b7ae_2335_17788571404.mp4
6923450659441_20240411-171445_back_addGood_70f754088050_2340_17327712807.mp4
6923450659441_20240411-171445_front_addGood_70f754088050_2340_17327712807.mp4
6923450659441_20240411-171456_back_returnGood_70f754088050_2340_17327712807.mp4
6923450659441_20240411-171456_front_returnGood_70f754088050_2340_17327712807.mp4
6923450677858_20240411-171658_back_addGood_70f75407b7ae_1310_17788571404.mp4
6923450677858_20240411-171658_front_addGood_70f75407b7ae_1310_17788571404.mp4
6923450677858_20240411-171709_back_returnGood_70f75407b7ae_1310_17788571404.mp4
6923450677858_20240411-171709_front_returnGood_70f75407b7ae_1310_17788571404.mp4
6923450677858_20240411-171720_back_addGood_70f754088050_1310_17327712807.mp4
6923450677858_20240411-171720_front_addGood_70f754088050_1310_17327712807.mp4
6923450677858_20240411-171730_back_returnGood_70f754088050_1315_17327712807.mp4
6923450677858_20240411-171730_front_returnGood_70f754088050_1315_17327712807.mp4
6928804011173_20240411-165756_back_addGood_70f75407b7ae_615_17788571404.mp4
6928804011173_20240411-165756_front_addGood_70f75407b7ae_615_17788571404.mp4
6928804011173_20240411-165808_back_returnGood_70f75407b7ae_620_17788571404.mp4
6928804011173_20240411-165808_front_returnGood_70f75407b7ae_620_17788571404.mp4
6928804011173_20240411-165822_back_addGood_70f754088050_620_17327712807.mp4
6928804011173_20240411-165822_front_addGood_70f754088050_620_17327712807.mp4
6928804011173_20240411-165830_back_returnGood_70f754088050_620_17327712807.mp4
6928804011173_20240411-165830_front_returnGood_70f754088050_620_17327712807.mp4
6976371220276_20240411-170159_back_addGood_70f75407b7ae_745_17788571404.mp4
6976371220276_20240411-170159_front_addGood_70f75407b7ae_745_17788571404.mp4
6976371220276_20240411-170211_back_returnGood_70f75407b7ae_745_17788571404.mp4
6976371220276_20240411-170211_front_returnGood_70f75407b7ae_745_17788571404.mp4
6976371220276_20240411-170230_back_addGood_70f754088050_745_17327712807.mp4
6976371220276_20240411-170230_front_addGood_70f754088050_745_17327712807.mp4
6976371220276_20240411-170240_back_returnGood_70f754088050_745_17327712807.mp4
6976371220276_20240411-170240_front_returnGood_70f754088050_745_17327712807.mp4
230537101280010007_20240412-114205_back_addGood_70f75407b7ae_720_17327712807.mp4
230537101280010007_20240412-114205_front_addGood_70f75407b7ae_720_17327712807.mp4
230537101280010007_20240412-114214_back_returnGood_70f75407b7ae_720_17327712807.mp4
230537101280010007_20240412-114214_front_returnGood_70f75407b7ae_720_17327712807.mp4
2500456001326_20240412-110503_back_addGood_70f75407b7ae_1085_17327712807.mp4
2500456001326_20240412-110503_front_addGood_70f75407b7ae_1085_17327712807.mp4
2500456001326_20240412-110513_back_returnGood_70f75407b7ae_1085_17327712807.mp4
2500456001326_20240412-110513_front_returnGood_70f75407b7ae_1085_17327712807.mp4
6901070613142_20240412-110200_back_addGood_70f754088050_1180_17788571404.mp4
6901070613142_20240412-110200_front_addGood_70f754088050_1180_17788571404.mp4
6901070613142_20240412-110207_back_returnGood_70f754088050_1180_17788571404.mp4
6901070613142_20240412-110207_front_returnGood_70f754088050_1180_17788571404.mp4
6901070613142_20240412-112959_back_addGood_70f75407b7ae_655_17327712807.mp4
6901070613142_20240412-112959_front_addGood_70f75407b7ae_655_17327712807.mp4
6901070613142_20240412-113011_back_returnGood_70f75407b7ae_655_17327712807.mp4
6901070613142_20240412-113011_front_returnGood_70f75407b7ae_655_17327712807.mp4
6901668053893_20240412-113635_back_addGood_70f75407b7ae_565_17327712807.mp4
6901668053893_20240412-113635_front_addGood_70f75407b7ae_565_17327712807.mp4
6901668053893_20240412-113645_back_returnGood_70f75407b7ae_640_17327712807.mp4
6901668053893_20240412-113645_front_returnGood_70f75407b7ae_640_17327712807.mp4
6902007010249_20240412-113050_back_addGood_70f75407b7ae_1460_17327712807.mp4
6902007010249_20240412-113050_front_addGood_70f75407b7ae_1460_17327712807.mp4
6902007010249_20240412-113103_back_returnGood_70f75407b7ae_1460_17327712807.mp4
6902007010249_20240412-113103_front_returnGood_70f75407b7ae_1460_17327712807.mp4
6902022135514_20240412-112914_back_addGood_70f75407b7ae_3640_17327712807.mp4
6902022135514_20240412-112914_front_addGood_70f75407b7ae_3640_17327712807.mp4
6902022135514_20240412-112926_back_returnGood_70f75407b7ae_3640_17327712807.mp4
6902022135514_20240412-112926_front_returnGood_70f75407b7ae_3640_17327712807.mp4
6902265114369_20240412-110609_back_addGood_70f75407b7ae_990_17327712807.mp4
6902265114369_20240412-110609_front_addGood_70f75407b7ae_990_17327712807.mp4
6902265114369_20240412-110618_back_returnGood_70f75407b7ae_990_17327712807.mp4
6902265114369_20240412-110618_front_returnGood_70f75407b7ae_990_17327712807.mp4
6902265908012_20240412-111958_back_addGood_70f75407b7ae_1520_17327712807.mp4
6902265908012_20240412-111958_front_addGood_70f75407b7ae_1520_17327712807.mp4
6902265908012_20240412-112012_back_returnGood_70f75407b7ae_1520_17327712807.mp4
6902265908012_20240412-112012_front_returnGood_70f75407b7ae_1520_17327712807.mp4
6902538007367_20240412-105046_back_addGood_70f754088050_930_17788571404.mp4
6902538007367_20240412-105046_front_addGood_70f754088050_930_17788571404.mp4
6902538007367_20240412-105057_back_returnGood_70f754088050_930_17788571404.mp4
6902538007367_20240412-105057_front_returnGood_70f754088050_930_17788571404.mp4
6902538007367_20240412-105130_back_addGood_70f754088050_930_17788571404.mp4
6902538007367_20240412-105130_front_addGood_70f754088050_930_17788571404.mp4
6902538007367_20240412-105143_back_returnGood_70f754088050_935_17788571404.mp4
6902538007367_20240412-105143_front_returnGood_70f754088050_935_17788571404.mp4
6907992103952_20240412-114032_back_addGood_70f75407b7ae_545_17327712807.mp4
6907992103952_20240412-114032_front_addGood_70f75407b7ae_545_17327712807.mp4
6907992103952_20240412-114043_back_returnGood_70f75407b7ae_540_17327712807.mp4
6907992103952_20240412-114043_front_returnGood_70f75407b7ae_540_17327712807.mp4
6907992106311_20240412-110328_back_addGood_70f75407b7ae_2565_17327712807.mp4
6907992106311_20240412-110328_front_addGood_70f75407b7ae_2565_17327712807.mp4
6907992106311_20240412-110342_back_returnGood_70f75407b7ae_2570_17327712807.mp4
6907992106311_20240412-110342_front_returnGood_70f75407b7ae_2570_17327712807.mp4
6920152400630_20240412-112256_back_addGood_70f75407b7ae_1230_17327712807.mp4
6920152400630_20240412-112256_front_addGood_70f75407b7ae_1230_17327712807.mp4
6920152400630_20240412-112308_back_returnGood_70f75407b7ae_1230_17327712807.mp4
6920152400630_20240412-112308_front_returnGood_70f75407b7ae_1230_17327712807.mp4
6920174757101_20240412-112806_back_addGood_70f75407b7ae_2120_17327712807.mp4
6920174757101_20240412-112806_front_addGood_70f75407b7ae_2120_17327712807.mp4
6920174757101_20240412-112823_back_returnGood_70f75407b7ae_2125_17327712807.mp4
6920174757101_20240412-112823_front_returnGood_70f75407b7ae_2125_17327712807.mp4
6920459905012_20240412-104811_back_addGood_70f754088050_840_17788571404.mp4
6920459905012_20240412-104811_front_addGood_70f754088050_840_17788571404.mp4
6920459905012_20240412-104906_back_returnGood_70f754088050_840_17788571404.mp4
6920459905012_20240412-104906_front_returnGood_70f754088050_840_17788571404.mp4
6920459905012_20240412-105345_back_addGood_70f754088050_830_17788571404.mp4
6920459905012_20240412-105345_front_addGood_70f754088050_830_17788571404.mp4
6920459905012_20240412-105356_back_returnGood_70f754088050_830_17788571404.mp4
6920459905012_20240412-105356_front_returnGood_70f754088050_830_17788571404.mp4
6920459905012_20240412-113755_back_addGood_70f75407b7ae_970_17327712807.mp4
6920459905012_20240412-113755_front_addGood_70f75407b7ae_970_17327712807.mp4
6920459905012_20240412-113808_back_returnGood_70f75407b7ae_970_17327712807.mp4
6920459905012_20240412-113808_front_returnGood_70f75407b7ae_970_17327712807.mp4
6920907810707_20240412-105728_back_addGood_70f754088050_150_17788571404.mp4
6920907810707_20240412-105728_front_addGood_70f754088050_150_17788571404.mp4
6920907810707_20240412-105739_back_returnGood_70f754088050_145_17788571404.mp4
6920907810707_20240412-105739_front_returnGood_70f754088050_145_17788571404.mp4
6920907810707_20240412-113710_back_addGood_70f75407b7ae_900_17327712807.mp4
6920907810707_20240412-113710_front_addGood_70f75407b7ae_900_17327712807.mp4
6920907810707_20240412-113720_back_returnGood_70f75407b7ae_900_17327712807.mp4
6920907810707_20240412-113720_front_returnGood_70f75407b7ae_900_17327712807.mp4
6922130119213_20240412-111854_back_addGood_70f75407b7ae_1310_17327712807.mp4
6922130119213_20240412-111854_front_addGood_70f75407b7ae_1310_17327712807.mp4
6922130119213_20240412-111904_back_returnGood_70f75407b7ae_1310_17327712807.mp4
6922130119213_20240412-111904_front_returnGood_70f75407b7ae_1310_17327712807.mp4
6922577700968_20240412-114323_back_addGood_70f75407b7ae_1045_17327712807.mp4
6922577700968_20240412-114323_front_addGood_70f75407b7ae_1045_17327712807.mp4
6922577700968_20240412-114333_back_returnGood_70f75407b7ae_1310_17327712807.mp4
6922577700968_20240412-114333_front_returnGood_70f75407b7ae_1310_17327712807.mp4
6922868291168_20240412-112724_back_addGood_70f75407b7ae_4540_17327712807.mp4
6922868291168_20240412-112724_front_addGood_70f75407b7ae_4540_17327712807.mp4
6922868291168_20240412-112736_back_returnGood_70f75407b7ae_4540_17327712807.mp4
6922868291168_20240412-112736_front_returnGood_70f75407b7ae_4540_17327712807.mp4
6923644286293_20240412-114002_back_addGood_70f75407b7ae_1535_17327712807.mp4
6923644286293_20240412-114002_front_addGood_70f75407b7ae_1535_17327712807.mp4
6923644286293_20240412-114012_back_returnGood_70f75407b7ae_1535_17327712807.mp4
6923644286293_20240412-114012_front_returnGood_70f75407b7ae_1535_17327712807.mp4
6924743915824_20240412-104437_back_addGood_70f754088050_455_17788571404.mp4
6924743915824_20240412-104437_front_addGood_70f754088050_455_17788571404.mp4
6924743915824_20240412-104448_back_returnGood_70f754088050_455_17788571404.mp4
6924743915824_20240412-104448_front_returnGood_70f754088050_455_17788571404.mp4
6924743915824_20240412-113553_back_addGood_70f75407b7ae_920_17327712807.mp4
6924743915824_20240412-113553_front_addGood_70f75407b7ae_920_17327712807.mp4
6924743915824_20240412-113603_back_returnGood_70f75407b7ae_920_17327712807.mp4
6924743915824_20240412-113603_front_returnGood_70f75407b7ae_920_17327712807.mp4
6928804011173_20240412-105808_back_addGood_70f754088050_915_17788571404.mp4
6928804011173_20240412-105808_front_addGood_70f754088050_915_17788571404.mp4
6928804011173_20240412-105818_back_returnGood_70f754088050_910_17788571404.mp4
6928804011173_20240412-105818_front_returnGood_70f754088050_910_17788571404.mp4
6934665095108_20240412-114106_back_addGood_70f75407b7ae_885_17327712807.mp4
6934665095108_20240412-114106_front_addGood_70f75407b7ae_885_17327712807.mp4
6934665095108_20240412-114116_back_returnGood_70f75407b7ae_885_17327712807.mp4
6934665095108_20240412-114116_front_returnGood_70f75407b7ae_885_17327712807.mp4
6935270642121_20240412-111602_back_addGood_70f75407b7ae_540_17327712807.mp4
6935270642121_20240412-111602_front_addGood_70f75407b7ae_540_17327712807.mp4
6935270642121_20240412-111614_back_returnGood_70f75407b7ae_540_17327712807.mp4
6935270642121_20240412-111614_front_returnGood_70f75407b7ae_540_17327712807.mp4
6952074634794_20240412-104633_back_addGood_70f754088050_855_17788571404.mp4
6952074634794_20240412-104633_front_addGood_70f754088050_855_17788571404.mp4
6952074634794_20240412-104700_back_returnGood_70f754088050_855_17788571404.mp4
6952074634794_20240412-104700_front_returnGood_70f754088050_855_17788571404.mp4
6952074634794_20240412-113515_back_addGood_70f75407b7ae_595_17327712807.mp4
6952074634794_20240412-113515_front_addGood_70f75407b7ae_595_17327712807.mp4
6952074634794_20240412-113524_back_returnGood_70f75407b7ae_595_17327712807.mp4
6952074634794_20240412-113524_front_returnGood_70f75407b7ae_595_17327712807.mp4
6972378998200_20240412-111721_back_addGood_70f75407b7ae_870_17327712807.mp4
6972378998200_20240412-111721_front_addGood_70f75407b7ae_870_17327712807.mp4
6972378998200_20240412-111741_back_returnGood_70f75407b7ae_865_17327712807.mp4
6972378998200_20240412-111741_front_returnGood_70f75407b7ae_865_17327712807.mp4

View File

@ -0,0 +1,12 @@
采集文件名字段规则:
230537101280010007_20240411-144945_back_returnGood_70f75407b7ae_565_17788571404.mp4
String targetName =
barCode + "_" (条形码字段)
+ recordFileName + "_" (文件名字段:时间格式精确到秒)
+ "back/front"+ "_" (后/前摄字段)
+ "addGood/returnGood"+ "_"(加/退购字段)
+ macId + "_" mac地址字段去除中间冒号
+ Math.abs(goodsWeight) + "_" (商品重量字段:变化的绝对值)
+ user.phone (采集人手机号字段)
+ ".mp4";

View File

@ -0,0 +1,6 @@
6920152400630_20240412-151024_back_addGood_70f754088050_580_13725988807.mp4
6920152400630_20240412-151024_front_addGood_70f754088050_580_13725988807.mp4
6920152400630_20240412-151036_front_returnGood_70f754088050_580_13725988807.mp4
6920459905012_20240412-150815_back_addGood_70f754088050_550_13725988807.mp4
6920459905012_20240412-150815_front_addGood_70f754088050_550_13725988807.mp4
6920459905012_20240412-150826_front_returnGood_70f754088050_550_13725988807.mp4

View File

@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Feb 23 11:04:48 2024
@author: ym
"""
import numpy as np
import cv2
from scipy.spatial.distance import cdist
# from trackers.utils import matching
def readDict(boxes, feat_dicts):
feat = []
for i in range(boxes.shape[0]):
tid, fid, bid = int(boxes[i, 4]), int(boxes[i, 7]), int(boxes[i, 8])
feat.append(feat_dicts[fid][bid])
# img = feat_dicts[fid][f'{bid}_img']
# cv2.imwrite(f'./result/imgs/{tid}_{fid}_{bid}.png', img)
return np.asarray(feat, dtype=np.float32)
def track_equal_track(atrack, btrack, feat_dicts):
# boxes: [x, y, w, h, track_id, score, cls, frame_index, box_index]
aboxes = atrack.boxes
bboxes = btrack.boxes
''' 1. 判断轨迹在时序上是否有交集 '''
afids = aboxes[:, 7].astype(np.int_)
bfids = bboxes[:, 7].astype(np.int_)
# 帧索引交集
interfid = set(afids).intersection(set(bfids))
# 或者直接判断帧索引是否有交集,返回 Ture or False
# interfid = set(afids).isdisjoint(set(bfids))
''' 2. 轨迹空间iou'''
alabel = np.array([0] * afids.size, dtype=np.int_)
blabel = np.array([1] * bfids.size, dtype=np.int_)
label = np.concatenate((alabel, blabel), axis=0)
fids = np.concatenate((afids, bfids), axis=0)
indices = np.argsort(fids)
idx_pair = []
for i in range(len(indices)-1):
idx1, idx2 = indices[i], indices[i+1]
if label[idx1] != label[idx2] and fids[idx2] - fids[idx1] == 1:
if label[idx1] == 0:
a_idx = idx1
b_idx = idx2-alabel.size
else:
a_idx = idx2
b_idx = idx1-alabel.size
idx_pair.append((a_idx, b_idx))
ious = []
for a, b in idx_pair:
abox, bbox = aboxes[a, :], bboxes[b, :]
xa1, ya1 = abox[0] - abox[2]/2, abox[1] - abox[3]/2
xa2, ya2 = abox[0] + abox[2]/2, abox[1] + abox[3]/2
xb1, yb1 = bbox[0] - bbox[2]/2, bbox[1] - bbox[3]/2
xb2, yb2 = bbox[0] + bbox[2]/2, bbox[1] + bbox[3]/2
inter = (np.minimum(xb2, xa2) - np.maximum(xb1, xa1)).clip(0) * \
(np.minimum(yb2, ya2) - np.maximum(yb1, ya1)).clip(0)
# Union Area
box1_area = abox[2] * abox[3]
box2_area = bbox[2] * bbox[3]
union = box1_area + box2_area - inter + 1e-6
ious.append(inter/union)
''' 3. 轨迹特征相似度判断'''
afeat = readDict(aboxes, feat_dicts)
bfeat = readDict(bboxes, feat_dicts)
feat = np.concatenate((afeat, bfeat), axis=0)
emb_simil = 1-np.maximum(0.0, cdist(feat, feat, 'cosine'))
emb_ = 1-cdist(np.mean(afeat, axis=0)[None, :], np.mean(bfeat, axis=0)[None, :], 'cosine')
cont1 = False if len(interfid) else True
cont2 = all(iou>0.5 for iou in ious)
cont3 = emb_[0, 0]>0.75
cont = cont1 and cont2 and cont3
return cont
def track_equal_str(atrack, btrack):
if atrack == btrack:
return True
else:
return False
def merge_track(Residual):
out_list = []
alist = [t for t in Residual]
while alist:
atrack = alist[0]
cur_list = []
cur_list.append(atrack)
alist.pop(0)
blist = [b for b in alist]
alist = []
for btrack in blist:
if track_equal_str(atrack, btrack):
cur_list.append(btrack)
else:
alist.append(btrack)
out_list.append(cur_list)
return out_list
def main():
Residual = ['a', 'b', 'c', 'd', 'a', 'b', 'c', 'b', 'c', 'd']
out_list = merge_track(Residual)
print(Residual)
print(out_list)
if __name__ == "__main__":
main()
# =============================================================================
# for i, atrack in enumerate(input_list):
# cur_list = []
# cur_list.append(atrack)
# del input_list[i]
#
# for j, btrack in enumerate(input_list):
# if track_equal(atrack, btrack):
# cur_list.append(btrack)
# del input_list[j]
#
# out_list.append(cur_list)
# =============================================================================

470
tracking/module_analysis.py Normal file
View File

@ -0,0 +1,470 @@
# -*- coding: utf-8 -*-
"""
Created on Thu May 30 14:03:03 2024
轨迹分析现场测试性能分析:
(1) 读取 data 文件中的轨迹数据,绘制轨迹图
(2) 读取本地运行 Yolo+Rsenet+Tracker+Tracking 的数据,绘制轨迹图
@author: ym
"""
import os
import cv2
import numpy as np
from pathlib import Path
import warnings
import sys
sys.path.append(r"D:\DetectTracking")
from tracking.utils.read_data import extract_data_realtime, read_tracking_output_realtime
from tracking.utils.plotting import Annotator, colors, draw_tracking_boxes
from tracking.utils import Boxes, IterableSimpleNamespace, yaml_load
from tracking.trackers import BOTSORT, BYTETracker
from tracking.dotrack.dotracks_back import doBackTracks
from tracking.dotrack.dotracks_front import doFrontTracks
from tracking.utils.drawtracks import plot_frameID_y2, draw_all_trajectories
from tracking.utils.read_data import extract_data, read_deletedBarcode_file, read_tracking_output, read_returnGoods_file
from contrast.one2n_contrast import get_contrast_paths, one2n_return
from tracking.utils.annotator import TrackAnnotator
W, H = 1024, 1280
Mode = 'front' #'back'
ImgFormat = ['.jpg', '.jpeg', '.png', '.bmp']
'''调用tracking()函数,利用本地跟踪算法获取各目标轨迹,可以比较本地跟踪算法与现场跟踪算法的区别。'''
def init_tracker(tracker_yaml = None, bs=1):
"""
Initialize tracker for object tracking during prediction.
"""
TRACKER_MAP = {'bytetrack': BYTETracker, 'botsort': BOTSORT}
cfg = IterableSimpleNamespace(**yaml_load(tracker_yaml))
tracker = TRACKER_MAP[cfg.tracker_type](args=cfg, frame_rate=30)
return tracker
def tracking(bboxes, ffeats):
tracker_yaml = r"./trackers/cfg/botsort.yaml"
tracker = init_tracker(tracker_yaml)
TrackBoxes = np.empty((0, 9), dtype = np.float32)
TracksDict = {}
'''========================== 执行跟踪处理 ============================='''
# dets 与 feats 应保持严格对应
for dets, feats in zip(bboxes, ffeats):
det_tracking = Boxes(dets).cpu().numpy()
tracks = tracker.update(det_tracking, features=feats)
'''tracks: [x1, y1, x2, y2, track_id, score, cls, frame_index, box_index]
0 1 2 3 4 5 6 7 8
这里frame_index 也可以用视频的 帧ID 代替, box_index 保持不变
'''
if len(tracks):
TrackBoxes = np.concatenate([TrackBoxes, tracks], axis=0)
FeatDict = {}
for track in tracks:
tid = int(track[8])
FeatDict.update({tid: feats[tid, :]})
frameID = tracks[0, 7]
# print(f"frameID: {int(frameID)}")
assert len(tracks) == len(FeatDict), f"Please check the func: tracker.update() at frameID({int(frameID)})"
TracksDict[f"frame_{int(frameID)}"] = {"feats":FeatDict}
return TrackBoxes, TracksDict
def read_imgs(imgspath, CamerType):
'''
inputs:
imgspath序列图像地址
CamerType相机类型0后摄1前摄
outputs
imgs图像序列
功能:
根据CamerType类型读取imgspath文件夹中的图像并根据帧索引进行排序。
do_tracking()中调用该函数实现1读取imgs并绘制各目标轨迹框2获取subimgs
'''
imgs, frmIDs = [], []
for filename in os.listdir(imgspath):
file, ext = os.path.splitext(filename)
flist = file.split('_')
if len(flist)==4 and ext in ImgFormat:
camID, frmID = flist[0], int(flist[-1])
if camID==CamerType:
img = cv2.imread(os.path.join(imgspath, filename))
imgs.append(img)
frmIDs.append(frmID)
if len(frmIDs):
indice = np.argsort(np.array(frmIDs))
imgs = [imgs[i] for i in indice]
return imgs
def do_tracking(fpath, savedir, event_name='images'):
'''
args:
fpath: 算法各模块输出的data文件地址匹配
savedir: 对 fpath 各模块输出的复现;
分析具体视频时,需指定 fpath 和 savedir
outputs:
img_tracking目标跟踪轨迹、本地轨迹分析算法的轨迹对比图
abimg现场轨迹分析算法、轨迹选择输出的对比图
'''
# fpath = r'D:\contrast\dataset\1_to_n\709\20240709-102758_6971558612189\1_track.data'
# savedir = r'D:\contrast\dataset\result\20240709-102843_6958770005357_6971558612189\error_6971558612189'
imgpath, dfname = os.path.split(fpath)
CamerType = dfname.split('_')[0]
'''1.1 构造 0/1_tracking_output.data 文件地址,读取文件数据'''
tracking_output_path = os.path.join(imgpath, CamerType + '_tracking_output.data')
basename = os.path.basename(imgpath)
if not os.path.isfile(fpath):
print(f"{basename}: Can't find {dfname} file!")
return None, None
if not os.path.isfile(tracking_output_path):
print(f"{basename}: Can't find {CamerType}_tracking_output.data file!")
return None, None
bboxes, ffeats, trackerboxes, tracker_feat_dict, trackingboxes, tracking_feat_dict = extract_data(fpath)
tracking_output_boxes, _ = read_tracking_output(tracking_output_path)
'''1.2 利用本地跟踪算法生成各商品轨迹'''
# trackerboxes, tracker_feat_dict = tracking(bboxes, ffeats)
'''1.3 分别构造 2 个文件夹,(1) 存储画框后的图像; (2) 运动轨迹对应的 boxes子图'''
save_dir = os.path.join(savedir, event_name + '_images')
subimg_dir = os.path.join(savedir, event_name + '_subimgs')
if not os.path.exists(save_dir):
os.makedirs(save_dir)
if not os.path.exists(subimg_dir):
os.makedirs(subimg_dir)
'''2. 执行轨迹分析, 保存轨迹分析前后的对比图示'''
traj_graphic = event_name + '_' + CamerType
if CamerType == '1':
vts = doFrontTracks(trackerboxes, tracker_feat_dict)
vts.classify()
plt = plot_frameID_y2(vts)
# ftpath = os.path.join(savedir, f"{traj_graphic}_front_y2.png")
# plt.savefig(ftpath)
plt.close()
edgeline = cv2.imread("./shopcart/cart_tempt/board_ftmp_line.png")
img_tracking = draw_all_trajectories(vts, edgeline, savedir, CamerType, draw5p=True)
elif CamerType == '0':
vts = doBackTracks(trackerboxes, tracker_feat_dict)
vts.classify()
edgeline = cv2.imread("./shopcart/cart_tempt/edgeline.png")
img_tracking = draw_all_trajectories(vts, edgeline, savedir, CamerType, draw5p=True)
# imgpth = os.path.join(savedir, f"{traj_graphic}_.png")
# cv2.imwrite(str(imgpth), img)
else:
print("Please check data file!")
'''3 tracking() 算法输出后多轨迹选择问题分析'''
if CamerType == '1':
aline = cv2.imread("./shopcart/cart_tempt/board_ftmp_line.png")
elif CamerType == '0':
aline = cv2.imread("./shopcart/cart_tempt/edgeline.png")
else:
print("Please check data file!")
bline = aline.copy()
annotator = TrackAnnotator(aline, line_width=2)
for track in trackingboxes:
annotator.plotting_track(track)
aline = annotator.result()
annotator = TrackAnnotator(bline, line_width=2)
if not isinstance(tracking_output_boxes, list):
tracking_output_boxes = [tracking_output_boxes]
for track in tracking_output_boxes:
annotator.plotting_track(track)
bline = annotator.result()
abimg = np.concatenate((aline, bline), axis = 1)
abH, abW = abimg.shape[:2]
cv2.line(abimg, (int(abW/2), 0), (int(abW/2), abH), (128, 255, 128), 2)
# algpath = os.path.join(savedir, f"{traj_graphic}_alg.png")
# cv2.imwrite(str(algpath), abimg)
'''4. 画框后的图像和子图保存若imgs数与tracker中fid数不匹配只保存原图不保存子图'''
'''4.0 读取 fpath 中对应的图像 imgs '''
imgs = read_imgs(imgpath, CamerType)
'''4.1 imgs数 < trackerboxes 的 max(fid),返回原图'''
if len(imgs) < np.max(trackerboxes[:,7]):
for i in range(len(imgs)):
img_savepath = os.path.join(save_dir, CamerType + "_" + f"{i}.png")
cv2.imwrite(img_savepath, imgs[i])
print(f"{basename}: len(imgs) = {len(imgs)} < Tracker max(fid) = {int(np.max(trackerboxes[:,7]))}, 无法匹配画框")
return img_tracking, abimg
'''4.2 在 imgs 上画框并保存'''
imgs_dw = draw_tracking_boxes(imgs, trackerboxes)
for fid, img in imgs_dw:
img_savepath = os.path.join(save_dir, CamerType + "_fid_" + f"{int(fid)}.png")
cv2.imwrite(img_savepath, img)
'''4.3.2 保存轨迹选择对应的子图'''
# for track in tracking_output_boxes:
for track in vts.Residual:
for *xyxy, tid, conf, cls, fid, bid in track.boxes:
img = imgs[int(fid-1)]
x1, y1, x2, y2 = int(xyxy[0]/2), int(xyxy[1]/2), int(xyxy[2]/2), int(xyxy[3]/2)
subimg = img[y1:y2, x1:x2]
subimg_path = os.path.join(subimg_dir, f'{CamerType}_tid{int(tid)}_{int(fid)}_{int(bid)}.png' )
cv2.imwrite(subimg_path, subimg)
for track in tracking_output_boxes:
for *xyxy, tid, conf, cls, fid, bid in track:
img = imgs[int(fid-1)]
x1, y1, x2, y2 = int(xyxy[0]/2), int(xyxy[1]/2), int(xyxy[2]/2), int(xyxy[3]/2)
subimg = img[y1:y2, x1:x2]
subimg_path = os.path.join(subimg_dir, f'x_{CamerType}_tid{int(tid)}_{int(fid)}_{int(bid)}.png' )
cv2.imwrite(subimg_path, subimg)
return img_tracking, abimg
def tracking_simulate(eventpath, savepath):
'''args:
eventpath: 事件文件夹
savepath: 存储文件夹
遍历eventpath
'''
# =============================================================================
# '''1. 获取事件名'''
# event_names = os.path.basename(eventpath).strip().split('_')
# if len(event_names)==2 and len(event_names[1])>=8:
# enent_name = event_names[1]
# elif len(event_names)==2 and len(event_names[1])==0:
# enent_name = event_names[0]
# else:
# return
# =============================================================================
enent_name = os.path.basename(eventpath)
## only for simplify the filename
idx = enent_name.find('2024')
if idx>=0:
enent_name = enent_name[idx:(idx+15)]
'''2. 依次读取 0/1_track.data 中数据,进行仿真'''
illu_tracking, illu_select = [], []
for filename in os.listdir(eventpath):
# filename = '1_track.data'
if filename.find("track.data") < 0: continue
fpath = os.path.join(eventpath, filename)
if not os.path.isfile(fpath): continue
img_tracking, img_select = do_tracking(fpath, savepath, enent_name)
if img_select is not None:
illu_select.append(img_select)
if img_tracking is not None:
illu_tracking.append(img_tracking)
'''3. 共幅8图上下子图显示的是前后摄每一行4个子图分别为
(1) tracker输出原始轨迹; (2)本地tracking输出; (3)现场算法轨迹选择前轨迹; (4)现场算法轨迹选择后的轨迹
'''
if len(illu_select)==2:
Img_s = np.concatenate((illu_select[0], illu_select[1]), axis = 0)
H, W = Img_s.shape[:2]
cv2.line(Img_s, (0, int(H/2)), (int(W), int(H/2)), (128, 255, 128), 2)
elif len(illu_select)==1:
Img_s = illu_select[0]
else:
Img_s = None
if len(illu_tracking)==2:
Img_t = np.concatenate((illu_tracking[0], illu_tracking[1]), axis = 0)
H, W = Img_t.shape[:2]
cv2.line(Img_t, (0, int(H/2)), (int(W), int(H/2)), (128, 255, 128), 2)
elif len(illu_tracking)==1:
Img_t = illu_tracking[0]
else:
Img_t = None
'''3.1 保存输出轨迹图若tracking、select的shape相同则合并输出否则单独输出'''
imgpath_tracking = os.path.join(savepath, enent_name + '_tracking.png')
imgpath_select = os.path.join(savepath, enent_name + '_select.png')
imgpath_ts = os.path.join(savepath, enent_name + '_tracking_select.png')
if Img_t is not None and Img_s is not None and np.all(Img_s.shape==Img_t.shape):
Img_ts = np.concatenate((Img_t, Img_s), axis = 1)
H, W = Img_ts.shape[:2]
cv2.line(Img_ts, (int(W/2), 0), (int(W/2), int(H)), (0, 0, 255), 4)
cv2.imwrite(imgpath_ts, Img_ts)
else:
if Img_s: cv2.imwrite(imgpath_select, Img_s) # 不会执行到该处
if Img_t: cv2.imwrite(imgpath_tracking, Img_t) # 不会执行到该处
Img_ts = None
'''3.2 单独另存保存完好的 8 轨迹图'''
if Img_ts is not None:
basepath, _ = os.path.split(savepath)
trajpath = os.path.join(basepath, 'trajs')
if not os.path.exists(trajpath):
os.makedirs(trajpath)
traj_path = os.path.join(trajpath, enent_name+'.png')
cv2.imwrite(traj_path, Img_ts)
return Img_ts
# warnings.simplefilter("error", category=np.VisibleDeprecationWarning)
def main_loop():
del_barcode_file = r'\\192.168.1.28\share\测试_202406\0723\0723_3\deletedBarcode.txt'
basepath = r'\\192.168.1.28\share\测试_202406\0723\0723_3' # 测试数据文件夹地址
# del_barcode_file = r'\\192.168.1.28\share\测试_202406\1030\images\returnGoods.txt'
# basepath = r'\\192.168.1.28\share\测试_202406\1030\images' # 测试数据文件夹地址
'''获取性能测试数据相关路径'''
SavePath = r'D:\contrast\dataset\resultx' # 结果保存地址
saveimgs = True
if os.path.basename(del_barcode_file).find('deletedBarcode'):
relative_paths = get_contrast_paths(del_barcode_file, basepath, SavePath, saveimgs)
elif os.path.basename(del_barcode_file).find('returnGoods'):
blist = read_returnGoods_file(del_barcode_file)
errpairs, corrpairs, err_similarity, correct_similarity = one2n_return(blist)
relative_paths = []
for getoutevent, inputevent, errevent in errpairs:
relative_paths.append(os.path.join(basepath, getoutevent))
relative_paths.append(os.path.join(basepath, inputevent))
relative_paths.append(os.path.join(basepath, errevent))
# prefix = ["getout_", "input_", "error_"]
'''开始循环执行每次测试过任务'''
k = 0
for tuple_paths in relative_paths:
'''1. 生成存储结果图像的文件夹'''
namedirs = []
for data_path in tuple_paths:
base_name = os.path.basename(data_path).strip().split('_')
if len(base_name[-1]):
name = base_name[-1]
else:
name = base_name[0]
namedirs.append(name)
sdir = "_".join(namedirs)
savepath = os.path.join(SavePath, sdir)
# if os.path.exists(savepath):
# continue
if not os.path.exists(savepath):
os.makedirs(savepath)
'''2. 循环执行操作事件:取出、放入、错误匹配'''
for eventpath in tuple_paths:
try:
tracking_simulate(eventpath, savepath)
except Exception as e:
print(f'Error! {eventpath}, {e}')
# k +=1
# if k==1:
# break
def main():
'''
eventPaths: data文件地址该 data 文件包括 Pipeline 各模块输出
SavePath: 包含二级目录,一级目录为轨迹图像;二级目录为与data文件对应的序列图像存储地址。
'''
# eventPaths = r'\\192.168.1.28\share\测试_202406\0723\0723_3'
eventPaths = r'\\192.168.1.28\share\测试视频数据以及日志\各模块测试记录\展厅测试\1120_展厅模型v801测试\扫A放A'
savePath = r'D:\exhibition\result'
k=0
for pathname in os.listdir(eventPaths):
pathname = "20241121-144901-fdba61c6-aefa-4b50-876d-5e05998befdc_6920459905012_6920459905012"
eventpath = os.path.join(eventPaths, pathname)
savepath = os.path.join(savePath, pathname)
if not os.path.exists(savepath):
os.makedirs(savepath)
tracking_simulate(eventpath, savepath)
# try:
# tracking_simulate(eventpath, savepath)
# except Exception as e:
# print(f'Error! {eventpath}, {e}')
k += 1
if k==1:
break
if __name__ == "__main__":
# main_loop()
main()
# try:
# main_loop()
# except Exception as e:
# print(f'Error: {e}')

View File

@ -0,0 +1 @@
求取购物车轮廓,判断任一点是否在购物车内、是否在购物车边框附近

View File

@ -0,0 +1,6 @@
5幅图
incart.png
outcart.png
incart_ftmp.png
outcart_ftmp.png
cartboarder.png

View File

@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
"""
Created on Tue Sep 19 18:17:55 2023
@author: ym
"""
import cv2
import os
import numpy as np
def tempt_add_adjc():
temp = cv2.imread("img.png")
path = r"D:\DeepLearning\yolov5\runs\trajectory"
patr = r"D:\DeepLearning\yolov5\tracking\result"
for filename in os.listdir(path):
imgpath = os.path.join(path, filename)
img = cv2.imread(imgpath)
img1 = cv2.add(img, temp)
img1path = os.path.join(patr, filename)
cv2.imwrite(img1path, img1)
def temp_add_boarder():
temp = cv2.imread("cartedge.png")
temp[640:, 0:20, :] = 255
temp[640:, -20:, :] = 255
temp[-20:, :, :] = 255
cv2.imwrite("cartboarder.png", temp)
def create_front_temp():
image = cv2.imread("./iCart4/b.png")
Height, Width = image.shape[:2]
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh, binary = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY_INV)
board = cv2.bitwise_not(binary)
contours, _ = cv2.findContours(board, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
k = 0
for cnt in contours:
img = np.zeros((Height, Width), dtype=np.uint8)
cv2.drawContours(img, [cnt], -1, 255, 3)
k += 1
cv2.imwrite(f"./iCart4/back{k}.png", img)
imgshow = cv2.drawContours(image, contours, -1, (0,255,0), 3)
cv2.imwrite("./iCart4/board_back_line.png", imgshow)
# cv2.imwrite("./iCart4/4.png", board)
# cv2.imwrite("1.png", gray)
# cv2.imwrite("2.png", binary)
def create_back_temp():
'''
image1.png从中获取轮廓的初始图像
image2.png主要用于显示效果
Returnimg.png
'''
image = cv2.imread("image1.png")
Height, Width = image.shape[:2]
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray[:405, :] = 0
thresh, binary = cv2.threshold(gray, 254, 255, cv2.THRESH_BINARY)
cv2.imwrite("shopcart.png", binary)
imgshow = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)
contours, _ = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
imgshow = cv2.drawContours(imgshow, contours, -1, (0,255,0), 1)
cv2.imwrite("imgshow.png", imgshow)
image2 = cv2.imread("image2.png")
image2 = cv2.drawContours(image2, contours, -1, (0,255,0), 3)
for cnt in contours:
_, start, _, num = cv2.boundingRect(cnt)
x1 = (cnt[:, 0, 0] != 0)
x2 = (cnt[:, 0, 0] != Width-1)
x3 = (cnt[:, 0, 1] != Height-1)
x = (x1 & x2) & x3
idx = np.where(x)
cntx = cnt[idx, :, :][0]
cnt1 = cntx[:,0,:].copy()
cntx[:, 0, 1] -= 60
cnt2 = cntx[:,0,:].copy()
cv2.drawContours(image2,[cntx], 0, (0,0,255), 2)
img = np.zeros(gray.shape, np.uint8)
for i in range(len(cnt1)):
x1, y1 = cnt1[i]
x2, y2 = cnt2[i]
cv2.rectangle(img, (x1-1, y1-1), (x1+1, y1+1), 255, 1)
cv2.rectangle(img, (x2-1, y2-1), (x2+1, y2+1), 255, 1)
cv2.imwrite("img.png", img)
if __name__ == "__main__":
# create_back_temp()
# temp_add_boarder()
# tempt_add_adjc()
create_front_temp()

Binary file not shown.

98
tracking/time_test.py Normal file
View File

@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
"""
Created on Tue Aug 13 09:39:42 2024
@author: ym
"""
import os
import time
import datetime
import numpy as np
import sys
sys.path.append(r"D:\DetectTracking")
from tracking.utils.read_data import extract_data, read_weight_timeConsuming
def main():
directory = r"\\192.168.1.28\share\测试_202406\0821\images"
TimeConsuming = []
DayHMS = []
for root, dirs, files in os.walk(directory):
if root.find('20240821') == -1: continue
for name in files:
if name.find('process.data') == -1: continue
datename = os.path.basename(root)[:15]
fpath = os.path.join(root, name)
WeightDict, SensorDict, ProcessTimeDict = read_weight_timeConsuming(fpath)
try:
t1 = ProcessTimeDict['algroDoStart'] # 算法处理的第一帧图像时间
t2 = ProcessTimeDict['breakinFirst'] # 第一次入侵时间
t3 = ProcessTimeDict['algroLastFrame'] # 算法处理的最后一帧图像时间
t4 = ProcessTimeDict['breakinLast'] # 最后一次入侵时间
t5 = ProcessTimeDict['weightStablityTime'] # 重力稳定时间
wv = ProcessTimeDict['weightValue'] # 重力值
t6 = ProcessTimeDict['YoloResnetTrackerEnd'] # Yolo、Resnet、tracker执行结束时间
t7 = ProcessTimeDict['trackingEnd'] # 轨迹分析结束时间
t8 = ProcessTimeDict['contrastEnd'] # 比对结束时间
t9 = ProcessTimeDict['algroStartToEnd'] # 算法总执行时间
t10 = ProcessTimeDict['weightstablityToEnd'] # 重力稳定至算法结束时间
t11 = ProcessTimeDict['frameEndToEnd'] # 最后一帧图像至算法结束时间
TimeConsuming.append((t1, t2, t3, t4, t5, wv, t6, t7, t8, t9, t10, t11))
DayHMS.append(datename)
except Exception as e:
print(f'Error! {datename}, {e}')
TimeConsuming = np.array(TimeConsuming, dtype = np.int64)
TimeTotal = np.concatenate((TimeConsuming,
TimeConsuming[:,4][:, None] - TimeConsuming[:,0][:, None],
TimeConsuming[:,4][:, None] - TimeConsuming[:,2][:, None]), axis=1)
tt = TimeTotal[:, 3]==0
TimeTotal0 = TimeTotal[tt]
DayHMS0 = [DayHMS[ti] for i, ti in enumerate(tt) if ti]
TimeTotalMinus = TimeTotal[TimeTotal[:, 5]<0]
TimeTotalAdd = TimeTotal[TimeTotal[:, 5]>=0]
TimeTotalAdd0 = TimeTotalAdd[TimeTotalAdd[:,3] == 0]
TimeTotalAdd1 = TimeTotalAdd[TimeTotalAdd[:,3] != 0]
TimeTotalMinus0 = TimeTotalMinus[TimeTotalMinus[:,3] == 0]
TimeTotalMinus1 = TimeTotalMinus[TimeTotalMinus[:,3] != 0]
print(f"Total number is {len(TimeConsuming)}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,94 @@
# Tracker
## Supported Trackers
- [x] ByteTracker
- [x] BoT-SORT
## Usage
### python interface:
You can use the Python interface to track objects using the YOLO model.
```python
from ultralytics import YOLO
model = YOLO("yolov8n.pt") # or a segmentation model .i.e yolov8n-seg.pt
model.track(
source="video/streams",
stream=True,
tracker="botsort.yaml", # or 'bytetrack.yaml'
show=True,
)
```
You can get the IDs of the tracked objects using the following code:
```python
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
for result in model.track(source="video.mp4"):
print(
result.boxes.id.cpu().numpy().astype(int)
) # this will print the IDs of the tracked objects in the frame
```
If you want to use the tracker with a folder of images or when you loop on the video frames, you should use the `persist` parameter to tell the model that these frames are related to each other so the IDs will be fixed for the same objects. Otherwise, the IDs will be different in each frame because in each loop, the model creates a new object for tracking, but the `persist` parameter makes it use the same object for tracking.
```python
import cv2
from ultralytics import YOLO
cap = cv2.VideoCapture("video.mp4")
model = YOLO("yolov8n.pt")
while True:
ret, frame = cap.read()
if not ret:
break
results = model.track(frame, persist=True)
boxes = results[0].boxes.xyxy.cpu().numpy().astype(int)
ids = results[0].boxes.id.cpu().numpy().astype(int)
for box, id in zip(boxes, ids):
cv2.rectangle(frame, (box[0], box[1]), (box[2], box[3]), (0, 255, 0), 2)
cv2.putText(
frame,
f"Id {id}",
(box[0], box[1]),
cv2.FONT_HERSHEY_SIMPLEX,
1,
(0, 0, 255),
2,
)
cv2.imshow("frame", frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
```
## Change tracker parameters
You can change the tracker parameters by editing the `tracker.yaml` file which is located in the ultralytics/cfg/trackers folder.
## Command Line Interface (CLI)
You can also use the command line interface to track objects using the YOLO model.
```bash
yolo detect track source=... tracker=...
yolo segment track source=... tracker=...
yolo pose track source=... tracker=...
```
By default, trackers will use the configuration in `ultralytics/cfg/trackers`. We also support using a modified tracker config file. Please refer to the tracker config files in `ultralytics/cfg/trackers`.
## Contribute to Our Trackers Section
Are you proficient in multi-object tracking and have successfully implemented or adapted a tracking algorithm with Ultralytics YOLO? We invite you to contribute to our Trackers section! Your real-world applications and solutions could be invaluable for users working on tracking tasks.
By contributing to this section, you help expand the scope of tracking solutions available within the Ultralytics YOLO framework, adding another layer of functionality and utility for the community.
To initiate your contribution, please refer to our [Contributing Guide](https://docs.ultralytics.com/help/contributing) for comprehensive instructions on submitting a Pull Request (PR) 🛠️. We are excited to see what you bring to the table!
Together, let's enhance the tracking capabilities of the Ultralytics YOLO ecosystem 🙏!

View File

@ -0,0 +1,10 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license
from .bot_sort import BOTSORT
from .byte_tracker import BYTETracker
from .track import register_tracker
__all__ = 'register_tracker', 'BOTSORT', 'BYTETracker' # allow simpler import

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,71 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license
from collections import OrderedDict
import numpy as np
class TrackState:
"""Enumeration of possible object tracking states."""
New = 0
Tracked = 1
Lost = 2
Removed = 3
class BaseTrack:
"""Base class for object tracking, handling basic track attributes and operations."""
_count = 0
track_id = 0
is_activated = False
state = TrackState.New
history = OrderedDict()
features = []
curr_feature = None
score = 0
start_frame = 0
frame_id = 0
time_since_update = 0
# Multi-camera
location = (np.inf, np.inf)
@property
def end_frame(self):
"""Return the last frame ID of the track."""
return self.frame_id
@staticmethod
def next_id():
"""Increment and return the global track ID counter."""
BaseTrack._count += 1
return BaseTrack._count
def activate(self, *args):
"""Activate the track with the provided arguments."""
raise NotImplementedError
def predict(self):
"""Predict the next state of the track."""
raise NotImplementedError
def update(self, *args, **kwargs):
"""Update the track with new observations."""
raise NotImplementedError
def mark_lost(self):
"""Mark the track as lost."""
self.state = TrackState.Lost
def mark_removed(self):
"""Mark the track as removed."""
self.state = TrackState.Removed
@staticmethod
def reset_id():
"""Reset the global track ID counter."""
BaseTrack._count = 0

View File

@ -0,0 +1,211 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license
from collections import deque
import numpy as np
from .basetrack import TrackState
from .byte_tracker import BYTETracker, STrack
from .utils import matching
# from .utils.gmc import GMC
from .utils.kalman_filter import KalmanFilterXYWH
# from .reid.reid_interface import ReIDInterface
# from .reid.config import config
from contrast.feat_extract.inference import FeatsInterface
from contrast.feat_extract.config import config as conf
class BOTrack(STrack):
shared_kalman = KalmanFilterXYWH()
def __init__(self, tlwh, score, cls, feat=None, feat_history=50):
"""Initialize YOLOv8 object with temporal parameters, such as feature history, alpha and current features."""
super().__init__(tlwh, score, cls)
self.smooth_feat = None
self.curr_feat = None
if feat is not None:
self.update_features(feat)
self.features = deque([], maxlen=feat_history)
self.alpha = 0.9
def update_features(self, feat):
"""Update features vector and smooth it using exponential moving average."""
feat /= np.linalg.norm(feat)
self.curr_feat = feat
if self.smooth_feat is None:
self.smooth_feat = feat
else:
self.smooth_feat = self.alpha * self.smooth_feat + (1 - self.alpha) * feat
self.features.append(feat)
self.smooth_feat /= np.linalg.norm(self.smooth_feat)
def predict(self):
"""Predicts the mean and covariance using Kalman filter."""
mean_state = self.mean.copy()
if self.state != TrackState.Tracked:
mean_state[6] = 0
mean_state[7] = 0
self.mean, self.covariance = self.kalman_filter.predict(mean_state, self.covariance)
def re_activate(self, new_track, frame_id, new_id=False):
"""Reactivates a track with updated features and optionally assigns a new ID."""
if new_track.curr_feat is not None:
self.update_features(new_track.curr_feat)
super().re_activate(new_track, frame_id, new_id)
def update(self, new_track, frame_id):
"""Update the YOLOv8 instance with new track and frame ID."""
if new_track.curr_feat is not None:
self.update_features(new_track.curr_feat)
super().update(new_track, frame_id)
@property
def tlwh(self):
"""Get current position in bounding box format `(top left x, top left y,
width, height)`.
"""
if self.mean is None:
return self._tlwh.copy()
ret = self.mean[:4].copy()
ret[:2] -= ret[2:] / 2
return ret
@staticmethod
def multi_predict(stracks):
"""Predicts the mean and covariance of multiple object tracks using shared Kalman filter."""
if len(stracks) <= 0:
return
multi_mean = np.asarray([st.mean.copy() for st in stracks])
multi_covariance = np.asarray([st.covariance for st in stracks])
for i, st in enumerate(stracks):
if st.state != TrackState.Tracked:
multi_mean[i][6] = 0
multi_mean[i][7] = 0
multi_mean, multi_covariance = BOTrack.shared_kalman.multi_predict(multi_mean, multi_covariance)
for i, (mean, cov) in enumerate(zip(multi_mean, multi_covariance)):
stracks[i].mean = mean
stracks[i].covariance = cov
def convert_coords(self, tlwh):
"""Converts Top-Left-Width-Height bounding box coordinates to X-Y-Width-Height format."""
return self.tlwh_to_xywh(tlwh)
@staticmethod
def tlwh_to_xywh(tlwh):
"""Convert bounding box to format `(center x, center y, width,
height)`.
"""
ret = np.asarray(tlwh).copy()
ret[:2] += ret[2:] / 2
return ret
class BOTSORT(BYTETracker):
def __init__(self, args, frame_rate=30):
"""Initialize YOLOv8 object with ReID module and GMC algorithm."""
super().__init__(args, frame_rate)
# ReID module
self.proximity_thresh = args.proximity_thresh
self.appearance_thresh = args.appearance_thresh
# if args.with_reid:
# # Haven't supported BoT-SORT(reid) yet
# # self.encoder = ReIDInterface(config)
# self.encoder = FeatsInterface(conf)
# print('load model {} in BOTSORT'.format(conf.testbackbone))
# self.gmc = GMC(method=args.gmc_method) # commented by WQG
def get_kalmanfilter(self):
"""Returns an instance of KalmanFilterXYWH for object tracking."""
return KalmanFilterXYWH()
def init_track(self, dets, scores, cls, image, features_keep):
"""Initialize track with detections, scores, and classes."""
if len(dets) == 0:
return []
if self.args.with_reid and self.encoder is not None:
if features_keep is None:
imgs, features_keep = self.encoder.inference(image, dets)
return [BOTrack(xyxy, s, c, f) for (xyxy, s, c, f) in zip(dets, scores, cls, features_keep)] # detections
else:
return [BOTrack(xyxy, s, c) for (xyxy, s, c) in zip(dets, scores, cls)] # detections
def get_dists(self, tracks, detections):
"""Get distances between tracks and detections using IoU and (optionally) ReID embeddings."""
dists = matching.iou_distance(tracks, detections)
# proximity_thresh 应该设较大的值表示只有两个boxes离得较远时不考虑reid特征
dists_mask = (dists > self.proximity_thresh)
# TODO: mot20
# if not self.args.mot20:
dists = matching.fuse_score(dists, detections)
if self.args.with_reid and self.encoder is not None:
emb_dists = matching.embedding_distance(tracks, detections) / 2.0
emb_dists[emb_dists > self.appearance_thresh] = 1.0
emb_dists[dists_mask] = 1.0
dists = np.minimum(dists, emb_dists)
return dists
def get_dists_1(self, tracks, detections):
"""Get distances between tracks and detections using IoU and (optionally) ReID embeddings."""
iou_dists = matching.iou_distance(tracks, detections)
iou_dists_mask = (iou_dists>0.9)
iou_dists = matching.fuse_score(iou_dists, detections)
weight = 0.4
if self.args.with_reid and self.encoder is not None:
emb_dists = matching.embedding_distance(tracks, detections)
'''============ iou_dists 和 emb_dists 融合有两种策略 ==========='''
'''1. reid 相似度阈值,低于该值的两 boxes 图像不可能是同一对象,需要确定一个合理的可信阈值
2. iou 的约束为若约束,故 iou_dists 应设置为较大的值
'''
emb_dists_mask = (emb_dists > 0.8)
iou_dists[emb_dists_mask] = 1
emb_dists[iou_dists_mask] = 1
dists = np.minimum(iou_dists, emb_dists)
'''2. embed 阈值'''
# dists = (1-weight)*iou_dists + weight*emb_dists
else:
dists = iou_dists.copy()
return dists
def multi_predict(self, tracks):
"""Predict and track multiple objects with YOLOv8 model."""
BOTrack.multi_predict(tracks)
def get_result(self):
'''written by WQG'''
activate_tracks = np.asarray([x.tlbr.tolist() + [x.track_id, x.score, x.cls, x.idx]
for x in self.tracked_stracks if x.is_activated], dtype=np.float32)
track_features = []
if self.args.with_reid and self.encoder is not None:
track_features = np.asarray([x.curr_feat for x in self.tracked_stracks if x.is_activated], dtype=np.float32)
return (activate_tracks, track_features)

View File

@ -0,0 +1,496 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license
import numpy as np
from .basetrack import BaseTrack, TrackState
from .utils import matching
from .utils.kalman_filter import KalmanFilterXYAH
def dists_update(dists, strack_pool, detections):
'''written by WQG'''
if len(strack_pool) and len(detections):
# alabel = np.array([int(stack.cls) if int(stack.cls)==0 or int(stack.cls)==9 else -1 for stack in strack_pool])
# blabel = np.array([int(stack.cls) if int(stack.cls)==0 or int(stack.cls)==9 else -1 for stack in detections])
alabel = np.array([int(stack.cls) for stack in strack_pool])
blabel = np.array([int(stack.cls) for stack in detections])
amlabel = np.expand_dims(alabel, axis=1).repeat(len(detections),axis=1)
bmlabel = np.expand_dims(blabel, axis=0).repeat(len(strack_pool),axis=0)
mlabel = bmlabel == amlabel
iou_dist = matching.iou_distance(strack_pool, detections) > 0.1 #boxes iou>0.9时,可以不考虑类别
dist_label = (1 - mlabel) & iou_dist # 不同类,且不是严格重叠,需考虑类别距离
dist_label = 1 - mlabel
dists = np.where(dists > dist_label, dists, dist_label)
return dists
class STrack(BaseTrack):
shared_kalman = KalmanFilterXYAH()
def __init__(self, tlwh, score, cls):
"""wait activate."""
self._tlwh = np.asarray(self.tlbr_to_tlwh(tlwh[:-1]), dtype=np.float32)
self.kalman_filter = None
self.mean, self.covariance = None, None
self.is_activated = False
self.first_find = False ###
self.score = score
self.tracklet_len = 0
self.cls = cls
self.idx = tlwh[-1]
def predict(self):
"""Predicts mean and covariance using Kalman filter."""
mean_state = self.mean.copy()
if self.state != TrackState.Tracked:
mean_state[7] = 0
self.mean, self.covariance = self.kalman_filter.predict(mean_state, self.covariance)
@staticmethod
def multi_predict(stracks):
"""Perform multi-object predictive tracking using Kalman filter for given stracks."""
if len(stracks) <= 0:
return
multi_mean = np.asarray([st.mean.copy() for st in stracks])
multi_covariance = np.asarray([st.covariance for st in stracks])
for i, st in enumerate(stracks):
if st.state != TrackState.Tracked:
multi_mean[i][7] = 0
multi_mean, multi_covariance = STrack.shared_kalman.multi_predict(multi_mean, multi_covariance)
for i, (mean, cov) in enumerate(zip(multi_mean, multi_covariance)):
stracks[i].mean = mean
stracks[i].covariance = cov
@staticmethod
def multi_gmc(stracks, H=np.eye(2, 3)):
"""Update state tracks positions and covariances using a homography matrix."""
if len(stracks) > 0:
multi_mean = np.asarray([st.mean.copy() for st in stracks])
multi_covariance = np.asarray([st.covariance for st in stracks])
R = H[:2, :2]
R8x8 = np.kron(np.eye(4, dtype=float), R)
t = H[:2, 2]
for i, (mean, cov) in enumerate(zip(multi_mean, multi_covariance)):
mean = R8x8.dot(mean)
mean[:2] += t
cov = R8x8.dot(cov).dot(R8x8.transpose())
stracks[i].mean = mean
stracks[i].covariance = cov
def activate(self, kalman_filter, frame_id):
"""Start a new tracklet."""
self.kalman_filter = kalman_filter
self.track_id = self.next_id()
self.mean, self.covariance = self.kalman_filter.initiate(self.convert_coords(self._tlwh))
self.tracklet_len = 0
self.state = TrackState.Tracked
if frame_id == 1:
self.is_activated = True
else:
self.first_find = True ### Add by WQG
self.frame_id = frame_id
self.start_frame = frame_id
def re_activate(self, new_track, frame_id, new_id=False):
"""Reactivates a previously lost track with a new detection."""
self.mean, self.covariance = self.kalman_filter.update(self.mean, self.covariance,
self.convert_coords(new_track.tlwh))
self.tracklet_len = 0
self.state = TrackState.Tracked
self.is_activated = True
self.first_find = False
self.frame_id = frame_id
if new_id:
self.track_id = self.next_id()
self.score = new_track.score
self.cls = new_track.cls
self.idx = new_track.idx
self._tlwh = new_track._tlwh
def update(self, new_track, frame_id):
"""
Update a matched track
:type new_track: STrack
:type frame_id: int
:return:
"""
self.frame_id = frame_id
self.tracklet_len += 1
new_tlwh = new_track.tlwh
self.mean, self.covariance = self.kalman_filter.update(self.mean, self.covariance,
self.convert_coords(new_tlwh))
self.state = TrackState.Tracked
self.is_activated = True
self.first_find = False
self.score = new_track.score
self.cls = new_track.cls
self.idx = new_track.idx
self._tlwh = new_track._tlwh
def convert_coords(self, tlwh):
"""Convert a bounding box's top-left-width-height format to its x-y-angle-height equivalent."""
return self.tlwh_to_xyah(tlwh)
@property
def tlwh(self):
"""Get current position in bounding box format `(top left x, top left y,
width, height)`.
"""
if self.mean is None:
return self._tlwh.copy()
ret = self.mean[:4].copy()
ret[2] *= ret[3]
ret[:2] -= ret[2:] / 2
return ret
@property
def tlbr(self):
"""Convert bounding box to format `(min x, min y, max x, max y)`, i.e.,
`(top left, bottom right)`.
"""
ret = self.tlwh.copy()
ret[2:] += ret[:2]
return ret
@staticmethod
def tlwh_to_xyah(tlwh):
"""Convert bounding box to format `(center x, center y, aspect ratio,
height)`, where the aspect ratio is `width / height`.
"""
ret = np.asarray(tlwh).copy()
ret[:2] += ret[2:] / 2
ret[2] /= ret[3]
return ret
@staticmethod
def tlbr_to_tlwh(tlbr):
"""Converts top-left bottom-right format to top-left width height format."""
ret = np.asarray(tlbr).copy()
ret[2:] -= ret[:2]
return ret
@staticmethod
def tlwh_to_tlbr(tlwh):
"""Converts tlwh bounding box format to tlbr format."""
ret = np.asarray(tlwh).copy()
ret[2:] += ret[:2]
return ret
def __repr__(self):
"""Return a string representation of the BYTETracker object with start and end frames and track ID."""
return f'OT_{self.track_id}_({self.start_frame}-{self.end_frame})'
class BYTETracker:
def __init__(self, args, frame_rate=30):
"""Initialize a YOLOv8 object to track objects with given arguments and frame rate."""
self.tracked_stracks = [] # type: list[STrack]
self.lost_stracks = [] # type: list[STrack]
self.removed_stracks = [] # type: list[STrack]
self.frame_id = 0
self.args = args
self.max_time_lost = int(frame_rate / 30.0 * args.track_buffer)
self.kalman_filter = self.get_kalmanfilter()
self.reset_id()
# Add by WQG
self.args.new_track_thresh = 0.5
def update(self, results, img=None, features=None):
"""Updates object tracker with new detections and returns tracked object bounding boxes."""
self.frame_id += 1
activated_stracks = []
refind_stracks = []
lost_stracks = []
removed_stracks = []
first_finded = []
scores = results.conf
cls = results.cls
# =============================================================================
# # get xyxy and add index
# bboxes = results.xyxy
# bboxes = np.concatenate([bboxes, np.arange(len(bboxes)).reshape(-1, 1)], axis=-1)
# =============================================================================
bboxes = results.xyxyb
remain_inds = scores > self.args.track_high_thresh
inds_low = scores > self.args.track_low_thresh
inds_high = scores < self.args.track_high_thresh
inds_second = np.logical_and(inds_low, inds_high)
dets_second = bboxes[inds_second]
dets = bboxes[remain_inds]
scores_keep = scores[remain_inds]
scores_second = scores[inds_second]
cls_keep = cls[remain_inds]
cls_second = cls[inds_second]
detections = self.init_track(dets, scores_keep, cls_keep, img, features)
# Add newly detected tracklets to tracked_stracks
unconfirmed = []
tracked_stracks = [] # type: list[STrack]
for track in self.tracked_stracks:
if not track.is_activated:
unconfirmed.append(track)
else:
tracked_stracks.append(track)
# Step 2: First association, with high score detection boxes
strack_pool = self.joint_stracks(tracked_stracks, self.lost_stracks)
# Predict the current location with KF
self.multi_predict(strack_pool)
# ============================================================= 没必要gmcWQG
# if hasattr(self, 'gmc') and img is not None:
# warp = self.gmc.apply(img, dets)
# STrack.multi_gmc(strack_pool, warp)
# STrack.multi_gmc(unconfirmed, warp)
# =============================================================================
dists = self.get_dists_1(strack_pool, detections)
'''written by WQG for different class'''
dists = dists_update(dists, strack_pool, detections)
matches, u_track, u_detection = matching.linear_assignment(dists, thresh=self.args.match_thresh)
for itracked, idet in matches:
track = strack_pool[itracked]
det = detections[idet]
if track.state == TrackState.Tracked:
track.update(det, self.frame_id)
activated_stracks.append(track)
else:
track.re_activate(det, self.frame_id, new_id=False)
refind_stracks.append(track)
# Step 3: Second association, with low score detection boxes
# association the untrack to the low score detections
detections_second = self.init_track(dets_second, scores_second, cls_second, img, features)
r_tracked_stracks = [strack_pool[i] for i in u_track if strack_pool[i].state == TrackState.Tracked]
# TODO
dists = matching.iou_distance(r_tracked_stracks, detections_second)
'''written by WQG for different class'''
dists = dists_update(dists, r_tracked_stracks, detections_second)
matches, u_track, u_detection_second = matching.linear_assignment(dists, thresh=0.5)
for itracked, idet in matches:
track = r_tracked_stracks[itracked]
det = detections_second[idet]
if track.state == TrackState.Tracked:
track.update(det, self.frame_id)
activated_stracks.append(track)
else:
track.re_activate(det, self.frame_id, new_id=False)
refind_stracks.append(track)
for it in u_track:
track = r_tracked_stracks[it]
if track.state != TrackState.Lost:
track.mark_lost()
lost_stracks.append(track)
# Deal with unconfirmed tracks, usually tracks with only one beginning frame
detections = [detections[i] for i in u_detection]
dists = self.get_dists_1(unconfirmed, detections)
'''written by WQG for different class'''
dists = dists_update(dists, unconfirmed, detections)
matches, u_unconfirmed, u_detection = matching.linear_assignment(dists, thresh=0.7)
for itracked, idet in matches:
unconfirmed[itracked].update(detections[idet], self.frame_id)
activated_stracks.append(unconfirmed[itracked])
for it in u_unconfirmed:
track = unconfirmed[it]
if self.frame_id - track.end_frame > 2: # Add by WQG
track.mark_removed()
removed_stracks.append(track)
# Step 4: Init new stracks
for inew in u_detection:
track = detections[inew]
if track.score < self.args.new_track_thresh:
continue
track.activate(self.kalman_filter, self.frame_id)
activated_stracks.append(track)
first_finded.append(track)
# Step 5: Update state
for track in self.lost_stracks:
if self.frame_id - track.end_frame > self.max_time_lost:
track.mark_removed()
removed_stracks.append(track)
self.tracked_stracks = [t for t in self.tracked_stracks if t.state == TrackState.Tracked]
self.tracked_stracks = self.joint_stracks(self.tracked_stracks, activated_stracks)
self.tracked_stracks = self.joint_stracks(self.tracked_stracks, refind_stracks)
self.lost_stracks = self.sub_stracks(self.lost_stracks, self.tracked_stracks)
self.lost_stracks.extend(lost_stracks)
self.lost_stracks = self.sub_stracks(self.lost_stracks, self.removed_stracks)
self.tracked_stracks, self.lost_stracks = self.remove_duplicate_stracks(self.tracked_stracks, self.lost_stracks)
self.removed_stracks.extend(removed_stracks)
if len(self.removed_stracks) > 1000:
self.removed_stracks = self.removed_stracks[-999:] # clip remove stracks to 1000 maximum
'''x.tlbr have update by function:
@property
def tlwh(self):
'''
##================ 原算法输出
# output = np.asarray([x.tlbr.tolist() + [x.track_id, x.score, x.cls, x.frame_id, x.idx]
# for x in self.tracked_stracks if x.is_activated], dtype=np.float32)
## ===== write by WQG
output1 = [x.tlwh_to_tlbr(x._tlwh).tolist() + [x.track_id, x.score, x.cls, x.frame_id, x.idx]
for x in self.tracked_stracks if x.is_activated]
output2 = [x.tlwh_to_tlbr(x._tlwh).tolist() + [x.track_id, x.score, x.cls, x.frame_id, x.idx]
for x in first_finded if x.first_find]
output = np.asarray(output1 + output2, dtype=np.float32)
out_feat1 = [(x.frame_id, x.idx, x.smooth_feat, x.curr_feat, x.features) for x in self.tracked_stracks if x.is_activated]
out_feat2 = [(x.frame_id, x.idx, x.smooth_feat, x.curr_feat, x.features) for x in first_finded if x.first_find]
return output, out_feat1 + out_feat2
def get_result(self):
'''written by WQG'''
# =============================================================================
# activate_tracks = np.asarray([x.tlbr.tolist() + [x.track_id, x.score, x.cls, x.idx]
# for x in self.tracked_stracks if x.is_activated], dtype=np.float32)
#
# track_features = []
# =============================================================================
tracks = []
feats = []
for t in self.tracked_stracks:
if t.is_activated or t.first_find:
track = t.tlbr.tolist() + [t.track_id, t.score, t.cls, t.idx]
feat = t.curr_feature
tracks.append(track)
feats.append(feat)
tracks = np.asarray(tracks, dtype=np.float32)
return (tracks, feats)
def get_kalmanfilter(self):
"""Returns a Kalman filter object for tracking bounding boxes."""
return KalmanFilterXYAH()
def init_track(self, dets, scores, cls, img=None, feats=None):
"""Initialize object tracking with detections and scores using STrack algorithm."""
return [STrack(xyxy, s, c) for (xyxy, s, c) in zip(dets, scores, cls)] if len(dets) else [] # detections
def get_dists(self, tracks, detections):
"""Calculates the distance between tracks and detections using IOU and fuses scores."""
dists = matching.iou_distance(tracks, detections)
# TODO: mot20
# if not self.args.mot20:
dists = matching.fuse_score(dists, detections)
return dists
def get_dists_1(self, tracks, detections):
"""Calculates the distance between tracks and detections using IOU and fuses scores."""
pass
def multi_predict(self, tracks):
"""Returns the predicted tracks using the YOLOv8 network."""
STrack.multi_predict(tracks)
def reset_id(self):
"""Resets the ID counter of STrack."""
STrack.reset_id()
@staticmethod
def joint_stracks(tlista, tlistb):
"""Combine two lists of stracks into a single one."""
exists = {}
res = []
for t in tlista:
exists[t.track_id] = 1
res.append(t)
for t in tlistb:
tid = t.track_id
if not exists.get(tid, 0):
exists[tid] = 1
res.append(t)
return res
@staticmethod
def sub_stracks(tlista, tlistb):
"""DEPRECATED CODE in https://github.com/ultralytics/ultralytics/pull/1890/
stracks = {t.track_id: t for t in tlista}
for t in tlistb:
tid = t.track_id
if stracks.get(tid, 0):
del stracks[tid]
return list(stracks.values())
"""
track_ids_b = {t.track_id for t in tlistb}
return [t for t in tlista if t.track_id not in track_ids_b]
@staticmethod
def remove_duplicate_stracks(stracksa, stracksb):
"""Remove duplicate stracks with non-maximum IOU distance."""
pdist = matching.iou_distance(stracksa, stracksb)
#### ===================================== written by WQG
mlabel = []
if len(stracksa) and len(stracksb):
alabel = np.array([int(stack.cls) for stack in stracksa])
blabel = np.array([int(stack.cls) for stack in stracksb])
amlabel = np.expand_dims(alabel, axis=1).repeat(len(stracksb),axis=1)
bmlabel = np.expand_dims(blabel, axis=0).repeat(len(stracksa),axis=0)
mlabel = bmlabel == amlabel
if len(mlabel):
condt = (pdist<0.15) & mlabel # 需满足iou足够小且类别相同才予以排除
else:
condt = pdist<0.15
pairs = np.where(condt)
dupa, dupb = [], []
for p, q in zip(*pairs):
timep = stracksa[p].frame_id - stracksa[p].start_frame
timeq = stracksb[q].frame_id - stracksb[q].start_frame
if timep > timeq:
dupb.append(q)
else:
dupa.append(p)
resa = [t for i, t in enumerate(stracksa) if i not in dupa]
resb = [t for i, t in enumerate(stracksb) if i not in dupb]
return resa, resb

View File

@ -0,0 +1,18 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license
# Default YOLO tracker settings for BoT-SORT tracker https://github.com/NirAharon/BoT-SORT
tracker_type: botsort # tracker type, ['botsort', 'bytetrack']
track_high_thresh: 0.5 # threshold for the first association
track_low_thresh: 0.1 # threshold for the second association
new_track_thresh: 0.6 # threshold for init new track if the detection does not match any tracks
track_buffer: 30 # buffer to calculate the time when to remove tracks
match_thresh: 0.8 # threshold for matching tracks
# min_box_area: 10 # threshold for min box areas(for tracker evaluation, not used for now)
# mot20: False # for tracker evaluation(not used for now)
# BoT-SORT settings
gmc_method: sparseOptFlow # method of global motion compensation
# ReID model related thresh (not supported yet)
proximity_thresh: 0.5
appearance_thresh: 0.25
with_reid: True

View File

@ -0,0 +1,11 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license
# Default YOLO tracker settings for ByteTrack tracker https://github.com/ifzhang/ByteTrack
tracker_type: bytetrack # tracker type, ['botsort', 'bytetrack']
track_high_thresh: 0.5 # threshold for the first association
track_low_thresh: 0.1 # threshold for the second association
new_track_thresh: 0.6 # threshold for init new track if the detection does not match any tracks
track_buffer: 30 # buffer to calculate the time when to remove tracks
match_thresh: 0.8 # threshold for matching tracks
# min_box_area: 10 # threshold for min box areas(for tracker evaluation, not used for now)
# mot20: False # for tracker evaluation(not used for now)

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jan 19 16:15:35 2024
@author: ym
"""

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jan 19 14:01:46 2024
@author: ym
"""
import torch
import os
# import torchvision.transforms as T
class Config:
# network settings
backbone = 'resnet18' # [resnet18, mobilevit_s, mobilenet_v2, mobilenetv3]
batch_size = 8
embedding_size = 256
img_size = 224
ckpt_path = r"ckpts\resnet18_1220\best.pth"
ckpt_path = r"ckpts\best_resnet18_1887_0311.pth"
current_path = os.path.dirname(os.path.abspath(__file__))
model_path = os.path.join(current_path, ckpt_path)
# model_path = "./trackers/reid/ckpts/resnet18_1220/best.pth"
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# =============================================================================
# metric = 'arcface' # [cosface, arcface]
# drop_ratio = 0.5
#
# # training settings
# checkpoints = "checkpoints/Mobilev3Large_1225" # [resnet18, mobilevit_s, mobilenet_v2, mobilenetv3]
# restore = False
#
# test_model = "./checkpoints/resnet18_1220/best.pth"
#
#
#
#
# device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# pin_memory = True # if memory is large, set it True to speed up a bit
# num_workers = 4 # dataloader
# =============================================================================
config = Config()

View File

@ -0,0 +1,83 @@
import torch.nn as nn
import torchvision
from torch.nn import init
class Flatten(nn.Module):
def forward(self, x):
return x.view(x.shape[0], -1)
class ChannelAttention(nn.Module):
def __int__(self,channel,reduction, num_layers):
super(ChannelAttention,self).__init__()
self.avgpool = nn.AdaptiveAvgPool2d(1)
gate_channels = [channel]
gate_channels += [len(channel)//reduction]*num_layers
gate_channels += [channel]
self.ca = nn.Sequential()
self.ca.add_module('flatten', Flatten())
for i in range(len(gate_channels)-2):
self.ca.add_module('',nn.Linear(gate_channels[i], gate_channels[i+1]))
self.ca.add_module('',nn.BatchNorm1d(gate_channels[i+1]))
self.ca.add_module('',nn.ReLU())
self.ca.add_module('',nn.Linear(gate_channels[-2], gate_channels[-1]))
def forward(self, x):
res = self.avgpool(x)
res = self.ca(res)
res = res.unsqueeze(-1).unsqueeze(-1).expand_as(x)
return res
class SpatialAttention(nn.Module):
def __int__(self, channel,reduction=16,num_lay=3,dilation=2):
super(SpatialAttention).__init__()
self.sa = nn.Sequential()
self.sa.add_module('', nn.Conv2d(kernel_size=1, in_channels=channel, out_channels=(channel//reduction)*3))
self.sa.add_module('',nn.BatchNorm2d(num_features=(channel//reduction)))
self.sa.add_module('',nn.ReLU())
for i in range(num_lay):
self.sa.add_module('', nn.Conv2d(kernel_size=3,
in_channels=(channel//reduction),
out_channels=(channel//reduction),
padding=1,
dilation= 2))
self.sa.add_module('',nn.BatchNorm2d(channel//reduction))
self.sa.add_module('',nn.ReLU())
self.sa.add_module('',nn.Conv2d(channel//reduction, 1, kernel_size=1))
def forward(self,x):
res = self.sa(x)
res = res.expand_as(x)
return res
class BAMblock(nn.Module):
def __init__(self,channel=512, reduction=16, dia_val=2):
super(BAMblock, self).__init__()
self.ca = ChannelAttention(channel, reduction)
self.sa = SpatialAttention(channel,reduction,dia_val)
self.sigmoid = nn.Sigmoid()
def init_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
init.kaiming_normal(m.weight, mode='fan_out')
if m.bais is not None:
init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
init.constant_(m.weight, 1)
init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
init.normal_(m.weight, std=0.001)
if m.bias is not None:
init.constant_(m.bias, 0)
def forward(self,x):
b, c, _, _ = x.size()
sa_out=self.sa(x)
ca_out=self.ca(x)
weight=self.sigmoid(sa_out+ca_out)
out=(1+weight)*x
return out
if __name__ =="__main__":
print(512//14)

View File

@ -0,0 +1,68 @@
import torch
import torch.nn as nn
import torch.nn.init as init
class channelAttention(nn.Module):
def __init__(self, channel, reduction=16):
super(channelAttention, self).__init__()
self.Maxpooling = nn.AdaptiveMaxPool2d(1)
self.Avepooling = nn.AdaptiveAvgPool2d(1)
self.ca = nn.Sequential()
self.ca.add_module('conv1',nn.Conv2d(channel, channel//reduction, 1, bias=False))
self.ca.add_module('Relu', nn.ReLU())
self.ca.add_module('conv2',nn.Conv2d(channel//reduction, channel, 1, bias=False))
self.sigmod = nn.Sigmoid()
def forward(self, x):
M_out = self.Maxpooling(x)
A_out = self.Avepooling(x)
M_out = self.ca(M_out)
A_out = self.ca(A_out)
out = self.sigmod(M_out+A_out)
return out
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=7):
super().__init__()
self.conv = nn.Conv2d(in_channels=2, out_channels=1, kernel_size=kernel_size, padding=kernel_size // 2)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
max_result, _ = torch.max(x, dim=1, keepdim=True)
avg_result = torch.mean(x, dim=1, keepdim=True)
result = torch.cat([max_result, avg_result], dim=1)
output = self.conv(result)
output = self.sigmoid(output)
return output
class CBAM(nn.Module):
def __init__(self, channel=512, reduction=16, kernel_size=7):
super().__init__()
self.ca = channelAttention(channel, reduction)
self.sa = SpatialAttention(kernel_size)
def init_weights(self):
for m in self.modules():#权重初始化
if isinstance(m, nn.Conv2d):
init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
init.constant_(m.weight, 1)
init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
init.normal_(m.weight, std=0.001)
if m.bias is not None:
init.constant_(m.bias, 0)
def forward(self, x):
# b,c_,_ = x.size()
# residual = x
out = x*self.ca(x)
out = out*self.sa(out)
return out
if __name__ == '__main__':
input=torch.randn(50,512,7,7)
kernel_size=input.shape[2]
cbam = CBAM(channel=512,reduction=16,kernel_size=kernel_size)
output=cbam(input)
print(output.shape)

View File

@ -0,0 +1,33 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
class GeM(nn.Module):
def __init__(self, p=3, eps=1e-6):
super(GeM, self).__init__()
self.p = nn.Parameter(torch.ones(1) * p)
self.eps = eps
def forward(self, x):
return self.gem(x, p=self.p, eps=self.eps, stride = 2)
def gem(self, x, p=3, eps=1e-6, stride = 2):
return F.avg_pool2d(x.clamp(min=eps).pow(p), (x.size(-2), x.size(-1)), stride=2).pow(1. / p)
def __repr__(self):
return self.__class__.__name__ + \
'(' + 'p=' + '{:.4f}'.format(self.p.data.tolist()[0]) + \
', ' + 'eps=' + str(self.eps) + ')'
class TripletLoss(nn.Module):
def __init__(self, margin):
super(TripletLoss, self).__init__()
self.margin = margin
def forward(self, anchor, positive, negative, size_average = True):
distance_positive = (anchor-positive).pow(2).sum(1)
distance_negative = (anchor-negative).pow(2).sum(1)
losses = F.relu(distance_negative-distance_positive+self.margin)
return losses.mean() if size_average else losses.sum()
if __name__ == '__main__':
print('')

View File

@ -0,0 +1,9 @@
from .fmobilenet import FaceMobileNet
from .resnet_face import ResIRSE
from .mobilevit import mobilevit_s
from .metric import ArcFace, CosFace
from .loss import FocalLoss
from .resbam import resnet
from .resnet_pre import resnet18, resnet34, resnet50
from .mobilenet_v2 import mobilenet_v2
from .mobilenet_v3 import MobileNetV3_Small, MobileNetV3_Large

View File

@ -0,0 +1,124 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
class Flatten(nn.Module):
def forward(self, x):
return x.view(x.shape[0], -1)
class ConvBn(nn.Module):
def __init__(self, in_c, out_c, kernel=(1, 1), stride=1, padding=0, groups=1):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(in_c, out_c, kernel, stride, padding, groups=groups, bias=False),
nn.BatchNorm2d(out_c)
)
def forward(self, x):
return self.net(x)
class ConvBnPrelu(nn.Module):
def __init__(self, in_c, out_c, kernel=(1, 1), stride=1, padding=0, groups=1):
super().__init__()
self.net = nn.Sequential(
ConvBn(in_c, out_c, kernel, stride, padding, groups),
nn.PReLU(out_c)
)
def forward(self, x):
return self.net(x)
class DepthWise(nn.Module):
def __init__(self, in_c, out_c, kernel=(3, 3), stride=2, padding=1, groups=1):
super().__init__()
self.net = nn.Sequential(
ConvBnPrelu(in_c, groups, kernel=(1, 1), stride=1, padding=0),
ConvBnPrelu(groups, groups, kernel=kernel, stride=stride, padding=padding, groups=groups),
ConvBn(groups, out_c, kernel=(1, 1), stride=1, padding=0),
)
def forward(self, x):
return self.net(x)
class DepthWiseRes(nn.Module):
"""DepthWise with Residual"""
def __init__(self, in_c, out_c, kernel=(3, 3), stride=2, padding=1, groups=1):
super().__init__()
self.net = DepthWise(in_c, out_c, kernel, stride, padding, groups)
def forward(self, x):
return self.net(x) + x
class MultiDepthWiseRes(nn.Module):
def __init__(self, num_block, channels, kernel=(3, 3), stride=1, padding=1, groups=1):
super().__init__()
self.net = nn.Sequential(*[
DepthWiseRes(channels, channels, kernel, stride, padding, groups)
for _ in range(num_block)
])
def forward(self, x):
return self.net(x)
class FaceMobileNet(nn.Module):
def __init__(self, embedding_size):
super().__init__()
self.conv1 = ConvBnPrelu(1, 64, kernel=(3, 3), stride=2, padding=1)
self.conv2 = ConvBn(64, 64, kernel=(3, 3), stride=1, padding=1, groups=64)
self.conv3 = DepthWise(64, 64, kernel=(3, 3), stride=2, padding=1, groups=128)
self.conv4 = MultiDepthWiseRes(num_block=4, channels=64, kernel=3, stride=1, padding=1, groups=128)
self.conv5 = DepthWise(64, 128, kernel=(3, 3), stride=2, padding=1, groups=256)
self.conv6 = MultiDepthWiseRes(num_block=6, channels=128, kernel=(3, 3), stride=1, padding=1, groups=256)
self.conv7 = DepthWise(128, 128, kernel=(3, 3), stride=2, padding=1, groups=512)
self.conv8 = MultiDepthWiseRes(num_block=2, channels=128, kernel=(3, 3), stride=1, padding=1, groups=256)
self.conv9 = ConvBnPrelu(128, 512, kernel=(1, 1))
self.conv10 = ConvBn(512, 512, groups=512, kernel=(7, 7))
self.flatten = Flatten()
self.linear = nn.Linear(2048, embedding_size, bias=False)
self.bn = nn.BatchNorm1d(embedding_size)
def forward(self, x):
#print('x',x.shape)
out = self.conv1(x)
out = self.conv2(out)
out = self.conv3(out)
out = self.conv4(out)
out = self.conv5(out)
out = self.conv6(out)
out = self.conv7(out)
out = self.conv8(out)
out = self.conv9(out)
out = self.conv10(out)
out = self.flatten(out)
out = self.linear(out)
out = self.bn(out)
return out
if __name__ == "__main__":
from PIL import Image
import numpy as np
x = Image.open("../samples/009.jpg").convert('L')
x = x.resize((128, 128))
x = np.asarray(x, dtype=np.float32)
x = x[None, None, ...]
x = torch.from_numpy(x)
net = FaceMobileNet(512)
net.eval()
with torch.no_grad():
out = net(x)
print(out.shape)

View File

@ -0,0 +1,18 @@
import torch
import torch.nn as nn
class FocalLoss(nn.Module):
def __init__(self, gamma=2):
super().__init__()
self.gamma = gamma
self.ce = torch.nn.CrossEntropyLoss()
def forward(self, input, target):
#print(f'theta {input.shape, input[0]}, target {target.shape, target}')
logp = self.ce(input, target)
p = torch.exp(-logp)
loss = (1 - p) ** self.gamma * logp
return loss.mean()

View File

@ -0,0 +1,83 @@
# Definition of ArcFace loss and CosFace loss
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
class ArcFace(nn.Module):
def __init__(self, embedding_size, class_num, s=30.0, m=0.50):
"""ArcFace formula:
cos(m + theta) = cos(m)cos(theta) - sin(m)sin(theta)
Note that:
0 <= m + theta <= Pi
So if (m + theta) >= Pi, then theta >= Pi - m. In [0, Pi]
we have:
cos(theta) < cos(Pi - m)
So we can use cos(Pi - m) as threshold to check whether
(m + theta) go out of [0, Pi]
Args:
embedding_size: usually 128, 256, 512 ...
class_num: num of people when training
s: scale, see normface https://arxiv.org/abs/1704.06369
m: margin, see SphereFace, CosFace, and ArcFace paper
"""
super().__init__()
self.in_features = embedding_size
self.out_features = class_num
self.s = s
self.m = m
self.weight = nn.Parameter(torch.FloatTensor(class_num, embedding_size))
nn.init.xavier_uniform_(self.weight)
self.cos_m = math.cos(m)
self.sin_m = math.sin(m)
self.th = math.cos(math.pi - m)
self.mm = math.sin(math.pi - m) * m
def forward(self, input, label):
#print(f"embding {self.in_features}, class_num {self.out_features}, input {len(input)}, label {len(label)}")
cosine = F.linear(F.normalize(input), F.normalize(self.weight))
# print('F.normalize(input)',input.shape)
# print('F.normalize(self.weight)',F.normalize(self.weight).shape)
sine = ((1.0 - cosine.pow(2)).clamp(0, 1)).sqrt()
phi = cosine * self.cos_m - sine * self.sin_m
phi = torch.where(cosine > self.th, phi, cosine - self.mm) # drop to CosFace
#print(f'consine {cosine.shape, cosine}, sine {sine.shape, sine}, phi {phi.shape, phi}')
# update y_i by phi in cosine
output = cosine * 1.0 # make backward works
batch_size = len(output)
output[range(batch_size), label] = phi[range(batch_size), label]
# print(f'output {(output * self.s).shape}')
# print(f'phi[range(batch_size), label] {phi[range(batch_size), label]}')
return output * self.s
class CosFace(nn.Module):
def __init__(self, in_features, out_features, s=30.0, m=0.40):
"""
Args:
embedding_size: usually 128, 256, 512 ...
class_num: num of people when training
s: scale, see normface https://arxiv.org/abs/1704.06369
m: margin, see SphereFace, CosFace, and ArcFace paper
"""
super().__init__()
self.in_features = in_features
self.out_features = out_features
self.s = s
self.m = m
self.weight = nn.Parameter(torch.FloatTensor(out_features, in_features))
nn.init.xavier_uniform_(self.weight)
def forward(self, input, label):
cosine = F.linear(F.normalize(input), F.normalize(self.weight))
phi = cosine - self.m
output = cosine * 1.0 # make backward works
batch_size = len(output)
output[range(batch_size), label] = phi[range(batch_size), label]
return output * self.s

View File

@ -0,0 +1,200 @@
from torch import nn
from .utils import load_state_dict_from_url
from ..config import config as conf
__all__ = ['MobileNetV2', 'mobilenet_v2']
model_urls = {
'mobilenet_v2': 'https://download.pytorch.org/models/mobilenet_v2-b0353104.pth',
}
def _make_divisible(v, divisor, min_value=None):
"""
This function is taken from the original tf repo.
It ensures that all layers have a channel number that is divisible by 8
It can be seen here:
https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
:param v:
:param divisor:
:param min_value:
:return:
"""
if min_value is None:
min_value = divisor
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
# Make sure that round down does not go down by more than 10%.
if new_v < 0.9 * v:
new_v += divisor
return new_v
class ConvBNReLU(nn.Sequential):
def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1, norm_layer=None):
padding = (kernel_size - 1) // 2
if norm_layer is None:
norm_layer = nn.BatchNorm2d
super(ConvBNReLU, self).__init__(
nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),
norm_layer(out_planes),
nn.ReLU6(inplace=True)
)
class InvertedResidual(nn.Module):
def __init__(self, inp, oup, stride, expand_ratio, norm_layer=None):
super(InvertedResidual, self).__init__()
self.stride = stride
assert stride in [1, 2]
if norm_layer is None:
norm_layer = nn.BatchNorm2d
hidden_dim = int(round(inp * expand_ratio))
self.use_res_connect = self.stride == 1 and inp == oup
layers = []
if expand_ratio != 1:
# pw
layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1, norm_layer=norm_layer))
layers.extend([
# dw
ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim, norm_layer=norm_layer),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
norm_layer(oup),
])
self.conv = nn.Sequential(*layers)
def forward(self, x):
if self.use_res_connect:
return x + self.conv(x)
else:
return self.conv(x)
class MobileNetV2(nn.Module):
def __init__(self,
num_classes=conf.embedding_size,
width_mult=1.0,
inverted_residual_setting=None,
round_nearest=8,
block=None,
norm_layer=None):
"""
MobileNet V2 main class
Args:
num_classes (int): Number of classes
width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount
inverted_residual_setting: Network structure
round_nearest (int): Round the number of channels in each layer to be a multiple of this number
Set to 1 to turn off rounding
block: Module specifying inverted residual building block for mobilenet
norm_layer: Module specifying the normalization layer to use
"""
super(MobileNetV2, self).__init__()
if block is None:
block = InvertedResidual
if norm_layer is None:
norm_layer = nn.BatchNorm2d
input_channel = 32
last_channel = 1280
if inverted_residual_setting is None:
inverted_residual_setting = [
# t, c, n, s
[1, 16, 1, 1],
[6, 24, 2, 2],
[6, 32, 3, 2],
[6, 64, 4, 2],
[6, 96, 3, 1],
[6, 160, 3, 2],
[6, 320, 1, 1],
]
# only check the first element, assuming user knows t,c,n,s are required
if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4:
raise ValueError("inverted_residual_setting should be non-empty "
"or a 4-element list, got {}".format(inverted_residual_setting))
# building first layer
input_channel = _make_divisible(input_channel * width_mult, round_nearest)
self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest)
features = [ConvBNReLU(3, input_channel, stride=2, norm_layer=norm_layer)]
# building inverted residual blocks
for t, c, n, s in inverted_residual_setting:
output_channel = _make_divisible(c * width_mult, round_nearest)
for i in range(n):
stride = s if i == 0 else 1
features.append(block(input_channel, output_channel, stride, expand_ratio=t, norm_layer=norm_layer))
input_channel = output_channel
# building last several layers
features.append(ConvBNReLU(input_channel, self.last_channel, kernel_size=1, norm_layer=norm_layer))
# make it nn.Sequential
self.features = nn.Sequential(*features)
# building classifier
self.classifier = nn.Sequential(
nn.Dropout(0.2),
nn.Linear(self.last_channel, num_classes),
)
# weight initialization
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
nn.init.zeros_(m.bias)
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
nn.init.ones_(m.weight)
nn.init.zeros_(m.bias)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.zeros_(m.bias)
def _forward_impl(self, x):
# This exists since TorchScript doesn't support inheritance, so the superclass method
# (this one) needs to have a name other than `forward` that can be accessed in a subclass
x = self.features(x)
# Cannot use "squeeze" as batch-size can be 1 => must use reshape with x.shape[0]
x = nn.functional.adaptive_avg_pool2d(x, 1).reshape(x.shape[0], -1)
x = self.classifier(x)
return x
def forward(self, x):
return self._forward_impl(x)
def mobilenet_v2(pretrained=True, progress=True, **kwargs):
"""
Constructs a MobileNetV2 architecture from
`"MobileNetV2: Inverted Residuals and Linear Bottlenecks" <https://arxiv.org/abs/1801.04381>`_.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
model = MobileNetV2(**kwargs)
if pretrained:
state_dict = load_state_dict_from_url(model_urls['mobilenet_v2'],
progress=progress)
src_state_dict = state_dict
target_state_dict = model.state_dict()
skip_keys = []
# skip mismatch size tensors in case of pretraining
for k in src_state_dict.keys():
if k not in target_state_dict:
continue
if src_state_dict[k].size() != target_state_dict[k].size():
skip_keys.append(k)
for k in skip_keys:
del src_state_dict[k]
missing_keys, unexpected_keys = model.load_state_dict(src_state_dict, strict=False)
#.load_state_dict(state_dict)
return model

View File

@ -0,0 +1,200 @@
'''MobileNetV3 in PyTorch.
See the paper "Inverted Residuals and Linear Bottlenecks:
Mobile Networks for Classification, Detection and Segmentation" for more details.
'''
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.nn import init
from ..config import config as conf
class hswish(nn.Module):
def forward(self, x):
out = x * F.relu6(x + 3, inplace=True) / 6
return out
class hsigmoid(nn.Module):
def forward(self, x):
out = F.relu6(x + 3, inplace=True) / 6
return out
class SeModule(nn.Module):
def __init__(self, in_size, reduction=4):
super(SeModule, self).__init__()
self.se = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(in_size, in_size // reduction, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(in_size // reduction),
nn.ReLU(inplace=True),
nn.Conv2d(in_size // reduction, in_size, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(in_size),
hsigmoid()
)
def forward(self, x):
return x * self.se(x)
class Block(nn.Module):
'''expand + depthwise + pointwise'''
def __init__(self, kernel_size, in_size, expand_size, out_size, nolinear, semodule, stride):
super(Block, self).__init__()
self.stride = stride
self.se = semodule
self.conv1 = nn.Conv2d(in_size, expand_size, kernel_size=1, stride=1, padding=0, bias=False)
self.bn1 = nn.BatchNorm2d(expand_size)
self.nolinear1 = nolinear
self.conv2 = nn.Conv2d(expand_size, expand_size, kernel_size=kernel_size, stride=stride, padding=kernel_size//2, groups=expand_size, bias=False)
self.bn2 = nn.BatchNorm2d(expand_size)
self.nolinear2 = nolinear
self.conv3 = nn.Conv2d(expand_size, out_size, kernel_size=1, stride=1, padding=0, bias=False)
self.bn3 = nn.BatchNorm2d(out_size)
self.shortcut = nn.Sequential()
if stride == 1 and in_size != out_size:
self.shortcut = nn.Sequential(
nn.Conv2d(in_size, out_size, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(out_size),
)
def forward(self, x):
out = self.nolinear1(self.bn1(self.conv1(x)))
out = self.nolinear2(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))
if self.se != None:
out = self.se(out)
out = out + self.shortcut(x) if self.stride==1 else out
return out
class MobileNetV3_Large(nn.Module):
def __init__(self, num_classes=conf.embedding_size):
super(MobileNetV3_Large, self).__init__()
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(16)
self.hs1 = hswish()
self.bneck = nn.Sequential(
Block(3, 16, 16, 16, nn.ReLU(inplace=True), None, 1),
Block(3, 16, 64, 24, nn.ReLU(inplace=True), None, 2),
Block(3, 24, 72, 24, nn.ReLU(inplace=True), None, 1),
Block(5, 24, 72, 40, nn.ReLU(inplace=True), SeModule(40), 2),
Block(5, 40, 120, 40, nn.ReLU(inplace=True), SeModule(40), 1),
Block(5, 40, 120, 40, nn.ReLU(inplace=True), SeModule(40), 1),
Block(3, 40, 240, 80, hswish(), None, 2),
Block(3, 80, 200, 80, hswish(), None, 1),
Block(3, 80, 184, 80, hswish(), None, 1),
Block(3, 80, 184, 80, hswish(), None, 1),
Block(3, 80, 480, 112, hswish(), SeModule(112), 1),
Block(3, 112, 672, 112, hswish(), SeModule(112), 1),
Block(5, 112, 672, 160, hswish(), SeModule(160), 1),
Block(5, 160, 672, 160, hswish(), SeModule(160), 2),
Block(5, 160, 960, 160, hswish(), SeModule(160), 1),
)
self.conv2 = nn.Conv2d(160, 960, kernel_size=1, stride=1, padding=0, bias=False)
self.bn2 = nn.BatchNorm2d(960)
self.hs2 = hswish()
self.linear3 = nn.Linear(960, 1280)
self.bn3 = nn.BatchNorm1d(1280)
self.hs3 = hswish()
self.linear4 = nn.Linear(1280, num_classes)
self.init_params()
def init_params(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
init.constant_(m.weight, 1)
init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
init.normal_(m.weight, std=0.001)
if m.bias is not None:
init.constant_(m.bias, 0)
def forward(self, x):
out = self.hs1(self.bn1(self.conv1(x)))
out = self.bneck(out)
out = self.hs2(self.bn2(self.conv2(out)))
out = F.avg_pool2d(out, conf.img_size // 32)
out = out.view(out.size(0), -1)
out = self.hs3(self.bn3(self.linear3(out)))
out = self.linear4(out)
return out
class MobileNetV3_Small(nn.Module):
def __init__(self, num_classes=conf.embedding_size):
super(MobileNetV3_Small, self).__init__()
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(16)
self.hs1 = hswish()
self.bneck = nn.Sequential(
Block(3, 16, 16, 16, nn.ReLU(inplace=True), SeModule(16), 2),
Block(3, 16, 72, 24, nn.ReLU(inplace=True), None, 2),
Block(3, 24, 88, 24, nn.ReLU(inplace=True), None, 1),
Block(5, 24, 96, 40, hswish(), SeModule(40), 2),
Block(5, 40, 240, 40, hswish(), SeModule(40), 1),
Block(5, 40, 240, 40, hswish(), SeModule(40), 1),
Block(5, 40, 120, 48, hswish(), SeModule(48), 1),
Block(5, 48, 144, 48, hswish(), SeModule(48), 1),
Block(5, 48, 288, 96, hswish(), SeModule(96), 2),
Block(5, 96, 576, 96, hswish(), SeModule(96), 1),
Block(5, 96, 576, 96, hswish(), SeModule(96), 1),
)
self.conv2 = nn.Conv2d(96, 576, kernel_size=1, stride=1, padding=0, bias=False)
self.bn2 = nn.BatchNorm2d(576)
self.hs2 = hswish()
self.linear3 = nn.Linear(576, 1280)
self.bn3 = nn.BatchNorm1d(1280)
self.hs3 = hswish()
self.linear4 = nn.Linear(1280, num_classes)
self.init_params()
def init_params(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
init.constant_(m.weight, 1)
init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
init.normal_(m.weight, std=0.001)
if m.bias is not None:
init.constant_(m.bias, 0)
def forward(self, x):
out = self.hs1(self.bn1(self.conv1(x)))
out = self.bneck(out)
out = self.hs2(self.bn2(self.conv2(out)))
out = F.avg_pool2d(out, conf.img_size // 32)
out = out.view(out.size(0), -1)
out = self.hs3(self.bn3(self.linear3(out)))
out = self.linear4(out)
return out
def test():
net = MobileNetV3_Small()
x = torch.randn(2,3,224,224)
y = net(x)
print(y.size())
# test()

View File

@ -0,0 +1,265 @@
import torch
import torch.nn as nn
from einops import rearrange
from ..config import config as conf
def conv_1x1_bn(inp, oup):
return nn.Sequential(
nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
nn.SiLU()
)
def conv_nxn_bn(inp, oup, kernal_size=3, stride=1):
return nn.Sequential(
nn.Conv2d(inp, oup, kernal_size, stride, 1, bias=False),
nn.BatchNorm2d(oup),
nn.SiLU()
)
class PreNorm(nn.Module):
def __init__(self, dim, fn):
super().__init__()
self.norm = nn.LayerNorm(dim)
self.fn = fn
def forward(self, x, **kwargs):
return self.fn(self.norm(x), **kwargs)
class FeedForward(nn.Module):
def __init__(self, dim, hidden_dim, dropout=0.):
super().__init__()
self.net = nn.Sequential(
nn.Linear(dim, hidden_dim),
nn.SiLU(),
nn.Dropout(dropout),
nn.Linear(hidden_dim, dim),
nn.Dropout(dropout)
)
def forward(self, x):
return self.net(x)
class Attention(nn.Module):
def __init__(self, dim, heads=8, dim_head=64, dropout=0.):
super().__init__()
inner_dim = dim_head * heads
project_out = not (heads == 1 and dim_head == dim)
self.heads = heads
self.scale = dim_head ** -0.5
self.attend = nn.Softmax(dim=-1)
self.to_qkv = nn.Linear(dim, inner_dim * 3, bias=False)
self.to_out = nn.Sequential(
nn.Linear(inner_dim, dim),
nn.Dropout(dropout)
) if project_out else nn.Identity()
def forward(self, x):
qkv = self.to_qkv(x).chunk(3, dim=-1)
q, k, v = map(lambda t: rearrange(t, 'b p n (h d) -> b p h n d', h=self.heads), qkv)
dots = torch.matmul(q, k.transpose(-1, -2)) * self.scale
attn = self.attend(dots)
out = torch.matmul(attn, v)
out = rearrange(out, 'b p h n d -> b p n (h d)')
return self.to_out(out)
class Transformer(nn.Module):
def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout=0.):
super().__init__()
self.layers = nn.ModuleList([])
for _ in range(depth):
self.layers.append(nn.ModuleList([
PreNorm(dim, Attention(dim, heads, dim_head, dropout)),
PreNorm(dim, FeedForward(dim, mlp_dim, dropout))
]))
def forward(self, x):
for attn, ff in self.layers:
x = attn(x) + x
x = ff(x) + x
return x
class MV2Block(nn.Module):
def __init__(self, inp, oup, stride=1, expansion=4):
super().__init__()
self.stride = stride
assert stride in [1, 2]
hidden_dim = int(inp * expansion)
self.use_res_connect = self.stride == 1 and inp == oup
if expansion == 1:
self.conv = nn.Sequential(
# dw
nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.SiLU(),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
else:
self.conv = nn.Sequential(
# pw
nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.SiLU(),
# dw
nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.SiLU(),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
def forward(self, x):
if self.use_res_connect:
return x + self.conv(x)
else:
return self.conv(x)
class MobileViTBlock(nn.Module):
def __init__(self, dim, depth, channel, kernel_size, patch_size, mlp_dim, dropout=0.):
super().__init__()
self.ph, self.pw = patch_size
self.conv1 = conv_nxn_bn(channel, channel, kernel_size)
self.conv2 = conv_1x1_bn(channel, dim)
self.transformer = Transformer(dim, depth, 4, 8, mlp_dim, dropout)
self.conv3 = conv_1x1_bn(dim, channel)
self.conv4 = conv_nxn_bn(2 * channel, channel, kernel_size)
def forward(self, x):
y = x.clone()
# Local representations
x = self.conv1(x)
x = self.conv2(x)
# Global representations
_, _, h, w = x.shape
x = rearrange(x, 'b d (h ph) (w pw) -> b (ph pw) (h w) d', ph=self.ph, pw=self.pw)
x = self.transformer(x)
x = rearrange(x, 'b (ph pw) (h w) d -> b d (h ph) (w pw)', h=h // self.ph, w=w // self.pw, ph=self.ph,
pw=self.pw)
# Fusion
x = self.conv3(x)
x = torch.cat((x, y), 1)
x = self.conv4(x)
return x
class MobileViT(nn.Module):
def __init__(self, image_size, dims, channels, num_classes, expansion=4, kernel_size=3, patch_size=(2, 2)):
super().__init__()
ih, iw = image_size
ph, pw = patch_size
assert ih % ph == 0 and iw % pw == 0
L = [2, 4, 3]
self.conv1 = conv_nxn_bn(3, channels[0], stride=2)
self.mv2 = nn.ModuleList([])
self.mv2.append(MV2Block(channels[0], channels[1], 1, expansion))
self.mv2.append(MV2Block(channels[1], channels[2], 2, expansion))
self.mv2.append(MV2Block(channels[2], channels[3], 1, expansion))
self.mv2.append(MV2Block(channels[2], channels[3], 1, expansion)) # Repeat
self.mv2.append(MV2Block(channels[3], channels[4], 2, expansion))
self.mv2.append(MV2Block(channels[5], channels[6], 2, expansion))
self.mv2.append(MV2Block(channels[7], channels[8], 2, expansion))
self.mvit = nn.ModuleList([])
self.mvit.append(MobileViTBlock(dims[0], L[0], channels[5], kernel_size, patch_size, int(dims[0] * 2)))
self.mvit.append(MobileViTBlock(dims[1], L[1], channels[7], kernel_size, patch_size, int(dims[1] * 4)))
self.mvit.append(MobileViTBlock(dims[2], L[2], channels[9], kernel_size, patch_size, int(dims[2] * 4)))
self.conv2 = conv_1x1_bn(channels[-2], channels[-1])
self.pool = nn.AvgPool2d(ih // 32, 1)
self.fc = nn.Linear(channels[-1], num_classes, bias=False)
def forward(self, x):
#print('x',x.shape)
x = self.conv1(x)
x = self.mv2[0](x)
x = self.mv2[1](x)
x = self.mv2[2](x)
x = self.mv2[3](x) # Repeat
x = self.mv2[4](x)
x = self.mvit[0](x)
x = self.mv2[5](x)
x = self.mvit[1](x)
x = self.mv2[6](x)
x = self.mvit[2](x)
x = self.conv2(x)
#print('pool_before',x.shape)
x = self.pool(x).view(-1, x.shape[1])
#print('self_pool',self.pool)
#print('pool_after',x.shape)
x = self.fc(x)
return x
def mobilevit_xxs():
dims = [64, 80, 96]
channels = [16, 16, 24, 24, 48, 48, 64, 64, 80, 80, 320]
return MobileViT((256, 256), dims, channels, num_classes=1000, expansion=2)
def mobilevit_xs():
dims = [96, 120, 144]
channels = [16, 32, 48, 48, 64, 64, 80, 80, 96, 96, 384]
return MobileViT((256, 256), dims, channels, num_classes=1000)
def mobilevit_s():
dims = [144, 192, 240]
channels = [16, 32, 64, 64, 96, 96, 128, 128, 160, 160, 640]
return MobileViT((conf.img_size, conf.img_size), dims, channels, num_classes=conf.embedding_size)
def count_parameters(model):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
if __name__ == '__main__':
img = torch.randn(5, 3, 256, 256)
vit = mobilevit_xxs()
out = vit(img)
print(out.shape)
print(count_parameters(vit))
vit = mobilevit_xs()
out = vit(img)
print(out.shape)
print(count_parameters(vit))
vit = mobilevit_s()
out = vit(img)
print(out.shape)
print(count_parameters(vit))

View File

@ -0,0 +1,134 @@
from .CBAM import CBAM
import torch
import torch.nn as nn
from .Tool import GeM as gem
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, inchannel, outchannel,stride =1,dowsample=None):
# super(Bottleneck, self).__init__()
super().__init__()
self.conv1 = nn.Conv2d(in_channels=inchannel,out_channels=outchannel, kernel_size=1, stride=1, bias=False)
self.bn1 = nn.BatchNorm2d(outchannel)
self.conv2 = nn.Conv2d(in_channels=outchannel, out_channels=outchannel,kernel_size=3,bias=False, stride=stride,padding=1)
self.bn2 = nn.BatchNorm2d(outchannel)
self.conv3 =nn.Conv2d(in_channels=outchannel, out_channels=outchannel*self.expansion,stride=1,bias=False,kernel_size=1)
self.bn3 = nn.BatchNorm2d(outchannel*self.expansion)
self.relu = nn.ReLU(inplace=True)
self.downsample = dowsample
def forward(self, x):
self.identity = x
# print('>>>>>>>>',type(x))
if self.downsample is not None:
# print('>>>>downsample>>>>', type(self.downsample))
self.identity = self.downsample(x)
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
# print('>>>>out>>>identity',out.size(),self.identity.size())
out = out+self.identity
out = self.relu(out)
return out
class resnet(nn.Module):
def __init__(self,block=Bottleneck, block_num=[3,4,6,3], num_class=1000):
super().__init__()
self.in_channel = 64
self.conv1 = nn.Conv2d(in_channels=3,
out_channels=self.in_channel,
stride=2,
kernel_size=7,
padding=3,
bias=False)
self.bn1 = nn.BatchNorm2d(self.in_channel)
self.relu = nn.ReLU(inplace=True)
self.cbam = CBAM(self.in_channel)
self.cbam1 = CBAM(2048)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 64, block_num[0],stride=1)
self.layer2 = self._make_layer(block, 128, block_num[1],stride=2)
self.layer3 = self._make_layer(block, 256, block_num[2],stride=2)
self.layer4 = self._make_layer(block, 512, block_num[3],stride=2)
self.avgpool = nn.AdaptiveAvgPool2d((1,1))
self.gem = gem()
self.fc = nn.Linear(512*block.expansion, num_class)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal(m.weight,mode = 'fan_out',
nonlinearity='relu')
if isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
nn.init.constant_(m.weight, 1.0)
nn.init.constant_(m.bias, 1.0)
def _make_layer(self,block ,channel, block_num, stride=1):
downsample = None
if stride !=1 or self.in_channel != channel*block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.in_channel, channel*block.expansion,kernel_size=1,stride=stride,bias=False),
nn.BatchNorm2d(channel*block.expansion))
layer = []
layer.append(block(self.in_channel, channel, stride, downsample))
self.in_channel = channel*block.expansion
for _ in range(1, block_num):
layer.append(block(self.in_channel, channel))
return nn.Sequential(*layer)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.cbam(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.cbam1(x)
# x = self.avgpool(x)
x = self.gem(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
class TripletNet(nn.Module):
def __init__(self, num_class, flag=True):
super(TripletNet, self).__init__()
self.initnet = rescbam(num_class)
self.flag = flag
def forward(self, x1, x2=None, x3=None):
if self.flag:
output1 = self.initnet(x1)
output2 = self.initnet(x2)
output3 = self.initnet(x3)
return output1, output2, output3
else:
output = self.initnet(x1)
return output
def rescbam(num_class):
return resnet(block=Bottleneck, block_num=[3,4,6,3],num_class=num_class)
if __name__ =='__main__':
input1 = torch.randn(4,3,640,640)
input2 = torch.randn(4,3,640,640)
input3 = torch.randn(4,3,640,640)
#rescbam测试
# Resnet50 = rescbam(512)
# output = Resnet50.forward(input1)
# print(Resnet50)
#trnet测试
trnet = TripletNet(512)
output = trnet(input1, input2, input3)
print(output)

View File

@ -0,0 +1,182 @@
"""resnet in pytorch
[1] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun.
Deep Residual Learning for Image Recognition
https://arxiv.org/abs/1512.03385v1
"""
import torch
import torch.nn as nn
from config import config as conf
class BasicBlock(nn.Module):
"""Basic Block for resnet 18 and resnet 34
"""
#BasicBlock and BottleNeck block
#have different output size
#we use class attribute expansion
#to distinct
expansion = 1
def __init__(self, in_channels, out_channels, stride=1):
super().__init__()
#residual function
self.residual_function = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels * BasicBlock.expansion, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(out_channels * BasicBlock.expansion)
)
#shortcut
self.shortcut = nn.Sequential()
#the shortcut output dimension is not the same with residual function
#use 1*1 convolution to match the dimension
if stride != 1 or in_channels != BasicBlock.expansion * out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels * BasicBlock.expansion, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(out_channels * BasicBlock.expansion)
)
def forward(self, x):
return nn.ReLU(inplace=True)(self.residual_function(x) + self.shortcut(x))
class BottleNeck(nn.Module):
"""Residual block for resnet over 50 layers
"""
expansion = 4
def __init__(self, in_channels, out_channels, stride=1):
super().__init__()
self.residual_function = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels, stride=stride, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels * BottleNeck.expansion, kernel_size=1, bias=False),
nn.BatchNorm2d(out_channels * BottleNeck.expansion),
)
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != out_channels * BottleNeck.expansion:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels * BottleNeck.expansion, stride=stride, kernel_size=1, bias=False),
nn.BatchNorm2d(out_channels * BottleNeck.expansion)
)
def forward(self, x):
return nn.ReLU(inplace=True)(self.residual_function(x) + self.shortcut(x))
class ResNet(nn.Module):
def __init__(self, block, num_block, num_classes=conf.embedding_size):
super().__init__()
self.in_channels = 64
# self.conv1 = nn.Sequential(
# nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False),
# nn.BatchNorm2d(64),
# nn.ReLU(inplace=True))
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64,stride=2,kernel_size=7,padding=3,bias=False),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
#we use a different inputsize than the original paper
#so conv2_x's stride is 1
self.conv2_x = self._make_layer(block, 64, num_block[0], 1)
self.conv3_x = self._make_layer(block, 128, num_block[1], 2)
self.conv4_x = self._make_layer(block, 256, num_block[2], 2)
self.conv5_x = self._make_layer(block, 512, num_block[3], 2)
self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(512 * block.expansion, num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal(m.weight,mode = 'fan_out',
nonlinearity='relu')
if isinstance(m, (nn.BatchNorm2d)):
nn.init.constant_(m.weight, 1.0)
nn.init.constant_(m.bias, 1.0)
def _make_layer(self, block, out_channels, num_blocks, stride):
"""make resnet layers(by layer i didnt mean this 'layer' was the
same as a neuron netowork layer, ex. conv layer), one layer may
contain more than one residual block
Args:
block: block type, basic block or bottle neck block
out_channels: output depth channel number of this layer
num_blocks: how many blocks per layer
stride: the stride of the first block of this layer
Return:
return a resnet layer
"""
# we have num_block blocks per layer, the first block
# could be 1 or 2, other blocks would always be 1
strides = [stride] + [1] * (num_blocks - 1)
layers = []
for stride in strides:
layers.append(block(self.in_channels, out_channels, stride))
self.in_channels = out_channels * block.expansion
return nn.Sequential(*layers)
def forward(self, x):
output = self.conv1(x)
output = self.conv2_x(output)
output = self.conv3_x(output)
output = self.conv4_x(output)
output = self.conv5_x(output)
print('pollBefore',output.shape)
output = self.avg_pool(output)
print('poolAfter',output.shape)
output = output.view(output.size(0), -1)
print('fcBefore',output.shape)
output = self.fc(output)
return output
def resnet18():
""" return a ResNet 18 object
"""
return ResNet(BasicBlock, [2, 2, 2, 2])
def resnet34():
""" return a ResNet 34 object
"""
return ResNet(BasicBlock, [3, 4, 6, 3])
def resnet50():
""" return a ResNet 50 object
"""
return ResNet(BottleNeck, [3, 4, 6, 3])
def resnet101():
""" return a ResNet 101 object
"""
return ResNet(BottleNeck, [3, 4, 23, 3])
def resnet152():
""" return a ResNet 152 object
"""
return ResNet(BottleNeck, [3, 8, 36, 3])

View File

@ -0,0 +1,120 @@
""" Resnet_IR_SE in ArcFace """
import torch
import torch.nn as nn
import torch.nn.functional as F
class Flatten(nn.Module):
def forward(self, x):
return x.reshape(x.shape[0], -1)
class SEConv(nn.Module):
"""Use Convolution instead of FullyConnection in SE"""
def __init__(self, channels, reduction):
super().__init__()
self.net = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(channels, channels // reduction, kernel_size=1, bias=False),
nn.ReLU(inplace=True),
nn.Conv2d(channels // reduction, channels, kernel_size=1, bias=False),
nn.Sigmoid(),
)
def forward(self, x):
return self.net(x) * x
class SE(nn.Module):
def __init__(self, channels, reduction):
super().__init__()
self.net = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Linear(channels, channels // reduction),
nn.ReLU(inplace=True),
nn.Linear(channels // reduction, channels),
nn.Sigmoid(),
)
def forward(self, x):
return self.net(x) * x
class IRSE(nn.Module):
def __init__(self, channels, depth, stride):
super().__init__()
if channels == depth:
self.shortcut = nn.MaxPool2d(kernel_size=1, stride=stride)
else:
self.shortcut = nn.Sequential(
nn.Conv2d(channels, depth, (1, 1), stride, bias=False),
nn.BatchNorm2d(depth),
)
self.residual = nn.Sequential(
nn.BatchNorm2d(channels),
nn.Conv2d(channels, depth, (3, 3), 1, 1, bias=False),
nn.PReLU(depth),
nn.Conv2d(depth, depth, (3, 3), stride, 1, bias=False),
nn.BatchNorm2d(depth),
SEConv(depth, 16),
)
def forward(self, x):
return self.shortcut(x) + self.residual(x)
class ResIRSE(nn.Module):
"""Resnet50-IRSE backbone"""
def __init__(self, ih,embedding_size, drop_ratio):
super().__init__()
ih_last = ih // 16
self.input_layer = nn.Sequential(
nn.Conv2d(3, 64, (3, 3), 1, 1, bias=False),
nn.BatchNorm2d(64),
nn.PReLU(64),
)
self.output_layer = nn.Sequential(
nn.BatchNorm2d(512),
nn.Dropout(drop_ratio),
Flatten(),
nn.Linear(512 * ih_last * ih_last, embedding_size),
nn.BatchNorm1d(embedding_size),
)
# ["channels", "depth", "stride"],
self.res50_arch = [
[64, 64, 2], [64, 64, 1], [64, 64, 1],
[64, 128, 2], [128, 128, 1], [128, 128, 1], [128, 128, 1],
[128, 256, 2], [256, 256, 1], [256, 256, 1], [256, 256, 1], [256, 256, 1],
[256, 256, 1], [256, 256, 1], [256, 256, 1], [256, 256, 1], [256, 256, 1],
[256, 256, 1], [256, 256, 1], [256, 256, 1], [256, 256, 1],
[256, 512, 2], [512, 512, 1], [512, 512, 1],
]
self.body = nn.Sequential(*[ IRSE(a,b,c) for (a,b,c) in self.res50_arch ])
def forward(self, x):
x = self.input_layer(x)
x = self.body(x)
x = self.output_layer(x)
return x
if __name__ == "__main__":
from PIL import Image
import numpy as np
x = Image.open("../samples/009.jpg").convert('L')
x = x.resize((128, 128))
x = np.asarray(x, dtype=np.float32)
x = x[None, None, ...]
x = torch.from_numpy(x)
net = ResIRSE(512, 0.6)
net.eval()
with torch.no_grad():
out = net(x)
print(out.shape)

View File

@ -0,0 +1,384 @@
import torch
import torch.nn as nn
# from config import config as conf
from ..config import config as conf
try:
from torch.hub import load_state_dict_from_url
except ImportError:
from torch.utils.model_zoo import load_url as load_state_dict_from_url
#from .utils import load_state_dict_from_url
__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
'resnet152', 'resnext50_32x4d', 'resnext101_32x8d',
'wide_resnet50_2', 'wide_resnet101_2']
model_urls = {
'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth',
'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth',
'wide_resnet50_2': 'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth',
'wide_resnet101_2': 'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth',
}
def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
"""3x3 convolution with padding"""
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=dilation, groups=groups, bias=False, dilation=dilation)
def conv1x1(in_planes, out_planes, stride=1):
"""1x1 convolution"""
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
base_width=64, dilation=1, norm_layer=None):
super(BasicBlock, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
if groups != 1 or base_width != 64:
raise ValueError('BasicBlock only supports groups=1 and base_width=64')
if dilation > 1:
raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
# Both self.conv1 and self.downsample layers downsample the input when stride != 1
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = norm_layer(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = norm_layer(planes)
self.downsample = downsample
self.stride = stride
def forward(self, x):
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
class Bottleneck(nn.Module):
# Bottleneck in torchvision places the stride for downsampling at 3x3 convolution(self.conv2)
# while original implementation places the stride at the first 1x1 convolution(self.conv1)
# according to "Deep residual learning for image recognition"https://arxiv.org/abs/1512.03385.
# This variant is also known as ResNet V1.5 and improves accuracy according to
# https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch.
expansion = 4
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
base_width=64, dilation=1, norm_layer=None):
super(Bottleneck, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
width = int(planes * (base_width / 64.)) * groups
# Both self.conv2 and self.downsample layers downsample the input when stride != 1
self.conv1 = conv1x1(inplanes, width)
self.bn1 = norm_layer(width)
self.conv2 = conv3x3(width, width, stride, groups, dilation)
self.bn2 = norm_layer(width)
self.conv3 = conv1x1(width, planes * self.expansion)
self.bn3 = norm_layer(planes * self.expansion)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
self.stride = stride
def forward(self, x):
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=conf.embedding_size, zero_init_residual=False,
groups=1, width_per_group=64, replace_stride_with_dilation=None,
norm_layer=None):
super(ResNet, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
self._norm_layer = norm_layer
self.inplanes = 64
self.dilation = 1
if replace_stride_with_dilation is None:
# each element in the tuple indicates if we should replace
# the 2x2 stride with a dilated convolution instead
replace_stride_with_dilation = [False, False, False]
if len(replace_stride_with_dilation) != 3:
raise ValueError("replace_stride_with_dilation should be None "
"or a 3-element tuple, got {}".format(replace_stride_with_dilation))
self.groups = groups
self.base_width = width_per_group
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
bias=False)
self.bn1 = norm_layer(self.inplanes)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2,
dilate=replace_stride_with_dilation[0])
self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
dilate=replace_stride_with_dilation[1])
self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
dilate=replace_stride_with_dilation[2])
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(512 * block.expansion, num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
# Zero-initialize the last BN in each residual branch,
# so that the residual branch starts with zeros, and each residual block behaves like an identity.
# This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
if zero_init_residual:
for m in self.modules():
if isinstance(m, Bottleneck):
nn.init.constant_(m.bn3.weight, 0)
elif isinstance(m, BasicBlock):
nn.init.constant_(m.bn2.weight, 0)
def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
norm_layer = self._norm_layer
downsample = None
previous_dilation = self.dilation
if dilate:
self.dilation *= stride
stride = 1
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
conv1x1(self.inplanes, planes * block.expansion, stride),
norm_layer(planes * block.expansion),
)
layers = []
layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
self.base_width, previous_dilation, norm_layer))
self.inplanes = planes * block.expansion
for _ in range(1, blocks):
layers.append(block(self.inplanes, planes, groups=self.groups,
base_width=self.base_width, dilation=self.dilation,
norm_layer=norm_layer))
return nn.Sequential(*layers)
def _forward_impl(self, x):
# See note [TorchScript super()]
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
#print('poolBefore', x.shape)
x = self.avgpool(x)
#print('poolAfter', x.shape)
x = torch.flatten(x, 1)
#print('fcBefore',x.shape)
x = self.fc(x)
# print('fcAfter',x.shape)
return x
def forward(self, x):
return self._forward_impl(x)
# def _resnet(arch, block, layers, pretrained, progress, **kwargs):
# model = ResNet(block, layers, **kwargs)
# if pretrained:
# state_dict = load_state_dict_from_url(model_urls[arch],
# progress=progress)
# model.load_state_dict(state_dict, strict=False)
# return model
def _resnet(arch, block, layers, pretrained, progress, **kwargs):
model = ResNet(block, layers, **kwargs)
if pretrained:
state_dict = load_state_dict_from_url(model_urls[arch],
progress=progress)
#print('state_dict',state_dict)
src_state_dict = state_dict
target_state_dict = model.state_dict()
skip_keys = []
# skip mismatch size tensors in case of pretraining
for k in src_state_dict.keys():
if k not in target_state_dict:
continue
if src_state_dict[k].size() != target_state_dict[k].size():
skip_keys.append(k)
for k in skip_keys:
del src_state_dict[k]
missing_keys, unexpected_keys = model.load_state_dict(src_state_dict, strict=False)
return model
def resnet18(pretrained=True, progress=True, **kwargs):
r"""ResNet-18 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress,
**kwargs)
def resnet34(pretrained=False, progress=True, **kwargs):
r"""ResNet-34 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress,
**kwargs)
def resnet50(pretrained=False, progress=True, **kwargs):
r"""ResNet-50 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress,
**kwargs)
def resnet101(pretrained=False, progress=True, **kwargs):
r"""ResNet-101 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, progress,
**kwargs)
def resnet152(pretrained=False, progress=True, **kwargs):
r"""ResNet-152 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, progress,
**kwargs)
def resnext50_32x4d(pretrained=False, progress=True, **kwargs):
r"""ResNeXt-50 32x4d model from
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['groups'] = 32
kwargs['width_per_group'] = 4
return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3],
pretrained, progress, **kwargs)
def resnext101_32x8d(pretrained=False, progress=True, **kwargs):
r"""ResNeXt-101 32x8d model from
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['groups'] = 32
kwargs['width_per_group'] = 8
return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3],
pretrained, progress, **kwargs)
def wide_resnet50_2(pretrained=False, progress=True, **kwargs):
r"""Wide ResNet-50-2 model from
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
The model is the same as ResNet except for the bottleneck number of channels
which is twice larger in every block. The number of channels in outer 1x1
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['width_per_group'] = 64 * 2
return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3],
pretrained, progress, **kwargs)
def wide_resnet101_2(pretrained=False, progress=True, **kwargs):
r"""Wide ResNet-101-2 model from
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
The model is the same as ResNet except for the bottleneck number of channels
which is twice larger in every block. The number of channels in outer 1x1
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['width_per_group'] = 64 * 2
return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3],
pretrained, progress, **kwargs)

View File

@ -0,0 +1,4 @@
try:
from torch.hub import load_state_dict_from_url
except ImportError:
from torch.utils.model_zoo import load_url as load_state_dict_from_url

View File

@ -0,0 +1,144 @@
# -*- coding: utf-8 -*-
"""
Created on Thu Jan 18 17:21:01 2024
@author: ym
"""
import numpy as np
import torch
import cv2
import torch.nn as nn
import torchvision.transforms as T
from .model import mobilevit_s, resnet18, resnet34, resnet50, mobilenet_v2, MobileNetV3_Small
from .config import config as conf
class ReIDInterface:
def __init__(self, config):
self.device = conf.device
if conf.backbone == 'resnet18':
# model = ResIRSE(img_size, embedding_size, conf.drop_ratio).to(device)
model = resnet18().to(self.device)
elif conf.backbone == 'resnet34':
model = resnet34().to(self.device)
elif conf.backbone == 'resnet50':
model = resnet50().to(self.device)
elif conf.backbone == 'mobilevit_s':
model = mobilevit_s().to(self.device)
elif conf.backbone == 'mobilenetv3':
model = MobileNetV3_Small().to(self.device)
else:
model = mobilenet_v2().to(self.device)
self.batch_size = conf.batch_size
self.embedding_size = conf.embedding_size
self.img_size = conf.img_size
self.model_path = conf.model_path
# 原输入为PIL
self.transform = T.Compose([
T.ToTensor(),
T.Resize((self.img_size, self.img_size)),
T.ConvertImageDtype(torch.float32),
T.Normalize(mean=[0.5], std=[0.5]),
])
# self.model = nn.DataParallel(model).to(self.device)
self.model = model
self.model.load_state_dict(torch.load(self.model_path, map_location=self.device))
self.model.eval()
def inference(self, images, detections):
if isinstance(images, np.ndarray):
features = self.inference_image(images, detections)
return features
batch_patches = []
patches = []
for i, img in enumerate(images):
img = img.copy()
patch = self.transform(img)
if str(self.device) != "cpu":
patch = patch.to(device=self.device).half()
else:
patch = patch.to(device=self.device)
patches.append(patch)
if (i + 1) % self.batch_size == 0:
patches = torch.stack(patches, dim=0)
batch_patches.append(patches)
patches = []
if len(patches):
patches = torch.stack(patches, dim=0)
batch_patches.append(patches)
features = np.zeros((0, self.embedding_size))
for patches in batch_patches:
pred=self.model(patches)
pred[torch.isinf(pred)] = 1.0
feat = pred.cpu().data.numpy()
features = np.vstack((features, feat))
return features
def inference_image(self, image, detections):
H, W, _ = np.shape(image)
batch_patches = []
patches = []
for d in range(np.size(detections, 0)):
tlbr = detections[d, :4].astype(np.int_)
tlbr[0] = max(0, tlbr[0])
tlbr[1] = max(0, tlbr[1])
tlbr[2] = min(W - 1, tlbr[2])
tlbr[3] = min(H - 1, tlbr[3])
img = image[tlbr[1]:tlbr[3], tlbr[0]:tlbr[2], :]
img = img[:, :, ::-1].copy() # the model expects RGB inputs
patch = self.transform(img)
# patch = patch.to(device=self.device).half()
if str(self.device) != "cpu":
patch = patch.to(device=self.device).half()
else:
patch = patch.to(device=self.device)
patches.append(patch)
if (d + 1) % self.batch_size == 0:
patches = torch.stack(patches, dim=0)
batch_patches.append(patches)
patches = []
if len(patches):
patches = torch.stack(patches, dim=0)
batch_patches.append(patches)
features = np.zeros((0, self.embedding_size))
for patches in batch_patches:
pred = self.model(patches)
pred[torch.isinf(pred)] = 1.0
feat = pred.cpu().data.numpy()
features = np.vstack((features, feat))
return features

View File

@ -0,0 +1,462 @@
import torch
import torch.nn as nn
from tools.config import config as conf
try:
from torch.hub import load_state_dict_from_url
except ImportError:
from torch.utils.model_zoo import load_url as load_state_dict_from_url
# from .utils import load_state_dict_from_url
__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
'resnet152', 'resnext50_32x4d', 'resnext101_32x8d',
'wide_resnet50_2', 'wide_resnet101_2']
model_urls = {
'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth',
'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth',
'wide_resnet50_2': 'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth',
'wide_resnet101_2': 'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth',
}
def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
"""3x3 convolution with padding"""
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=dilation, groups=groups, bias=False, dilation=dilation)
def conv1x1(in_planes, out_planes, stride=1):
"""1x1 convolution"""
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=7):
super(SpatialAttention, self).__init__()
assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
padding = 3 if kernel_size == 7 else 1
self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = torch.mean(x, dim=1, keepdim=True)
max_out, _ = torch.max(x, dim=1, keepdim=True)
x = torch.cat([avg_out, max_out], dim=1)
x = self.conv1(x)
return self.sigmoid(x)
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
base_width=64, dilation=1, norm_layer=None, cam=False, bam=False):
super(BasicBlock, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
if groups != 1 or base_width != 64:
raise ValueError('BasicBlock only supports groups=1 and base_width=64')
if dilation > 1:
raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
self.cam = cam
self.bam = bam
# Both self.conv1 and self.downsample layers downsample the input when stride != 1
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = norm_layer(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = norm_layer(planes)
self.downsample = downsample
self.stride = stride
if self.cam:
if planes == 64:
self.globalAvgPool = nn.AvgPool2d(56, stride=1)
elif planes == 128:
self.globalAvgPool = nn.AvgPool2d(28, stride=1)
elif planes == 256:
self.globalAvgPool = nn.AvgPool2d(14, stride=1)
elif planes == 512:
self.globalAvgPool = nn.AvgPool2d(7, stride=1)
self.fc1 = nn.Linear(in_features=planes, out_features=round(planes / 16))
self.fc2 = nn.Linear(in_features=round(planes / 16), out_features=planes)
self.sigmod = nn.Sigmoid()
if self.bam:
self.bam = SpatialAttention()
def forward(self, x):
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
identity = self.downsample(x)
if self.cam:
ori_out = self.globalAvgPool(out)
out = out.view(out.size(0), -1)
out = self.fc1(out)
out = self.relu(out)
out = self.fc2(out)
out = self.sigmod(out)
out = out.view(out.size(0), out.size(-1), 1, 1)
out = out * ori_out
if self.bam:
out = out*self.bam(out)
out += identity
out = self.relu(out)
return out
class Bottleneck(nn.Module):
# Bottleneck in torchvision places the stride for downsampling at 3x3 convolution(self.conv2)
# while original implementation places the stride at the first 1x1 convolution(self.conv1)
# according to "Deep residual learning for image recognition"https://arxiv.org/abs/1512.03385.
# This variant is also known as ResNet V1.5 and improves accuracy according to
# https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch.
expansion = 4
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
base_width=64, dilation=1, norm_layer=None, cam=False, bam=False):
super(Bottleneck, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
width = int(planes * (base_width / 64.)) * groups
self.cam = cam
self.bam = bam
# Both self.conv2 and self.downsample layers downsample the input when stride != 1
self.conv1 = conv1x1(inplanes, width)
self.bn1 = norm_layer(width)
self.conv2 = conv3x3(width, width, stride, groups, dilation)
self.bn2 = norm_layer(width)
self.conv3 = conv1x1(width, planes * self.expansion)
self.bn3 = norm_layer(planes * self.expansion)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
self.stride = stride
if self.cam:
if planes == 64:
self.globalAvgPool = nn.AvgPool2d(56, stride=1)
elif planes == 128:
self.globalAvgPool = nn.AvgPool2d(28, stride=1)
elif planes == 256:
self.globalAvgPool = nn.AvgPool2d(14, stride=1)
elif planes == 512:
self.globalAvgPool = nn.AvgPool2d(7, stride=1)
self.fc1 = nn.Linear(planes * self.expansion, round(planes / 4))
self.fc2 = nn.Linear(round(planes / 4), planes * self.expansion)
self.sigmod = nn.Sigmoid()
def forward(self, x):
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
identity = self.downsample(x)
if self.cam:
ori_out = self.globalAvgPool(out)
out = out.view(out.size(0), -1)
out = self.fc1(out)
out = self.relu(out)
out = self.fc2(out)
out = self.sigmod(out)
out = out.view(out.size(0), out.size(-1), 1, 1)
out = out * ori_out
out += identity
out = self.relu(out)
return out
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=conf.embedding_size, zero_init_residual=False,
groups=1, width_per_group=64, replace_stride_with_dilation=None,
norm_layer=None, scale=0.75):
super(ResNet, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
self._norm_layer = norm_layer
self.inplanes = 64
self.dilation = 1
if replace_stride_with_dilation is None:
# each element in the tuple indicates if we should replace
# the 2x2 stride with a dilated convolution instead
replace_stride_with_dilation = [False, False, False]
if len(replace_stride_with_dilation) != 3:
raise ValueError("replace_stride_with_dilation should be None "
"or a 3-element tuple, got {}".format(replace_stride_with_dilation))
self.groups = groups
self.base_width = width_per_group
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
bias=False)
self.bn1 = norm_layer(self.inplanes)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, int(64*scale), layers[0])
self.layer2 = self._make_layer(block, int(128*scale), layers[1], stride=2,
dilate=replace_stride_with_dilation[0])
self.layer3 = self._make_layer(block, int(256*scale), layers[2], stride=2,
dilate=replace_stride_with_dilation[1])
self.layer4 = self._make_layer(block, int(512*scale), layers[3], stride=2,
dilate=replace_stride_with_dilation[2])
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(int(512 * block.expansion*scale), num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
# Zero-initialize the last BN in each residual branch,
# so that the residual branch starts with zeros, and each residual block behaves like an identity.
# This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
if zero_init_residual:
for m in self.modules():
if isinstance(m, Bottleneck):
nn.init.constant_(m.bn3.weight, 0)
elif isinstance(m, BasicBlock):
nn.init.constant_(m.bn2.weight, 0)
def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
norm_layer = self._norm_layer
downsample = None
previous_dilation = self.dilation
if dilate:
self.dilation *= stride
stride = 1
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
conv1x1(self.inplanes, planes * block.expansion, stride),
norm_layer(planes * block.expansion),
)
layers = []
layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
self.base_width, previous_dilation, norm_layer))
self.inplanes = planes * block.expansion
for _ in range(1, blocks):
layers.append(block(self.inplanes, planes, groups=self.groups,
base_width=self.base_width, dilation=self.dilation,
norm_layer=norm_layer))
return nn.Sequential(*layers)
def _forward_impl(self, x):
# See note [TorchScript super()]
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
# print('poolBefore', x.shape)
x = self.avgpool(x)
# print('poolAfter', x.shape)
x = torch.flatten(x, 1)
# print('fcBefore',x.shape)
x = self.fc(x)
# print('fcAfter',x.shape)
return x
def forward(self, x):
return self._forward_impl(x)
# def _resnet(arch, block, layers, pretrained, progress, **kwargs):
# model = ResNet(block, layers, **kwargs)
# if pretrained:
# state_dict = load_state_dict_from_url(model_urls[arch],
# progress=progress)
# model.load_state_dict(state_dict, strict=False)
# return model
def _resnet(arch, block, layers, pretrained, progress, **kwargs):
model = ResNet(block, layers, **kwargs)
if pretrained:
state_dict = load_state_dict_from_url(model_urls[arch],
progress=progress)
src_state_dict = state_dict
target_state_dict = model.state_dict()
skip_keys = []
# skip mismatch size tensors in case of pretraining
for k in src_state_dict.keys():
if k not in target_state_dict:
continue
if src_state_dict[k].size() != target_state_dict[k].size():
skip_keys.append(k)
for k in skip_keys:
del src_state_dict[k]
missing_keys, unexpected_keys = model.load_state_dict(src_state_dict, strict=False)
return model
def resnet14(pretrained=True, progress=True, **kwargs):
r"""ResNet-14 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet18', BasicBlock, [2, 1, 1, 2], pretrained, progress,
**kwargs)
def resnet18(pretrained=True, progress=True, **kwargs):
r"""ResNet-18 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress,
**kwargs)
def resnet34(pretrained=False, progress=True, **kwargs):
r"""ResNet-34 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress,
**kwargs)
def resnet50(pretrained=False, progress=True, **kwargs):
r"""ResNet-50 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress,
**kwargs)
def resnet101(pretrained=False, progress=True, **kwargs):
r"""ResNet-101 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, progress,
**kwargs)
def resnet152(pretrained=False, progress=True, **kwargs):
r"""ResNet-152 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, progress,
**kwargs)
def resnext50_32x4d(pretrained=False, progress=True, **kwargs):
r"""ResNeXt-50 32x4d model from
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['groups'] = 32
kwargs['width_per_group'] = 4
return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3],
pretrained, progress, **kwargs)
def resnext101_32x8d(pretrained=False, progress=True, **kwargs):
r"""ResNeXt-101 32x8d model from
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['groups'] = 32
kwargs['width_per_group'] = 8
return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3],
pretrained, progress, **kwargs)
def wide_resnet50_2(pretrained=False, progress=True, **kwargs):
r"""Wide ResNet-50-2 model from
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
The model is the same as ResNet except for the bottleneck number of channels
which is twice larger in every block. The number of channels in outer 1x1
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['width_per_group'] = 64 * 2
return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3],
pretrained, progress, **kwargs)
def wide_resnet101_2(pretrained=False, progress=True, **kwargs):
r"""Wide ResNet-101-2 model from
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
The model is the same as ResNet except for the bottleneck number of channels
which is twice larger in every block. The number of channels in outer 1x1
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['width_per_group'] = 64 * 2
return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3],
pretrained, progress, **kwargs)

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jan 19 16:10:39 2024
@author: ym
"""
import torch
from model.resnet_pre import resnet18
def main():
model_path = "best.pth"
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = resnet18().to(device)
model.load_state_dict(torch.load(model_path, map_location=device))
if __name__ == "__main__":
main()

View File

@ -0,0 +1,66 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license
from functools import partial
import torch
from ultralytics.utils import IterableSimpleNamespace, yaml_load
from ultralytics.utils.checks import check_yaml
from .bot_sort import BOTSORT
from .byte_tracker import BYTETracker
TRACKER_MAP = {'bytetrack': BYTETracker, 'botsort': BOTSORT}
def on_predict_start(predictor, persist=False):
"""
Initialize trackers for object tracking during prediction.
Args:
predictor (object): The predictor object to initialize trackers for.
persist (bool, optional): Whether to persist the trackers if they already exist. Defaults to False.
Raises:
AssertionError: If the tracker_type is not 'bytetrack' or 'botsort'.
"""
if hasattr(predictor, 'trackers') and persist:
return
tracker = check_yaml(predictor.args.tracker)
cfg = IterableSimpleNamespace(**yaml_load(tracker))
assert cfg.tracker_type in ['bytetrack', 'botsort'], \
f"Only support 'bytetrack' and 'botsort' for now, but got '{cfg.tracker_type}'"
trackers = []
for _ in range(predictor.dataset.bs):
tracker = TRACKER_MAP[cfg.tracker_type](args=cfg, frame_rate=30)
trackers.append(tracker)
predictor.trackers = trackers
def on_predict_postprocess_end(predictor):
"""Postprocess detected boxes and update with object tracking."""
bs = predictor.dataset.bs
im0s = predictor.batch[1]
for i in range(bs):
det = predictor.results[i].boxes.cpu().numpy()
if len(det) == 0:
continue
tracks = predictor.trackers[i].update(det, im0s[i])
if len(tracks) == 0:
continue
idx = tracks[:, -1].astype(int)
predictor.results[i] = predictor.results[i][idx]
predictor.results[i].update(boxes=torch.as_tensor(tracks[:, :-1]))
def register_tracker(model, persist):
"""
Register tracking callbacks to the model for object tracking during prediction.
Args:
model (object): The model object to register tracking callbacks for.
persist (bool): Whether to persist the trackers if they already exist.
"""
model.add_callback('on_predict_start', partial(on_predict_start, persist=persist))
model.add_callback('on_predict_postprocess_end', on_predict_postprocess_end)

View File

@ -0,0 +1,3 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More