首页 > 其他分享 >关联(Association) && 聚合(Aggregation) && 组合(Composition)

关联(Association) && 聚合(Aggregation) && 组合(Composition)

时间:2024-07-07 08:57:14浏览次数:21  
标签:const name 组合 对象 部件 Aggregation && 聚合 Composition

组合

概述

在现实生活中,复杂的对象通常是由小的,简单的组成,从简单对象构建复杂对象的过程称为对象组合

例如

  • 汽车是用金属框架,发动机,轮胎,变速器和其他大量零件制造而成的
  • 个人电脑由CPU,主板,内存等组成
  • 即便是你也是由较小部分组成:头,身体,腿,手

从广义上讲,两个对象存在关系构成了对象组合模型

  • 汽车有变速箱,你的电脑有CPU,你自己有心脏
  • 复杂对象有时被称为整体或父对象,简单的对象通常被称为零件,子零件或组件

在C++中,你已经看到结构和类可以具体各种类型的数据成员(例如基础类型和其他类)

当我们使用数据成员构建类时,实际上是从较简单的部分构造复杂对象,即对象组合,因此,有时将结构和类称为复合类型

对象组合在C++上下文中很有用,因为它使我们可以通过组合更简单,更易于管理的创建复杂的类

这降低了复杂,并且使我们能够更快地编写代码并减少错误,因此我们可以重用已经编写,测试和验证过的有效代码

对象组合的类型

对象组合有两种基本的子类型

  • 组合(composition)
  • 聚合(aggregation)

关于术语的注释:术语“组合”通常用于指组合和聚合,而不仅指组合

在本篇文章中,当我们提及两者时,将术语“对象组合”,而当具体提及组合,将使用术语组合(composition)

组合

为了符合组合条件,对象和零件必须具有以下关系

  • 该部件(成员)是对象(类)的一部分
  • 该部件(成员)一次只能属于一个对象(类)
  • 该部件(成员)的存在由对象(类)管理
  • 该部件(成员)不知道对象(类)的存在

一个真实的组合例子就是一个人的身体和心脏的关系

组合的构成是部件 - 整体的关联,部件是整体的一部分

  • 例如,心脏是人的身体的一部分。组合中的部件一次只能是一个对象的一部分
  • 属于一个人的身体的心脏不能同时属于另一个人的身体

在组合构建中,对象负责部件的存亡

  • 意味着,部件在对象创建的时候创建,并在销毁对象时销毁
  • 从更广的范围来说,这意味着,对象不需要用户参与管理部件的生命周期
  • 例如,当创建一个身体时,心脏也被创建。当一个人的身体被破坏时,他们的心脏也被破坏。因此,这就构成了“死亡关系”

部件不知道整体的存在

  • 你的心脏幸福地运转着,却没有意识到它是更大结构的一部分
  • 我们称其为单向关系,因此身体了解心脏,但反之未然

部件的可移植说明

  • 心脏可以从一个身体移植到另外一个身体
  • 移植后仍然符合组合关系的要求(心脏现已由接受者拥有,除非再次移植,否则它只能是接受者的一部分)

例子

class Fraction
{
    private:
        int m_numerator;
        int m_denominator;

    public:
        Fraction(int numerator=0, int denominator=1):
            m_numerator{ numerator }, m_denominator{ denominator }
        {
            // We put reduce() in the constructor to ensure any fractions we make get reduced!
            // Since all of the overloaded operators create new Fractions, we can guarantee this will get called here
            reduce();
        }
};
  • 此类具有两个数据成员:分子和分母。分子和分母是分数的一部件(包含在其中)
  • 它们一次不能属于一个以上分数。分子和分母不知道它们是分数的部件,它们知道表示某个整数
  • 创建分数实例的时候,同时创建分子和分母两个成员变量。当分数被销毁的时候,分子和分母的成员变量也被销毁

更多例子

#include <iostream>
#include <string.h>

class Point2D
{
    private:
        int m_x;
        int m_y;

    public:
        // a default constructof
        Point2D(): m_x{0}, m_y{0}
        {

        }

        // a specific constructor
        Point2D(int x, int y): m_x{x}, m_y{y}
        {

        }

        // an overloaded output operator
        friend std::ostream& operator <<(std::ostream& out, const Point2D &point)
        {
            out << '(' << point.m_x << ", " << point.m_y << ')';
            return out;
        }

        // Access functions
        void setPoint(int x, int y)
        {
            m_x = x;
            m_y = y;
        }
};


class Creature
{
    private:
        std::string m_name;
        Point2D m_location;
    
    public:
        Creature(const std::string &name, const Point2D &location)
            :m_name{name}, m_location{location}
        {
            
        }

        friend std::ostream& operator<<(std::ostream& out, const Creature &creature)
        {
            out << creature.m_name << " is at " << creature.m_location;
            return out;
        }

        void moveTo(int x, int y)
        {
            m_location.setPoint(x, y);
        }
};

int main() {

    std::cout << "Enter a name for your creature.";
    std::string name;
    std::cin >> name;
    Creature creature{name, {4, 7}};

    while (true) {
        // print the creature's name and location
        std::cout << creature << '\n';

        std::cout << "Enter new X location for creature (-1 to quit): ";
        int x{0};
        std::cin >> x;
        if (x == -1) {
            break;
        }
        
        std::cout << "Enter new Y location for creature (-1 to quit):";
        int y{0};
        std::cin >> y;
        if (y == -1) {
            break;
        }
        creature.moveTo(x, y);
    }

    return 0;
}

组合的变体

尽管大多数组合模型中,部分随着整体创建而创建,部分随整体销毁而销毁。但是还有一些不一样的情况

例如

  • 在组合关系中,可能会延迟某些部分的创建,直到它们需要为止。例如,用户为字符串分配一些要保留的数据之前,字符串类可能不会创建动态字符数组
  • 在组合关系中,可能会选择使用已提供给它的零件作为输入,而不是自己创建零件
  • 在组合关系中,可能会将其部分的销毁要求委托给其他对象。如,垃圾回收例程

这里的关键点是,组合中整体管理部分,而组合的用户则不需要进行任何管理

组合和子类

当涉及对象组合时新程序员经常会问一个问题:“我什么时候应该使用子类而不是直接实现功能?”

例如,我们可以不使用Point2D类来实现Creature的位置,而可以只向Creature类添加2个整数,并在Creature类中编写代码来处理位置

将Point2D设为自己的类有很多好处:

  • 每个对象可以保持简单明了,专注于出色地完成一项任务,由于它们职责更分明,因此使这些类更容易编写和理解
    • 例如,Point2D只关注与点有关的时间,这使理解和编写代码更加简单
  • 每个子类可以独立的,这使得它们可以重用
    • 例如,我们可以在完全不同的应用程序中重用Point2D类
    • 或者,我们需要另外一个点作为试图要到达的目的地,那么可以简单添加另一个Point2D成员变量
  • 父类可以让子类完成大部分的工作,而自己专注协调子类之间的数据流。这有助于降低父对象的整体复杂性,因为它可以将任务委派给已经知道如何执行这些任务的子对象
    • 例如,当我们移动Creature时,它将任务委派给Point类,该类已经了解如何设置点
    • 因此,Creature类不必担心如何实现这些事情

一个好的经验法则是,应该构建单个类对应单个任务

在我们的示例中, 很明显Creature不必担心Point的实现方式或名称的存储方式

  • Creature的工作不需要知道哪些私密的细节
  • Creature的工作是担心如何协调数据流,并确保每个子类都知道应该做什么。接下来有各个子类担心它们怎么做

聚合

概述

要符合聚合条件,整个对象及其各个部件必须具有以下关系

  • 该部件(成员)是对象(类)的一部分
  • 该部件 (成员) 一次可以属于多个对象(类)
  • 该部件 (成员)的存在不由对象(类)管理
  • 该部件(成员)不知道对象(类)的存在

像组合一样,聚合仍然是部件与整体的关系,其中部件包含在整体中,并且是单向关系,但是与组合不同,部件一次可以属于一个以上的对象,而整个对象对部件的存在和生命周期不负责

创建聚合时,聚合不负责创建部件,销毁聚合时,聚合不负责销毁部件

例如,考虑一个人与其家庭住址之间的关系

  • 在此示例中,为简单起见,我们将说每个人都有一个地址。但是,该地址一次可以属于一个以上的人:如你和你的室友或其他重要的人
  • 但是,该地址不是由该人管理的-该地址可能在此人到达之前就已存在,而在该人离开后仍然存在
  • 另外,一个人知道他们住的地方,但地址不知道他们住的地方
  • 因为这是一个聚合关系

另外,考虑一下汽车和引擎

  • 汽车发动机是汽车的一部分,尽管引擎属于汽车,但它也可以属于其他事物,例如拥有汽车的人
  • 汽车不负责发动机的制造和毁坏
  • 虽然汽车知道它有引擎,但是引擎却不知道它是汽车的一部分

在对物理对象建模时,使用“破坏”一词可能会有些麻烦

  • 有人可能会争辩说“如果流星从天上掉下来砸碎了汽车,汽车零件也不会全部被毁吗?”当然是,但这是流星的错
  • 重要的是,汽车对零件的损坏不承担任何责任(但可能会受外力的影响)

我们可以说聚合模型“有”关系(一个部分有老师,汽车有引擎)。与组合类似,集合的各个部件可以单独的或相互调用

聚合案例

因为聚合与组合相似,因为它们都是部件 - 整体关系,因此它们的实现几乎完全相同,它们之间的差异主要是语义上的

在组合中,我们通常使用普通成员变量(或有组合类处理分配和释放过程的指针)将零件添加到组合中

在聚合中,我们还将部件添加为成员变量,但是,这些成员变量通常是引用或指针,用于指向已在类范围之外创建的对象。因此,聚合通常要么指向的对象用作构造函数参数,要么开始为空,然后再通过访问函数或运算符添加子对象

因为这些部分存在于类范围之外,所以当销毁该类时,指针或引用成员变量将销毁(但不会删除)。因此,部件本身仍将存在

#include <iostream>
#include <string>

class Teacher
{
    private:
        std::string m_name{};
    
    public:
        Teacher(const std::string& name): m_name{name}
        {

        }

        const std::string& getName() const
        {
            return m_name;
        }
};

class Department
{
    private:
        const Teacher& m_teacher;   // This dept holds only one teacher of simplicity, but it could hold many teachers
    public:
        Department(const Teacher& teacher): m_teacher{teacher}
        {

        }
};

int main() {

    // Create a teacher outside the scope of the Department
    Teacher bob {"Bob"};    // create a teacher

    {
        // Create a department and use the consturctos partameter to pass
        // the teacher to it.
        Department department {bob};
    }   // department goes out of scope here and is destoryed

    // bob still exists here, but the department doesn't
    std::cout << bob.getName() << " still exists! \n";
    return 0;
}
  • 首先,该部门只能容纳一位老师。其次,老师不会知道他们所属的部门
  • 所以在本例中,bob是独立于department创建的,然后传递给department的构造函数
  • 当department 被销毁的时,m_teacher引用被销毁。但是是教师本身并没有被销毁,因此它仍然存在,直到后来在main()被独立销毁

为建模选择正确的关系

尽管上面的例子中教师不知道他们为那么部门工作似乎有些愚蠢,但在给定程序的背景下这可能完全没问题

在确定要实现哪种关系时,请实现最简单的满足你的需求,而不是看起来最适合现实生活的关系

例如,如果你正在编写车身修理厂模拟器,则可能希望将汽车和引擎作为聚合来实现,因此可以将引擎卸下并放在架子上以备后用

但是,如果你正在编写赛车模拟游戏,则可能希望将汽车和引擎作为组合来实现,因为在这种情况下,引擎永远不会存在于汽车外部

关联

概述

要符合关联条件,一个对象与另一个对象必须具有以下关系

  • 关联的对象(成员)与对象(类)无关
  • 关联的对象(成员)一次可以属于多个对象(类)
  • 关联的对象(成员)没有由该成员(类)管理其存在
  • 关联的对象(成员)可能知道或可能不知道对象(类)的存在

与组合和聚合的部分是构成整体的一部分不同,在关联中,关联的对象与该对象无关

就像聚合一样,关联的对象可以同时属于多个对象,并且不受这些对象的管理

但是,与聚合(关系始终是单向的)不同的是,在关联中,关系可以是单向的也可以是双向的(两个对象相互了解)

  • 医生与患者之间的关系就是一个很好的例子
  • 医生显然与患者有关系,但从概念上讲,这个不是部分/整体(对象组合)的关系
  • 一个医生一天可以看很多病人,一个病人可以看很多医生(也许想要第二个医生意见,或者他们正在拜访不同类型的医生)
  • 该对象的寿命都没有和另外一个有关系
  • 我们可以说关联模型的是“用户-单个”关系。医生“使用”病人(以赚取收入)。患者使用医生(出于他们所需的任何健康目的)

案例

#include <cstdint>
#include <functional>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

// Since Doctor and Patient have a circular dependency, we're going to farward declare Patient
class Patient;

class Doctor
{
    private:
        string m_name{};
        vector<reference_wrapper<const Patient>> m_patient{};

    public:
        Doctor(const string& name): m_name{name}
        {

        }

        void addPatient(Patient& patient);

        // We'll implement this function below Patient since we need Patient to be defined at that point
        friend ostream& operator<<(ostream &out, const Doctor &doctor);

        const string& getName() const 
        {
            return m_name;
        }
};

class Patient
{
    private:
        string m_name{};
        vector<reference_wrapper<const Doctor>> m_doctor{}; // so that we can use it here

        // We're going to make private because we don't want the public to use it
        // They should use Doctor::addPatient() instead, which is publiy exposed
        void addDoctor(const Doctor& doctor) 
        {
            m_doctor.push_back(doctor);
        }

    public:
        Patient(const string& name): m_name {name}
        {

        }

        // We'll friend Doctor::addPatient() so it can access the private function Patient::addDoctor()
        friend ostream& operator << (ostream &out, const Patient &patient);

        const string& getName() const 
        {
            return m_name;
        }

        // We'll friend Doctor::addPatient() so it can access the private function Patient::addDoctor()
        friend void Doctor::addPatient(Patient& patient);
};

void Doctor::addPatient(Patient& patient)
{
    // Our doctor will add this patient
    m_patient.push_back(patient);

    // and the patient will also add this doctor
    patient.addDoctor(*this);
}

ostream& operator<<(ostream &out, const Doctor &doctor)
{
    if (doctor.m_patient.empty()) {
        out << doctor.m_name << " has no patients right now";
        return out;
    }

    out << doctor.m_name << " is seeing patients: ";
    for (const auto& patient: doctor.m_patient) {
        out << patient.get().getName() << ' ';
    }

    return out;
}

ostream& operator<<(ostream &out, const Patient &patient)
{
    if (patient.m_doctor.empty()) {
        out << patient.getName() << " has no doctors right now";
        return out;
    }

    out << patient.m_name << " is seeding doctors: ";
    for (const auto& doctor: patient.m_doctor) {
        out << doctor.get().getName() << ' ';
    }

    return out;
}

int main() {

    // Create a Patient outside the scope of the Doctor
    Patient dave {"Dave"};
    Patient frank {"Frank"};
    Patient betsy {"Betsy"};

    Doctor james {"James"};
    Doctor scott {"Scott"};

    james.addPatient(dave);

    scott.addPatient(dave);
    scott.addPatient(betsy);

    cout << james << '\n';
	cout << scott << '\n';
	cout << dave << '\n';
	cout << frank << '\n';
	cout << betsy << '\n';
    return 0;
}

因为关联是一种广泛的关系,所以可以用许多不同的方式来实现它们。但是,大多数情况下,关联是使用指针实现的,其中对象指向关联的对象

在这个示例中,我们将实现双向“医生/患者”关系,因为让医生知道他们的患者是有意义的,反之亦然

通常,如果可以使用单向关联,则应避免使用双向关联,因为它们会增加复杂性,并且往往更容易编写而不会错误

反身关联

有时对象可能与相同类型的其他对象有关系,这被称为反身关联

反身关联的一个很好例子是大学课程与其先决条件(也就是大学课程)之间的关系

考虑简化的情况,其中一门课程只能有一个前提条件。我们可以做这样的是事情

include <string>
   class Course
   {
       private:
           std::string m_name;
           const Course *m_prerequisite;

       public:
           Course(const std::string &name, const Course *prerequisite = nullptr): m_name{name}, m_prerequisite{prerequisite}
           {
           }
   };
  • 这可能会导致关联链(课程具有先决条件等)

关联可以是间接的

#include <iostream>
#include <string>
using namespace std;

class Car
{
    private:
        string m_name;
        int m_id;
    
    public:
        Car(const string& name, int id): m_name{name}, m_id{id}
        {

        }

        const string& getName() const
        {
            return m_name;
        }

        int getId() const
        {
            return m_id;
        }
};

// Our CarLot is essentially just a static array of Cars and a lookup function to retrieven them
// Because it's static, we don't need to allocate an object of type CarLot to use it
class CarLot
{
    private:
        static Car s_carLot[4];
    
    public:
        CarLot() = delete;  // Ensure we don't try to create a CarLot

        static Car* getCar(int id)
        {
            for (int count{0}; count < 4; ++count) {
                if (s_carLot[count].getId() == id) {
                    return &(s_carLot[count]);
                }
            }
            
            return nullptr;
        }
};

Car CarLot::s_carLot[4]{{"Prius", 4}, {"Corolla", 17}, {"Accord", 84}, {"Matrix", 62}};

class Driver
{
    private:
        string m_name;
        int m_carId;        // we're associated with the Car by ID rather than pointer

    public:
        Driver(const string& name, int carId): m_name{name}, m_carId{carId}
        {

        }

        const string& getName() const 
        {
            return m_name;
        }

        int getCarId() const
        {
            return m_carId;
        }
};

int main() {

    Driver d {"Franz", 17};                     // Franz is driving the car with ID 17

    Car *car {CarLot::getCar(d.getCarId())};    // Get that car from the car lot

    if (car) {
        cout << d.getName() << " is driving a " << car->getName() << '\n';
    } else {
        cout << d.getName() << " couldn't find his car \n";
    }

    return 0;
}

在上面的示例中,我们有一个CarLot来存放我们的汽车。需要汽车的驾驶员没有指向他的汽车的指针-相反,他具有汽车ID,我们可以使用该ID在需要时从CarLot中获取汽车

在这个特定示例中,以这种方式执行操作有点愚蠢,因为将Car将退出CarLot要求的查找效率很低(将两者连接的指针要快得多)

但是,通过唯一的ID而不是指针来引用事物也具有优势

  • 例如,你可以引用当前不在内存中的内容(也许它们在文件或数据中,并且可以按需加载)
  • 同样,指针可以占用4或8个字节。如果空间有限,并且唯一对象的数量很少,则用8位或16位整数引用它们可以节省大量内存

总结

组合和聚合的总结

组合

  • 通常使用普通成员变量
  • 如果类本身处理对象分配/取消,则可以使用指针成语
  • 负责部件的创建/销毁

聚合

  • 通常使用指向或引用位于聚合类范围之外的对象的指针或引用成员
  • 不负责创建/销毁部件

值得注意的是,组合和聚合的概念不是相互排斥的,可以在同一类中自由混合。完全可以编写一个类来负责创建/销毁某些部分,而不是其他

例如,我们的部门类可以有一个名字和一个老师类

  • 该名称可以按组合添加到部门中,并随部门一起创建和销毁
  • 另一方面,将通过汇总将教师添加到部门,并独立创建/销毁该老师

尽管聚合可能非常有用,但它们也可能更加危险,因为聚合无法处理其部分的重新分配,解除分配留给外部人员完成。如果外部方不再指向废弃部分的指针或引用,或者只是忘记进行清理(假设该类将进行清理),则内存将被泄漏

出于这个原因,应该优先使用组合而不是聚合

例子

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Teacher
{
    private:
        std::string m_name{};
    
    public:
        Teacher(const std::string& name): m_name{name}
        {

        }

        const std::string& getName() const
        {
            return m_name;
        }
};

class Department
{
    private:
        vector<Teacher> m_listOfTeachers;
    public:
        Department()
        {

        }

        void add(const Teacher& teacher)
        {
            m_listOfTeachers.push_back(teacher);
        }

        friend std::ostream& operator<<(std::ostream& out, const Department &department)
        {
            out << "Department: ";
            for (auto& teac : department.m_listOfTeachers) {
                out << teac.getName() << ' ';
            }
            return out;
        }
};

int main() {

    // Create a teacher outside the scope of the Department
    Teacher t1 {"Bob"};
    Teacher t2 {"Frank"};
    Teacher t3 {"Beth"};

    {
        // Create a department and add some Teachers to it
        Department department{};    // create an empty Department

        department.add(t1);
        department.add(t2);
        department.add(t3);

        cout << department;
    }   // department goes out of scope here and is destoryed

    cout << t1.getName() << " still exists! \n";
    cout << t2.getName() << " still exists! \n";
    cout << t3.getName() << " still exists! \n";
    return 0;
}

组合 vs 聚合 vs 关联 总结

属性组合聚合关联
关系类型整体/部分整体/部分没有特定关系
成员属于多个类别
类管理成员生命周期
方向性单向单向单向或双向
关系动词Part-ofHas-aUses-a

标签:const,name,组合,对象,部件,Aggregation,&&,聚合,Composition
From: https://blog.csdn.net/weixin_40398522/article/details/140241178

相关文章

  • Vue3的Composition API:Composition API是Vue3的一大新特性,它提供了一种更灵活的方式来
    1.介绍1.什么是CompositionAPI CompositionAPI是Vue.js3.0中引入的一项新特性,提供了一种新的、更灵活的方式来组织Vue组件的代码。CompositionAPI是基于函数的,并允许在组件的setup函数中直接处理响应式数据和生命周期钩子,使得代码更加清晰,更便于维护和测......
  • Mathematica Partial Fraction Decomposition
    遇到的问题Mathematica中有一个自带的部分分式分解函数Apart。In:=Apart[(-3+x)/((-1+x)(1+x))]Out:=-(1/(-1+x))+2/(1+x)但是Apart遇到分解结果中带无理数的就会摆烂:In:=Apart[x/(1-x-x^2)]Out:=-(x/(-1+x+x^2))解决方案1我们有一个......
  • 覆盖路径规划经典算法 The Boustrophedon Cellular Decomposition 详解
    2000年一篇论文CoverageofKnownSpaces:TheBoustrophedonCellularDecomposition横空出世,解决了很多计算机和机器人领域的覆盖路径问题,今天我来详细解读这个算法。TheBoustrophedonCellularDecomposition算法详解这篇论文标题为"CoveragePathPlanning:TheB......
  • Vue 3 Composition API与Hooks模式
    Vue3的CompositionAPI引入了Hook函数的概念,这是一种更加模块化和可重用的状态管理和逻辑组织方式。自定义Hook首先,我们创建一个自定义Hook,例如useCounter,它封装了计数器的逻辑://useCounter.jsimport{ref}from'vue';exportfunctionuseCounter(){c......
  • Vue3使用Composition API实现响应式
    title:Vue3使用CompositionAPI实现响应式date:2024/5/29下午8:10:24updated:2024/5/29下午8:10:24categories:前端开发tags:Vue3CompositionRefsReactiveWatchLifecycleDebugging1.介绍CompositionAPI是Vue.js3中新增的一组API,用于在组件中组合逻辑......
  • Vue3使用Composition API实现响应式
    title:Vue3使用CompositionAPI实现响应式date:2024/5/29下午8:10:24updated:2024/5/29下午8:10:24categories:前端开发tags:Vue3CompositionRefsReactiveWatchLifecycleDebugging1.介绍CompositionAPI是Vue.js3中新增的一组API,用于在组件中组合逻辑和功......
  • wpf 动画显示隐藏_[UWP]用Win2D和CompositionAPI实现文字的发光效果,并制作动画
    weixin_39880899于2020-12-1109:26:23发布阅读量521 收藏点赞数文章标签: wpf动画显示隐藏  1.成果 献祭了周末的晚上,成功召唤出了上面的番茄钟。正当我在感慨“不愧是Shadow大人,这难道就是传说中的五彩斑斓的黑?”“那才不是什么阴影效果,那是......
  • 【智应数】Singular Value Decomposition
    SVDDef(eigenvalue\eigenvector).Eigenvalue\(\lambda\)andeigenvector\(\bm{v}\)ofmatrix\(A\)satisfy$$A\bm{v}=\lambda\bm{v}.$$Lem1.Let\(M\in\mathbb{R}^{n\timesn}\)isasymmetricmatrix.Let\(\lambda_i\)and\(\bm{u}_i......
  • 今天学了vue3的composition API
    代码量:60左右时间:1h搏客量:1<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><linkrel="icon"href="/favicon.ico"><metaname="viewport"content="wi......
  • (MEGA详解)Memory enhanced global-local aggregation for video object detection (CVPR
    在视频中检测物体和在图像中检测物体的最大区别在于:信息存在于时间维度中。视频中孤立的帧可能会出现运动模糊、遮挡或失焦等问题,自然可以想到从整个视频中寻找线索来识别物体。当我们无法确定一个目标的类别时,我们会从其它帧中寻找一个与当前目标具有高度语义相似性的独特目标,并......