Home

终于写好Z Buffer作业了

Z buffer这个算法比较简单,基本就照教材上写的,省去了活化多边形链表,感觉没必要。就核心代码,有位师兄用一个晚上就写好了,我比较挫用了一天时间。为了添加一些一般3D SDK(e.g. D3D OpenGL)的简单绘制功能,如局部光照模型、逐像素光照计算(Per Pixel Lighting)等,重新整理了程序架构和算法。

主要功能

  • obj文件载入,只用到顶点位置和法向量信息,没有法向的根据模型计算法向,取差角小于90度的邻面共点平均。
  • Gouraud和Phong绘制模型,即对颜色的双线性插值(Gouraud Shading or Per Vertex Shading)和对法向量的双线性插值(Phong Shading or Per Pixel Shading)。
  • 光照计算,Phong光照模型,已实现点光源和方向光。
  • 正交投影(Orthogonal Projection)和透视投影(Perspective Projection)。

数据结构是仿D3D的,接口是仿OpenGL的,依照OpenCV代码学用template,界面是Qt的,没有使用第三方3D库。

class CScanLine
{
public:
    CScanLine(int w, int h, QImage *img);

    void setRenderTarget(int w, int h, QImage *img);

    // start create target
    void begin(TargetType type);
    void end();

    // 数据输入
    void vertex3d(double x, double y, double z);
    void color3f(float r, float g, float b); 

    // 清空缓存
    void clear(int target, const Color4u & c = Color4u(0,0,0,255), double depth = 1.0);

    // 相机相关, facade design model
    void lookAt(const Vec3d & eye, const Vec3d & at, const Vec3d & up);
    void perspective(double fovy, double aspect, double zNear, double zFar);
    void frustum(double left, double right, double bottom, double top, double near, double far);
    void ortho(double left, double right, double bottom, double top, double near, double far);

    void setRenderState(int state, int val);
};

存在问题

多边形边沿部分有锯齿,主要是因为相邻多边形在这里深度都一样,反复覆盖。

截图

Click to read more ...

ln for windows

Introduction

Linux下有个很好用的小工具 —— ln,可以用它来创建目录或文件的硬链接(hard link)或软链接(symbolic)。所谓硬链接就是一个文件(目录)可以有多个名称,在Windows下不常见。而软链接就是一个指向另一个文件(目录)的符号文件了,类Windows下的快捷方式了,不过我觉得功能更强大些,如Window下就不能cd到一个目录快捷方式中,因为它被看作一个文件(.lnk)。硬链接要求在同一个硬盘分区,只有全部删除才真正删除该文件(目录);软链接可以任意,甚至网络文件(目录),但原文件(目录)删除了,软链接就失效了。使用如下:

ln [-s] TAEGET LINK_NAME

硬链接和软链接都很有用,如可以同一个文件(目录)多处使用,据说Win7下大量使用。可以下面介绍几个Windows下使用硬链接的方法(软链接也就是快捷方式,就不说了,大家都会用; ))。

就罗列下好了,要用的可以 Google 下,或参考 Reference

Fsutil: hardlink *

Windows XP 自带的,方便,可以创建文件和目录的 hard link。使用方法:>fsutil hardlink create NewFilename ExistingFilename

Junction *

第三方工具,只能创建目录的 hard link。使用方法:创建>junction LINK_NAME TARGET
删除>junction -d LINK_NAME

注意:一定要用命令删除,不然连原始原始数据也删除了,这点与 Linux 下的 ln -s 不同。

Win2K Resource Kit也有相关工具 * ,不过没用过。

Tweak

可以把上面 Windows 系统文件硬链接创建方式简单化

@echo off
if [%1] == [] fsutil hardlink create else fsutil hardlink create "%2" "%1"

保存成 lnfile.bat 文件到 PATH 包含的目录下(如X:\Windows\system32),小工具建议 独立保存到一个目录,再把该目录加到 PATH 环境变量下。现在还有一个功能没用实现,就 是跨磁盘分区的文件软链接,只能用快捷方式。

Reference

ln for windows *
Dropbox免费网盘高级使用技巧 *
ln命令详细用法 *
NTFS symbolic link *

Click to read more ...

OpenCV 源码中的安全指针和指针对齐

OpenCV 2.0

OpenCV 2.0中为很多1.0中 C 语言的数据结构提供了 C++ 的类了,考虑到兼容性,保留旧的API。但是凭借C++构造和析造的功能,OpenCV2.0的内存管理方便了许多,使用新API的代码中类似 cvRelease的代码将不复存在。同时CV2.0也提供了一个安全指针类,让旧的需要手动管理内存的数据结构(如IplImage, CvMat等)也可以不用手动释放了,快哉!不过推荐直接用新的API(一些是仿Matlab的),如Mat,程序可以更简洁、更直观。一个精典的OpenCV的Hello World程序现在可以这样写:

#include <cv.h>
#include <highgui.h>

using namespace cv;

#ifdef _DEBUG
#pragma comment(lib, "cv200d.lib")
#pragma comment(lib, "cxcore200d.lib")
#pragma comment(lib, "highgui200d.lib")
#else
#pragma comment(lib, "cv200.lib")
#pragma comment(lib, "cxcore200.lib")
#pragma comment(lib, "highgui200.lib")
#endif // _DEBUG

const string WIN_NAME = "Lena";

int main(int argc, char* argv[])
{
    Mat img = imread("lena.jpg");
    namedWindow(WIN_NAME, CV_WINDOW_AUTOSIZE);
    imshow(WIN_NAME, img);
    waitKey(0);

    return 0;
}

安全指针

如果你还是习惯用CV1.0的API,那一定要试试CV2.0的安全指针(也叫智能指针)Ptr template。据说这个是参考了 C++0xBoost 库的相关技术。使用也很简单:

Ptr<IplImage> img = cvReadImage("lena.jpg"); // 不用cvReleaseImage();

我把 Ptr 类从 OpenCV 中独立出来,template class 定义如下:

template<typename Tp> class Ptr
{
public:
    Ptr();
    Ptr(Tp* obj);
    ~Ptr();
    Ptr(const Ptr& ptr);
    Ptr& operator = (const Ptr& ptr);
    void addref(); 
    void release();
    void deleteobj();
    bool empty() const;

    Tp* operator -> ();
    const _Tp* operator -> () const;

    operator _Tp* ();
    operator const _Tp() const;
protected:
    _Tp obj;
    int* refcount;
};

简单地说,就是加了个指针引用数(refcount)和一些方便调用的操作符重装(operator->())。值得注意的是,Ptr template 对指针指向的对象有一个要求,就是可以用delete操作土符来释放内存。你可能就想到IplImage就不满足这个要求了,这怎么办?可以使用模板特化(template specialization)重载 Ptr<Iplimage>::delete_obj() 函数:

template<> inline void Ptr<IplImage>::deleteobj()
{
    cvReleaseImage(&obj);
}

PS:考虑到多线程时,CV2.0中的一些基本操作(如加法运算CV_ADD)都写成了函数或宏,保证互斥资源访问安全,看源代码时可注意下。

指针对齐

指针对齐也可以叫作内存地址对齐,主要是考虑到在一些架构上,只有被指定数(如4)整除的内存地址才可以正常访问,否则程序就会Crash了。CV2.0中的很多指针都是“对齐”过的,如指针的地址都是可以被16整除。CV2.0的内存主要是通过 malloc来分配的,返回的内存地址不可能都可以被16整除,所以要进行对齐操作。那如何对齐,对齐后截断后剩下来的内存怎么维护?

CV2.0的这样维护的:在malloc是多申请一个指针的空间,这个指针指向malloc得到的真实内存地址,只在free时使用它。相关函数有

typedef unsigned char uchar;
#define CVMALLOCALIGN 16
//////////////////////////////////////////////////////////////////////////

template<typename Tp> static inline _Tp* alignPtr(Tp* ptr, int n=(int)sizeof(Tp))
{
    return (Tp)(((sizet)ptr + n-1) & -n);
}

_declspec(dllexport) void fastMalloc( sizet size )
{
    uchar* udata = (uchar)malloc(size + sizeof(void) + CVMALLOCALIGN); 
                                            // ^- HERE 多申请一个 void* 的空间,
                                            //     用于存储 udata
    uchar** adata = alignPtr((uchar**)udata + 1, CVMALLOCALIGN);
    adata[-1] = udata; // <- 存储 udata
    return adata;
}

declspec(dllexport) void fastFree(void* ptr)
{
    if(ptr)
    {
        uchar* udata = ((uchar*)ptr)[-1];
        assert(udata < (uchar)ptr &&
            ((uchar*)ptr - udata) <= (ptrdifft)(sizeof(void*)+CVMALLOCALIGN)); 
        free(udata);
    }
}

Reference

</ol>

Click to read more ...

Web 3D App

很早之前就了解到一些在Web上开发3D程序相关的技术,如基于flash的3D API,还有 Unity3D ,还有Google的O3D,当时也太在意,只试玩了些Unity3D的Web游戏。因为当时电脑比较挫,Unity3D的程序可以也比常规的3D程序慢(我运行过独立的Unity3D程序,效率还可以),所以印象不深刻。今天上 O3D 官网 逛了下,上面的 Demo 在我的新电脑(NV285)上跑得很顺,也没时间看具体的Develop Guide,就截几个图吧。

Click to read more ...

注意const Type **引起的Compile Error

问题

这个问题是在写计算机视觉的作业时发现的,使用OpenCV2.0库的C++ API来计算图像直方图,程序如下:

int bins = 256;
int histSize[] = { bins };
float granges[] = {0, 255};
float* ranges[] = { granges };
int channels[] = { 0 };

calcHist(&gray, 1, channels, Mat(), // do not use mask
    hist, 1, histSize, ranges<<NOTE HERE>>);

可是提示:

error C2665: 'cv::calcHist' : none of the 2 overloads could convert all the argument types
......
while trying to match the argument list '(cv::Mat *, int, int [1], cv::Mat, cv::MatND, int, int [1], float *[1], bool, bool)'

calcHist 声明有两个

CV_EXPORTS void calcHist( const Mat* images, int nimages,
                          const int* channels, const Mat&mask,
                          MatND&hist, int dims, const int* histSize,
                          const float** ranges, bool uniform=true,
                          bool accumulate=false );

CV_EXPORTS void calcHist( const Mat* images, int nimages,
                          const int* channels, const Mat&mask,
                          SparseMat&hist, int dims, const int* histSize,
                          const float** ranges, bool uniform=true,
                          bool accumulate=false );

可是都没法匹配

尝试

calcHist 的每个参数进行强制类型转换,luckly,我从后面开始

calcHist(&gray, 1, channels, Mat(), // do not use mask
    hist, 1, histSize, (const float**)ranges<<NOTE HERE>>);

这样是可以的,但下面就不行了。

calcHist(&gray, 1, channels, Mat(), // do not use mask
    hist, 1, histSize, const_cast<float**>(ranges)<<NOTE HERE>>);

分析

参考《Effective c++》Item21上的做法,如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。 也就是说上面的 const float** ranges 中 ranges 不是 const,ranges[i] 才是const,但这还是没法说明问题。等以后考虑了。。。

参考

为什么 char** 不能自动转化为 const char** 1.

  1. 《Effective c++》
Click to read more ...

MSVC环境下的template编译[纠正]

一直以为自己对 template 的理解有问题,考虑到 template 是用到时再实例化,所以不能像一般的 class 那样写,比如这样:

// foo.h
class Foo
{
public:
    Foo();
    Type bar(Type&);
private:
    Type _val;
};
// foo.cpp
#include"foo.h"Foo::Foo()
: _val(0)
{};

Type Foo::bar(Type&val)
{
    _val = val;
    return _val;
}

这就是所谓的“分离编译模式”了,也就是类的定义和成员函数声明在头文件中,成员函数实现在另一个文件中。这样做的好处很多:

  1. 代码条理清晰,代码多了也碍眼。
  2. 包括该模块时,可以隐藏具体实现细节。
  3. 减少调用者包括的头文件

实现中用到的库文件、头文件,可以不被该模块包含者包含,如实现中用到了 assert,可以只在cpp文件中包括cassert文件。

  1. 优化编译速度

如只有cpp文件改动头文件没有改动时,调用者不用重新编译;而如果是“包含编译模式”(合在一起)的话,调用者也得重新编译了,也不知道写代码(特别是Debug)时,有什么时间都花在了编译上了。

上面的 class 封装成 template 就成了:

// foo.h
template <class> 
class Foo
{
public:
    Foo();
    Type bar(Type&);
private:
    Type _val;
}; 
// foo.cpp
template <class> 
Foo <type> 
::Foo()
: _val(0)
{};

template <class> 
Type Foo <type> 
::bar(Type&val)
{
    _val = val;
    return _val;
} 

一些资料上也提到了 template 的“分离编译模式”,大致有以下这几种:

  1. export 关键字法
  2. cpp 实现文件包含法
  3. 只在头文件中声明(伪分离)

1. export 关键字法

也就是实现文件中的函数定义都加上关键字 export,来声明一个可导出的函数模板。

// model2.h
// 分离模式:只提供模板声明
template <typename> 
Type min( Type t1, Type t2 ); 
// model2.c
// 模板定义
export template <typename> 
Type min( Type t1, Type t2 ) {/* . . . */} 

使用函数模板min()实例的程序只需在使用该实例之前包含这个头文件:

// user.c
#include"model2.h"int i, j;
double d = min( i, j ); // OK: 用法,需要一个实例

可惜对于类是没法用了。。。

2. cpp 实现文件包含法

也就是在头文件的底部包含cpp文件

// foo.h
#ifndef _FOO_H_
#define _FOO_H_

template <class> 
class Foo
{
public:
    Foo();
    Type bar(Type&);
private:
    Type _val;
};

#include"foo.cpp"#endif // _FOO_H_ 

3. 只在头文件中声明(伪分离)

这个就比较无语了。。。

// foo.h
#ifndef _FOO_H_
#define _FOO_H_
#include"foo.cpp"#endif // _FOO_H_
// foo.cpp
template <class> 
class Foo
{
public:
    Foo();
    Type bar(Type&);
private:
    Type _val;
};

template <class> 
Foo <type> 
::Foo()
: _val(0)
{};

template <class> 
Type Foo <type> 
::bar(Type&val)
{
    _val = val;
    return _val;
} 

总结:

经过试验,VS2008不支持分离编译模式(前两种),只支持“第三种”,可以忽略了。。

纠正:

2009-11-27 查看 OpenCV2.0 源码后纠正上面的总结,并得到VS2008的“分离编译模式”注意点,解决方法有两种:

  1. 到实现文件的后缀名改成属于头文件的后缀名,如h,hpp等,这样VS就不会编译实现文件了,也就是不生成obj文件。
  2. 或者,自定义实现文件的编译方式,右击实现文件-》属性中设置,快捷键ALt + F7,选择自定义,也就是默认不编译。

再次总结:

问题的真正原因往往会被表象所迷惑。

参考:

C++ Template学习笔记之函数模板(5)——模板编译模式 1.
请问在vs2005中类模板的编译采用的是哪种编译模型? 1.
C++ Template: Why do I get unresolved externals with my template code? 1.

Click to read more ...

Compile error: unexpected class

VS2008错误提示

error C2236: unexpected ‘class’ ‘SkyCloudParticle’. Did you forget a ‘;’?

这是一个令人郁闷的错误提示,提示停在:

class SkyCloudParticle
{ // << HERE
};

分析

怀疑是包含的头文件的类定义有问题,但检查后一切正常。后来终于想到可能是在其它地方 include 这个头文件时,前面的头文件中有 class 定义没写好。经查看所有头类定义,终于发现了问题。问题出现在另一个类(SkyCloudsManager)的定义上,包括关系如下:

// file: SkyCloudsManager.cpp
#include "SkyCloudsManager.hpp"
#include "SkyCloud.hpp"
// file: SkyCloud.hpp
#include "SkyCloudParticle.hpp"
// file: SkyCloudsManager.hpp
class SkyCloudsManager
{
} << DEFINITION ERROR

总结

冷静思考!

Click to read more ...