多目标跟踪算法,零基础极速入门(一)

目前为止,我们已经推出了《从零开始学习 深度学习》和《从零开始学习模型部署》系列教程,方便大家入门算法开发。
欢迎大家进抠抠裙:
deeplearningYYDS裙3:1015081610
威信裙需先jia个人威信:deeplearningYYDS 经审核后进入。
话不多说,进入实操:
一、首先说多目标跟踪
多目标跟踪处理的对象是视频,从视频的第一帧到最后一帧,里边有多个目标在不断运动。多目标跟踪的目的就是将每个目标和其他目标进行区分开来,具体方法是给每个目标分配一个 ID,并记录他们的轨迹。
刚开始接触,可能觉得直接将目标检测的算法应用在视频的每一帧就可以完成这个任务了。实际上,目标检测的效果是很不稳定的,其实最大的区别在于,仅仅使用目标检测无法给对象分配 ID,并且跟踪能够优化整个跟踪过程,能让目标框更加稳定。
多目标跟踪中一个比较经典的和深度学习结合比较好的方法就是 DetectionBased Tracking,对前后两帧进行目标检测检测,然后根据得到的前后两帧的所有目标进行匹配,从而维持 ID。初学者接触比较多的就是 SORT 和 Deep SORT。
二、MOT16 数据集
MOT16 数据集是在 2016 年提出来的用于衡量多目标跟踪检测和跟踪方法标准的数据集,专门用于行人跟踪。官网地址是:https://motchallenge.net/
从官网下载的数据是按照以下的文件结构进行组织的:
多目标跟踪算法,零基础极速入门(一)_第1张图片
多目标跟踪算法,零基础极速入门(一)_第2张图片
在 MOT16 数据集中,是包含了检测得到的框的,这样是可以免去目标检测这个部分,提供统一的目标检测框以后,然后可以比较目标跟踪更关注的部分,而不用在花费精力在目标检测上。

  1. seqinfo.ini
    在每个子文件夹中都有这个,主要用于说明这个文件的一些信息,比如长度,帧率,图片的长和宽,图片的后缀名。
    多目标跟踪算法,零基础极速入门(一)_第3张图片
  2. det.txt
    这个文件中存储了图片的检测框的信息 (检测得到的信息文件),部分内容展示如下:
    多目标跟踪算法,零基础极速入门(一)_第4张图片
    从左到右分别代表:
    • frame: 第几帧图片
    • id: 这个检测框分配的 id,在这里都是-1 代表没有 id 信息
    • bbox(四位): 分别是左上角坐标和长宽
    • conf:这个 bbox 包含物体的置信度,可以看到并不是传统意义的 0-1,分数越高代表置信度越高• MOT3D(x,y,z): 是在 MOT3D 中使用到的内容,这里关心的是 MOT2D,所以都设置为-1可以看出以上内容主要提供的和目标检测的信息没有区别,所以也在一定程度上可以用于检测器的训练。
  3. gt.txt
    这个文件只有 train 的子文件夹中有,test 中没有,其中内容的格式和 det.txt 有一些类似,部分内容
    如下:
    多目标跟踪算法,零基础极速入门(一)_第5张图片
    从左到右分别是:
    • frame: 第几帧图片
    • ID: 也就是轨迹的 ID,可以看出 gt 里边是按照轨迹的 ID 号进行排序的
    • bbox: 分别是左上角坐标和长宽
    • 是否忽略:0 代表忽略
    • classes: 目标的类别个数(这里是驾驶场景包括 12 个类别),7 代表的是静止的人。
    第 8 个类代表错检,9-11 代表被遮挡的类别
    多目标跟踪算法,零基础极速入门(一)_第6张图片
    • 最后一个代表目标运动时被其他目标包含、覆盖、边缘裁剪的情况。
    总结:
    • train 中含有的标注信息主要来自 det.txt 和 gt.txt。test 中只含有 det.txt。
    • det.txt 含有的有用信息有:frame, bbox, conf
    • gt.txt 含有的有用信息有:frame,bbox, conf, id, class
    • output.txt(使用 deepsort 得到的文件) 中含有的有用信息有:frame,bbox, id
  4. MOT 中的评价指标
    多目标跟踪算法,零基础极速入门(一)_第7张图片
    评价出发点:
    • 所有出现的目标都要及时能够找到;
    • 目标位置要尽可能与真实目标位置一致;
    • 每个目标都应该被分配一个独一无二的 ID,并且该目标分配的这个 ID 在整个序列中保持不变。
    评价指标数学模型:评价过程的步骤:
    1)建立目标与假设最优间的最优一一对应关系,称为 correspondence
    2)对所有的 correspondence,计算位置偏移误差
    3)累积结构误差 a. 计算漏检数 b. 计算虚警数(不存在目标却判断为目标)c. 跟踪目标发生跳变的次数
    多目标跟踪算法,零基础极速入门(一)_第8张图片
    MOTA(Multiple Object Tracking Accuracy)
    多目标跟踪算法,零基础极速入门(一)_第9张图片
    FN 为 False Negative, FP 为 False Positve, IDSW 为 ID Switch, GT 是 Ground Truth 物体的数量。
    MOTA 主要考虑的是 tracking 中所有对象匹配错误,主要是 FP,FN,IDs. MOTA 给出的是非常直观的衡量跟踪其在检测物体和保持轨迹时的性能,与目标检测精度无关。
    MOTA 取值小于 100,但是当跟踪器产生的错误超过了场景中的物体,MOTA 可以变为负数。
    ps: MOTA&MOTP 是计算所有帧相关指标后再进行平均的,不是计算每帧的 rate 然后进行 rate 平均。
    MOTP(Multiple Object Tracking Precision)
    请添加图片描述
    d 为检测目标 和给它分配的 ground truth 之间在所有帧中的平均度量距离,在这里是使用 bonding
    box 的 overlap rate 来进行度量(在这里 MOTP 是越大越好,但对于使用欧氏距离进行度量的就是
    MOTP 越小越好,这主要取决于度量距离 d 的定义方式);而 c 为在当前帧匹配成功的数目。MOTP 主
    要量化检测器的定位精度,几乎不包含与跟踪器实际性能相关的信息。
    MT(Mostly Tracked)
    满足 Ground Truth 至少在 80% 的时间内都匹配成功的 track,在所有追踪目标中所占的比例。注意
    这里的 MT 和 ML 与当前 track 的 ID 是否发生变化无关,只要 Ground Truth 与目标匹配上即可。
    ML (Mostly Lost)
    满足 Ground Truth 在小于 20% 的时间内匹配成功的 track,在所有追踪目标中所占的比例。
    ID Switch
    Ground Truth 所分配的 ID 发生变化的次数,如图 1 中 (a) 所示。
    多目标跟踪算法,零基础极速入门(一)_第10张图片
    FM (Fragmentation)
    FM 计算的是跟踪有多少次被打断(既 Ground Truth 的 track 没有被匹配上),换句话说每当轨迹将其状态从跟踪状态改变为未跟踪状态,并且在稍后的时间点跟踪相同的轨迹时,就会对 FM 进行计数。
    此处需要注意的是,FM 计数时要求 ground truth 的状态需要满足:tracked->untracked->tracked,如图 1 中 (b) 所示,而 © 中的不算 FM。需要注意的是,FM 与 ID 是否发生变化无关。
    FP (False Positive)
    当前帧预测的 track 和 detection 没有匹配上,将错误预测的 track 点称为 FP,如图 1 所示。是否匹配成功与匹配时所设置的阈值有关。
    FN (False Negative)
    当前帧预测的 track 和 detection 没有匹配上,将未被匹配的 ground truth 点称为 FN(也可以称为Miss)
    ID scores
    MOTA 的主要问题是仅仅考虑跟踪器出错的次数,但是有一些场景(比如航空场景)更加关注一个跟踪器是否尽可能长的跟踪一个目标。这个问题通过构建二分图来解决,主要计算对象是 IDTP、IDFP、IDFN。
    多目标跟踪算法,零基础极速入门(一)_第11张图片
    三、MOT 数据标注工具 DarkLabel
    DarkLabel 是一个轻量的视频标注软件,相比于 ViTBAT 等软件而言,不需要安装就可以使用, 本文将介绍 darklabel 软件的使用指南。
    笔者最终从公开的软件中选择了 DarkLabel。DarkLabel 体积非常小,开箱即用,不需要配置环境
    (Vatic 需要在 linux 下配置相关环境),对 window 用户很友好。不过该软件使用说明实际上不多,本文总结了大部分的用法,实际运用还需要读者研究。
    DarkLabel 导出的格式可以通过脚本转化,变成标准的目标检测数据集格式、ReID 数据集格式、MOT数据集格式。
    之后会在这个视频标注的基础上进行一些脚本的编写,可以批量构建 ReID 数据集、目标检测数据集和 MOT 数据集。
    1、 官方说明
    多目标跟踪算法,零基础极速入门(一)_第12张图片
    软件示意:
    多目标跟踪算法,零基础极速入门(一)_第13张图片
    工具栏在左侧

2、主要功能和特点
• 支持各种格式的视频(avi,mpg 等)和图像列表(jpg,bmp,png 等)
• 多框设置和标签设置支持
• 支持对象识别和图像跟踪中使用的各种数据格式
• 使用图像跟踪器自动标记(通过跟踪标记)
• 支持使用插值功能的间隔标签
• 自动标记功能,可按类别自动为每个对象分配唯一的 ID
3、主要用法
鼠标/键盘界面(Shift / Ctrl = Shift 或 Ctrl)
• 鼠标拖动:创建一个框
• Shift / Ctrl + 拖动:编辑框
• 双击:选择/取消相同 ID 对象的轨迹
• 右键单击:删除所有选定的对象轨迹(删除部分)
• 右键单击:删除最近创建的框(如果未选择任何轨迹)
• Shift / Ctrl + 右键单击(特定框):仅删除所选框
• Shift / Ctrl + 右键单击(空):删除当前屏幕上的所有框
• Shift / Ctrl + 双击(特定框):修改所选框的标签
• Shift / Ctrl + 双击(轨迹):在所选轨迹上批量更改标签
• 箭头键/ PgUp / PgDn / Home / End:移动视频帧(图像)
• Enter 键:使用图像跟踪功能自动生成框(通过跟踪进行标记)
指定标签和 ID
• 无标签:创建未标签的框
• 框标签:用户指定的标签(例如,人类)
• box 标签 + 自动编号:自动编号自定义标签(例如 human0,human1 等)
• 如果指定了 id,则可以选择/编辑轨迹单位对象
• popuplabeleditor:注册标签列表窗口的弹出窗口(已在 labels.txt 文件中注册)
• 如果在弹出窗口中按快捷键(1 9),则会自动输入标签。
• Label + id 显示在屏幕上,但在内部,标签和 ID 分开。
• 当另存为 gt 数据时,选择仅标签格式以保存可见标签(标签 + id) • 另存为 gt 数据时,如果选择了标签和 ID 分类格式,则标签和 ID 将分开保存。
追踪功能
这是这个软件比较好的功能之一,可以用传统方法(KCF 类似的算法)跟踪目标,只需要对不准确的目标进行人工调整即可,大大减少了工作量。
• 通过使用图像跟踪功能设置下一帧的框(分配相同的 ID /标签)
• 多达 100 个同时跟踪
• tracker1(稳健)算法:长时间跟踪目标
• tracker2(准确)算法:准确跟踪目标(例如汽车)
• 输入键/下一步和预测按钮
• 注意!使用跟踪时,下一帧上的原始框消失
插值功能
• 跟踪功能方便,但问题不准确
• 在视频部分按对象标记时使用
– 开始插补按钮:开始插补功能
– 在目标对象的轨迹的一半处绘制一个方框(航路点的种类)
– 航路点框为紫色,插值框为黑色。
– 更正插值错误的部分(Shift / Ctrl + 拖动),添加任意数量的航路点(不考虑顺序)/删除
– 结束插补按钮:将工作结束和工作轨迹注册为数据
导入视频/视频并在帧之间移动
• 打开视频文件:打开视频文件(avi,mpg,mp4,wmv,mov,…)
• 打开图像目录:打开文件夹中的所有图像(jpg,bmp,png 等)
• 在视频帧之间移动:键盘 →,←,PgUp,PgDn,Home,End,滑块控制
保存并调出作业数据
• 加载 GT:以所选格式加载地面真相文件。
• 保存 GT:以所选数据格式保存到目前为止已获得的结果。
• 导入数据时,需要选择与实际数据文件匹配的格式,但是在保存数据时,可以将其保存为所需
的任何格式。
• 在图像列表中工作时,使用帧号(frame #)格式,按文件名排序时的图像顺序将变为帧号(对
于诸如 00000.jpg,00002.jpg 等的列表很有用)
• 保存设置:保存当前选择的数据格式和选项(运行程序时自动还原)
数据格式(语法)
• |:换行
• []:重复短语
• frame #:帧号(视频的帧号,图像列表中的图像顺序)
• iname:图像文件名(仅在使用图像列表时有效)
• 标签:标签
• id:对象的唯一 ID
• n:在图像上设置的边界矩形的数量
• x,y:边界矩形的左侧和顶部位置
• w,h:边界矩形的宽度和高度
• cx,cy:边界矩形的中心坐标
• x1,y1,x2,y2:边界矩形的左上,右下位置
ffmpeg 切割视频
ffmpeg -i C:/plutopr.mp4 -acodec copy
-vf scale=1280:720
-ss 00:00:10 -t 15 C:/cutout1.mp4 -y
5. -ss time_off set the start time offset 设置从视频的哪个时间点开始截取,上文从视频的第 10s
开始截取
6. -to 截到视频的哪个时间点结束。上文到视频的第 15s 结束。截出的视频共 5s. 如果用-t 表示截
取多长的时间如上文-to 换位-t 则是截取从视频的第 10s 开始,截取 15s 时长的视频。即截出来
的视频共 15s.
7. -vcodec copy 表示使用跟原视频一样的视频编解码器。
8. -acodec copy 表示使用跟原视频一样的音频编解码器。
9. -i 表示源视频文件
10. -y 表示如果输出文件已存在则覆盖。
总结
这个软件是笔者自己进行项目的时候用到的一款标注软件,大部分视频标注软件要不就是太大(ViTBAT 软件),要不就是需要 Linux 环境,所以在 Window 上标注的话很不方便,经过了很长时间探
索,最终找到这款软件。此外,这款软件源码没有公开,开发者声明可以用于非商业目的。
DarkLabel 软件的获取可以在 GiantPandaCV 公众号后台回复“darklabel”,即可得到该软件的下载链
接。
四、DarkLabel 配套代码
先附上脚本地址:https://github.com/pprp/SimpleCVReproduction/tree/master/DarkLabel
先来了解一下为何 DarkLabel 能生成这么多格式的数据集,来看看 DarkLabel 的格式:
frame(从 0 开始计), 数量, id(从 0 开始), box(x1,y1,x2,y2), class=null
0,4,0,450,194,558,276,null,1,408,147,469,206,null,2,374,199,435,307,null,3,153,213,218,314,null
1,4,0,450,194,558,276,null,1,408,147,469,206,null,2,374,199,435,307,null,3,153,213,218,314,null
2,4,0,450,194,558,276,null,1,408,147,469,206,null,2,374,199,435,307,null,3,153,213,218,3
每一帧,每张图片上的目标都可以提取到,并且每个目标有 bbox、分配了一个 ID、class
这些信息都可以满足目标检测、ReID、跟踪数据集。
ps:说明一下,以下脚本都是笔者自己写的,专用于单类的检测、跟踪、重识别的代码,如果有需要多类
的,还需要自己修改多类部分的代码。另外以下只针对 Darklabel 中 frame#,n,[,id,x1,y1,x2,y2,label]
格式。
DarkLabel 转 Detection
这里笔者写了一个脚本转成 VOC2007 中的 xml 格式的标注,代码如下:

import cv2
import os
import shutil
import tqdm
import sys
root_path = r"I:\Dataset\VideoAnnotation"
def print_flush(str):
print(str, end='\r')
sys.stdout.flush()
def genXML(xml_dir, outname, bboxes, width, height):
xml_file = open((xml_dir + '/' + outname + '.xml'), 'w')
xml_file.write('\n')
xml_file.write(' VOC2007\n')
xml_file.write(' ' + outname + '.jpg' + '\n')
xml_file.write(' \n')
xml_file.write(' ' + str(width) + '\n')
xml_file.write(' ' + str(height) + '\n')
xml_file.write(' 3\n')
xml_file.write(' \n')
for bbox in bboxes:
x1, y1, x2, y2 = bbox
xml_file.write(' \n')
xml_file.write(' ' + 'cow' + '\n')
xml_file.write(' Unspecified\n')
xml_file.write(' 0\n')
xml_file.write(' 0\n')
xml_file.write(' \n')
xml_file.write(' ' + str(x1) + '\n')
xml_file.write(' ' + str(y1) + '\n')
xml_file.write(' ' + str(x2) + '\n')
xml_file.write(' ' + str(y2) + '\n')
xml_file.write(' \n')
xml_file.write(' \n')
xml_file.write('')
def gen_empty_xml(xml_dir, outname, width, height):
xml_file = open((xml_dir + '/' + outname + '.xml'), 'w')
xml_file.write('\n')
xml_file.write(' VOC2007\n')
xml_file.write(' ' + outname + '.png' + '\n')
xml_file.write(' \n')
xml_file.write(' ' + str(width) + '\n')
xml_file.write(' ' + str(height) + '\n')
xml_file.write(' 3\n')
xml_file.write(' \n')
xml_file.write('')
def getJPG(src_video_file, tmp_video_frame_save_dir):
# gen jpg from video
cap = cv2.VideoCapture(src_video_file)
if not os.path.exists(tmp_video_frame_save_dir):
os.makedirs(tmp_video_frame_save_dir)
frame_cnt = 0
isrun, frame = cap.read()
width, height = frame.shape[1], frame.shape[0]
while (isrun):
save_name = append_name + "_" + str(frame_cnt) + ".jpg"
cv2.imwrite(os.path.join(tmp_video_frame_save_dir, save_name), frame)
frame_cnt += 1
print_flush("Extracting frame :%d" % frame_cnt)
isrun, frame = cap.read()
return width, height
def delTmpFrame(tmp_video_frame_save_dir):
if os.path.exists(tmp_video_frame_save_dir):
shutil.rmtree(tmp_video_frame_save_dir)
print('delete %s success!' % tmp_video_frame_save_dir)
def assign_jpgAndAnnot(src_annot_file, dst_annot_dir, dst_jpg_dir,
↪ tmp_video_frame_save_dir, width, height):
# get coords from annotations files
txt_file = open(src_annot_file, "r")
content = txt_file.readlines()
for line in content:
item = line[:-1]
items = item.split(',')
frame_id, num_of_cow = items[0], items[1]
print_flush("Assign jpg and annotion : %s" % frame_id)
bboxes = []
for i in range(int(num_of_cow)):
obj_id = items[1 + i * 6 + 1]
obj_x1, obj_y1 = int(items[1 + i * 6 + 2]), int(items[1 + i * 6 + ↪ 3])
obj_x2, obj_y2 = int(items[1 + i * 6 + 4]), int(items[1 + i * 6 + ↪ 5])
# preprocess the coords
obj_x1 = max(1, obj_x1)
obj_y1 = max(1, obj_y1)
obj_x2 = min(width, obj_x2)
obj_y2 = min(height, obj_y2)
bboxes.append([obj_x1, obj_y1, obj_x2, obj_y2])
genXML(dst_annot_dir, append_name + "_" + str(frame_id), bboxes,
↪ width,
height)
shutil.copy(
os.path.join(tmp_video_frame_save_dir,
append_name + "_" + str(frame_id) + ".jpg"),
os.path.join(dst_jpg_dir, append_name + "_" + str(frame_id) + ↪ ".jpg"))
txt_file.close()
if __name__ == "__main__":
append_names = ["cutout%d" % i for i in range(19, 66)]
for append_name in append_names:
print("processing",append_name)
src_video_file = os.path.join(root_path, append_name + ".mp4")
if not os.path.exists(src_video_file):
continue
src_annot_file = os.path.join(root_path, append_name + "_gt.txt")
dst_annot_dir = os.path.join(root_path, "Annotations")
dst_jpg_dir = os.path.join(root_path, "JPEGImages")
tmp_video_frame_save_dir = os.path.join(root_path, append_name)
width, height = getJPG(src_video_file, tmp_video_frame_save_dir)
assign_jpgAndAnnot(src_annot_file, dst_annot_dir, dst_jpg_dir,
↪ tmp_video_frame_save_dir, width, height)
delTmpFrame(tmp_video_frame_save_dir)
`

如果想转成 U 版 yolo 需要的格式可以点击 https://github.com/pprp/voc2007_for_yolo_torch 使用这
里的脚本。
DarkLabel 转 ReID 数据集
ReID 数据集其实与分类数据集很相似,最出名的是 Market1501 数据集,对这个数据集不熟悉的可以先百度一下。简单来说 ReID 数据集只比分类中多了 query, gallery 的概念,也很简单。转换代码如下:

import os
import shutil
import cv2
import numpy as np
import glob
import sys
import random
"""[summary]
根据视频和 darklabel 得到的标注文件
"""
def preprocessVideo(video_path):
'''
预处理,将视频变为一帧一帧的图片
'''
if not os.path.exists(video_frame_save_path):
os.mkdir(video_frame_save_path)
vidcap = cv2.VideoCapture(video_path)
(cap, frame) = vidcap.read()
height = frame.shape[0]
width = frame.shape[1]
cnt_frame = 0
while (cap):
cv2.imwrite(
os.path.join(video_frame_save_path, "frame_%d.jpg" % ↪ (cnt_frame)),
frame)
cnt_frame += 1
print(cnt_frame, end="\r")
sys.stdout.flush()
(cap, frame) = vidcap.read()
vidcap.release()
return width, height
def postprocess(video_frame_save_path):
'''
后处理,删除无用的文件夹
'''
if os.path.exists(video_frame_save_path):
shutil.rmtree(video_frame_save_path)
def extractVideoImgs(frame, video_frame_save_path, coords):
'''
抠图
'''
x1, y1, x2, y2 = coords
# get image from save path
img = cv2.imread(
os.path.join(video_frame_save_path, "frame_%d.jpg" % (frame)))
if img is None:
return None
# crop
save_img = img[y1:y2, x1:x2]
return save_img
def bbox_ious(box1, box2):
b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
# Intersection area
inter_area = (min(b1_x2, b2_x2) - max(b1_x1, b2_x1)) * \
(min(b1_y2, b2_y2) - max(b1_y1, b2_y1))
# Union Area
w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1
w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1
union_area = (w1 * h1 + 1e-16) + w2 * h2 - inter_area
return inter_area / union_area
def bbox_iou(box1, box2):
# format box1: x1,y1,x2,y2
# format box2: a1,b1,a2,b2
x1, y1, x2, y2 = box1
a1, b1, a2, b2 = box2
i_left_top_x = max(a1, x1)
i_left_top_y = max(b1, y1)
i_bottom_right_x = min(a2, x2)
i_bottom_right_y = min(b2, y2)
intersection = (i_bottom_right_x - i_left_top_x) * (i_bottom_right_y -
i_left_top_y)
area_two_box = (x2 - x1) * (y2 - y1) + (a2 - a1) * (b2 - b1)
return intersection * 1.0 / (area_two_box - intersection)
def restrictCoords(width, height, x, y):
x = max(1, x)
y = max(1, y)
x = min(x, width)
y = min(y, height)
return x, y
if __name__ == "__main__":
total_cow_num = 0
root_dir = "./data/videoAndLabel"
reid_dst_path = "./data/reid"
done_dir = "./data/done"
txt_list = glob.glob(os.path.join(root_dir, "*.txt"))
video_list = glob.glob(os.path.join(root_dir, "*.mp4"))
for i in range(len(txt_list)):
txt_path = txt_list[i]
video_path = video_list[i]
print("processing:", video_path)
if not os.path.exists(txt_path):
continue
video_name = os.path.basename(video_path).split('.')[0]
video_frame_save_path = os.path.join(os.path.dirname(video_path),
video_name)
f_txt = open(txt_path, "r")
width, height = preprocessVideo(video_path)
print("done")
# video_cow_id = video_name + str(total_cow_num)
for line in f_txt.readlines():
bboxes = line.split(',')
ids = []
frame_id = int(bboxes[0])
box_list = []
if frame_id % 30 != 0:
continue
num_object = int(bboxes[1])
for num_obj in range(num_object):
# obj = 0, 1, 2
obj_id = bboxes[1 + (num_obj) * 6 + 1]
obj_x1 = int(bboxes[1 + (num_obj) * 6 + 2])
obj_y1 = int(bboxes[1 + (num_obj) * 6 + 3])
obj_x2 = int(bboxes[1 + (num_obj) * 6 + 4])
obj_y2 = int(bboxes[1 + (num_obj) * 6 + 5])
box_list.append([obj_x1, obj_y1, obj_x2, obj_y2])
# process coord
obj_x1, obj_y1 = restrictCoords(width, height, obj_x1, obj_y1)
obj_x2, obj_y2 = restrictCoords(width, height, obj_x2, obj_y2)
specific_object_name = video_name + "_" + obj_id
# mkdir for reid dataset
id_dir = os.path.join(reid_dst_path, specific_object_name)
if not os.path.exists(id_dir):
os.makedirs(id_dir)
# save pic
img = extractVideoImgs(frame_id, video_frame_save_path,
(obj_x1, obj_y1, obj_x2, obj_y2))
print(type(img))
if img is None or img.shape[0] == 0 or img.shape[1] == 0:
print(specific_object_name + " is empty")
continue
# print(frame_id)
img = cv2.resize(img, (256, 256))
normalizedImg = np.zeros((256, 256))
img = cv2.normalize(img, normalizedImg, 0, 255,
cv2.NORM_MINMAX)
cv2.imwrite(
os.path.join(id_dir, "%s_%d.jpg") %
(specific_object_name, frame_id), img)
max_w = width - 256
max_h = height - 256
# 随机选取左上角坐标
select_x = random.randint(1, max_w)
select_y = random.randint(1, max_h)
rand_box = [select_x, select_y, select_x + 256, select_y + 256] # 背景图保存位置
bg_dir = os.path.join(reid_dst_path, "bg")
if not os.path.exists(bg_dir):
os.makedirs(bg_dir)
iou_list = []
for idx in range(len(box_list)):
cow_box = box_list[idx]
iou = bbox_iou(cow_box, rand_box)
iou_list.append(iou)
# print("iou list:" , iou_list)
if np.array(iou_list).all() < 0:
img = extractVideoImgs(frame_id, video_frame_save_path,
rand_box)
if img is None:
print(specific_object_name + "is empty")
continue
normalizedImg = np.zeros((256, 256))
img = cv2.normalize(img, normalizedImg, 0, 255,
cv2.NORM_MINMAX)
cv2.imwrite(
os.path.join(bg_dir, "bg_%s_%d.jpg") %
(video_name, frame_id), img)
f_txt.close()
postprocess(video_frame_save_path)
shutil.move(video_path, done_dir)
shutil.move(txt_path, done_dir)

DarkLabel 转 MOT16 格式
其实 DarkLabel 标注得到信息和 MOT16 是几乎一致的,只不过需要转化一下,脚本如下:

import os
'''
gt.txt:
 ---------
frame(从 1 开始计), id, box(left top w, h),ignore=1(不忽略), class=1(从 1 开始),
↪ 覆盖 =1),
1,1,1363,569,103,241,1,1,0.86014
2,1,1362,568,103,241,1,1,0.86173
3,1,1362,568,103,241,1,1,0.86173
4,1,1362,568,103,241,1,1,0.86173
cutout24_gt.txt
---
frame(从 0 开始计), 数量, id(从 0 开始), box(x1,y1,x2,y2), class=null
0,4,0,450,194,558,276,null,1,408,147,469,206,null,2,374,199,435,307,null,3,153,213,218,314,null
1,4,0,450,194,558,276,null,1,408,147,469,206,null,2,374,199,435,307,null,3,153,213,218,314,null
2,4,0,450,194,558,276,null,1,408,147,469,206,null,2,374,199,435,307,null,3,153,213,218,314,null
def xyxy2xywh(x):
# Convert bounding box format from [x1, y1, x2, y2] to [x, y, w, h]
# y = torch.zeros_like(x) if isinstance(x,
# torch.Tensor) else np.zeros_like(x)
y = [0, 0, 0, 0]
y[0] = (x[0] + x[2]) / 2
y[1] = (x[1] + x[3]) / 2
y[2] = x[2] - x[0]
y[3] = x[3] - x[1]
return y
def process_darklabel(video_label_path, mot_label_path):
f = open(video_label_path, "r")
f_o = open(mot_label_path, "w")
contents = f.readlines()
for line in contents:
line = line[:-1]
num_list = [num for num in line.split(',')]
frame_id = int(num_list[0]) + 1
total_num = int(num_list[1])
base = 2
for i in range(total_num):
print(base, base + i * 6, base + i * 6 + 4)
_id = int(num_list[base + i * 6]) + 1
_box_x1 = int(num_list[base + i * 6 + 1])
_box_y1 = int(num_list[base + i * 6 + 2])
_box_x2 = int(num_list[base + i * 6 + 3])
_box_y2 = int(num_list[base + i * 6 + 4])
y = xyxy2xywh([_box_x1, _box_y1, _box_x2, _box_y2])
write_line = "%d,%d,%d,%d,%d,%d,1,1,1\n" % (frame_id, _id, y[0],
y[1], y[2], y[3])
f_o.write(write_line)
f.close()
f_o.close()
if __name__ == "__main__":
root_dir = "./data/videosample"
for item in os.listdir(root_dir):
full_path = os.path.join(root_dir, item)
video_path = os.path.join(full_path, item+".mp4")
video_label_path = os.path.join(full_path, item + "_gt.txt")
mot_label_path = os.path.join(full_path, "gt.txt")
process_darklabel(video_label_path, mot_label_path)
'''

以上就是 DarkLabel 转各种数据集格式的脚本了,DarkLabel 还是非常方便的,可以快速构建自己的数据集。通常两分钟的视频可以生成 2880 张之多的图片,但是在目标检测中并不推荐将所有的图片都作为训练集,因为前后帧之间差距太小了,几乎是一模一样的。这种数据会导致训练速度很慢、泛化能力变差。
有两种解决方案:
• 可以选择隔几帧选取一帧作为数据集,比如每隔 10 帧作为数据集。具体选择多少作为间隔还是具体问题具体分析,如果视频中变化目标变化较快,可以适当缩短间隔;如果视频中大部分都是静止对象,可以适当增大间隔。
• 还有一种更好的方案是:对原视频用 ffmpeg 提取关键帧,将关键帧的内容作为数据集。关键帧和关键帧之间的差距比较大,适合作为目标检测数据集。

你可能感兴趣的:(目标跟踪,算法,人工智能,深度学习,python)