精华内容
下载资源
问答
  • 谷歌开源机器学习可视化工具 Facets:从全新角度观察数据 By 黄小天2017年7月18日 10:51 近日,出于支持 PAIR initiative的目的,谷歌发布了 Facets,一款开源的可视化工具。它可以帮助你理解、分析和...

    谷歌开源机器学习可视化工具 Facets:从全新角度观察数据

    By 黄小天2017年7月18日 10:51

    近日,出于支持 PAIR initiative的目的,谷歌发布了 Facets,一款开源的可视化工具。它可以帮助你理解、分析和调试 ML 数据集。Facets 包含两个部分——Facets Overview 和 Facets Dive——允许用户从不同的粒度观看数据的全景图,还可以轻易地被用在 Jupyter notebooks 之内,或者嵌入网页之中。除了开放 Facets 源代码,谷歌还创建了演示网站,Github 和网站地址见文中。


    • Github:https://github.com/pair-code/facets
    • 演示网站:https://pair-code.github.io/facets/


    从机器学习(ML)模型中取得最佳结果需要你对有数据有真正的理解。然而,ML 数据集的数据点一般有数百万种,每种包含数百个(甚至数千个)特征,致使不可能直观地理解整个数据集。可视化有助于解决大型数据集的这一难题。一图胜千言,而一个交互式可视化不止胜千言。


    出于支持 PAIR initiative,我们发布了 Facets,一款开源的可视化工具,帮助你理解和分析 ML 数据集。Facets 包含两个部分——Facets Overview 和 Facets Dive——允许用户从不同的粒度观看其数据的全景图。你可以使用 Facets Overview 可视化数据每一个特征,或者使用 Facets Dive 探索个别的数据观察集。这些可视化允许你调试数据,这在机器学习中和调试模型一样重要;还可以轻易地被用在 Jupyter notebooks 之内,或者嵌入网页之中。我们除了开放 Facets 源代码,还创建了演示网站,允许任何人在浏览器中直接可视化数据集而无需安装任何软件或设置,也无需数据离开你的计算机。


    Facets Overview


    Facets Overview 自动地帮助用户快速理解数据集中所有特征的值分布。多个数据集(比如训练集和测试集)可在同一个可视化中进行比较。束缚机器学习的一般性数据难题被推向最前端,比如出乎意料的特征值、具有高比例遗失值的特征、带有不平衡分布的特征,数据集之间的特征分布偏态(distribution skew)。



    加州大学尔湾分校(UCI)人口普查数据集 [1] 的 6 个数字特征的 Facets Overview。


    特征按照不均匀性排序,带有最大不均匀性分布的特征排在顶部。标红的数字表示可能的问题点,在这种情况下,带有高比例值的数字特征设置为 0。右边的柱状图允许你比较训练集(蓝色)和测试集(橙色)之间的分布。



    Facets Overview 展示了加州大学尔湾分校人口普查数据集 9 个分类特征中的 2 个。


    这些特征通过分布间距被排序,把训练集(蓝色)和测试集(橙色)之间带有最大偏态的特征排在顶部。由于测试集中的尾随时段(「<=50K」vs「<=50K.」),「目标」特征中标签值在训练和测试集中有所不同。这可在特征的图表中查看,也可在表中「顶部」列的条目中看到。该标签不匹配将导致对该数据进行训练和测试的模型不能被正确评估。


    Facets Dive


    Facets Dive 提供了一个易于定制的直观界面,用于探索数据集中不同特征数据点之间的关系。通过 Facets Dive,你可以控制位置、颜色和视觉表现。如果数据点有与其相关的图像,则图像可以用作视觉表示。



    Facets Dive 可视化显示了加州大学尔湾分校人口普查测试数据集中的 16281 个数据点。


    动图展示了通过对数据点颜色不同特征「关系」进行分别着色,连续特征「年龄」为一个维度,离散特征「婚姻状况」为另一个维度进行排列。



    Facets Dive 从「Quick Draw」数据集中生成的可视化效果,它显示了「Quick Draw」图片中笔画和点被正确地分类为人脸。


    Quick Draw 数据集:https://github.com/googlecreativelab/quickdraw-dataset


    Fun Fact:在大数据集中(如 CIFAR-10 数据集),一个小小的标签错误是很容易被忽视的。我们利用 Dive 检查了 CIFAR-10 数据集,并发现了一只青蛙猫——一只被标记为猫的青蛙。



    使用 Facets Dive 探索 CIFAR-10 数据集。在这里,基本分类标签为行,预测分类标签为列。


    这种组合就产生了混淆矩阵视图,我们可以在其中找到特定类型的错误分类。在上面的例子中,我们可以看到机器学习模型错误地将一些猫的图片分类为青蛙。把真实图形放在混淆矩阵中让我们发现的一个有趣现象是:这些「真猫」中的一只被模型预测为青蛙是因为它在视觉检查中被定义为青蛙,这是由于模型训练的数据集中它被人为地错误分类了。



    你能区分出猫和青蛙吗?


    在谷歌内部,Facets 已经展现出了巨大价值。现在,谷歌希望将这份便利分享到全世界,通过发现数据中更有趣的新特征来创造更加强大和准确的机器学习模型。因为 Facets 已经开源,你可以根据自己的需求自定义可视化内容,或为项目作出贡献。


    参考文献


    [1] Lichman, M. (2013). UCI Machine Learning Repository 

    [http://archive.ics.uci.edu/ml/datasets/Census+Income]. Irvine, CA: University of California, School of Information and Computer Science

    [2] Learning Multiple Layers of Features from Tiny Images , Alex Krizhevsky, 2009:https://www.cs.toronto.edu/~kriz/learning-features-2009-TR.pdf

    展开全文
  • 设计模式之--观察者模式

    千次阅读 2011-07-29 14:20:29
    在日常生活中,有很需要我们关注的事务(比如,股市,楼市等),这些事务我们可以称之为主题或者叫信息发布者,观察主题的目的是想了解主题的变化(消息)。一种方法当然是采用盯人策略,但这种方法有个固有的缺点...

    在日常生活中,有很多需要我们关注的事务(比如,股市,楼市等),这些事务我们可以称之为主题或者叫信息发布者,观察主题的目的是想了解主题的变化(消息)。一种方法当然是采用盯人策略,但这种方法有个固有的缺点,就是你盯住主题的时候,无法干其他事情,如果需要了解的主题比较多,这种办法就很麻烦了;另外一种就是主题广播,我想听的时候我就去听,不想听的时候我就不听,这种方式的好处就是可以使得观察者不用盯住主题,但缺点是如果信息发布者的信息发布是不固定的,观察者(信息接收者)可能会漏掉信息。这两种方式都各有利弊,局域网中的信息传送采用的其实就是主题广播方式,只是接收者也是盯人战术。还有一种方式就是信息发布者提供一个注册机制,如果你要关注这个主题,就可以登记,如果主题有消息时,就按协定好的方式来通知注册的观察者。这就是观察者模式的现实生活中的原型。

    《设计模式》中对观察模式的定义是:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。简图如下:

    示例代码如下:

    using System;
    using System.Collections;

    namespace DesignModelStudy
    {
    #region 观察者模式
    /* 名称:Observer 观察者模式
      * 意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
      * 适用性:
      * 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
         * 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
         * 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
      *
      * 结构:
      *      | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|                                  | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
      *      |   Subject        | observers                        |   Observer       |
      *      |------------------|--------------------------------→|------------------|
      *      | Attach(Observer) |                                  |   Update()       |
      *      | Detach(Observer) | | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|     |__________________|
      *      | Notify()  ○-----|-| for all o in observers{  |             |
      *      |__________________| |     o->Update() }        |             |
      *              △           |__________________________|             |
      *              |                                                     |
      *              |                                                     |
      *              |                                                     △
      *              |                                                     |             
      *  | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|                                  |   
      *  |    ConcreateSubject          | subject               | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|     
      *  |------------------------------|←---------------------|  ConcreateObserver     |         
      *  |    GetState()  ○------------|-| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄||------------------------|    | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|       
      *  |    SetState()                | |return subjectState ||   Update()  ○---------|----| observerState=subject->GetState()    |                    
      *  |------------------------------| |____________________||------------------------|    |______________________________________|
      *  |    subjectState              |                       |   observerState        |
      *  |______________________________|                       |________________________|
      *
      *
      *
      *
      * */
    #endregion
    /// <summary>
    /// 抽象观察者,定义了一种主题与具体观察者之间进行通信的标准,有两种含义:1,为主题定义了所能接收其注册的观察者的接口标准,
        /// 2定义了主题发布信息时与观察者之间通信的接口方法(观察者提供给主题进行传送消息的方法)。
    /// </summary>
    public abstract class Observer_Observer
    {
           public abstract void Update();
    }
        /// <summary>
        /// 抽象主题,主要实现主题注册和通知的机制(即观察者模式的基本机制的实现)
        /// 这样做的好处是只要是继承自抽象主题的子类都可以应用观察者模式。有利于主题的变化和扩展。
        /// </summary>
    public class Observer_Subject
    {
      private ArrayList observers = new ArrayList();
      //注册
      public virtual void Attach(Observer_Observer observer)
      {
       if(observers.IndexOf(observer)<0)
       {
        observers.Add(observer);
       }
      }
      //取消注册
      public virtual void Detach(Observer_Observer observer)
      {
       observers.Remove(observer);
      }
      //通知
      public virtual void Notify()
      {
       foreach( Observer_Observer o in observers)
       {
                   o.Update();
       }
      }
    }
        /// <summary>
        /// 具体的主题
        /// </summary>
    public class Observer_ConcreateSubject : Observer_Subject
    {
      private string _Name;
      private double _Value;
      public Observer_ConcreateSubject(string name,double val ) : base()
      {
       this._Name = name;
       this._Value = val;
      }
      public string Name
      {
       get
       {
        return _Name;
       }
       set
       {
        _Name = value;
       }
      }
      public double Value
      {
       get
       {
        return _Value;
       }
       set
       {
        _Value =value;
       }
      }
    }
    /// <summary>
    /// 具体观察者1
    /// </summary>
    public class Observer_ConcreateObserverA : Observer_Observer
    {
            private string _Name;
      private double _Value;
      private Observer_ConcreateSubject subject;
      public override void Update()
      {
       _Name = this.ToString()+"."+subject.Name;
       _Value = subject.Value;
       System.Windows.Forms.MessageBox.Show(_Name+":"+_Value.ToString());
      }
      public Observer_ConcreateObserverA(Observer_ConcreateSubject s)
      {
       this.subject = s;
      }
    }
        /// <summary>
        /// 具体观察者2
        /// </summary>
    public class Observer_ConcreateObserverB : Observer_Observer
    {
      private string _Name;
      private double _Value;
      private Observer_ConcreateSubject subject;
      public override void Update()
      {
       _Name = this.ToString()+"."+subject.Name;
       _Value = subject.Value;
       System.Windows.Forms.MessageBox.Show(_Name+":"+_Value.ToString());
      }
      public Observer_ConcreateObserverB(Observer_ConcreateSubject s)
      {
       this.subject = s;
      }
    }
    public class Observer_Client
    {
      public static void Test()
      {
       Observer_ConcreateSubject subject = new Observer_ConcreateSubject("Item",1000);
                Observer_Observer o1= new Observer_ConcreateObserverA(subject);
       Observer_Observer o2= new Observer_ConcreateObserverB(subject);
       subject.Attach(o1);
       subject.Attach(o2);
       subject.Notify();
      }
    }
    }

    考虑到主题的多样性和观察者的关注点和角度的多样性,上面的观察者模式具有很大的局限性:

    1)抽象主题采用抽象类来实现,就会导致一个主题只能接收一种观察者,如果主题本身就是有类层次结构的就要求系统必须支持多继承方式,这显然是个很过分的要求。抽象主题采用接口方式实现可以解决上述问题,这是一种改进;但又造成一个问题,因为从设计来讲,我们希望对象的责任能够比较单一,如果采用接口实现,那么对观察者的注册管理就必须由具体主题类来实现,这显然增加了主题类的责任(而且是额外的),一个解决办法就是将注册管理委托给一个对象类来进行,我们可以称之为注册管理对象,这样一来好处有2:一是使得主题的责任单一,二是解除了主题对观察者的依赖,这时候的多个主题之间不需要有公共的接口。当然,主题需要维护一个对注册类的引用,但即使是这样,好处还是很多,而且可扩展性变得更强;

    2)在实际应用中不同的观察者对于关注的主题和侧重点是不一样的,观察者必须知道具提要观察的主题的结构,这就造成了观察者对主题的依赖。这种依赖即使观察者模式的局限,也是观察者模式在很大程度上无法避免的情况。

    3)增加注册管理对象后,除了分离了一部分主题的责任,使得主题责任单一,并对观察者解耦外,还有一个好处就是可以在注册对象增加针对观察者的额外功能(比如观察者计数等)。但如果在不需要对观察者进行管理的情况,无论是改进前的还是改进后的观察者模式都有一个不利的地方,就是增加了开销,因为都需要维护一个观察者对象池。改进的办法就是利用事件来实现。利用事件来实现观察者模式有两个好处,一是只要一个事件类型定义,不需要定义接口和实现接口,二是不需要维护观察者对象池。如果将主题要发布的信息参数化,而且观察者除了要知道这个消息,并不需要了解主题的具体细节外,还可以解除观察者与主题之间的耦合关系。

    下面是改进后的观察者模式的实现示例:

    using System;
    using System.Collections;
    namespace DesignModelStudy
    {

    //观察者接口定义
    public interface Observer1
    {
      //被观察者发生改变时会调用观察者的这个方法。
      void Update(object subject);
    }
    /// <summary>
    /// 观察者聚合集,这样被观察者就知道了有那些观察者在注视它们,以便被观察者发生改变时好通知它们
    /// 但这里仅仅是个接口,具体的管理有实现类完成。
    /// </summary>
    public interface Subject1
    {
      //注册观察者
      void AddObserver(Observer1 observer);
      //取消注册
      void RemoveObserver(Observer1 observer);
      //实现该接口以通知所有的观察者.
      void Notify(object realSubject);
    }
    /// <summary>
    /// 具体实现观察者管理的具体对象。
    /// </summary>
    public class SubjectHelper : Subject1
    {
      private ArrayList observers = new ArrayList();
      public void AddObserver(Observer1 observer)
      {
       observers.Add(observer);
      }
      public void RemoveObserver(Observer1 observer)
      {
       observers.Remove(observer);
      }
      public void Notify(object realSubject)
      {
       foreach(Observer1 observer in observers)
       {
        observer.Update(realSubject);
       }
      }
    }
    //注意这里的Album不从Subject或SubjectHelper继承,显示了另外一种观察者模式
    //这样可以使得对象间的耦合度更小。
    //这是被观察者对象
    public class Album
    {
      private String name;
      //被观察者要知道观察者管理对象,以便通过这个类实现对观察者的管理和通知。
      private Subject1 playSubject = new SubjectHelper();
      public Album(String name)
      { this.name = name; }
      public void Play()
      {
       playSubject.Notify(this);
       // code to play the album
      }
      public String Name
      {
       get { return name; }
      }
      public Subject1 PlaySubject
      {
       get { return playSubject; }
      }
    }
       
    class Observer1_Client
    {
      //普通方式
         public static void Test()
      {
       BillingService billing = new BillingService();
       CounterService counter = new CounterService();
       Album album = new Album("Up");
       album.PlaySubject.AddObserver(billing);
       album.PlaySubject.AddObserver(counter);
       album.Play();
      }
            //利用事件来实现
      public static void Test1()
      {
       BillingService billing = new BillingService();
       CounterService counter = new CounterService();
       Album_1 album = new Album_1("Up");
       album.PlayEvent += new Album_1.PlayHandler(counter.Update);
       album.PlayEvent += new Album_1.PlayHandler(billing.Update);
       album.Play();
      }
    }
    //具体的观察者类,每个观察者对被观察对象的关注角度是不同的,下面仅仅列出两种观察类
    public class CounterService : Observer1
    {
      public void Update(object subject)
      {
       if(subject is Album_1)
        GenerateCharge((Album_1)subject);
      }
      private void GenerateCharge(Album_1 album)
      {
       string name = album.Name;
       //code to generate charge for correct album
       System.Windows.Forms.MessageBox.Show("Counter:"+name);
      }
    }
    public class BillingService : Observer1
    {
      public void Update(object subject)
      {
       if(subject is Album_1)
        GenerateCharge((Album_1)subject);
      }
      private void GenerateCharge(Album_1 album)
      {
       string name = album.Name;
       //code to generate charge for correct album
       System.Windows.Forms.MessageBox.Show("Billing:"+name);
      }
    }
        //事件方式实现.
        public class Album_1
        {
            private String name;
            public delegate void PlayHandler(object sender);
            public event PlayHandler PlayEvent;
            public Album_1(String name)
            { this.name = name; }
            public void Play()
            {
                Notify();
                // code to play the album
            }
            private void Notify()
            {
                if (PlayEvent != null)
                    PlayEvent(this);
            }
            public String Name
            {
                get { return name; }
            }
        }
    }
    后记:

    上面的讨论基本都局限于观察者与主题之间的组织,没有讨论观察者和主题之间的通信(上面的模式基本都采用调用通信),实际上主题和观察者之间的通信方式很多,但这与具体的操作系统和网络有关。在通信方式可以支持的情况下,不仅可以实现消除观察者和主题之间的耦合关系,还可以让观察者和主题位于不同的地址空间。

    典型应用:MVC模式,Delphi的数据感知控件和数据集,C#的控件和绑定数据源等。

    展开全文
  • 单目、双目与多目的区别

    万次阅读 多人点赞 2018-12-23 09:24:04
    在不断的学习中,发现了一个讲单目、双目与目特别好的文章,复制过来共享,不属于原创,如果原创作者追究版权问题,可以直接与我联系。 前言 摄像头在日常生活中非常常见,一般用来完成拍照、摄像这些基本的功能。...

    在不断的学习中,发现了一个讲单目、双目与多目特别好的文章,复制过来共享,不属于原创,如果原创作者追究版权问题,可以直接与我联系。

    前言

    摄像头在日常生活中非常常见,一般用来完成拍照、摄像这些基本的功能。但自动驾驶以及人工智能的到来,使得人们有了从摄像头中,获取更为智慧的结果的需求,即通过摄像头的视野,分析感知环境的变化,做出判断,将结果反馈到终端或者云端的处理器当中,服务于更丰富的应用。

    自动驾驶作为最先尝鲜的应用领域之一,摄像头很早就进入了科研专家的考察范围。业内研究最早同时实力最强的,是Mobileye。这家诞生于以色列的单目视觉公司,有着在汽车高级辅助驾驶系统领域12年的研发经验,提供芯片搭载系统和计算机视觉算法运行 DAS 客户端功能。

    公司的产品可实现车道偏离警告 (LDW)、基于雷达视觉融合的车辆探测、前部碰撞警告 (FCW)、车距监测 (HMW)、行人探测、智能前灯控制 (IHC)、交通标志识别 (TSR)、仅视觉自适应巡航控制 (ACC) 等功能,产品占据全球市场超过70%的份额。

    之所以要花这么大的篇幅去介绍Mobileye,是因为他们是业内单目摄像头解决方案的绝对领导者。

    对,没有之一,也没有任何含混不清,他们就是NO.1。这也是为什么芯片巨头英特尔愿意花153亿美金买下这家在从前名不见经传的小公司的原因。

    以上的表述中,眼尖的人一定主要到了一个词:单目摄像头。那么什么是单目摄像头呢?文章开头提到的三目摄像头又是什么鬼?有没有双目的呢?接下来,我将带大家一一解开这一困扰我一个多月的问题。

    单目摄像头

    目前应用于自动驾驶的路况判断,多以单目摄像头方案为主。也就是业界鼻祖Mobileye的看家本领。

    但单目摄像头有一个问题是,在测距的范围和距离方面,有一个不可调和的矛盾,即摄像头的视角越宽,所能探测到精准距离的长度越短,视角越窄,探测到的距离越长。这类似于人眼看世界,看的越远的时候,所能覆盖的范围就窄,看的近的时候,则覆盖的范围就广一些。

    人眼是双目的,在性能上要远优于人造产品,但在观察周遭环境的时候,也依然会遇到覆盖不全的问题。通俗点来讲,眼观六路,耳闻八方常常用来形容一个人机敏,能快速感知周围的环境状况。我们将这样的人视为聪慧的人,一般人很难达到这样的水平。但一双眼睛,怎能达到眼观六路?

    这不过是汉字语言的一种比喻的说法,并不能当真。因此,即使是如人一般昂贵精妙的双眼,也在实际的使用环境中,有力所不能及的时候。

    车载摄像头是定焦的,它无法像人眼一样快速变焦。不同的焦距可以满足不同的范围。

    目前的ADAS(高级辅助驾驶)所要求的是40米-120米的范围,未来将会达到200米甚至以上。Mobileye现在的技术水平,所能达到的也是120米范围以内,但从下一代EyeQ4、EyeQ5开始,Mobileye将会达到150-200米的视距。

    那么如何用一个定焦镜头解决不同距离的观察,就成为了困惑行业的难题。也因此,业内产生了双目甚至多目的方案,用来解决不同距离下摄像头看清、看准的问题。

    多目摄像头

    不同焦距的摄像头,与成像的清晰度是直接挂钩的。车载摄像头一般是固定焦距的,目前车载摄像头每秒处理的图像在20帧左右,每秒处理的数据量巨大。

    一方面车载摄像头从技术上是很难达到频繁变焦的,另一方面单个摄像头频繁变焦根本无法应对秒级的巨量数据接收处理。

    这也是为什么Mobileye前两年开始多目研究的原因。多目摄像头,可以通过不同的摄像头来覆盖不同范围的场景,既解决了摄像头无法来回切换焦距的问题,也可以一次性解决不同距离下识别清晰度的问题。

    比如说广角镜头用来看近处的环境,80度的覆盖30米左右的环境,60度覆盖中远距离,40度负责远距离观察。不同的摄像头负责观察不同距离、角度范围的场景,各司其职,互不干扰。

    但多目摄像头目前也并不是完美的解决方案,它会生出另外的一些难解问题。

    比如,第一,在汽车上如何放置的问题。汽车挡风玻璃处通常是各种配件聚集的地方,这里本身需要安装雨量传感器,以及有可能额外添加的激光雷达、行车记录仪,摄像头等等。这个地方本身已经拥挤不堪,留给多余设备的空间并不大。

    而多目摄像头本身又有一些要求,比如摄像头之间的距离要在10-20cm左右,这就更加剧了挡风玻璃处的拥挤。

    第二,多目摄像头的成本会翻倍,只从简单的硬件上就是单目的N倍,而且算法上的复杂度和成本也时成倍增加。多路图像数据的处理比单路数据处理的难度要大,这对处理芯片的要求以及硬件的可靠性要求增高。

    因此,多目摄像头的出现,既解决了一些现有问题,也衍生出了各种各样其它的问题。

    双目摄像头

    业内很多新兴的初创公司,为了避开与Mobileye的正面竞争,选择从双目切入。但双目也存在两个关键的问题。第一是成本问题,第二是安装位的问题。

    第一个问题是,双目的方案,两个镜头理论上要一模一样,因为一旦存在差异,会使得测量的准确性大打折扣。业内也会称之为立体摄像头。

    但一个摄像头是由6个光学的镜片和一些传感器组成,而玻璃镜片的生产制造是打磨出来的,并不是压制而成的。这就从根本上产生了镜片生产存在差异性的问题。

    所谓的单反摄像机,最昂贵的莫属一个个动辄数万的镜头,原因就在这里。大家都听过卡尔蔡司镜头吧,这个公司已经有超过100年的历史,他们的镜头在业内可谓鼎鼎大名。

    曾经手机界的扛把子诺基亚,在自己的手机中使用的就是这家公司的镜头。不客气的讲,当时同档次的诺基亚旗舰机N95的成像质量,真真是甩了iPhone一座珠港澳大桥的距离。

    摄像机镜头一般都有些参数,比如畸变度,对焦度,随着准确度的上升,误差范围的收窄,成本也是扶摇直上的,业内一般使用的镜头误差在5%的范围内。一般,单目摄像头的误差可通过后期算法的调教,进行有效的处理。

    但双目摄像头,由于本身测距的原理,导致会要求两个镜头之间的误差越小越好。如果两个镜头各自都有5%左右的误差,那么对于后期调教的算法,难度就会加大许多,而且还不能保证确定性。

    而如果要将两个摄像头的误差缩减到1%左右,达到使用的要求,那么摄像头的成本就会高不可攀。同时无论镜头生产商还是采购方,都需要面对产品不良率的问题。

    双目摄像头同时也存在摆放位置的问题。两个镜头之间的距离是10-20cm之间,这个距离需要非常精准,因为这会直接关系到测距的准确性。

    由于汽车使用的环境复杂多变,只是温度要求,都是在-40—85度。而传统器材必然有热胀冷缩的问题,那么这就会影响到两个镜头之间的距离。因此只有很高端的一些车型,会使用到立体摄像头,而且即使是使用了,场景也会非常有限。主动巡航和自动驾驶根本做不到。

    业内做的好的公司,能够将单目摄像头的误差控制在3%以内。理论上立体摄像头的误差可以做到1%以内,但从实际的应用层面来看,1%跟3%在现有的应用环境下,并不存在太大的差别。尤其是在单目摄像头搭配一些毫米雷达等传感器以后,完全可以达到类似的精度。

    双目摄像头的方案,在成本、制造工艺、可靠性、精准度等等综合因素的制约下,导致其难以在市场上推广,而单目摄像头低成本可靠性的解决方案,搭配其他传感器,完全可以满足L1、L2以及部分L3场景下的功能。因此在现有市场环境下,单目摄像头的解决方案依然会是主流。

    ES8的全系产品都搭载了三目前向摄像头、4个环视摄像头、5个毫米波雷达、12个超声波雷达。

    三目摄像头

    除了较为多见的双目摄像头方案,三目摄像头也进入了一些公司的视野,如蔚来的ES8。但三目摄像头是终极的解决方案吗?目前看来,并不是。

    三目摄像头诞生之初,目的是为了解决汽车前向测距的问题。众所周知,汽车行驶的过程中,如果要满足自动驾驶的要求,需要车身感知设备对前方两百米左右的道路环境做到精准测量,以求做出相应的控制决策。

    三个摄像头在判断、测算障碍物距离的时候,会有一个核心的逻辑问题。由于摄像头的精准度是有一定的误差范围,因此三个不同的摄像头,检测障碍物的精准性都会有一定的误差。

    那么在不同摄像头覆盖距离范围的交汇处,两个相邻摄像头测算出障碍物的距离很可能是不同的,会存在10米左右的误差。

    还有同样一个场景,两个镜头获取到的图像可能会不一样。对于这些“不寻常”的情况,专家并没有良策。而这些数据都是在同一时间获取到的,后台的算法会随时处理,运算的结果也会直接反馈到中央控制器,用来对汽车的驾驶行为做出调整。

    不同摄像头获取到同一场景的不同数据,需要在后台进行融合,某种意义上而言,就是数据处理结果的PK,谁更准确就采纳谁。但由于硬件的差异性本身存在,导致后台目前对于这种误差并没有合理的规则和解决方案去进行优胜劣汰的处理。

    这种无法决策的局面,还会同其它的传感器如激光雷达、毫米波雷达探测到的结果再进行一次融合(PK),多重的不确定性导致最终难以做出普遍意义上“正确合理”的决策。

    Mobileye也正是因为无法解决这一根本性的逻辑处理问题,导致在尝试了三目摄像头的方案之后,又回到了原来的单目摄像头的方案上。

    5年内单目还是主流

    处理摄像头数据的芯片,是制约摄像头解决方案的重要因素。Mobileye花了十年,才制造出了满足汽车电子规范要求的芯片,可见,符合车规要求的芯片难度之大。

    目前市场上,还未出现可以满足双目图像处理、符合车规要求、大规模商业化量产的芯片,已经量产的一些车型中类似于斯巴鲁使用的,仍然配合使用了FPGA的方案,来达到双目算法处理所需的算力要求。

    现在市场上并没有商业化的芯片能够支持多目摄像头的方案,都是FPGA的方式。而Mobileye的EyeQ4和 EyeQ5可以满足这样的要求,但Mobileye本身觉得这样的搭配方案并没有太大的必要。

    人眼实际上就是立体摄像头,但人都需要大规模的训练,才能达到测距的准确性。但坦白讲,印象中只有特种兵才可以通过手指比对的方式手动测出较为精准的距离,而我们普通人要通过双眼判断物体的准确距离,其实还是很困难的。

    双目摄像头的方案,理论上是可以达到很高的精度,但需要非常专业的技术团队,才有可能研发出来。但即使研发出来,也会面临严重的成本问题。

    企业本身做产品的目的就是推而广之,让产品大规模应用,而非束之高阁,以作观瞻。一个产品如果价格居高不下,对于实际的商业化应用,并没有太大的意义。

    也因此,有部分业内人士表示,至少在5年内,单目摄像头还将会是市场的主流,多目的方案还需匍匐前行。

    异形镜头可能是新的出路

    新的传感器技术,从镜头成像,到特殊异形镜头的设计,传感器技术的迭代更新,将有可能解决现有摄像头存在的问题。

    Mobileye用到的传感器是1/2.7寸的,或者1/3寸的,现在新的传感器技术可以达到1/1.7或1/1.5寸,已经提高了一到两倍。Mobileye跟摄像头厂商类似于舜宇这样的企业,正在共同研发下一代的异形摄像头。

    通常,业内使用的镜头是球形、椭圆形的,而新的异形镜头光圈很大,可以覆盖不同的测距范围,同时保证成像质量。

    另外,目前大部分摄像头厂家使用的摄像头都是100万像素,而Mobileye下一代的EyeQ4或者EyeQ5则会使用到400万甚至800万像素的摄像头。

    随着更大尺寸的传感器应用,单个摄像头可以保证1-200米内,任何一个焦段成像清晰。这将会彻底解决多目摄像头现存的一些问题,虽然目前成本较高,但它的前景还是非常值得期待的。

    文章来源: http://www.52rd.com/News/APP/Detail/?ID=102990

    本来上周计划这周要更新PCL的知识点,老师临时调整研究任务,目前在做深度图,可能又会更一些关于深度图的相关理论知识,还没有跑代码,不知道会怎么样、、、、
    共勉,加油!!

    展开全文
  • iOS设计模式之观察者模式

    万次阅读 2014-04-18 18:08:05
    什么是观察者模式?我们先打个比方,这就像你订报纸。比如你想知道美国最近放生了些新闻,你可能会订阅一份美国周刊,然后一旦美国有了新的故事,美国周刊就发一刊,并邮寄给你,当你收到这份报刊,然后你就能够了解...

    什么是观察者模式?我们先打个比方,这就像你订报纸。比如你想知道美国最近放生了些新闻,你可能会订阅一份美国周刊,然后一旦美国有了新的故事,美国周刊就发一刊,并邮寄给你,当你收到这份报刊,然后你就能够了解美国最新的动态。其实这就是观察者模式,A对B的变化感兴趣,就注册为B的观察者,当B发生变化时通知A,告知B发生了变化。这是一种非常典型的观察者的用法,我把这种使用方法叫做经典观察者模式。当然与之相对的还有另外一种观察者模式——广义观察者模式。

    从经典的角度看,观察者模式是一种通知变化的模式,一般认为只在对象发生变化感兴趣的场合有用。主题对象知道有观察者存在,设置会维护观察者的一个队列;而从广义的角度看,观察者模式是中传递变化数据的模式,需要查看对象属性时就会使用的一种模式,主题对象不知道观察者的存在,更像是围观者。需要知道主题对象的状态,所以即使在主题对象没有发生改变的时候,观察者也可能会去访问主题对象。换句话说广义观察者模式,是在不同的对象之间传递数据的一种模式。

    观察者模式应当是在面向对象编程中被大规模使用的设计模式之一。从方法论的角度出发,传统的认知论认为,世界是由对象组成的,我们通过不停的观察和了解就能够了解对象的本质。整个人类的认知模型就是建立在“观察”这种行为之上的。我们通过不停与世界中的其他对象交互,并观察之来了解这个世界。同样,在程序的世界中,我们构建的每一个实例,也是通过不不停的与其他对象交互(查看其他对象的状态,或者改变其他对象的状态),并通过观察其他实例的变化并作出响应,以来完成功能。这也就是,为什么会把观察模式单独提出来,做一个专门的剖析的原因——在我看来他是很多其他设计模式的基础模式,并且是编程中极其重要的一种设计模式。


    经典观察者模式

    经典观察者模式被认为是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。经典观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己或者做出相应的一些动作。在文章一开始举的例子就是典型观察者模式的应用。

    而在IOS开发中我们可能会接触到的经典观察者模式的实现方式,有这么几种:NSNotificationCenter、KVO、Delegate等


    感知通知方式

    在经典观察者模式中,因为观察者感知到主题对象变化方式的不同,又分为推模型和拉模型两种方式。

    推模型

    ios desing pattern observer 1

    主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或者部分数据。推模型实现了观察者和主题对象的解耦,两者之间没有过度的依赖关系。但是推模型每次都会以广播的方式,向所有观察者发送通知。所有观察者被动的接受通知。当通知的内容过多时,多个观察者同时接收,可能会对网络、内存(有些时候还会涉及IO)有较大影响。

    在IOS中典型的推模型实现方式为NSNotificationCenter和KVO。

    NSNotificationCenter

    NSnotificationCenter是一种典型的有调度中心的观察者模式实现方式。以NSNotificationCenter为中心,观察者往Center中注册对某个主题对象的变化感兴趣,主题对象通过NSNotificationCenter进行变化广播。这种模型就是文章开始发布订阅报纸在OC中的一种类似实现。所有的观察和监听行为都向同一个中心注册,所有对象的变化也都通过同一个中心向外广播。

    SNotificationCenter就像一个枢纽一样,处在整个观察者模式的核心位置,调度着消息在观察者和监听者之间传递。

    ios desing pattern observer 2

    一次完整的观察过程如上图所示。整个过程中,关键的类有这么几个(介绍顺序按照完成顺序):

    1. 观察者Observer,一般继承自NSObject,通过NSNotificationCenter的addObserver:selector:name:object接口来注册对某一类型通知感兴趣.在注册时候一定要注意,NSNotificationCenter不会对观察者进行引用计数+1的操作,我们在程序中释放观察者的时候,一定要去报从center中将其注销了。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      - (void) handleMessage:(NSNotification*)nc{
           //解析消息内容
       NSDictionary* userInfo = [nc userInfo];
       }
       - (void) commonInit
       {
           //注册观察者
           [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMessage:) name:kDZTestNotificatonMessage object:nil];
       }
    2. 通知中心NSNotificationCenter,通知的枢纽。
    3. 主题对象,被观察的对象,通过postNotificationName:object:userInfo:发送某一类型通知,广播改变。
      1
      2
      3
      4
      - (void) postMessage
       {
           [[NSNotificationCenter defaultCenter] postNotificationName:kDZTestNotificatonMessage object:Nil userInfo:@{}];
       }
    4. 通知对象NSNotification,当有通知来的时候,Center会调用观察者注册的接口来广播通知,同时传递存储着更改内容的NSNotification对象。

    apple版实现的NotificationCenter让我用起来不太爽的几个小问题

    在使用NSNotificationCenter的时候,从编程的角度来讲我们往往不止是希望能够做到功能实现,还能希望编码效率和整个工程的可维护性良好。而Apple提供的以NSNotificationCenter为中心的观察者模式实现,在可维护性和效率上存在以下缺点:

    1. 每个注册的地方需要同时注册一个函数,这将会带来大量的编码工作。仔细分析能够发现,其实我们每个观察者每次注册的函数几乎都是雷同的。这就是种变相的CtrlCV,是典型的丑陋和难维护的代码。
    2. 每个观察者的回调函数,都需要对主题对象发送来的消息进行解包的操作。从UserInfo中通过KeyValue的方式,将消息解析出来,而后进行操作。试想一下,工程中有100个地方,同时对前面中在响应变化的函数中进行了解包的操作。而后期需求变化需要多传一个内容的时候,将会是一场维护上的灾难。
    3. 当大规模使用观察者模式的时候,我们往往在dealloc处加上一句:
      [[NSNotificationCenter defaultCenter] removeObserver:self]
      而在实际使用过程中,会发现该函数的性能是比较低下的。在整个启动过程中,进行了10000次RemoveObserver操作,

      1
      2
      3
      4
      5
      6
      @implementation DZMessage
       - (void) dealloc
       {
          [[NSNotificationCenter defaultCenter] removeObserver:self];
       }
       ....
      1
      2
      3
      for (int i = 0 ; i &lt; 10000; i++) {
            DZMessage* message = [DZMessage new];
        }

      通过下图可以看出这一过程消耗了23.4%的CPU,说明这一函数的效率还是很低的。
      ios desing pattern observer 6
      这还是只有一种消息类型的存在下有这样的结果,如果整个NotificationCenter中混杂着多种消息类型,那么恐怕对于性能来说将会是灾难性的。

      1
      2
      3
      4
      for (int i = 0 ; i &lt; 10000; i++) {
            DZMessage* message = [DZMessage new];
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle) name:[@(i) stringValue] object:nil];
        }

      增加了多种消息类型之后,RemoveObserver占用了启动过程中63.9%的CPU消耗。
      ios desing pattern observer 7
      而由于Apple没有提供Center的源码,所以修改这个Center几乎不可能了。

    改进版的有中心观察者模式(DZNotificationCenter)

    GitHub地址 在设计的时候考虑到以上用起来不爽的地方,进行了优化:

    1. 将解包到执行函数的操作进行了封装,只需要提供某消息类型的解包block和消息类型对应的protocol,当有消息到达的时候,消息中心会进行统一解包,并直接调用观察者相应的函数。
    2. 对观察者的维护机制进行优化(还未做完),提升查找和删除观察者的效率。

    DZNotificationCenter的用法和NSNotificationCenter在注册和注销观察者的地方是一样的,不一样的地方在于,你在使用的时候需要提供解析消息的block。你可以通过两种方式来提供。

    • 直接注册的方式
      1
      2
      3
      4
      5
      6
      7
      8
      [DZDefaultNotificationCenter addDecodeNotificationBlock:^SEL(NSDictionary *userInfo, NSMutableArray *__autoreleasing *params) {
            NSString* key = userInfo[@"key"];
            if (params != NULL) {
                *params = [NSMutableArray new];
            }
            [*params  addObject:key];
            return @selector(handleTestMessageWithKey:);
        } forMessage:kDZMessageTest];
    • 实现DZNotificationInitDelegaete协议,当整个工程中大规模使用观察者的时候,建议使用该方式。这样有利于统一管理所有的解析方式。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    - (DZDecodeNotificationBlock) decodeNotification:(NSString *)message forCenter:(DZNotificationCenter *)center
    {
        if (message == kDZMessageTest) {
            return ^(NSDictionary* userInfo, NSMutableArray* __autoreleasing* params){
                NSString* key = userInfo[@"key"];
                if (params != NULL) {
                    *params = [NSMutableArray new];
                }
                [*params  addObject:key];
                return @selector(handlePortMessage:);
            };
        }
        return nil;
    }

    在使用的过程中为了,能够保证在观察者处能够回调相同的函数,可以实现针对某一消息类型的protocol

    1
    2
    3
    @protocol DZTestMessageInterface &lt;NSObject&gt;
    - (void) handleTestMessageWithKey:(NSString*)key;
    @end

    这样就能够保证,在使用观察者的地方不用反复的拼函数名和解析消息内容了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @interface DZViewController () &lt;DZTestMessageInterface&gt;
    @end
    @implementation DZViewController
    ....
    - (void) handleTestMessageWithKey:(NSString *)key
    {
        self.showLabel.text = [NSString stringWithFormat:@"get message with %@", key];
    }
    ....
    KVO

    KVO的全称是Key-Value Observer,即键值观察。是一种没有中心枢纽的观察者模式的实现方式。一个主题对象管理所有依赖于它的观察者对象,并且在自身状态发生改变的时候主动通知观察者对象。 让我们先看一个完整的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    static NSString* const kKVOPathKey = @"key";
    @implementation DZKVOTest
    - (void) setMessage:(DZMessage *)message
    {
        if (message != _message) {
            if (_message) {
                [_message removeObserver:self forKeyPath:kKVOPathKey];
            }
            if (message) {
                [message addObserver:self forKeyPath:kKVOPathKey options:NSKeyValueObservingOptionNew context:Nil];
            }
            _message = message;
        }
    }
    - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
        if ([keyPath isEqual:kKVOPathKey] &amp;&amp; object == _message) {
            NSLog(@"get %@",change);
        }
    }
    - (void) postMessage
    {
        _message.key = @"asdfasd";
    }
    @end

    完成一次完整的改变通知过程,经过以下几次过程:

    1. 注册观察者[message addObserver:self forKeyPath:kKVOPathKey options:NSKeyValueObservingOptionNew context:Nil];
    2. 更改主题对象属性的值,即触发发送更改的通知 _message.key = @"asdfasd";
    3. 在制定的回调函数中,处理收到的更改通知
      1
      2
      3
      4
      5
      6
      - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
      {
       if ([keyPath isEqual:kKVOPathKey] &amp;&amp; object == _message) {
           NSLog(@"get %@",change);
       }
      }
    4. 注销观察者 [_message removeObserver:self forKeyPath:kKVOPathKey];

    KVO实现原理

    一般情况下对于使用Property的属性,objc会为其自动添加键值观察功能,你只需要写一句@property (noatomic, assign) float age 就能够获得age的键值观察功能。而为了更深入的探讨一下,KVO的实现原理我们先手动实现一下KVO:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @implementation DZKVOManual
    - (void) setAge:(int)age
    {
        [self willChangeValueForKey:kKVOPathAge];
        if (age !=_age) {
            _age = age;
        }
        [self didChangeValueForKey:kKVOPathAge];
    }
    //经验证  会先去调用automaticallyNotifiesObserversForKey:当该函数没有时才会调用automaticallyNotifiesObserversOfAge。这个函数应该是编译器,自动增加的一个函数,使用xcode能够自动提示出来。的确很强大。
    //+(BOOL) automaticallyNotifiesObserversOfAge
    //{
    //    return NO;
    //}
    + (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key
    {
        if (key ==kKVOPathAge) {
            return NO;
        }
        return [super automaticallyNotifiesObserversForKey:key];
    }
    @end

    首先,需要手动实现属性的 setter 方法,并在设置操作的前后分别调用 willChangeValueForKey: 和 didChangeValueForKey方法,这两个方法用于通知系统该 key 的属性值即将和已经变更了;

    其次,要实现类方法 automaticallyNotifiesObserversForKey,并在其中设置对该 key 不自动发送通知(返回 NO 即可)。这里要注意,对其它非手动实现的 key,要转交给 super 来处理。

    在这里的手动实现,主要是手动实现了主题对象变更向外广播的过程。后续如何广播到观察者和观察者如何响应我们没有实现,其实这两个过程apple已经封装的很好了,猜测一下的话,应该是主题对象会维护一个观察者的队列,当本身属性发生变动,接受到通知的时候,找到相关属性的观察者队列,依次调用observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context来广播更改。 还有一个疑问,就是在自动实现KVO的时候,系统是否和我们手动实现做了同样的事情呢?

    自动实现KVO及其原理

    我们仔细来观察一下在使用KVO的过程中类DZMessage的一个实例发生了什么变化: 在使用KVO之前:

    ios desing pattern observer 3

    当调用Setter方法,并打了断点的时候:

    ios desing pattern observer 4

    神奇的发现类的isa指针发生了变化,我们原本的类叫做DZMessage,而使用KVO后类名变成了NSKVONotifying_DZMessage。这说明objc在运行时对我们的类做了些什么。

    我们从Apple的文档Key-Value Observing Implementation Details找到了一些线索。

    Automatic key-value observing is implemented using a technique called isa-swizzling. The isa pointer, as the name suggests, points to the object’s class which maintains a dispatch table.This dispatch table essentially contains pointers to the methods the class implements, among other data. When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

    当某一个类的实例第一次使用KVO的时候,系统就会在运行期间动态的创建该类的一个派生类,该类的命名规则一般是以NSKVONotifying为前缀,以原本的类名为后缀。并且将原型的对象的isa指针指向该派生类。同时在派生类中重载了使用KVO的属性的setter方法,在重载的setter方法中实现真正的通知机制,正如前面我们手动实现KVO一样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。

    同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。


    拉模型

    ios desing pattern observer 5

    拉模型是指主题对象在通知观察者的时候,只传递少量信息或者只是通知变化。如果观察者需求要更具体的信息,由观察者主动从主题对象中拉取数据。相比推模型来说,拉模型更加自由,观察者只要知道有情况发生就好了,至于什么时候获取、获取那些内容、甚至是否获取都可以自主决定。但是,却存在两个问题:

    • 如果某个观察者响应过慢,可能会漏掉之前通知的内容
    • 观察者必须保存一个对目标对象的引用,而且还需要了解主题对象的结构,这就使观察者产生了对主题对象的依赖。

    可能每种设计模式都会存在或多或少的一些弊端,但是他们的确能够解决问题,也有更多有用的地方。在使用的时候,就需要我们权衡利弊,做出一个合适的选择。而工程师的价值就体现在,能够在纷繁复杂的工具世界中找到最有效的那个。而如果核桃没被砸开,不是你手力气不大的问题,而是你选错了工具,谁让你非得用门缝夹,不用锤子呢!

    当然,上面那段属于题外话。言归正传,在OBJC编程中,典型的一种拉模型的实现是delegate。可能很多人会不同意我的观点,说delegate应当是委托模式。好吧,我不否认,delegate的确是委托模式的一种极度典型的实现方式。但是这并不妨碍,他也是一种观察者模式。其实本来各种设计模式之间就不是泾渭分明的。在使用和解释的时候,只要你能够说得通,而且能够解决问题就好了,没必要纠缠他们的名字。而在通知变化这个事情上delegate的确是能够解决问题的。

    我们来看一个使用delegate实现拉模型的观察者的例子:

    • 先实现一个delegate方便注册观察者,和回调函数
    1
    2
    3
    4
    5
    6
    7
    8
    @class DZClient;
    @protocol DZClientChangedDelegate &lt;NSObject&gt;
    - (void) client:(DZClient*)client didChangedContent:(NSString*)key;
    @end
    @interface DZClient : NSObject
    @property (nonatomic, weak) id&lt;DZClientChangedDelegate&gt; delegate;
    @property (nonatomic, strong) NSString* key;
    @end
    • 注册观察者
    1
    2
    3
    4
    //DZAppDelegate
    DZClient* client = [DZClient new];
    client.delegate = self;
    client.key = @"aa";
    • 当主题对象的属性发生改变的时候,发送内容有变化的通知
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @implementation DZClient
    - (void) setKey:(NSString *)key
    {
        if (_key != key) {
            _key = key;
            if ([_delegate respondsToSelector:@selector(client:didChangedContent:)]) {
                [_delegate client:self didChangedContent:@"key"];
            }
        }
    }
    • 观察者收到主题对象有变化的通知后,主动去拉取变化的内容。
    1
    2
    3
    4
    5
    6
    7
    //DZAppDelegate
    - (void) client:(DZClient *)client didChangedContent:(NSString *)key
    {
        if ([key  isEqual: @"key"]) {
            NSLog(@"get changed key %@",client.key);
        }
    }

    广义观察者模式

    在上面介绍了,观察者被动的接受主题改变的经典意义上的观察者模式之后,我们再来看一下广义观察者模式。当然上面所讲的经典观察者模式,也是一种一种传递数据的方式。广义观察者涵盖了经典观察者模式。

    往往我们会有需要在“观察者”和“主题对象”之间传递变化的数据。而这种情况下,主题对象可能不会像经典观察者模式中的主题对象那样勤劳,在发生改变的时候不停的广播。在广义观察者模式中,主题对象可能是懒惰的,而是由观察者通过不停的查询主题对象的状态,来获知改变的内容。

    我们熟悉的服务器CS架构,始终比较典型的冠以观察者模式,服务器是伺服的,等待着客户端的访问,客户端通过访问服务器来获取最新的内容,而不是服务器主动的推送。

    之所以,要提出广义观察者模式这样一个概念。是为了探讨一下观察者模式的本质。方便我们能够更深刻的理解观察者模式,并且合理的使用它。而且我们平时更多的将注意力放在了通知变化上面,而观察者根本的目的是在于,在观察者和主题对象之间,传递变化的数据。这些数据可能是变化这个事件本身,也可能是变化的内容,甚至可能是一些其他的内容。

    从变化数据传递的角度来思考的话,能够实现这个的模式和策略实在是数不胜数,比如传统的网络CS模型,比如KVC等等。在这里就先不详细展开讨论了。

    参考:

    1. http://www.cnblogs.com/java-my-life/archive/2012/05/16/2502279.html
    2. http://www.cppblog.com/kesalin/archive/2012/11/17/kvo.html
    3. 《模式工程化实现及扩展》
    本文作者: 伯乐在线 一水流年
    本文链接: http://blog.jobbole.com/55505/
    展开全文
  • 这是我第二次学习OPENGL,第一次学习是在大二的计算机图形学课堂上,那是对opengl只是走马观花,现在过了两年,我打算把opengl进行新一编完整而系统的学习,有三个目的:1.熟练掌握opengl编程,2从opegl的体系中加深...
  • 观察力训练(福尔摩斯演绎法)

    千次阅读 2012-12-02 13:31:58
     在技能性训练中,由于摄影(包括密拍)、驾驶(包括驾车跟踪与反跟踪)、化装等几项本身的技术性较强,就训练本身可值得一提的并不,所以主要以下边这些技术为例进行介绍。(注:由于这些方面很少有资料可供借鉴...
  • 单元测试的目的及使用

    千次阅读 2019-07-05 14:46:06
    这样解决起来速度就提高了很,每次找到错误都去修改单元测试,那么下次就不会再出现相同的错误了。 如果通过模拟,单元测试也没有出现任何异常,这时也可以断定,并非该代码出现的错误,而是其他相关的代码出现...
  • 网上已经有很针对各种知识点的面试题,面试时有些人未必真正理解也能通过背题看上去很懂。我自己总结了4道面试题,好快速的判断这个人是否是一个合格的工程师,欢迎大家点评。 1.struct和class的区别 在...
  • 好,下面我将结合一些实例,说一下我对组合模式以及观察者模式的了解: 1、组合模式: 组合模式在对象间形成树形结构; 组合模式中基本对象和组合对象被一致对待; 无须关心对象有多少层, 调用时只需在根部进行调用; ...
  • 最后,仔细观察广播的参与方—— 接收者 和 发送者 ,是不是就是Publisher/Subscriber模式?嗯,果然就是换了一个马甲。 上面举得广播的例子,相信大家有一点理解本文的目的了。对, 我并不是要教大家Android中广播...
  • 从技术债务的角度, 谈谈重构

    万次阅读 2019-10-20 13:31:25
    它的名词定义是:对软件内部结构的一种调整, 目的是在不改变软件可观察行为的前提下, 提高其可靠性, 降低其修改成本. 所以重构在我们眼里 它应该是这样的 关于技术债务(Technical Debt): 开发团队在设计或架构...
  • 申耀的科技观察读懂科技,赢取未来!众所周知,如今的世界正在以加速度进入到智能时代,特别是随着各项业务的云化深入以及万物互联,数据处理能力的进一步提升,智能算法一次又一次的...
  • 慵懒的博主又诈尸了,如此不称职,割了! ========================可爱的分割线===============...相信大家在玩游戏的时候都会或多或少遇到这么个情况:得到了一件宝物,需要仔细观察宝物,这里有两种实现方法:1、拖
  • 数据分析的目的是把隐没在一大批看来杂乱无章的数据中的信息集中、萃取和提炼出来,以找出所研究对象的内在规律。 在实用中,数据分析可帮助人们作出判断,以便采取适当行动。数据分析是组织有目的...
  • 转自凤凰网 核心提示:房价收入比高到大家都以为房价有泡沫,所以傻乎乎的一年又一年等下去,等待泡沫破灭。但是所谓的“泡沫”不但没破,...货币发行过多过快,我相信很容易理解,钱太了,钱也就越来越不值钱了
  • XGBOOST从原理到实战:二分类 、分类

    万次阅读 多人点赞 2018-07-17 12:49:05
    从Bias-variance tradeoff角度来讲,正则项降低了模型的variance,使学习出来的模型更加简单,防止过拟合,这也是xgboost优于传统GBDT的一个特性。 2.2 并行处理 XGBoost工具支持并行。Boosting不是一种串行的...
  • 不懂的就去学,然后把整理的笔记分享出来,数据分析方面我涉入不,内容由于缺少实战经验,会比较基础和理论,希望同样对你有帮助。 1. 明确数据分析的目的 做数据分析,必须要有一个明确的目...
  • 通过上一篇gradle渠道打包的介绍后,本篇我们就来介绍一下版本apk的打包。在项目的开发过程,有可能我们会有需要打包多种版本apk的需求,比如当测试部门在测试需要我们提供两种当前正在开发的apk的版本,而且这...
  • px4官网调参指南 旋翼无人机PID调参指南

    万次阅读 多人点赞 2017-02-13 21:31:39
    译文部分:旋翼无人机PID调参指南不用碳化纤维或增强碳化纤维桨调整多轴,不使用损坏的桨片。 出于安全考虑,系统默认增益都设置的比较小。请增加增益以便获取更好的控制响应。 本指导方案适用于所有的多轴飞行...
  • 从几何角度看SVD

    千次阅读 2014-07-26 21:32:04
    我的目的主要也只是让大家对svd有一个直观印象,理解svd的主要思想。 参考资料   Gilbert Strang, Linear Algebra and Its Applications. Brooks Cole.  Strang's book is something ...
  • 为了达到这个目的,只有在CPU的情形下才有可能,在单CPU的场合(单机单CPU…),是无法实现的。 在 第二种场合下,我们会自然而然的关注数据的分离,从而很好的利用上CPU的能力;而在第一种场合,我们习惯了...
  • 从贝叶斯角度深入理解正则化

    万次阅读 多人点赞 2017-01-13 15:42:24
    渐渐地,听到的了,看到的了,再加上平时做东西都会或多或少的接触,有了一些新的理解。 1. 正则化的目的:防止过拟合! 2. 正则化的本质:约束(限制)要优化的参数。 二、贝叶斯理论 ...
  • 为了解释这个问题,我们呈现了...在这个过程中,我们尽量解释神经网络每一步操作对应的现实意义和最终目的。可是,神经网络的可解释性往往是个非常大的难题。为此,我们采用了最易于理解的“交集”、“并集”神经网络。
  • 本章介绍如何跨个数据中心、可用性`zones`或区域使用 Akka 集群。 了解使用 Akka 集群时数据中心边界的原因是,与同一数据中心中的节点之间的通信相比,跨数据中心的通信通常具有更高的延迟和更高的故障率。 ...
  • 机器学习系列(3)_逻辑回归应用之Kaggle泰坦尼克之灾

    万次阅读 多人点赞 2015-11-12 12:07:12
    f作者: 寒小阳 &&龙心尘 时间:2015年10月。 出处: 声明:版权所有,转载请注明出处,谢谢。手把手机器学习之逻辑回归应用...写完前两篇逻辑回归的介绍和各个角度理解之后,小伙伴们纷纷表示『好像很高级的样纸,
  • PredNet阅读笔记——从视频预测的角度学习视频表征

    千次阅读 热门讨论 2017-04-09 19:58:41
     文章是从视频预测的角度设计网络PredNet:为了达到预测视频的目的,需要学习视频的特征表示。然而实验证明,PredNet在视频预测任务表现一般,预测时间短且不够清晰;但在学习视频表征方面表现突出,可以提取物体...
  • 23种设计模式分类+SOLID设计原则+从设计模式角度看MVC框架
  • 牛逼!Java 从入门到精通,超全汇总版

    万次阅读 多人点赞 2021-05-06 19:40:33
    Java 设计模式Java 进阶Java 并发编程实战Java 并发编程艺术Java 并发编程之美图解Java线程设计模式JVM深入理解 Java 虚拟机Java 虚拟机规范HotSpot 实战自己动手写 Java 虚拟机MySQLMySQL 基础教程SQL 基础教程...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 80,468
精华内容 32,187
关键字:

多角度观察的目的