一次试验 十二月 28th, 2011
最近在重写一个人脸检测器,流程比较简单,加载一系列的模型和分类文件,然后输入图片进行检测。其中模型文件有两类,一类是正面分类的(是人脸Or不是人脸),另外一类是负分类(不是人脸Or可能是人脸),这里的正负是私自取的名字,为了书写方便。其中还涉及到一些预处理和后处理,算法框架不算复杂,但之前写的时候可能因为仓促,内部实现写了很多类,外部接口用的是IMPL方式,层次关系大概是这样的:Base class –> Interface Class –> IMPL class。在IMPL class内部有包含八种其他的类:两种IMAGE class,四种RECT class,两种Classifier class。拜C++所赐,IMPL class的舒适化方法写了七八种,Classifier的初始化方法写了四五种,都是考虑从不同的源来加载和初始化。不知道原作者是如何想的,好似在写一些小类的时候无法摆脱“写库”的冲动,动不动就写了很多根本就不会设计的方法,或者一个十分小的RECT,居然也写getter和setter。刚开始看到的代码的时候,并不了解其中的流程,随便在Emacs里打开一个文件,第一印象就不好。之前看programmers at work, 大牛说你站在10步开外看一份代码,如果觉得代码很丑,那这代码就很可能有问题,而且是算法逻辑上的问题。当然,我手头上的这份原始码逻辑上应该没什么问题,仅丑而已。
所以我就开始给这份代码动手术,整容。
前些天,在回家的路上突然体味到DRY和KISS的重要,以至于我厌恶起那些动辄就罗列设计模式的教条式方式。我并不否认使用设计模式可能会使一个系统更加的灵活,但是写程序中最重要的事情莫过于你知道你在做什么?你的目标是什么?你有时候还要想你设计的系统是什么样子的,解决的是什么问题,应该具有什么功能等等,这些或许是一些老生常谈,但我看到很多人在实际工作的时候确实没有实现将这些问题想明白再动手的习惯。当然,有些问题还是会在实现的过程中暴露出来,但这是一个过程,你越习惯这样的方式,这个过程就会越来越顺利。
我这里就是要给出一个我刚制造的反例,在实验过程中暴露了设计的问题。
我将系统分割为三部分,接口,算法单元和数据处理单元。其中接口指的是开放的API,其中接着沿用IMPL的方式,但是提供原生的C API以及一个C++ Class作为Wrapper。去掉原始码中的基类,理由是基类的存在无异于鸡肋。我觉得想要给系统提供扩展性这个想法是没错的,但是要考虑到系统以后要以什么样的方式扩展,对应的给予扩展机制。无端的加一个基类,给我的感觉就是,好像这里将会有很多人脸检测器的实现,到时候可以多态的进行调用…………在我而言,发生这种事情的可能性太小,即使将来需要多个检测器,最好的方式还是模块化吧。这样无论调试还是复用都会更加灵活方便。
分割出一个算法单元来,基于的考虑是:大多数基于分类的检测算法里,最核心的算法往往很简单也很通用,卷积点乘等等。我这里其实也差不多,外加一个预处理和后处理的算法,这部分如果单独隔离开,以后更改或者复用也会更加的方便,没有必要使用一个类将这些算法和数据块包装在一起。
需要处理的数据比较多,比如IMAGEs,RECTs,Classifiers,Models等等,如何串联处理单元和数据单元呢?我当时的想法是,在中间加一层纯的数据结构,数据单元的功能是在这些数据结构上做操作,不涉及其他的数据单元,彼此独立性较高。所以最后成了如下结构:
proc module <- structs –> struct1_impl,struct2_impl,struct3_impl…
这个结构搭建起来之后,我发现我忽略了一个问题。这个抽象实现和代码实现中间有点脱轨。因为结构都在structs 里面,之后的每一个单独实现都需要include这个头文件,这个造成了不必要的编译压力。同时这里违反了高内聚和低耦合的原则,虽然我试图通过这种划分减少减少耦合从而是整个代码在结构上更加接近Plain的模式,但中间使用structs链接的方式导致每一个数据结构和自己的实现分离了,这在以后,会给代码维护造成一定的困难,同时对于单独的结构而言,违反了高内聚的原则,在形式上,比如RECT和它自身的一些方法没有必要在两个地方里面实现。单独在一个地方解决会更加紧凑。
我依然坚持最初的想法,将系统分割为那几个部分,但是需要简化的部分是中间联系处理模块和数据模块的结构。其实并非所有的结构都需要放在structs里面,最后的设计其实就是一个结构,这个结构正好对应处理模块的需求,不多也不少。右边的结构变成了散点分布。虽然也是各种IMAGE,RECT实现,但是做到最简,并且数据和方法完全隔离,虽然有时候觉得这样增加了一个传递参数,但是,总觉得这样层次更加鲜明,所以还是这样去做了。另外这样做的原因是想为之后的数据缓存和并行加速提供更大的灵活性。
现在的结构就是这样的。中间走了点弯路,但也收获了一些东西。最后说一个在看programmers at work时看到的笑话,关于匈牙利命名法,很多编程规范里都有,其实这最开始是一个笑话,说一个人写的变量让代码变得可读性非常差,就像使用匈牙利语写的一样。后来匈牙利法变成了一系列书写name的规则,虽说代码是会给别人看和理解的,但是知道名字便能读懂代码并非有直接联系,代码代表一系列的逻辑运算,我觉得在一些难懂的地方写好注释会比写冗长的名字更加漂亮。阅读lcc源代码就深有体会,代码非常简洁漂亮,那些名字都短到极致,但是你仍然不用费多大的劲就能看懂它,为什么?因为作者写了一本解释这个代码的书……汗吧。
就这些。
Tips record 十二月 22nd, 2011
在C或者C++源代码里查错或者使用一些技巧的时候,很可能要用到一些编译器处理的中间结果。这里记录一下,以便以后参考。
记录之前查了一下,有很多资料可考,但是没有和具体的例子联系起来,这样我以后看到了也不知道我当初为什么用到这个,所以我还是打算写一下,这里,这里,这里,等,给了一些其他参考链接。
上面说的中间结果是来自于预处理、编译、汇编、链接这四个地方的,关于其他的比如细致的或者运行时的一些中间结果不涉及,为了记录的速度,只考虑GCC,多文字,少代码。我的出发点是一些零散的日常需求,比如,有时候要定义一些类似的结构,比如
这个时候我不清楚,unsigned int这个参数能否成功的传递进去,这里就需要预处理之后的结果,在GCC下面只需要加一个-E选项就可以得到预处理之后的结果。
gcc –E test.c
其实如果熟悉预处理的话,这里就不用看了。我没有查看标准,但在GCC下面和MSVC下面测试,上面代码能按照我想象的工作,那么这就有意思了。也许有人认为用宏来这样写结构作用不大,比如这个Image结构,成员很少,我写的时候基本上也是走极简的路线,一点多余的东西都不肯往里面放,使用宏的话不但可以少些很多代码(比如以后定义float类型的image),还具有很大的扩展性。因为,加入unsigned int都能传递进去,说明宏这里的参数替换规则和函数的很不同,利用这一点,就能得到很大的扩展性。比如
这样就扩展了原有成员,不用重复敲代码。如果是C++的话,在这种情况下也没有必要用集成了,plain is good.在这里使用预处理的作用是……假如你对自己并没有那么多信心,同时你觉得查标准太麻烦,那么-E一下,立马就可以得到答案。
有时候写完代码,进行编译,如果代码是多个人的话,有时候会出现宏重定义的错误,或者并不仅仅是重定义。比如
如果成了这样子,不会产生任何警告和错误,编译通过,直接从代码里面看也很难发现。这时,查看预处理之后的文件也是比较有效的。
因为我们写代码大部分时间都是根据编译信息来订正。大家常说的忠告也是:treat warning as error.其他的我也不说了。在GCC里选项是-O,生成.o的目标代码。
需要查看汇编输出的时候,一般是想对代码进行优化,或者查看优化后的代码是否保持功能不变。查看在GCC里看生成的汇编代码需要加选项-S
gcc –S test.c
有时候你不清楚函数是以什么方式压栈,不清楚参数执行顺序,担心操作符是否有副作用等时候,你都可以写一个简单的源文件,看一下汇编代码,心里就会有底。三元操作符的执行顺序,逗号操作符的求值顺序,++操作符何时执行,switch-case为什么比if-else快等等都可以在这一步找到答案。
这里就不写代码了。
写程序,链接错误可能是最恼人的一类错误了。很多人心里并不清楚为什么会出现链接错误,什么原因导致的,就盲目的一阵心烦。特别是,当你使用一些IDE时,解决链接错误的trick往往是rebuild一下,这或许让人脑袋后吊一坨汗,但却是比较恶心。链接其实就是一个查找的过程,出错的原因无法是查找不到。这里查找的对象并非是写入的名字,很多编译器都会做中间处理,扩展函数名。不同的调用说明(calling convention)会导致扩展后的函数名不一样。这样就弄得二进制不兼容了。当然二进制不兼容常出现在调用链接库上,因为修改,导致函数偏移量改变。COM是一种解决策略,在Windows下很流行,但我觉得我要是开发小规模的东西的话,COM太大了。
链接过程是将不同的目标文件弄到一起,手动完成链接过程会得到一部分信息,其实大部分信息还是可以在预处理后的代码里面找到。所以在这里出现了错误,排错的话仍然可以回到第一步,看看调用说明是否一致。
就这些。
update:
最近不断的发现,有些同学对图像数据中width和widthStep或者stride的差别不是很理解,恰好此文中的几个截图里面又没有给出这个差异,当时只是为了说明宏的表达能力,为了不误导人,所以在这里更新一下。widthStep或者stride,是指在row-major存储的图像数据里一行的字节数多少。在计算机里面一个char或者uchar的长度一般是字长的四分之一,数据总线的数目一般是等于字长的,所以大多数图像处理的库在编解码图像数据的时候,都将图像的一行align成字长的整数倍,比如一行又111个字节,align后就是112,这个数据通常就是ws的值,此时align的单位长度是4,这和机器有关,有时候也会是8。在实际编码中,可以使用很简单的方法测试一下数据是否align,假设align_sz = 4,112 = (111 + (4-1))& ~(4-1)。
推荐Kindle 4 十二月 21st, 2011
从粉岭到Lab需要大概四十分钟,其中大部分时间是在地铁上站过。之前度过那段时间的方式是在地铁站取一份免费的报纸,一般来说报纸的厚度都会刚好度过这段时间,但是HK这地方太小报纸上很多信息比较乏味。后来也端着一本书看,也觉得比较不方便,一是因为书比较沉,一是因为书容易暴露。后来就买了一个Kindle 4,在淘宝上买的,700RMB。这几天一直在使用,写一下相关的东西。
总体来说,觉得不错。看书的时候确实不伤眼睛,开始我以为晚上也能直接看,后来发现不行,屏幕本身不发光,当时觉得不习惯,慢慢觉得这样也好,可以更好的保护眼睛。轻,屏幕只有6寸,加上皮套也感觉不到什么重量,拿在手上放在兜里不会有多大不便。电池续航时间长,冲一次电,据说可以续航一两个月,当然这是在不怎么使用WIFI上网的情况下。另外操作方便,外观设计可以给八分。
刚拿到手的时候,体验并非那么满意。使用说明书什么的都是英文,链接WIFI如果需要redirect的话,就没法链接上。如果是txt的书,看起来挺不错。如果是PDF,阅读体验会差一些。当然,支持最好的应该是mobi文件格式。去新浪爱问之类的网站上直接搜书名加mobi一般能找到相应的电子书,当然无法避免打开之后乱码的情况。
我一直在犹豫要不要贴屏保,不贴怕弄脏损坏,贴了又着实影响体验(气泡,反光等问题),后来还是贴了,然后一看,擦,撕掉了。
我买的这个版本就是所谓的广告版,一旦连上WIFI,它自动下载广告,无论是Home页面还是屏保,都会让你……当然有些例外的……觉得不爽,所以去掉广告的方法就是:
0.连上PC,Kindle不要联网。
1.设置所有文件可见,此时你应能看到一个system文件夹。
2.赋值documents和system文件夹到你的电脑上。
3.拔掉Kindle,按Menu到setting,再按Menu,恢复出厂设置。
4.上个厕所。
5.设置好语言后,重连PC。
6.将documents和system赋值到Kindle
7.在system文件夹下,创建一个.assets文件(windows:打开一个文本编辑器(比如notepad),新建一个空文件,随便写点儿什么,Ctrl+S 保存…此时注意:文件类型一定要选择 All Files 类型。然后,输入文件名 .assets ,然后保存.)
8.重启Kindle
Kindle操作系统是基于Linux的,代码已经开源,这样也挺方便的。
综合来说,我觉得手上这款Kindle还是蛮值的,推荐一下。同时最近看完两本书,《Rework/重来》和《人月神话》,觉得都是很好的书,虽然我知道推荐书应该指定对象,但我也不清楚怎么去指定对象,界限有点模糊,我觉得这两本书的话,拿起来翻两页,如果想读下去就对了,觉得无聊就放下。我也不多说书评,之前准备写读后感,到豆瓣上看了一下之后觉得再写的话就多余了。大家可以去看看,上面都有相应链接。
就这些。