实现流量统计算法有两个前提:
-
能够实现目标检测,最基本的前提,必须能够识别到视频帧中的车辆和行人。
-
能够进行目标跟踪,在检测的基础上,为目标分配一个唯一的ID。流量计数依赖于目标的唯一ID。
目标检测算法以YOLO系列为例。
跟踪算法以ByteTrack跟踪结果为例。将检测结果objects作为ByteTrack跟踪算法的参数,下面代码中,跟踪结果output_stracks
是一个STrack
类型的向量,这个类型的具体方法和成员变量可以去看ByteTrack源码。
BYTETracker tracker(fps, 30);
auto yolov8 = new YOLOv8(engine_file_path);
`while(true){
yolov8->copy_from_Mat(image, size);
yolov8->infer();
yolov8->postprocess(objs, score_thres, iou_thres, topk, num_labels);
std::vector<STrack> output_stracks = tracker.update(objs);
}`
对于统计方法,我有两个思路,对于场景比较简单的,使用划线方法;对于场景比较复杂的,使用多边形方法。
划线方法顾名思义,就是在画面中划一条分界线,只要目标BBox的某个点(比如左上角、中心点等)穿过了这条线,则可以进行对应计数。那如何用数学的方式去描述目标相对于分界线的位置?答:用叉积。代码如下。这里顺便提一嘴,在2D目标检测中,tlwh和xywh都是描述BBox的,约定俗成的是,tlwh指矩形的左上角 点、宽和高;xywh指的则是矩形的中心点、宽和高。
cv::Point linePt1(0, 800);
cv::Point linePt2(1920, 800);
cv::Point centerPt(tlwh[0] + tlwh[2]/2, tlwh[1] + tlwh[3]/2);
cv::Point vec1 = centerPt - linePt1;
cv::Point vec2 = linePt2 - linePt1;
int crossProduct = vec1.x * vec2.y - vec1.y * vec2.x;
多边形方法要求目标BBox的某个点的位置关系和给定多边形区域发生了变化(由内到外&由外到内)
bool isInside;
cv::Rect ROI(0,640,1920,1080-640);
if(centerPt.x > ROI.x && centerPt.x < ROI.x + ROI.width && centerPt.y > ROI.y && centerPt.y < ROI.y + ROI.height){
isInside = true;
}
else{
isInside = false;
}
在获取了当前帧目标相对给定区域的位置后,使用map来保存<目标ID,其他信息>这个键值对。因为我自己还需要统计类别(道路上不只有车,还有人),因此修改了STrack
的一些属性。
std::map<int, std::pair<std::string, int>> trafficInfoPrev, trafficInfoCurr;
if(!isInside){
trafficInfoCurr.insert(std::make_pair(output_stracks[i].track_id, std::make_pair("Outside", output_stracks[i].label)));
}
else{
trafficInfoCurr.insert(std::make_pair(output_stracks[i].track_id, std::make_pair("Inside", output_stracks[i].label)));
}
然后判断trafficInfoPrev
和trafficInfoCurr
中相同Key对应Value是否相同,即可判断目标是进入区域,还是离开区域。
for (const auto& pair : trafficInfoPrev) {
// 获取当前键在trafficInfoCurr中的值
auto it = trafficInfoCurr.find(pair.first);
// 如果键在trafficInfoCurr中存在
if (it != trafficInfoCurr.end()) {
// 比较值是否相同
if (it->second.first == pair.second.first) {
}
else {
if(pair.second.first == "Outside" && it->second.first=="Inside"){
std::cout << "Key " << pair.first
<< " has different values: Prev: " << pair.second.first
<< ", Curr: " << it->second.first
<< std::endl;
if(it->second.second == 0){
personCnt ++;
}
else{
carCnt++;
}
}
}
}
trafficInfoPrev = trafficInfoCurr;
trafficInfoCurr.clear();
总的来说,流量统计的关键就在于获取目标的ID,稳定的目标跟踪能带来更准确的统计结果。