Dahua's profile笑对人生,傲立寰宇PhotosBlogLists Tools Help
    January 20

    继往开来

    首先报告一个消息:ECCV'06 结果正式公布了。这次取得了很好的成绩: both of my two papers are accepted for oral presentation.


    今年ECCV一共被投了约900篇paper,接受了193篇,其中只有40篇达到oral presentation。期盼着CVPR'06能获得更好的成绩。


    在写这个blog的时候,完成了CPPUT (C++ Unit Tester)的第一个可用版本。这是一个用于对C++进行单元测试的库。
    为了它忙活了5天,写了6000行源码,现在终于可以release了。我给它建了一个简单的website,大家可以看看:


    http://personal.ie.cuhk.edu.hk/~dhlin4/projects/cpput/


    里面有一个简要的introduction, 还有一个详细的documentation (那是使用doxygen从3000行按照一定规范撰写的注释中提取生成的)。


    这个library只是后面需要做的一个更大的library的铺垫:computer vision sdk。单元测试工具的完成对于撰写一个大型SDK有着重要的意义,它能大大提高测试的效率和可靠性。

     

    对于有兴趣想着程序架构方面继续提高的朋友,推荐两本经典的书:


    Design Patterns (设计模式):介绍如何进行设计。

    Refactoring (重构):介绍如何对现有的设计进行改造。


    还推荐三个library:

    STL (Standard Template Library):这是被无数人研究过的库,确实很经典。学习generic programming的都要研读这个library的。读懂这个了,就可以研究一下下面两个了。

    C++ Boost Library (这个东西据说差点被编入C++ Standard了,现在在C++ community中也被视为准标准库。它里面由多个子库构成,很多设计做的非常精巧)

    Loki Library (就是Modern C++ Design的作者所写的库,提供了很多令人叹为观止的meta programming的C++实现。比如我在前面的blog中提到的编译期计算,就是这本书的入门级设计)

    January 11

    今天,再度发生

    今天凌晨,回去躺了两小时,没睡着,于是回来实验室继续工作。这个现象是距离上一次发生只有4天。
     
    我不知道我最近为什么对于手上的工作有如此大的热情,以至到了废寝忘食的地步了,呵呵。
     
    和做research一样,programming是一种艺术。当你发现你为艺术献身,而不是被任务所驱使时,也许才能开始激发一点潜力。
     
     
    顺便提到,昨天(就是凌晨前的若干小时),了解到了ECCV 2006的录取结果了(正式结果还没有officially公布,不过这个内部渠道相信是非常可靠了)
     
    我在今年ECCV投的两篇第一作者文章,其中一篇确定是oral了,另外一篇也确定录取了,是oral还是poster尚需最后确定。这是一个可喜可贺的结果。于是,被实验室集体敲诈请客。

    好了,今天继续讨论一个C++ Design的paradigm。今天这个比较有趣,就是让编译器计算最大公约数。对于一个程序来说,有两个阶段:编译期(Compile phase)和运行期(Run-time phase)。在编译期,由编译器对源代码进行编译,产生二进制代码。在运行期,由系统执行生成的二进制代码。
     
    下面,先列出一个小程序:使用辗转相除法计算最大公约数(greatest common divisor)。
     
    unsigned int gcd(unsigned int a, unsigned int b)
    {
        if (a == 0 || b == 0) return 0;
        //原理:
        //  如果 b 整除 a, 那么 b 就是最大公约数;返回b
        //  否则 a := b; b := r (余数)
        //  这个过程迭代到整除为止。

        for(unsigned int r = a % b; r != 0; a = b, b = r);
        return b;
    }

    这就是经典的在运行期求最大公约数的过程。
    上面的过程可以简化为递归的形式,思路就更为清晰了:
     
    unsigned int gcd(unsigned int a, unsigned int b)
    {
        if (a == 0 || b == 0) return 0;
        unsigned int r = a % b;
        return r == 0 ? b : gcd(b, r);
    }
     
    这还是在运行期进行计算的。下面设计一个有趣的程序,在编译器,编译器就能把gcd算出来。
     
    template<int a, int b> struct gcd_solver
    {
       enum{ gcd = (a % b == 0) ? b : gcd_solver<b, (a % b)>::gcd };
    }
     
    #define GCD(a, b) gcd_solver<12, 8>::gcd
     
    这样编译器就会自动把 GCD(12, 8) 替换为 它们的最大公约数 4,etc。这个最大公约数,不是在运行期通过执行程序算出来的,而是编译器在编译时就算好的
     
    编译器的这种计算,利用了两个重要的特点:
    1)编译器进行常数的基本运算;
    2) C++编译器的模板(template)生成机制。
     
    编译器遇到gcd_solver<12, 8>的时候,会自动仅进行基本常量计算,产生出

    enum{ gcd = gcd_solver<8, 4>::gcd };

    接下来会产生gcd_solver<8, 4>这个类,这时候就会产生

    enum{ gcd = 4 };

    然后编译器会把这个回代。
     
    实质上,编译器通过它的模板形成机制,自动做了递归。
     
    January 07

    凌晨冲回实验室

    今天开始参与项目了,凌晨4点回去睡觉。可是睡在床上满脑子的program design。越想越兴奋,干脆爬起来,冒着寒风,一路小跑冲回实验室去了。人,最可贵的就是这点责任和热情。

    讨论几个C++ design的问题,和大家分享一下:

    (1)关于对象的拷贝和引用计数(reference counting)

    考虑一个图像类,就叫CImage吧。要实现下面的图像处理函数:
     
    CImage myProcess(const CImage& imgsrc)
    {
         ... some processing codes ...
         return imgdst; //imgdst is the resultant image of class CImage
    }
     
    如果调用语句是
    CImage img2 = myProcess(img1);

    那么在返回过程中,会经历两次拷贝构造的过程:

    首先,程序会从函数体的局部变量imgdst,拷贝构造一个临时的CImage变量,记为temp_img吧;
    然后,它会从temp_img拷贝构造img2。

    如果按照通常教科书的方式,我们会给CImage写一个深拷贝(deep copy), 大概类似下面的代码:

    CImage(const CImage& imgsrc)
    {
        m_width = imgsrc.m_width;
        m_height = imgsrc.m_height;
       
        m_pdata = new pixel[width * height];
        memcpy(m_pdata, imgsrc.m_pdata, sizeof(pixel) * width * height);    
    }

    上面的代码只是体现了大概的思路,当然,正式写程序时还有一些细节要考虑。这里我们仅讨论效率问题。对于一个大图像来说,深拷贝需要耗费大量的时间和内存资源,是非常低效的。

    现在的C++设计上,更为推崇reference counting的设计方式。就是多个object share一个实体拷贝。通过一定的方式,记录对实体拷贝引用的数目。当引用这个拷贝的数目降为0的时候,销毁实体。下面的代码大致体现了这个基本思路:

    class ImageBody
    {
    protected:
        int m_width;
        int m_height;
        byte* m_pdata;
     
    public:
        ...
        ImageBody(const ImageBody& imgsrc) \\ a deep copy   
        ...
    }
     
    template<typename Ty>
    class SmartPtr
    {
    public:
       typedef Ty element_type;
    public:
      
       SmartPtr(element_type* pnew) : m_ptr(0), m_pnum(0)
       {
          if (pnew != 0)
          {
              m_ptr = pnew;
              m_pnum = new int(1);
          }
       }
     
       SmartPtr(SmartPtr& psrc)
       {
          m_ptr = psrc.m_ptr;      //share the same copy
          m_pnum = psrc.m_pnum;
          ++(*m_pnum);             //add reference counting
       }
     
       SmartPtr& operator = (SmartPtr& psrc)
       {
          if (this.m_ptr == psrc.m_ptr)    // already pointing to the same body
              return *this;
          Detach();                //detach from current body
          m_ptr = psrc.m_ptr;      //share the same copy with source object
          m_pnum = psrc.m_pnum;
          ++(*m_pnum);             //add reference counting
         
       } 
     
       virtual ~SmartPtr()
       {
          Detach(); 
       }
     
       void Detach()   // detach from current body
       {
          if (m_ptr)   // not a null object
          {
              if (--(*m_pnum) == 0)   // release a unreferred body
              {
                  delete m_pnum;
                  delete m_ptr;
              }
             
              m_ptr = 0;
              m_pnum = 0;
          }
       }
     
    protected:
       element_type*  m_ptr;
       unsigned int*  m_pnum;
    }
     
    class CImage : public SmartPtr<ImageBody>
    {
        ...
    }

    这里CImage继承SmartPtr指针,而不是包含它作为member,其考虑在于更好地实现继承性。事实上,使用这样的方式,将使得建立CImage的继承类更为方便。

    (2)关于内存安全
     
    假设有一个Array类,我们需要访问其基地址。这个时候,如果直接返回基地址,会有一些问题的。
     
    class Array
    {
    public:  
       ...
       element* PtrBase()
       {
           return m_pdata;
       }
     
       void Resize(n)
       {
           ... some code release m_pdata ...
           ... some code allocate new memory with n elements ...
       }

    private:
       ...
       element* m_pdata;
    }
     
    假如调用函数写成:
    Array arr = new Array(10);
    ...
    element* p1 = arr.PtrBase();
    arr.Resize(20);     // the base address may be changed
    p1[0] = something;  // dangerous action! It may write a memory area used by others
     
    当然,也许我们可以去掉某个成员函数来保证安全,但是这会以严重损害类的功能为代价。这里我们可以通过对内存区域进行固化的方式来提供安全机制。
     
    class SafeArray
    {
    public:
       ...
       element* Fix()
       {
           ++ m_num_users;
           return m_pdata;
       }
     
       void Unfix()
       {
          -- m_num_users;
       }
     
       void Resize(n)
       {
           if (m_num_users > 0)
              throw Attempt_to_modify_fixed_memory();
           ... some code release m_pdata ...
           ... some code allocate new memory with n elements ...
       }
     
    private:
       ...
       element* m_pdata;
       int      m_num_users;
         
    }

    这里,使用对user计数的方法,允许多个user同时存在和不同时释放使用权。如果只是使用一个bool变量记录是否fixed,则无法实现multi-users了。
     
    内存的安全使用,除了涉及fix问题之外,还有涉及多线程共享的安全。这个可以通过lock和unlock实现,而且往往在系统API就会提供相应的支持。比如在win32,就提供了诸如HeapLock和HeapUnlock的方法,可以通过适当途径封装到内存类里面。
     
    程序设计还有很多问题,以后有时间再写吧。
    January 01

    盘点2005


    在辞旧迎新的时刻,各大媒体都热闹纷呈地盘点着过去一年所发生的大事。我也利用这个机会盘点一下我在2005年所经历的人生。

    不同于2004年的风雨跌宕,我在2005年的感情生活,平淡中透出几分温馨。和朋友们相处和谐而愉快。

    在2005年,第一次拨打国际长途电话,这源于某人宣称要脱离msn。从此一发不可收拾,现在已经成为精神生活中不可缺少的部分了。这些电话见证了我这一年生活之旅的点点滴滴。

    在2005年,认识了一批新的朋友。主要是新来的同学:小呆,欢子,振国,世峰和李云。感谢他们,他们的到来给我的生活增添了无数的欢乐。还有一位朋友:晓燕,和她的认识真是蛮有传奇色彩的。在她的引领下,我第一次踏入了传说中的intel。

    在2005年,我第一次以校友,而不是在校学生的身份回访我的母校——中国科学技术大学。我看到了我当年的实验室,老师和同学,还有三教的课室,芳花园的草木,一切都让我觉得无比亲切。

    在2005年,我第一次做公开的demo,对象是香港的中学生。Raymond Yeung的这个任务,使我的生活从此现在demo的漩涡之中,到现在还不能脱身,sigh。后来,我还到会展中心面向全港做demo,我的玉照因此出现在多家媒体。

    在2005年,我的第一篇正式paper发表。它发表在CVPR 2005——计算机视觉领域的一个顶尖国际会议。就在同一年,我写出并成功发表了我的第一篇第一作者的paper。就这一年里,一共成功发表了9篇paper,另外投出了9篇paper,在review阶段。

    在2005年,我第一次成为独立reviewer。以前曾经替老板review文章,这一年得到汤老师推荐,成为IEEE Transaction on PAMI的正式reviewer。全年先后为CVPR 2005, ICCV 2005, ACCV 2006, ECCV 2006 和 PAMI 审稿。

    在2005年,我有生以来第一次真正意义踏出国门。我到了传说中的美国加州为CVPR的paper做oral presentation。这也是我第一次面对来自全世界的优秀学者做演讲,介绍我们的工作。以前,我坐过无数次飞机,可是十几个小时的航程还是第一次经历了,呵呵。

    在2005年,我来香港后第一次走进电影院。这一年,我在电影院看了三部电影:《七剑》,《无极》,《如果•爱》。都是一个人看的,都在九龙塘。

    在2005年,我买了ipod photo,IBM Thinkpad X41, 换了新的眼镜。

    现在,
    我满怀感激地告别2005年,感谢它给我的创造的精彩生活;
    我满怀期待地迎接2006年,期待着在新的一年创造新的惊喜。