Stereo Matching文献笔记之(四):《Stereo Matching Using Tree Filtering》读后感~

non-local算法在双目立体匹配中的应用:上采样、细化与纹理处理
本文详细阐述了non-local算法在双目立体匹配领域的创新应用,包括上采样、细化与纹理处理。通过分析,揭示了这些方法如何提高匹配速度和精度,特别关注于克服纹理丰富区域的挑战和改进求精过程。此外,还对比了代码实现,展示了理论与实践的一致性。

前段时间研究了non-local算法在双目立体匹配上的应用,这几天又看到作者在PAMI上发表的这篇文章,于是仔细的拜读了一下,惊讶的发现原来NL算法竟然可以应用在多个方向,其实在《A Non-Local Cost Aggregation Method for Stereo Matching》一文中,已经有过关于refinement方面的论述,但是被自己给“忽略”掉了,原来自己的算法知识面还是很窄,汗颜!本文就说一下对于NL在”upsampling”和“refinement”方面的应用的理解。
(转载请注明:http://blog.youkuaiyun.com/wsj998689aa/article/details/45584725)

upsampling

upsampling的意思是上采样,是图像方面的概念,之所以要用NL作上采样,主要原因还是速度问题,往往stereo mathcing方向的算法的时间复杂度都比较高,于是为了提高速度,考虑在原图像的下采样图像计算视差图,然后在对低分辨率的视差图进行上采样成为高分辨率的视差图,这是一个折中的方法,效果肯定没有直接在高分辨率图像上生成视差图好,但是速度却能够提高几倍不止。
一般的上采样方法相信大家都比较熟悉,最基本的思想就是根据当前像素邻域内的像素值进行估计,比如说权值滤波,也有的文献是基于3Dtof相机获得低分辨率视差图和高分辨率的彩色图,然后在进行上采样操作。这篇文献就是基于权值滤波的思想,将NL应用在了视差图上采样方向,其流程图如下所示:
upsampling流程图

权重中值滤波公式如下所示:
权值滤波公式

Note:我认为此处作者的分析存在问题,作者在文章中说:由于可将权重中值滤波等价为代价聚合,进一步可以将上式的绝对值部分视作代价计算项。我认同前一句话,但是不认同后一句话,我们看绝对值中含有自变量b,而代价计算项是不可能含有自变量的,这里严格意义上来说,应该等同于区域内的任意像素与中心像素的比较差值,b和I(q)自身才可被视为代价计算项。不知道有没有童鞋读到这里的时候,和我产生同样的疑问?并且此处作者称之为权重中值滤波,我感觉也说错了,应该是权重均值滤波吧?

由于本文是基于最小生成树的全局算法, 所以N所指的邻域其实包含整幅图像的像素。如此一来,得到的权值更加准确,这也正是NL的优势所在。文献中给出了上采样64倍大小的图像,效果还不错,大家可以去看看。
这里写图片描述

texture handing

texture handing一般翻译成为纹理抑制或者是纹理处理,指的是对于具有复杂的纹理区域,一般要在边缘保持的同时模糊区域内的纹理,这是一个图像处理上经常用的方法,作者在这上面也下了功夫,目的还是估计视差图,因为NLCA算法对于带噪声图像效果很差,尤其是在其纹理丰富区域,所以催生了这一块应用。我描述了它的算法流程图,如下所示,具体达到的效果大家可以去文献上看看,我就不贴了。

这里写图片描述

至于如何可以基于MST对纹理进行处理,其实很简单,作者事先检讨了NLCA的不足,他认为距离计算公式(父子节点的像素差值的绝对值)导致了纹理抑制失效,如下图所示,其实我们可以发现,前两幅图基本差不多,差不多就说明了纹理处理没效果。后来他简单的将MST中的距离都设置为1,效果很明显,当然第四幅图由于yita的值设置的过大,抑制的过猛,效果也很差,作者给出了yita的经验值。
这里写图片描述

但是这样做有个弊端,就是时间复杂度增加了,因为我们为了纹理抑制,要先对图像计算一次MST,只不过将权值设置为1,等代价计算步骤结束之后,还得重新计算一次MST,当然这时是基于纹理抑制之后的图像喽。

refinement

视差图求精是重头戏,由于遮挡,光照等原因,求出来的视差图往往部分点视差不正确,需要对视差图重新求精,是stereo matching必不可少的一步,NL同样在这里有应用,它事先利用left-right-check对左右视差图进行处理,得到视差图的稳定点和不稳定点,同时直接在左视差图上定义新的代价值,再同样利用原图所得的MST,对所有像素点重新进行代价聚合,最后利用”胜者为王”算法估计新的视差。乍一听,感觉这没什么,重点在新的代价值的定义上,公式如下所示:
这里写图片描述
我刚开始看到这个公式,感觉到特别的奇怪,D(p)已经是视差值,它又和视差d相减,这是啥意思?后来明白了作者的意图,这么做其实很直观,就是对旧视差值进行修正,如果旧视差值完全正确,那么计算的将会是d=D(p)。并且,如果p是不稳定点,那么直接赋值为0,注意这只是代价计算的那一步,在后续代价聚合的时候,会重新根据上述公式估计不稳定点的视差。作者称为”利用MST,使得视差值从稳定点扩散至不稳定点”,这是一种很唯美的说法,究其原因,由于不稳定点的代价为0,那么MST上的权值w也就失去了作用,换句话说,不稳定点的代价聚合值完全由稳定点的代价值和权值来累加计算,不能怪人家,谁让你自己不够稳定呢?说白了就是让稳定点更加精确,连带着不稳定点通过MST的计算,也更加精确罢了。
算法流程图如下所示:
这里写图片描述

代码比较

通过调试作者在网上提供的代码,发现权重中值滤波和求精两个应用其实是完全相同的,返回来再看理论部分,二者采用的公式都完全一样,作者将一个公式应用在了两个方向,并且各自说了一通,成功的屏蔽了俺的双眼,为了予以证明,粘贴作者源码(含有自己的注释)

    // upsampling
    for(int y=0;y<m_h;y++) for(int x=0;x<m_w;x++)
    {
        if(disparity[y][x]>0)   // 稀疏视差图为0就视为野值
        {
            for(int d=0;d < m_nr_plane; d++) 
            {
                m_cost_vol[y][x][d]=abs(disparity[y][x]-d); 
            }
        }
    }
        // refinement
        for(int y=0;y<m_h;y++) for(int x=0;x<m_w;x++)       if(!m_mask_occlusion[y][x])   // 只对稳定点进行处理
        {
            for(int d=0;d<m_nr_plane;d++) 
                m_cost_vol[y][x][d]=abs(disparity[y][x]-d);                         
        }

大家可以比较一下二者的差异,除了名称不同之外代码基本相同,前者是针对稀疏视差图中视差不为0的像素点,后者针对稳定点,其实都是一样一样的点,再粘贴一下稀疏视差图生成的代码,相信大家一目了然。

    // 生成稀疏视差图,注意其与原视差图同样大小
    for(int yi=0;yi<(h>>downsample_rate);yi++) for(int xi=0;xi<(w>>downsample_rate);xi++)
    {
        int y=(yi<<downsample_rate);
        int x=(xi<<downsample_rate);

        // 由于是视差图下采样,所以必须改变视差,好比一个物体距离你远了,视差自然得变小
        disparity[y][x]=(double)disparity_gt[y][x]/scalar;  
        if(disparity[y][x]<=2)
        {
            disparity[y][x]=0;
        }
    }

总结

PAMI上的文章有个特点,它的内容往往是很全面的,并且是总结类的文章,就是说作者在之前已经发表过类似的文章,只不过当时只是在某一方面进行了应用,那么在PAMI中往往会添加其他的应用以及完善算法的理论基础,形成一个框架。本文正是如此,如果细看,其实很多内容都与《A Non-Local Cost Aggregation Method for Stereo Matching》一文重复,添加了诸如”texture handling”,”upsampling”,”refinement”方面的应用和解释,再配合以大量的实验,证明这个non-local的idea具有多方面的好处。
特别提的是,本文的序言部分很值得一读,对现有方法总结的很好,希望能够对大家有所帮助。

/** * implement a container like std::map */ #ifndef SJTU_MAP_HPP #define SJTU_MAP_HPP #include <cstdio> // only for std::less<T> #include <functional> #include <cstddef> #include "utility.hpp" #include "exceptions.hpp" namespace sjtu { template< class Key, class T, class Compare = std::less <Key> > class AAtree{ public: typedef pair<const Key, T> value_type; struct Node { Node *lson, *rson, *parent; int level; value_type data; Node(const value_type &d, int lv = 1, Node *p = nullptr, Node *l = nullptr, Node *r = nullptr) : data(d), level(lv), parent(p), lson(l), rson(r) {} }; Node *root; size_t tree_size; Compare comp; AAtree(): root(nullptr), tree_size(0) {} AAtree(const AAtree &other) { root = copyTree(other.root, nullptr); tree_size = other.tree_size; } AAtree &operator=(const AAtree &other) { if (this != &other) { clear(root); root = copyTree(other.root, nullptr); tree_size = other.tree_size; } return *this; } ~AAtree() { clear(root); tree_size = 0; } Node *skew(Node *node) { if (!node || !node->lson) return node; if (node->lson->level == node->level) { Node *L = node->lson; node->lson = L->rson; if (L->rson) L->rson->parent = node; L->rson = node; L->parent = node->parent; node->parent = L; return L; } return node; } Node *split(Node *node) { if (!node || !node->rson || !node->rson->rson) return node; if (node->level == node->rson->rson->level) { Node *R = node->rson; node->rson = R->lson; if (R->lson) R->lson->parent = node; R->lson = node; R->parent = node->parent; node->parent = R; ++R->level; return R; } return node; } Node *insert(Node *node, const value_type &value, Node *parent = nullptr) { if (!node) { ++tree_size; return new Node(value, 1, parent); } if (comp(value.first, node->data.first)) { node->lson = insert(node->lson, value, node); node->lson->parent = node; } else if (comp(node->data.first, value.first)) { node->rson = insert(node->rson, value, node); node->rson->parent = node; } else return node; node = skew(node); node = split(node); return node; } Node *erase(Node *node, const Key &key) { if (!node) return nullptr; if (comp(key, node->data.first)) { node->lson = erase(node->lson, key); if (node->lson) node->lson->parent = node; } else if (comp(node->data.first, key)) { node->rson = erase(node->rson, key); if (node->rson) node->rson->parent = node; } else { if (!node->lson && !node->rson) { if (node->parent) { if (node->parent->lson == node) { node->parent->lson = nullptr; } else { node->parent->rson = nullptr; } } delete node; node = nullptr; --tree_size; return nullptr; } else if (!node->lson) { Node *temp = node->rson; temp->parent = node->parent; delete node; node = nullptr; --tree_size; return temp; } else if (!node->rson) { Node *temp = node->lson; temp->parent = node->parent; delete node; node = nullptr; --tree_size; return temp; } Node *predecessor = node->lson; while (predecessor->rson) predecessor = predecessor->rson; Node *newNode = new Node(predecessor->data, node->level, node->parent, node->lson, node->rson); if (newNode->lson) newNode->lson->parent = newNode; if (newNode->rson) newNode->rson->parent = newNode; if (newNode->parent) { if (newNode->parent->lson == node) { newNode->parent->lson = newNode; } else { newNode->parent->rson = newNode; } } newNode->lson = erase(newNode->lson, predecessor->data.first); if (newNode->lson) newNode->lson->parent = newNode; delete node; node = newNode; } if (node->lson && node->rson) { size_t min_level = node->lson->level < node->rson->level ? node->lson->level : node->rson->level; if (node->level > min_level + 1) { node->level = min_level + 1; if (node->rson && node->rson->level > node->level) node->rson->level = node->level; } } node = skew(node); node->rson = skew(node->rson); if (node->rson) node->rson->rson = skew(node->rson->rson); node = split(node); node->rson = split(node->rson); return node; } Node *find(Node *node, const Key &key) const { while (node) { if (comp(key, node->data.first)) node = node->lson; else if (comp(node->data.first, key)) node = node->rson; else return node; } return nullptr; } Node *findMin(Node *node) const { if (!node) return nullptr; // printf("In findmin: %d\n", (node)); while (node->lson) node = node->lson; // printf("finish find min\n"); return node; } Node *findMax(Node *node) const { if (!node) return nullptr; while (node->rson) node = node->rson; return node; } Node *successor(Node *node) const { // printf("in successor\n"); if (!node) return nullptr; // printf("in successor2\n"); if (node->rson) return findMin(node->rson); // printf("in successor3\n"); Node *parent = node->parent; while (parent && node == parent->rson) { node = parent; parent = parent->parent; } // printf("finish successor\n"); return parent; } Node *predecessor(Node *node) const { if (!node) return nullptr; if (node->lson) return findMax(node->lson); Node *parent = node->parent; while (parent && node == parent->lson) { node = parent; parent = parent->parent; } return parent; } Node *copyTree(Node *node, Node *parent) { if (!node) return nullptr; Node *newNode = new Node(node->data, node->level, parent); newNode->lson = copyTree(node->lson, newNode); newNode->rson = copyTree(node->rson, newNode); return newNode; } void clear(Node *node) { if (!node) return; clear(node->lson); clear(node->rson); delete node; node = nullptr; } }; template< class Key, class T, class Compare = std::less <Key> > class map { public: /** * the internal type of data. * it should have a default constructor, a copy constructor. * You can use sjtu::map as value_type by typedef. */ typedef pair<const Key, T> value_type; AAtree<Key, T, Compare> aa_tree; /** * see BidirectionalIterator at CppReference for help. * * if there is anything wrong throw invalid_iterator. * like it = map.begin(); --it; * or it = map.end(); ++end(); */ typedef typename AAtree<Key, T, Compare>::Node Node; class const_iterator; class iterator { private: /** * TODO add data members * just add whatever you want. */ map* container; Node *node; public: iterator(map *c = nullptr, Node *n = nullptr): container(c), node(n) { // TODO } iterator(const iterator &other):container(other.container), node(other.node) { // TODO } /** * TODO iter++ */ iterator operator++(int) { if (!node) throw invalid_iterator(); iterator temp = *this; node = container->aa_tree.successor(node); return temp; } /** * TODO ++iter */ iterator &operator++() { if (!node) throw invalid_iterator(); node = container->aa_tree.successor(node); return *this; } /** * TODO iter-- */ iterator operator--(int) { iterator temp = *this; if (node == nullptr) { node = container->aa_tree.findMax(container->aa_tree.root); if (!node) throw invalid_iterator(); } else { node = container->aa_tree.predecessor(node); if (!node) throw invalid_iterator(); } return temp; } /** * TODO --iter */ iterator &operator--() { if (node == nullptr) { node = container->aa_tree.findMax(container->aa_tree.root); if (!node) throw invalid_iterator(); } else { node = container->aa_tree.predecessor(node); if (!node) throw invalid_iterator(); } return *this; } /** * a operator to check whether two iterators are same (pointing to the same memory). */ value_type &operator*() const { if (!node) throw invalid_iterator(); return node->data; } bool operator==(const iterator &rhs) const { return node == rhs.node && container == rhs.container; } bool operator==(const const_iterator &rhs) const { return node == rhs.getNode() && container == rhs.getContainer(); } /** * some other operator for iterator. */ bool operator!=(const iterator &rhs) const { return node != rhs.node || container != rhs.container; } bool operator!=(const const_iterator &rhs) const { return node != rhs.getNode() || container != rhs.getContainer(); } map *getContainer() const { return container; } Node *getNode() const { return node; } /** * for the support of it->first. * See <http://kelvinh.github.io/blog/2013/11/20/overloading-of-member-access-operator-dash-greater-than-symbol-in-cpp/> for help. */ value_type *operator->() const noexcept { // if (!node) throw invalid_iterator(); return &(node->data); } }; class const_iterator { // it should has similar member method as iterator. // and it should be able to construct from an iterator. private: // data members. map* container; Node *node; public: // const_iterator(const map *c = nullptr, Node *n = nullptr): container(const_cast<map*>(c)), node(n) { // // TODO // } const_iterator(map *c = nullptr, Node *n = nullptr): container(c), node(n) { // TODO } const_iterator(const const_iterator &other): container(other.container), node(other.node) { // TODO } const_iterator(const iterator &other) { // TODO container = other.getContainer(); node = other.getNode(); } /** * TODO iter++ */ const_iterator operator++(int) { if (!node) throw invalid_iterator(); const_iterator temp = *this; node = container->aa_tree.successor(node); return temp; } /** * TODO ++iter */ const_iterator &operator++() { if (!node) throw invalid_iterator(); node = container->aa_tree.successor(node); return *this; } /** * TODO iter-- */ const_iterator operator--(int) { const_iterator temp = *this; if (node == nullptr) { node = container->aa_tree.findMax(container->aa_tree.root); if (!node) throw invalid_iterator(); } else { node = container->aa_tree.predecessor(node); if (!node) throw invalid_iterator(); } return temp; } /** * TODO --iter */ const_iterator &operator--() { if (node == nullptr) { node = container->aa_tree.findMax(container->aa_tree.root); if (!node) throw invalid_iterator(); } else { node = container->aa_tree.predecessor(node); if (!node) throw invalid_iterator(); } return *this; } /** * a operator to check whether two iterators are same (pointing to the same memory). */ const value_type &operator*() const { if (!node) throw invalid_iterator(); return node->data; } bool operator==(const iterator &rhs) const { return node == rhs.getNode() && container == rhs.getContainer(); } bool operator==(const const_iterator &rhs) const { return node == rhs.node && container == rhs.container; } /** * some other operator for iterator. */ bool operator!=(const iterator &rhs) const { return node != rhs.getNode() || container != rhs.getContainer(); } bool operator!=(const const_iterator &rhs) const { return node != rhs.node || container != rhs.container; } map *getContainer() const { return container; } Node *getNode() const { return node; } /** * for the support of it->first. * See <http://kelvinh.github.io/blog/2013/11/20/overloading-of-member-access-operator-dash-greater-than-symbol-in-cpp/> for help. */ const value_type *operator->() const noexcept { // if (!node) throw invalid_iterator(); return &(node->data); } }; /** * TODO two constructors */ map(): aa_tree() {} map(const map &other): aa_tree(other.aa_tree) {} /** * TODO assignment operator */ map &operator=(const map &other) { if (this != &other) { aa_tree = other.aa_tree; } return *this; } /** * TODO Destructors */ ~map() {} /** * TODO * access specified element with bounds checking * Returns a reference to the mapped value of the element with key equivalent to key. * If no such element exists, an exception of type `index_out_of_bound' */ T &at(const Key &key) { auto node = aa_tree.find(aa_tree.root, key); if (!node) throw index_out_of_bound(); return node->data.second; } const T &at(const Key &key) const { auto node = aa_tree.find(aa_tree.root, key); if (!node) throw index_out_of_bound(); return node->data.second; } /** * TODO * access specified element * Returns a reference to the value that is mapped to a key equivalent to key, * performing an insertion if such key does not already exist. */ T &operator[](const Key &key) { auto node = aa_tree.find(aa_tree.root, key); if (node) return node->data.second; aa_tree.root = aa_tree.insert(aa_tree.root, value_type(key, T())); return aa_tree.find(aa_tree.root, key)->data.second; } /** * behave like at() throw index_out_of_bound if such key does not exist. */ const T &operator[](const Key &key) const { return at(key); } /** * return a iterator to the beginning */ iterator begin() { return iterator(this, aa_tree.findMin(aa_tree.root)); } const_iterator cbegin() const { return const_iterator(const_cast<map*>(this), aa_tree.findMin(aa_tree.root)); } /** * return a iterator to the end * in fact, it returns past-the-end. */ iterator end() { return iterator(this, nullptr); } const_iterator cend() const { return const_iterator(const_cast<map*>(this), nullptr); } /** * checks whether the container is empty * return true if empty, otherwise false. */ bool empty() const { return aa_tree.tree_size == 0; } /** * returns the number of elements. */ size_t size() const { return aa_tree.tree_size; } /** * clears the contents */ void clear() { aa_tree.clear(aa_tree.root); aa_tree.root = nullptr; aa_tree.tree_size = 0; } /** * insert an element. * return a pair, the first of the pair is * the iterator to the new element (or the element that prevented the insertion), * the second one is true if insert successfully, or false. */ pair<iterator, bool> insert(const value_type &value) { auto node = aa_tree.find(aa_tree.root, value.first); if (node) return {iterator(this, node), false}; aa_tree.root = aa_tree.insert(aa_tree.root, value); node = aa_tree.find(aa_tree.root, value.first); return {iterator(this, node), true}; } /** * erase the element at pos. * * throw if pos pointed to a bad element (pos == this->end() || pos points an element out of this) */ void erase(iterator pos) { if (pos == end() || pos.getContainer() != this) throw invalid_iterator(); aa_tree.root = aa_tree.erase(aa_tree.root, (*pos).first); } /** * Returns the number of elements with key * that compares equivalent to the specified argument, * which is either 1 or 0 * since this container does not allow duplicates. * The default method of check the equivalence is !(a < b || b > a) */ size_t count(const Key &key) const { return aa_tree.find(aa_tree.root, key) ? 1 : 0; } /** * Finds an element with key equivalent to key. * key value of the element to search for. * Iterator to an element with key equivalent to key. * If no such element is found, past-the-end (see end()) iterator is returned. */ iterator find(const Key &key) { return iterator(this, aa_tree.find(aa_tree.root, key)); } const_iterator find(const Key &key) const { return const_iterator(const_cast<map*>(this), aa_tree.find(aa_tree.root, key)); } }; } #endif 下面是valgrind的报错: ==9160== Invalid read of size 4 ==9160== at 0x10FDB9: check13() (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x110CC9: main (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== Address 0x6c8bcfc is 28 bytes inside a block of size 40 free'd ==9160== at 0x484BB6F: operator delete(void*, unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==9160== by 0x1150A1: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x115001: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x115001: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x115001: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x115292: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x115001: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x114F98: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x115001: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x115001: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x115001: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x115001: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== Block was alloc'd at ==9160== at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==9160== by 0x1147E3: sjtu::AAtree<int, int, std::less<int> >::insert(sjtu::AAtree<int, int, std::less<int> >::Node*, sjtu::pair<int const, int> const&, sjtu::AAtree<int, int, std::less<int> >::Node*) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x114850: sjtu::AAtree<int, int, std::less<int> >::insert(sjtu::AAtree<int, int, std::less<int> >::Node*, sjtu::pair<int const, int> const&, sjtu::AAtree<int, int, std::less<int> >::Node*) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x1148A7: sjtu::AAtree<int, int, std::less<int> >::insert(sjtu::AAtree<int, int, std::less<int> >::Node*, sjtu::pair<int const, int> const&, sjtu::AAtree<int, int, std::less<int> >::Node*) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x1148A7: sjtu::AAtree<int, int, std::less<int> >::insert(sjtu::AAtree<int, int, std::less<int> >::Node*, sjtu::pair<int const, int> const&, sjtu::AAtree<int, int, std::less<int> >::Node*) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x114850: sjtu::AAtree<int, int, std::less<int> >::insert(sjtu::AAtree<int, int, std::less<int> >::Node*, sjtu::pair<int const, int> const&, sjtu::AAtree<int, int, std::less<int> >::Node*) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x1148A7: sjtu::AAtree<int, int, std::less<int> >::insert(sjtu::AAtree<int, int, std::less<int> >::Node*, sjtu::pair<int const, int> const&, sjtu::AAtree<int, int, std::less<int> >::Node*) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x1148A7: sjtu::AAtree<int, int, std::less<int> >::insert(sjtu::AAtree<int, int, std::less<int> >::Node*, sjtu::pair<int const, int> const&, sjtu::AAtree<int, int, std::less<int> >::Node*) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x1148A7: sjtu::AAtree<int, int, std::less<int> >::insert(sjtu::AAtree<int, int, std::less<int> >::Node*, sjtu::pair<int const, int> const&, sjtu::AAtree<int, int, std::less<int> >::Node*) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x1148A7: sjtu::AAtree<int, int, std::less<int> >::insert(sjtu::AAtree<int, int, std::less<int> >::Node*, sjtu::pair<int const, int> const&, sjtu::AAtree<int, int, std::less<int> >::Node*) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x1148A7: sjtu::AAtree<int, int, std::less<int> >::insert(sjtu::AAtree<int, int, std::less<int> >::Node*, sjtu::pair<int const, int> const&, sjtu::AAtree<int, int, std::less<int> >::Node*) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x1148A7: sjtu::AAtree<int, int, std::less<int> >::insert(sjtu::AAtree<int, int, std::less<int> >::Node*, sjtu::pair<int const, int> const&, sjtu::AAtree<int, int, std::less<int> >::Node*) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== Test 13 Passed! Test 14 Passed! ==9160== ==9160== HEAP SUMMARY: ==9160== in use at exit: 0 bytes in 0 blocks ==9160== total heap usage: 325,886 allocs, 325,886 frees, 13,111,520 bytes allocated ==9160== ==9160== All heap blocks were freed -- no leaks are possible ==9160== ==9160== ERROR SUMMARY: 4308 errors from 1 contexts (suppressed: 0 from 0) ==9160== ==9160== 4308 errors in context 1 of 1: ==9160== Invalid read of size 4 ==9160== at 0x10FDB9: check13() (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x110CC9: main (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== Address 0x6c8bcfc is 28 bytes inside a block of size 40 free'd ==9160== at 0x484BB6F: operator delete(void*, unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==9160== by 0x1150A1: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x115001: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) ==9160== by 0x115001: sjtu::AAtree<int, int, std::less<int> >::erase(sjtu::AAtree<int, int, std::less<int> >::Node*, int const&) (in /root/code/STLite/STLite-ACM-2025/map/build/map_three_men) map_three_men: bool check13(){ // erase(it--) sjtu::map<int, int> Q; std::map<int, int> stdQ; int num[30001]; num[0] = 0; for(int i = 1; i <= 30000; i++) num[i] = num[i - 1] + rand() % 325 + 1; for(int i = 1; i <= 60000; i++) swap(num[rand() % 30000 + 1], num[rand() % 30000 + 1]); for(int i = 1; i <= 30000; i++){ int t = rand(); stdQ[num[i]] = t; Q[num[i]] = t; } // cout << "Stage 1" << endl; sjtu::map<int, int>::iterator it; std::map<int, int>::iterator stdit; for(int i = 1; i <= 60000; i++) swap(num[rand() % 30000 + 1], num[rand() % 30000 + 1]); for(int i = 1; i <= 10325; i++){ it = Q.find(num[i]); if(it != Q.begin()) { Q.erase(it--); // cout << "Erase it" << endl; } stdit = stdQ.find(num[i]); if(stdit != stdQ.begin()) { stdQ.erase(stdit--); // cout << "Erase it" << endl; } // cout << "i = " << i << " it, std = " << (it)->first << " " << stdit->first << endl; if(it -> first != stdit -> first)return 0; } // cout << "Stage 2" << endl; if(Q.size() != stdQ.size()) return 0; it = Q.begin(); // cout << "Stage 3" << endl; for(stdit = stdQ.begin(); stdit != stdQ.end(); stdit++){ if(stdit -> first != it -> first) return 0; if(stdit -> second != (*it).second) return 0; it++; } stdit = --stdQ.end(); for(it = --Q.end(); it != Q.begin(); it--){ if(stdit -> first != it -> first) return 0; if(stdit -> second != (*it).second) return 0; stdit--; } return 1; }
最新发布
05-28
<think>好的,我现在需要解决用户提到的C++中自定义AA树实现的map容器在erase操作时出现的valgrind内存错误问题,具体是Invalidread ofsize4。首先,我得回忆一下Valgrind的这个错误通常意味着什么。Invalidread通常表示程序试图访问已经释放的内存,或者越界访问。而size4可能是指读取了一个int类型的变量,因为int在32位系统通常是4字节。用户使用的是自己实现的AA树作为map容器,erase操作时出现问题。AA树是一种自平衡二叉搜索树,erase操作涉及节点删除和树的再平衡。内存错误可能出现在节点删除过程中没有正确管理指针或内存释放。 首先,可能的问题点有几个方面: 1. **悬空指针**:在删除节点后,可能还有指针指向已释放的内存。例如,父节点或兄弟节点仍然引用已删除的节点,导致后续操作读取了无效内存。 2. **重复释放**:同一块内存被释放多次,可能在删除节点时没有正确置空指针,导致后续再次释放。 3. **内存泄漏**:相反,如果节点没有被正确释放,也可能导致其他问题,但valgrind的Invalidread更可能是访问已释放内存。接下来,我需要考虑AA树erase的具体实现。通常,删除节点需要处理几种情况:节点是叶子节点、有一个子节点、或有两个子节点。在AA树中,删除可能涉及 skew和 split操作来维持平衡。可能的错误点: -**未正确更新父节点指针**:删除节点后,父节点的左右指针可能仍指向被删除的节点,导致后续访问时读取无效内存。- **未正确处理节点的子节点**:在删除有两个子节点的节点时,通常会找到后继节点替换,并删除后继节点。如果后继节点的处理不当,可能导致指针错误。- **未在删除后正确调整树结构**:AA树的平衡操作(skew/split)可能在删除后未正确执行,导致树的结构出现异常,进而引发内存访问错误。 需要检查代码中删除节点的逻辑,特别是释放内存后是否将所有指向该节点的指针置为nullptr,或者是否确保没有其他地方保留该节点的引用。例如,在删除节点时,如果父节点没有及时更新子指针,就可能出现悬空指针。 另外,参考用户提供的引用内容,他们之前可能遇到深拷贝问题。虽然当前问题与erase相关,但深拷贝问题可能导致对象复制时资源管理不当。但当前问题更可能出现在删除逻辑,而不是拷贝构造。不过,如果节点的数据成员在拷贝时没有正确管理,可能在删除时引发问题。例如,如果节点中的数据是浅拷贝的,删除时可能导致重复释放。根据用户提供的引用代码中的深拷贝示例,可能用户之前遇到过拷贝构造函数未正确实现的问题。如果在AA树的实现中,节点复制时没有正确进行深拷贝,可能导致多个节点指向同一块内存,删除其中一个节点后,其他节点仍引用该内存,导致Invalid read。不过,当前问题出现在erase操作,所以更应关注删除过程中的资源释放和指针管理。可能的解决方案步骤: 1. **检查删除节点的指针更新**:确保删除节点后,父节点、子节点的指针正确更新,避免留下悬空指针。2.**验证内存释放是否正确**:确保每个delete或delete[]操作对应正确的new/new[],并且没有重复释放。3.**使用Valgrind详细日志**:通过Valgrind的输出确定具体哪一行代码引发了Invalid read,从而定位到错误的源头。 4. **检查AA树平衡操作的实现**:skew和split操作可能在删除后未正确执行,导致树结构破坏,进而引发非法内存访问。例如,在删除节点后,若在平衡调整过程中访问了已经释放的节点,就会导致Invalidread。需要确保在调整树结构时,所有操作的节点都是有效的,未被释放的。另外,考虑使用智能指针管理节点内存,但可能在自定义数据结构中,手动管理更为常见,所以需要仔细检查手动管理的部分。可能的具体代码问题:假设节点结构类似:struct Node{Key key;Value value;Node*left;Node* right;int level; };在删除节点时,首先找到要删除的节点,处理其子节点,然后释放该节点内存。如果在释放后,某些操作(如平衡调整)仍然试图访问该节点,就会导致错误。 例如,在删除节点后,父节点的left或right指针未被及时置空,后续遍历时访问该指针就会读取已释放内存。 解决方案:在释放节点内存后,立即将父节点对应的指针置为nullptr,或者在删除过程中确保所有相关指针都正确更新。另一个可能的情况是,在删除节点时,未正确处理子节点的父指针。例如,如果节点有左子节点,在删除该节点后,左子节点的父指针需要正确指向新的父节点,否则可能在后续操作中访问到无效的父节点。 此外,在AA树的实现中,可能需要递归地删除节点,此时需要确保递归调用中没有使用已经释放的节点指针。 总结可能的解决步骤: 1.使用Valgrind定位具体出错的位置,查看调用堆栈,确定哪部分的代码引发了Invalidread。2.检查该处代码涉及的指针是否在删除后未被置空或未正确更新。 3.确保所有删除操作后,相关的父节点、子节点指针都被正确更新,避免遗留悬空指针。4.验证AA树的删除算法实现是否正确,特别是处理节点替换和平衡调整的部分。 例如,在删除含有两个子节点的节点时,通常需要找到后继节点(右子树的最小节点),然后用后继节点的值替换当前节点,再删除后继节点。如果在这个过程中,后继节点的父指针未正确更新,可能导致后续操作访问无效内存。另外,检查内存释放是否正确,例如对于Node结构体,是否使用delete而非delete[],反之如果节点内部分配了数组,是否使用delete[]。例如,如果节点中的key或value是动态分配的内存,并且在删除节点时没有正确释放这些内存,可能导致内存泄漏,但当前问题更可能是访问已释放的节点结构体本身。 最后,参考用户之前提到的深拷贝问题,如果在AA树的节点复制过程中(如果有的话)未正确实现深拷贝,可能导致多个节点共享同一资源,删除时重复释放。但通常map容器中的erase操作不涉及节点复制,而是直接删除,所以可能该问题与拷贝构造函数无关,除非在实现中有其他操作导致节点被错误复制。需要用户提供更多代码细节才能更准确判断,但基于现有信息,以上是可能的原因和解决方案方向。</think>### C++ AA树map容器erase操作内存错误解决方案 #### 问题定位与常见原因 Valgrind报告`Invalid read of size 4`通常由**访问已释放内存**或**指针未正确更新**引起。在AA树实现中,具体原因可能包括: 1. **悬空指针**:删除节点后,父节点或兄弟节点仍保留对已释放节点的引用[^1]。 2. **平衡操作错误**:`skew`或`split`操作中访问了已删除节点的子节点。 3. **后继节点处理不当**:替换被删除节点时,未正确更新父节点指针。 --- #### 分步解决方案 1. **验证指针更新逻辑** - 在释放节点前,确保父节点的`left`/`right`指针已置空: ```cpp void erase(Node* target) { Node* parent = find_parent(target); if (parent->left == target) { parent->left = nullptr; // 立即置空 } else { parent->right = nullptr; } delete target; // 最后释放内存 } ``` 2. **检查平衡操作** - 在`skew`和`split`函数中,添加空指针检查: ```cpp Node* skew(Node* node) { if (node == nullptr || node->left == nullptr) return node; // 平衡操作代码... } ``` 3. **后继节点替换的完整性** - 当用后继节点替换被删除节点时,需更新父节点指针: ```cpp Node* successor = find_min(node->right); node->key = successor->key; node->value = successor->value; erase(successor); // 递归删除需确保指针更新 ``` 4. **Valgrind调试辅助** - 通过`--track-origins=yes`定位未初始化值的来源: ```bash valgrind --tool=memcheck --leak-check=full --track-origins=yes ./your_program ``` --- #### 关键代码示例 ```cpp // 删除节点时正确处理父子关系 void erase_impl(Node*& node, const Key& key) { if (!node) return; if (key < node->key) { erase_impl(node->left, key); } else if (key > node->key) { erase_impl(node->right, key); } else { if (node->left && node->right) { // 处理有两个子节点的情况 Node* successor = find_min(node->right); node->key = successor->key; node->value = successor->value; erase_impl(node->right, successor->key); // 递归删除后继节点 } else { Node* temp = node; node = (node->left) ? node->left : node->right; delete temp; // 释放内存后,node指针已被上层更新 } } // 删除后重新平衡 if (node) { node = skew(node); node = split(node); } } ``` --- #### 验证方法 1. **单元测试**:覆盖删除根节点、叶子节点、内部节点的场景。 2. **Valgrind内存检查**:确保无`Invalid read`和`definitely lost`错误。 3. **压力测试**:插入/删除10万次后验证树的高度平衡性。 ---
评论 10
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值