足球视频AI(五)——球员与球的对象跟踪

一、基础概念

在这里插入图片描述

在之前的四节中,我们尝试解决:

1,球员识别、足球识别、裁判识别;

2,队伍的分类

3,平面坐标的换算

存在关键的问题是:每一帧的画面,每次都是重新识别,无法将特定的人与坐标对应上。

我们需要知道每个球员、裁判的实时位置,并将实时位置记录对应到关键帧,能够通过世界坐标的变换,得到想要的足球参数。

1.1 识别目标

利用对象跟踪,实现定人的实时追踪。

1.2 实现思路

1)初始帧画面,利用对象检测识别球员、裁判、球,并生成编号便于跟踪。

2)利用CSRT实现多目标追踪,跟踪检测到的对象,确保不丢失。

3)设计简单的刷新算法,确保视频范围内人员进出画面进行新的目标检测。

4)探讨当跟踪丢失(如对象离开画面、人员重叠后的跟踪丢失)

二、代码实现

2.1 对象检测

参见《足球视频AI(二)——球员与球的目标检测

2.2 CSRT目标追踪技术

2.2.1 依赖包

Nuget Install OpenCvSharp4
Nuget Install OpenCvSharp4.Extensions
Nuget Install OpenCvSharp4.runtime.win
Nuget Install Numpy.Bare

其中OpenCvSharp包中,提供了类TrackerCSRT的实现。

CSRT概念性的内容需要大家翻阅数据,在此不再详述,本系列主要是实操内容。

2.2.2 跟踪的数据结构

    internal class TrackerObject
    {
    
    
        public TrackerObject(Tracker tracker, YoloPrediction prediction) 
        {
    
    
            Tracker = tracker;
            Prediction = prediction;
        }

        public Tracker Tracker{
    
     get;set;}

        public Rect Rect {
    
     get;set;}

        public YoloPrediction Prediction {
    
     get; set; }

    }

该数据结构,将首次对象检测到的球员Bound,存储在YoloPrediction中。

因采用了多跟踪器的实现,所以每个球员、裁判、球对应一个Tracker对象。

2.2.3 接口定义

    public interface ITracker<T> : IDisposable
    {
    
    
        bool NeedFlush {
    
     get;}

        public int MaxCount {
    
     get; set; }

        void Init(List<T> detections, Mat frame);

        List<T>? Update(Mat frame);
    }

1, 对于人的跟踪,我们设计的简单的算法,假设视频首帧中检测到14人,其它球员在视频的视界范围外。存在以下情况:

​ 1),新的人员跑进视界;

​ 2),既有人员从视界中跑出去;

​ 3),跟踪器丢失了跟踪。

​ 算法中每格60帧重新对象检测,通过人员总数的变化率大于30%,则重设跟踪(已有跟踪没有进行鉴别)。

​ 当触发了30%阈值情况,则NeedFlush标志为True;MaxCount用来定义视界中的人员总数。

2, 对于球的跟踪,仅适用于跟踪丢失的情况。

2.2.4 跟踪器设计

    internal class MutiTrackerCV : ITracker<YoloPrediction>
    {
    
    
        private List<TrackerObject> trackers;
        private int MissCount=0;

        /// <summary>
        /// <inheritdoc/>
        /// </summary>
        public int MaxCount {
    
     get; set; }

        /// <summary>
        /// <inheritdoc/>
        /// </summary>
        public bool NeedFlush
        {
    
    
            get => MaxCount == 0 ? true : ((float)MissCount / MaxCount > 0.3);
        } 

        public MutiTrackerCV()
        {
    
    
            trackers = new List<TrackerObject>();
        }

        public void Dispose()
        {
    
    
            for (int i = 0; i < trackers?.Count; i++)
            {
    
    
                TrackerObject? tracker = trackers[i];
                tracker.Tracker?.Dispose();
                tracker = null;
            }
        }

        /// <summary>
        /// <inheritdoc/>
        /// </summary>
        public void Init(List<YoloPrediction> detections, Mat frame)
        {
    
    
            trackers = new List<TrackerObject>();
            int i = 0;
            foreach (var d in detections)
            {
    
    
                i++;
                var tracker = TrackerCSRT.Create();
                var rect = new Rect((int)d.Rectangle.X, (int)d.Rectangle.Y, (int)d.Rectangle.Width, (int)d.Rectangle.Height);
                tracker.Init(frame, rect);
                if (null != d.Label)
                    d.Label = new YoloLabel() {
    
     Color = d.Label.Color, Kind = d.Label.Kind, Name = d.Label.Name, Id = i};
                var trackerObject = new TrackerObject(tracker, d) {
    
     Rect = rect};
                trackers?.Add(trackerObject);
            }
            MaxCount = trackers.Count;
        }

        /// <summary>
        /// <inheritdoc/>
        /// </summary>
        public List<YoloPrediction>? Update(Mat frame)
        {
    
    
            var missObjects = new List<TrackerObject>();
            foreach (var tracker in trackers)
            {
    
    
                var rect = (Rect)tracker.Rect;
                var ismiss = tracker.Tracker?.Update(frame, ref rect);
                if (ismiss == null || ismiss == false)
                    missObjects.Add(tracker);
                else
                    tracker.Prediction.Rectangle = new System.Drawing.RectangleF(rect.X, rect.Y, rect.Width, rect.Height);
            }
            foreach (var tracker in missObjects)
                trackers.Remove(tracker);
            MissCount = missObjects.Count;
            return trackers?.Select(p => p.Prediction).ToList();
        }
    }

其中,Tracker?.Update函数返回的是单个跟踪对象是否丢失的标志位。

当为false时说明跟踪对象丢失——例如白色的足球,滚动仅纯白色区域后。再出现时跟踪会丢失。

2.2.5 目标跟踪实现

[Fact]
        public void TestPlayerTracker()
        {
    
    
            var detector = new DetectorYolov7();
            var mutiTracker = new MutiTrackerCV();
            var mats = LoadImages.LoadVideo("test.mp4");
            int frameNumber = 0;
            //逐帧处理
            foreach (var mat in mats)
            {
    
    
                if ((frameNumber % 60) == 0)
                {
    
    
                    //目标检测
                    var predictions = detector.Detect(mat);
                    var playerNumber = 1;
                    //绘制对象Bound
                    predictions.ForEach(item=>
                    {
    
    
                        //略...区分人、球
                        //略...原始视频CV绘制球员Bound
                        //原始视频CV绘制球员号码
                        Cv2.PutText(mat, $"{
      
      item.Label?.Id}", new OpenCvSharp.Point(item.Rectangle.X, item.Rectangle.Y),
                                   HersheyFonts.HersheySimplex, 0.5, Scalar.AliceBlue, 1);
                        //略...平面投影坐标点绘制,参见《足球视频AI(一)——位置与平面坐标的转换》
                        //略...平面投影球员号码绘制
                        playerNumber++;
                    });
                    //跟踪人员
                    if (mutiTracker.NeedFlush || (predictions.Count > mutiTracker.MaxCount * 1.3))
                        mutiTracker.Init(predictions, mat);
                }
                //刷新跟踪
                mutiTracker.Update(mat);
                frameNumber++;
            }
            Assert.True(mutiTracker?.MaxCount >0);
        }

三、总结

通过目标跟踪的实现,确定球员、裁判、球三个要素的唯一性。

结合二维平面投影,记录顺时的位置,便可以统计相关的比赛数据。

其中,总移动距离、顺时速度、持球速度、传射、防守等换算信息,不是我们研究的重点,属于工程化问题。

猜你喜欢

转载自blog.csdn.net/black0707/article/details/128568084