【问题标题】:How to sort contours?如何对轮廓进行排序?
【发布时间】:2020-02-20 17:25:56
【问题描述】:

我想知道是否有人可以阐明一种能够基于每帧对轮廓进行排序的策略。

我正在尝试检测“事件” - 在这种情况下,事件被定义为 4 帧的运动增长。

如果轮廓在连续 4 帧中“生长”/具有较大的轮廓区域,则会记录一个事件,并且我必须存储并输出第一帧生长的轮廓的中心位置。

如果只有一个事件要检测,我可以通过对轮廓区域列表执行成对检查来粗略地检测事件的起源,如果这是真的,通过取 (currentFrameNo - 4) 位置元素轮廓位置列表。

但是,尝试检测多个事件似乎完全不同。

在任何给定的帧上,可能会找到 (n) 个轮廓。每个轮廓都被传递到一个候选对象中,具有表征轮廓的属性,例如帧数、位置和轮廓大小。

最终,我需要一种按帧对这些轮廓进行排序的方法,这样我就可以根据它们的相对位置来组织它们,然后对轮廓的“正确列表”执行成对检查。

我不确定我是否需要多个 (4+) 列表,一个用于每个可能的事件,然后在每一帧上根据最近的中心位置将候选人传递到一个单独的列表中,或者我是否应该继续将它们添加到单个列表,然后查询该列表。

我希望在使用 linq/排序集合方面有更多经验的人可以帮助确定合适的方法。

感谢您抽出宝贵时间阅读这篇文章。

public class CandidateList
{
    public List<Candidate> candidates;

    public CandidateList()
    {
        candidates = new List<Candidate> candidates;
    }

    public void Add(Candidate candidate)
    {
        candidates.Add(candidate)
    }
}

public class Candidate
{
    //Attributes shown in constructor.

    public Candidate(VectorOfPoint contour, int frameNumber, double contourSize, Point location)
    {
        Contour = contour;
        FrameNumber = frameNumber;
        ContourSize = contourSize;
        Location = location;
        Location_x = Location.X;
        Location_y = Location.Y;
    }
}

_vc = new VideoCapture(someURLorFilePath);
_candidates = new CandidateList();
_vc.ImageGrabbed += ProcessFrame;

public void ProcessFrame(object sender, EventArgs e)
{
    Mat _frame = new Mat(); 
    // read frame.. + other operations to get desired data.

    Mat _contourOutput = _frame.Clone();
    VectorOfVectorOfPoint _contours = new VectorOfVectorOfPoint();

    CvInvoke.FindContours(_contourOutput, _contours, new Mat(), RetrType.External, ChainApproxMethod.ChainApproxSimple);

    // If there are any contours
    if (_contours.Size > 0)
    {
        // Iterate through contours
        for (var i = 0; i < _contours.Size; i++)
        {
            // Find contour area of each contour (VectorOfPoint)
            double _contourArea = CvInvoke.ContourArea(_contours[i]);

            // Find centre of contour
            Moments M = CvInvoke.Moments(_contours[i]);

            Point _contourCentre = new Point(Convert.ToInt16(M.M10 / M.M00), Convert.ToInt16(M.M01 / M.M00));

            //  Create a candidate based on frame number, contourSize and location
            Candidate _candidate = new Candidate(_contours[i], _currentFrameNo, _contourArea, _contourCentre);
            _candidates.Add(_candidate)                     

        }
    }

    _currentFrameNo ++
}



以下图片描述了我必须处理的一种非常可能的情况:

第 1 帧 - 四个候选人。

第 2 帧 - 四个候选者,位置略有偏移

第 3 帧 - 四个候选者,位置略有偏移

第 4 帧 - 四个候选者,位置偏移 检测到两个事件。 从第 1 帧检索中心位置。

【问题讨论】:

    标签: c# .net linq sorting opencv


    【解决方案1】:

    您可以使用circular buffers 的列表来存储每个候选人的历史记录:

    public class CandidateBufferList
    {
        private List<CircularBuffer<Candidate>> _candidateList = new List<CircularBuffer<Candidate>>();   
        private void Add(Candidate candidate)
        {           
            //Find a matching buffer for the candidate based on distance. More on this later
            //here maxDistance is the maximum distance a candidate can move each frame
            var matches = _candidateList.Where(cb => Distance(candidate.Location, cb.Last.Location) < maxDistance);
            int matchCount = matches.Count();
    
            if (matchCount == 0)
            {
                var cb = new CircularBuffer<Candidate>();
                cb.Add(candidate);
                _candidateList.Add(cb);
            }
            else if (matchCount == 1)
            {
                var match = matches.First();
                if (match.Last.FrameNumber == candidate.FrameNumber)
                {
                    // Ambiguous match 1.
                    throw new Exception("A candidate was already added to this buffer this frame.");
                }
                match.Add(candidate);
            }
            else
            {
                // Ambiguous match 2.
                throw new Exception("More than one matching buffer was found for this candidate");
            }
        }
    
        public void Update(int frameNumber, List<Candidate> candidates)
        {
            candidates.ForEach(c => Add(c));
            //Remove buffers that didn't match this frame.
            _candidateList.RemoveAll(cb => cb.Last.FrameNumber != frameNumber);
        }
    
        public List<Point> GetEvents()
        {
            return _candidateList
                .Where(cb => ContourHasGrouwn(cb))                
                .Select(cb => cb.First.Location)
                .ToList();
        }
    
        private bool ContourHasGrouwn(CircularBuffer<Candidate> cb)
        {
            //if contour is not older than 4 frames
            if (!cb.IsFull) return false;
    
            for (int i = 1; i < cb.Size; i++)
            {
                if (cb[i].ContourSize < cb[i - 1].ContourSize) return false;
            }
            return true;
        }
    }
    

    在每个 ProcessFrame 上:

    //CandidateBufferList candidatesHistory
    //List<Candidate> candidatesThisFrame
    candidatesHistory.Update(frameNumber, candidatesThisFrame);
    var events = candidatesHistory.GetEvents();
    

    我想我应该提一下,如果您尝试仅通过距离查找匹配项,则可能有问题,也可能没有问题,具体取决于您的具体问题:

    • 一个候选人可能更接近另一个候选人中心,例如。一个新的候选人被添加到靠近另一个人的中心(模糊匹配 1)

    • 一个候选人可能会获得多个匹配项,您可以选择距离最小的一个,但您如何确定这是正确的? (模棱两可的匹配 2)

    • 更糟糕的是,您可以有 2 个候选者,并且两者都可以在下一帧更靠近另一个人的中心。

    这是CircularBuffer 的实现:

    class CircularBuffer<T>
    {
        private const int _size = 4;
        private int _index;
        private T[] _elements = new T[_size];
    
        public int Size => _size;
        public int Count { get; private set; }
        public bool IsFull => Count == Size;
    
        public T this[int i] => _elements[(_index + i)%_size];
        public T First => this[0];
        public T Last => this[_size-1];
    
        public void Add(T element)
        {
            _elements[_index] = element;
            _index = (_index+1) % _size;
            if (Count < _size) Count++;
        }
    }
    

    【讨论】:

      【解决方案2】:

      不确定我是否理解正确。基本上这听起来像是一个跟踪任务。

      您可能希望逐帧跟踪您的轮廓,帧中的“相同”轮廓 f+1 的位置与帧中的轮廓接近 f .您可以通过拥有一个 TrackedContour 类来实现这一点,该类包含最近 4 个(或更多,如果您愿意)Countours 的历史记录。

      f+1中的轮廓与帧f中的某些轮廓的匹配可以通过轮廓的成对比较来完成,这很容易实现但效率低下对于许多轮廓,n (n-1) / 2 比较 n 个轮廓。为了提高效率,在二维空间中,可以将 TrackedContours 保存在 2 个列表中,一个按 X 排序,另一个按 Y 坐标排序。然后减少要检查的新轮廓的数量,因为只需检查具有“相似”XY 的轮廓。

      所以基本上策略是:

      • 将 TrackedContours 保存在 2 个排序列表中。
      • 逐帧匹配。
      • 在 TrackedContours 中维护轮廓历史。
      • 检测事件条件并在历史记录中查找所需的属性。

      希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 2012-11-09
        • 1970-01-01
        • 1970-01-01
        • 2022-09-24
        • 2022-07-20
        • 2021-08-21
        • 1970-01-01
        • 2019-03-15
        • 2016-12-03
        相关资源
        最近更新 更多