根据向量大小

时间:2016-12-07 21:21:36

标签: c++ opencv vector

我已经创建了一个函数来过滤掉我在std :: vector中不喜欢的元素,在本例中是一个opencv轮廓的向量。下面的代码看起来会起作用,但它没有,我怀疑这是因为每当我擦除索引时索引都会改变,但是我继续下一个i值迭代。

void FilterContours( std::vector<std::vector<cv::Point>> contours )
{
    for ( int i = 0; i < contours.size(); i++ ) {

        //Remove contours smaller than 5 from vector - example
        if ( contours[i].size() < 5 ) {
            contours.erase(contours.begin() + i);
            continue;
        }

        //Other filtering...
    }
    return;
}

所以问题是,这是否按预期工作(我认为不是这样),如果没有,我该如何使其按预期工作?我应该在擦除后添加i - = 1以保持正确的索引位置吗?

4 个答案:

答案 0 :(得分:3)

使用擦除删除习语:

contours.erase(
  std::remove_if(contours.begin(), contours.end(), [](const std::vector<cv::Point>& v){
    return v.size() < 5;
  }),
  contours.end()
);

答案 1 :(得分:2)

每次erase()来自容器的元素,其size()递减,其余元素的索引也会递减。但是你无条件地递增你的循环计数器,所以每当你擦除一个元素时,你跳过跟随它的下一个元素!

此外,您传递的是vector按值,因此您正在使用vector副本进行操作,并且调用者不会看到任何更改原vector

正确的方法是:

  1. 仅在未删除元素时才在循环体内增加索引变量。在擦除元素时保持变量原样:

    void FilterContours( std::vector<std::vector<cv::Point>> &contours )
    {
        int i = 0;
        while ( i < contours.size() ) {
            if ( contours[i].size() < 5 ) {
                contours.erase(contours.begin() + i);
                continue;
            }
    
            //Other filtering...
    
            ++i;
        }
    }
    
  2. 使用迭代器而不是索引:

    void FilterContours( std::vector<std::vector<cv::Point>> &contours )
    {
        auto it = contours.begin();
        while ( it != contours.end() ) {
            if ( it->size() < 5 ) {
                it = contours.erase(it);
                continue;
            }
    
            //Other filtering...
    
            ++it;
        }
    }
    
  3. 使用erase-remove成语:

    void FilterContours( std::vector<std::vector<cv::Point>> &contours )
    {
        contours.erase(
            std:::remove_if(
                contours.begin(),
                contours.end(),
                [](const std::vector<cv::Point> &v)
                {
                    if (v.size() < 5) return true; 
                    //Other filtering...
                    return false;
                }
            ),
            contours.end()
        );
    }
    

答案 2 :(得分:2)

一般情况下,当您迭代删除时,最好迭代向后

for ( int i = contours.size()-1; i >=0; --i)

这会起作用,但会导致代码变慢,因为每次删除时,删除后面的元素都会被复制/移回。因此,使用标准算法库提供的专用习语更好,更快,更具可读性,这些习惯用法通常都是非常优化的。在这种情况下,您拥有erase/remove_if组合:

contours.erase(std::remove_if(contours.begin(), contours.end(), [](const auto& elem) { return elem.size() < 5; }), contours.end() );

这里的一大优势是std::remove_if()以比直觉循环更聪明的方式起作用:它首先&#34;标记&#34;要删除的元素,然后将剩余的元素压缩在一起。这个过程是O(N),而(直观)循环是O(N ^ 2),对于大向量来说是一个巨大的差异。

p.s。:FilterContours函数的签名,用于通过引用获取向量:

void FilterContours( std::vector<std::vector<cv::Point>>& contours ) // <-- by reference

答案 3 :(得分:1)

您的FilterContours应该参考,否则不会对来电者产生任何影响。

void FilterContours(std::vector<std::vector<cv::Point>>& contours)
{
    for (auto it = contours.begin(); it != contours.end(); )
    {
        if (it->size() < 5)
            it = contours.erase(it);
        else
            ++it;
    }
}

编辑: 如果您想按相反的顺序执行此操作:

void FilterContours_reverse(std::vector<std::vector<cv::Point>>& contours)
{
    for (auto it = contours.rbegin(); it != contours.rend(); )
    {
        if (it->size() < 5)
            contours.erase(std::next(it++).base());
        else
            ++it;
    }
}