[Bug] g++4.6 参数顺序

遇到一个bug, 看起来像是g++-4.6的问题。

问题是这样的。这个源文件用到了OpenCV:

//< file: test.cpp
#include 

int main (int argc, char** argv) {
    cv::Mat image;
    return 0;
}

用这样一行命令编译:

g++-4.6 `pkg-config --libs opencv`  -o test.bin test.cpp

遇到了错误:

/tmp/ccs2MlQz.o: In function `cv::Mat::~Mat()':
test.cpp:(.text._ZN2cv3MatD2Ev[_ZN2cv3MatD5Ev]+0x39): undefined reference to `cv::fastFree(void*)'
/tmp/ccs2MlQz.o: In function `cv::Mat::release()':
test.cpp:(.text._ZN2cv3Mat7releaseEv[cv::Mat::release()]+0x47): undefined reference to `cv::Mat::deallocate()'
collect2: ld returned 1 exit status

错误的原因应该是g++没有正确的链接到OpenCV的库。各种尝试之后发现只要调换一下参数的位置就可以正常编译 -_-!!
改用这样一行命令编译就没有问题了。

g++-4.6 test.cpp `pkg-config --libs opencv`  -o test.bin

具体原因不明,但是如果把g++-4.6换作g++-4.4就没有这个问题。
这行命令也可以正常编译:

g++-4.4 `pkg-config --libs opencv`  -o test.bin test.cpp 

这么看起来很有可能是g++-4.6的bug,或者是改进..?

[Vim]用行号参与替换

一个小技巧。Vim有好处千种,”替换”只是其中一个。

除了强大的正则表达式,\=也是一个好用的工具。
比如要生成这么一个文件

This is number 1
This is number 2
This is number 3
This is number 4
This is number 5
This is number 6
This is number 7
This is number 8
This is number 9
This is number 10

方法当然有很多。用\=可以这么做:
先输入一行

This is number X

复制出另外9行

yy9p

得到

This is number X
This is number X
This is number X
This is number X
This is number X
This is number X
This is number X
This is number X
This is number X

然后冒号进入Command-line模式 (关于Vim的几种模式)

:%s@X@\=line('.')

就得到了

This is number 1
This is number 2
This is number 3
This is number 4
This is number 5
This is number 6
This is number 7
This is number 8
This is number 9
This is number 10

\=其实就是对\=之后的表达式求值用来做替换。line(‘.’)是一个返回数值的函数,返回当前行的行号,所以每一行的行号被作为\=的返回值,用来替换X,就得到了需要的结果。

其他方法比如做一个宏(Macro)来逐行递增也可以达到效果,但是不如用这个\=方便。
因为\=后面的部分是作为表达式来处理的,所以更复杂一些的替换都可以很简单的得到实现,比如 (先撤销掉之前的改动,下同):

:%s@X@\=line('.')*line('.')

就可以得到

This is number 1
This is number 4
This is number 9
This is number 16
This is number 25
This is number 36
This is number 49
This is number 64
This is number 81
This is number 100

我个人觉得最好用的是这个功能

:%s@X@\=printf("%03d", line('.'))

可以得到

This is number 001
This is number 002
This is number 003
This is number 004
This is number 005
This is number 006
This is number 007
This is number 008
This is number 009
This is number 010

printf的加入又带来了太多种可能的玩法,非常称手。

[OpenCV] detectMultiScale: output detection score

OpenCV provides quite decent implementation of the Viola-Jones Face detector.

A quick example looks like this (OpenCV 2.4.5 tested):

// File: main.cc
#include 

using namespace cv;

int main(int argc, char **argv) {

    CascadeClassifier cascade;
    const float scale_factor(1.2f);
    const int min_neighbors(3);

    if (cascade.load("./lbpcascade_frontalface.xml")) {

        for (int i = 1; i < argc; i++) {

            Mat img = imread(argv[i], CV_LOAD_IMAGE_GRAYSCALE);
            equalizeHist(img, img);
            vector objs;
            cascade.detectMultiScale(img, objs, scale_factor, min_neighbors);

            Mat img_color = imread(argv[i], CV_LOAD_IMAGE_COLOR);
            for (int n = 0; n < objs.size(); n++) {
                rectangle(img_color, objs[n], Scalar(255,0,0), 8);
            }
            imshow("VJ Face Detector", img_color);
            waitKey(0);
        }
    }

    return 0;
}
g++ -std=c++0x -I/usr/local/include `pkg-config --libs opencv` main.cc -o main

The detection results are as shown below:
result

For more serious user, it would be nice to have a detection result for each detected face.
The OpenCV provides a overloaded function designed for this usage which is lack of detailed documentation:

vector reject_levels;
vector level_weights;
cascade.detectMultiScale(img, objs, reject_levels, level_weights, scale_factor, min_neighbors);

The reject_levels and level_weights will keep being empty until you write it like this (The whole file):

// File: main.cc
#include 

using namespace cv;

int main(int argc, char **argv) {

    CascadeClassifier cascade;
    const float scale_factor(1.2f);
    const int min_neighbors(3);

    if (cascade.load("./lbpcascade_frontalface.xml")) {

        for (int i = 1; i < argc; i++) {

            Mat img = imread(argv[i], CV_LOAD_IMAGE_GRAYSCALE);
            equalizeHist(img, img);
            vector objs;
            vector reject_levels;
            vector level_weights;
            cascade.detectMultiScale(img, objs, reject_levels, level_weights, scale_factor, min_neighbors, 0, Size(), Size(), true);

            Mat img_color = imread(argv[i], CV_LOAD_IMAGE_COLOR);
            for (int n = 0; n < objs.size(); n++) {
                rectangle(img_color, objs[n], Scalar(255,0,0), 8);
                putText(img_color, std::to_string(level_weights[n]),
                        Point(objs[n].x, objs[n].y), 1, 1, Scalar(0,0,255));
            }
            imshow("VJ Face Detector", img_color);
            waitKey(0);
        }
    }

    return 0;
}

However, this will give you a large number of detected rectangles:
result-org

This is because OpenCV skips the step of filtering out the overlapped small rectangles. I have no idea whether this is by design. But output likes this would not be helpful at least in my own case.

So we would need to make our own changes in the OpenCV's source code.
There are different ways to design detection score, such as
"In the OpenCV implementation, stage_sum is computed and compared against the i stage_threshold for each stage to accept/reject a candidate window. We define the detection score for a candidate window as K*stage_when_rejected + stage_sum_for_stage_when_rejected. If a window is accepted by the cascade, we just K*last_stage + stage_sum_for_last_stage. Choosing K as a large value e.g., 1000, we ensure that windows rejected at stage i have higher score than those rejected at stage i-1." from http://vis-www.cs.umass.edu/fddb/faq.html

Actually, I found a straightforward design of detection score works well in my own work. In the last stage of the face detector in OpenCV, detection rectangles are grouped into clustered to eliminated small overlapped rectangles while keeping the most potential rectangles. The number of final detected faces is at most same as the number of clusters. So we can simply use the number of rectangles grouped into the cluster as the detection score of the associated final rectangle, which may not be accurate but could work.

To make this change, in OpenCV-2.4.5, find the file modules/objdetect/src/cascadedetect.cpp (line 200)

// modules/objdetect/src/cascadedetect.cpp (line 200)
// int n1 = levelWeights ? rejectLevels[i] : rweights[i]; //< comment out this line
int n1 = rweights[i]; //< the change

We then modify the file main.cc accordingly:

// File: main.cc
#include 

using namespace cv;

int main(int argc, char **argv) {

    CascadeClassifier cascade;
    const float scale_factor(1.2f);
    const int min_neighbors(3);

    if (cascade.load("./lbpcascade_frontalface.xml")) {

        for (int i = 1; i < argc; i++) {

            Mat img = imread(argv[i], CV_LOAD_IMAGE_GRAYSCALE);
            equalizeHist(img, img);
            vector objs;
            vector reject_levels;
            vector level_weights;
            cascade.detectMultiScale(img, objs, reject_levels, level_weights, scale_factor, min_neighbors, 0, Size(), Size(), true);

            Mat img_color = imread(argv[i], CV_LOAD_IMAGE_COLOR);
            for (int n = 0; n < objs.size(); n++) {
                rectangle(img_color, objs[n], Scalar(255,0,0), 8);
                putText(img_color, std::to_string(reject_levels[n]),
                        Point(objs[n].x, objs[n].y), 1, 1, Scalar(0,0,255));
            }
            imshow("VJ Face Detector", img_color);
            waitKey(0);
        }
    }

    return 0;
}

And we can have the detection scores like this:
result-final

PhD们的总结

感叹一下,牛人们不但paper写得好,也很善于总结经验。
一早(好像也不早了…)在Facebook上看到行内CMU牛人田渊栋的PhD生涯总结,行文流畅,条理清晰。不在这里贴全文了,可以在这里看到:

[1] 博士五年总结 (一) – 田渊栋
[2] 博士五年总结 (二) – 田渊栋
[3] 博士五年总结 (三) – 田渊栋 

写得太好了,值得花时间好好读一读。

也可以对比之前比较火的MIT的Philip Guo的Ph.D Grind看看:
Ph.D Grind – Philip Guo 

[CV]检测灰色块

遇到一个看起来非常简单的问题:一张图片里面有一些某种颜色的色块,怎么样找到它们?
sample-mask
比较囧的是这个问题的起因。因为图片的标注文件丢了,不得不这么反向做检测来找回这些标注…想想人脸那么麻烦的结构都可以被检测出来,CV对付这种几乎完美的单颜色色块应该是小菜一碟吧。所以,大家虽然感觉反向检测自己处理的图片比较囧,但是完全不觉得这是个问题。同屋的哥们当场表示,他可以在10分钟之内搞定。

他的做法是我们一开始的想法,先按照色块的颜色(RGB: 128,128,128)把图片二值化,由于JPEG压缩,色块可能会有噪声。
sample-mask-binarized

然后我们准备对每行求和,对每列求和,会得到两个“直方图”,然后根据“峰”的位置和宽度就可以知道色块的位置和大小了。这个方法的确可以处理一张图里只有一个色块的情况,但是当图里有多个色块的时候,会出现“峰”的叠加,比如这张图,按行求和之后,由于有并排的色块。直方图就会变成这样:
sample-mask-histogram
这种情况之前这种方法就不好处理了。

结果这个看起来非常简单的问题,我们也折腾了好半天。最后还是得人指点,用连通分量来做,才得以解决。

做法是在二值化的图像上,找到不同的连通块 (Connected component),然后留下比较大的,就是灰色块了。为了处理噪声,当然需要用Gaussian做一做模糊之类的。效果还不错。(彩色色块表示检测出来的灰色色块)

large-sample-mask

large-sample-mask-result

问题总是没有看起来的那么简单。
matlab 代码放到 Github 上了: detect-gray-square

程序设计启示 (摘记)

刚开始看代码大全,真是大部头,不知道毕业之前能不能看完,或者看完的时候有没有毕业…(泪)。应该是还没有看到干货部分,不过对于程序设计里的问题,有很多很好的总结,所以摘录一点在这里。这部分原文是 Design Heuristics,直译应该是“设计启示”,可是我觉得应该算是一些心得。想起来以前在图书馆见过有一本书叫做《软件启示录》,按照当年的习惯,应该看过并且仅看过前言 ^^

罗列一下书上的总结,就不瞎翻译了。

Major Heuristics

  • Find Real-World Objects
  • Form Consistent Abstraction
  • Encapsulate Implementation Details
  • Inherit When Possible
  • Hide Secrets (Information Hiding)
  • Identify Areas Likely to Change
  • Keep Coupling Loose
  • Look for Common Design Patterns

Useful Heuristics

  • Aim For Strong Cohesion
  • Build Hierarchies
  • Formalize Class Contracts
  • Assign Responsibilities
  • Design for Test
  • Avoid Failure
  • Choose Binding Time Consciously
  • Make Central Points of Control
  • Consider Using Brute Force
  • Draw a Diagram
  • Keep Your Design Modular

封装、抽象和解耦合是时时记住的准则。但是我没有系统地学过设计模式,这部分要去补习了。

第二个列表提到了层次 (Hierarchies),最近感觉层次是方法,但是也是问题。有时候是为了解耦,有时候单纯为了处理重复代码,很容易就会增加抽象层次,结果就是代码越写越厚,为了保持各层之间独立,新增加底层功能要暴露给上面的代码需要修改很多地方。深以为是自己做的不对,每次大改完之后会好一些,但是层次的增加似乎真是不可避免。想到最近云风大喷C++的时候说C++会引导程序员增加层次,不知道是不是我现在遇到的情况。

最近这次改代码,尽量做到扁平,最理想的情况其实是各个功能可以独立出来,像EM、聚类这些算法可以随时独立的拿到其他工程里去用,但是除非是大量的用代码生成代码 (刚刚想到,不知道可不可行),否则就得手动处理很多重复代码。所以,我觉得库都要有支持基本类型的结构,可以把float*、int*很方便的、高效地转成它们的基本类,否则如果只需要用到库的一小部分功能就要以库里的类型渗透到所有的代码逻辑里为代价,就太不划算了。

还有一点有同感的就是 Make Central Points of Control 这一点,写实验的时候这一点太重要了。实验里有很多参数、设置都放到一个地方直观而且不容易出错。我之前的做法是非常简单的config类,然后是 key-value 来存取值,但是当配置躲起来的时候,一则 key 太多放在一起很没有逻辑,二则 key 是没有类型的,如果程序里到处都是valueOfKey()这种调用,很容易出错。为了把key-value和对应的模块放到一起,又需要有一些结构做配置之上的封装,形成像em的配置、取特征的配置这类的结构。现在的问题就是,如果要增减一个配置结构里面的内容,需要改动至少两个不同的地方。感觉这个是老问题,应该有现成的解决方法,但还是没有找到。

配置文件的另外一个问题就是需要有东西生成配置文件,现在用bash写configuration文件,然后C++读,所以C++需要和bash共享一部分内容,比如key-value pairs的key。现在的做法是类似用bash读一个config.h。所以bash脚本就必须知道源文件的位置,不知道有没有更好的解决方法。

本来就想写个笔记,没想到吐槽了一大堆。在实验室写代码的好处就是可以自己控制整个工程,虽然工作量大一点,也没有测试帮忙保证质量,但是可以默默地改代码,满足一下码农的完美主义。

On 2 dimensional array of C++

I was asked about this today. In practice, I rarely use 2-dimensional array, instead I use vector of vectors.

To allocate a 2-d array on the stack, a C-style array is

int d[2][3];

Then to refer to an element it is like

d[i][j];

To make a dynamical allocation, one can NOT write his code like this

int **wrong_d = new int[2][3];

since the d in int d[2][3]; is not a int**, instead it is of the type

int (*)[3]

or in your evil human words, it is a int[3] pointer.

It is a little tricky to declare a 2-d array dynamically.

int (*d)[3] = new int[2][3];

Or

int v1 = 2;
int (*d)[3] = new int[v1][3];

The number 3 here can NOT be replace by a non-constant. My understanding is that since this value is associated with the type of d, in a strong type language like C/C++ which should be known by the compiler.

We can check the size of variables to verify this interpretation.

 1 #include 
  2 
  3 using namespace std;
  4 
  5 int main(int argc, char **argv)
  6 {
  7     int v1 = 2;
  8     int (*d)[3] = new int[v1][3];
  9     cout << "sizeof(d): " << sizeof(d) << endl;
 10     cout << "sizeof(d[0]): " << sizeof(d[0]) << endl;
 11     cout << "sizeof(d[1]): " << sizeof(d[1]) << endl;
 12     cout << "sizeof(d[0][0]): " << sizeof(d[0][0]) << endl;
 13     return 0;
 14 }

The output is:

$./test 
sizeof(d): 8        //< 2 pointers, &d[0] and &d[1]
sizeof(d[0]): 12    //< int[3], d[0][0] d[0][1] d[0][2]
sizeof(d[1]): 12    //< int[3], d[1][0] d[1][1] d[1][2]
sizeof(d[0][0]): 4  //< an integer

To save your life, I would recommend to use vectors.

int v1 = 2;
int v2 = 3;
vector > d(v1, vector(v2, 0)); 

Nested Array in Bash

用了Bash这么久,才知道Bash支持Array。但是却缺乏对嵌套数组,或者是多维数组的支持。自己的实验里面需要用到结构性的数据,这样看起来或者改起来会比较方便,而且因为这部分是用来处理实验结果,需要经常修改,所以不适合放到C++里面去写。
因此就有了需要Bash支持嵌套数组的需求。

最终的解决方法不是很漂亮,但是也足够我自己用了。这个想法的出发点是这样的:

Bash在处理数组的时候会用到IFS这个环境变量。比如这样一段字符

Li,Age*1;Weight*2;Height*3;Friends*Sun^Wang Wang,Age*11;Weight*12;Height*13;Friends*Li^Sun

如果IFS是空格,我们就可以得到两个元素

  • Li,Age*1;Weight*2;Height*3;Friends*Sun^Wang
  • Wang,Age*11;Weight*12;Height*13;Friends*Li^Sun

如果IFS是分号(;),我们可以得到另一个数组

  • Li,Age*1
  • Weight*2
  • Height*3
  • Friends*Sun^Wang Wang,Age*11
  • Weight*12
  • Height*13
  • Friends*Li^Sun

也就是说我们可以通过指定不同的IFS让一段字符成为不同的数组。

所以,我们可以通过对每一层使用不同的IFS来表达一个嵌套数组。比如上面那段字符,我们用不同的IFS字符,先用空格( ),再用逗号(,),之后用分号(;)……
如此做下去就可以达到层层盘剥的效果(-_-!)

在实际使用中,我们只要保证每一层用到的IFS不会在数组内容中出现就行了。
为了能够生成那样一段字符,我们自然需要一些函数做辅助。

具体的代码放到Github上了。目前这个方法虽然有效,但是使用起来仍然不够简洁,看起来如果找不到更好的Bash下的解决方法,就得考虑换用一种20世纪的脚本语言了…

Github: Nested-Array-Bash

最近

已经好久没有写东西了,三言两语也是一种存在。前两天把文章投出去,这个学期也快要结束了。

真是充实的一个学期,短短不到三个月的时间里:mm过来,生活状态上带来了不小的变化,体重上来好几斤;第一篇文章被接收,总算有了Publication,感觉挺欣慰;之后是自己的PhD资格考试,严肃地讲了一次自己的工作;接着接着就是做新的文章;最后就是还没有结束的TA,第一次做助教,给小本上复习课感觉压力山大。

之后做什么呢?要做新的东西,要去学开车,暑假要去实习。很是期待暑假的实习,可以见见同学,可以看看另外一种生活状态。埋着头,慢慢走吧。忽然很想听崔健。

rand函数不可重入

写C代码的时候,srand(int seed) 和 rand() 是常用的伪随机数生成函数。
这两个函数的使用方法很简单,但是一个可能被忽略的细节是,rand() 依赖一个内部的、全局的状态变量。所以 rand() 是不可重入,也是不是线程安全的 (thread-safe) 。

如果多个线程同时调用 rand() 函数,那么无论你如何使用 srand(int seed) 都无法保证结果是可以重现的。每次运行程序,各个线程中 rand() 函数生成的伪随机数序列都和上次不同。

在调试的时候,不能重现的结果会是比较棘手的障碍。

幸好,我们可以用C++11 提供的伪随机数生成器 Pseudo-random number generation (这么翻译好机械-_-!)用法很容易在网络上找到,这里有一个最简单的例子。

#include 
//.....
{
std::default_random_engine gen(0);
int a_random_number = gen();
}

default_random_engine维护自己的内部状态,各个线程都用同样的参数初始化default_random_engine,就可以得到一致的伪随机数序列了。