维奥拉-琼斯人脸检测声称有180,000个特征。

89
我一直在实现Viola-Jones人脸检测算法的改编版。该技术依赖于在图像中放置一个24x24像素的子框架,随后在其中的每个位置上以及每种可能的大小上放置矩形特征。
这些特征可以由两个、三个或四个矩形组成。以下是一个示例。

Rectangle features

他们声称完整的矩形特征集超过180,000(第2节):
鉴于探测器的基本分辨率为24x24,矩形特征的详尽集非常大,超过180,000。请注意,与Haar基不同,矩形特征集是过完备的。
以下声明在论文中没有明确说明,因此它们是我假设的:
1.只有2个双矩形特征,2个三矩形特征和1个四矩形特征。其背后的逻辑是我们观察突出显示的矩形之间的差异,而不是明确的颜色、亮度或其他任何形式的差异。
2.我们不能将A型特征定义为1x1像素块;它必须至少为1x2像素。同样,D型特征必须至少为2x2像素,并且此规则相应地适用于其他特征。
3.我们不能将A型特征定义为1x3像素块,因为中间像素无法分区,将其从自身中减去等同于1x2像素块;这种特征类型仅适用于偶数宽度。此外,C型特征的宽度必须可被3整除,并且此规则相应地适用于其他特征。
4.我们不能定义宽度和/或高度为0的特征。因此,我们将x和y迭代到24减去特征的大小。
基于这些假设,我已经计算了详尽的集合:
const int frameSize = 24;
const int features = 5;
// All five feature types:
const int feature[features][2] = {{2,1}, {1,2}, {3,1}, {1,3}, {2,2}};

int count = 0;
// Each feature:
for (int i = 0; i < features; i++) {
    int sizeX = feature[i][0];
    int sizeY = feature[i][1];
    // Each position:
    for (int x = 0; x <= frameSize-sizeX; x++) {
        for (int y = 0; y <= frameSize-sizeY; y++) {
            // Each size fitting within the frameSize:
            for (int width = sizeX; width <= frameSize-x; width+=sizeX) {
                for (int height = sizeY; height <= frameSize-y; height+=sizeY) {
                    count++;
                }
            }
        }
    }
}

结果为162,336

我找到的唯一逼近维奥拉和琼斯所说的“超过180,000”的方法是放弃假设#4,并在代码中引入错误。这涉及将四行分别更改为:

for (int width = 0; width < frameSize-x; width+=sizeX)
for (int height = 0; height < frameSize-y; height+=sizeY)

结果是180,625。请注意,这将有效地防止特征接触子框的右侧和/或底部。现在当然的问题是:他们在实现中犯了错误吗?考虑表面积为零的特征有意义吗?还是我看错了?

当我运行你的代码时,为什么会得到count=114829? - Niki
你的x/y循环为什么从1开始?我假设x/y是特征矩形的左上角坐标。那么x/y不应该从0/0开始吗? - Niki
如果x/y从0开始,我得到162336。如果我放弃假设#4,并让宽度/高度从0开始,我得到212256。我想知道他们是如何得到180k的... - Niki
哈哈,太棒了,现在我们都处于同一阶段!布列塔尼说得好,这个大小永远无法达到24。我会再看一下的。 - Paul Lammertsma
是的,我也无法使它接近 180,000。这是我的糟糕尝试。http://jsbin.com/imase/edit(按输出选项卡查看结果,JavaScript 选项卡查看源代码) - Breton
显示剩余5条评论
6个回答

42

仔细看了一下,您的代码在我的看法下是正确的;这让人想知道原作者是否存在一个差一的bug。我猜有人应该去看看OpenCV是如何实现它的!

尽管如此,为了更容易理解,建议通过先遍历所有尺寸,然后再根据尺寸循环遍历可能的位置,来翻转for循环的顺序:

#include <stdio.h>
int main()
{
    int i, x, y, sizeX, sizeY, width, height, count, c;

    /* All five shape types */
    const int features = 5;
    const int feature[][2] = {{2,1}, {1,2}, {3,1}, {1,3}, {2,2}};
    const int frameSize = 24;

    count = 0;
    /* Each shape */
    for (i = 0; i < features; i++) {
        sizeX = feature[i][0];
        sizeY = feature[i][1];
        printf("%dx%d shapes:\n", sizeX, sizeY);

        /* each size (multiples of basic shapes) */
        for (width = sizeX; width <= frameSize; width+=sizeX) {
            for (height = sizeY; height <= frameSize; height+=sizeY) {
                printf("\tsize: %dx%d => ", width, height);
                c=count;

                /* each possible position given size */
                for (x = 0; x <= frameSize-width; x++) {
                    for (y = 0; y <= frameSize-height; y++) {
                        count++;
                    }
                }
                printf("count: %d\n", count-c);
            }
        }
    }
    printf("%d\n", count);

    return 0;
}

使用与之前的162336相同的结果。


为了验证它,我测试了一个4x4窗口的情况,并手动检查了所有情况(很容易计算,因为1x2 / 2x1和1x3 / 3x1形状只是旋转90度而已):

2x1 shapes:
        size: 2x1 => count: 12
        size: 2x2 => count: 9
        size: 2x3 => count: 6
        size: 2x4 => count: 3
        size: 4x1 => count: 4
        size: 4x2 => count: 3
        size: 4x3 => count: 2
        size: 4x4 => count: 1
1x2 shapes:
        size: 1x2 => count: 12             +-----------------------+
        size: 1x4 => count: 4              |     |     |     |     |
        size: 2x2 => count: 9              |     |     |     |     |
        size: 2x4 => count: 3              +-----+-----+-----+-----+
        size: 3x2 => count: 6              |     |     |     |     |
        size: 3x4 => count: 2              |     |     |     |     |
        size: 4x2 => count: 3              +-----+-----+-----+-----+
        size: 4x4 => count: 1              |     |     |     |     |
3x1 shapes:                                |     |     |     |     |
        size: 3x1 => count: 8              +-----+-----+-----+-----+
        size: 3x2 => count: 6              |     |     |     |     |
        size: 3x3 => count: 4              |     |     |     |     |
        size: 3x4 => count: 2              +-----------------------+
1x3 shapes:
        size: 1x3 => count: 8                  Total Count = 136
        size: 2x3 => count: 6
        size: 3x3 => count: 4
        size: 4x3 => count: 2
2x2 shapes:
        size: 2x2 => count: 9
        size: 2x4 => count: 3
        size: 4x2 => count: 3
        size: 4x4 => count: 1

非常有说服力。我相当确信我们是正确的。我已经给作者发送了一封电子邮件,看看我在推理中是否犯了一些基本错误。我们将看看这个忙碌的人是否有时间回复。 - Paul Lammertsma
请记住,这个东西已经发布了几年了,自那时以来进行了许多改进。 - Amro
29
提到"180k"的原始文件来自于2001年计算机视觉和模式识别会议的论文集。一篇经修订的论文于2003年被接受,并在2004年发表在国际计算机视觉杂志上,第139页(第2节结尾)中指出:"矩形的详尽集相当大,有160,000个"。看起来我们是正确的! - Paul Lammertsma
5
好的,谢谢更新。对于有兴趣的人,我找到了一篇IJCV'04论文的链接:http://lear.inrialpes.fr/people/triggs/student/vj/viola-ijcv04.pdf - Amro
是的,就是这样。160k,不是180k。 - Paul Lammertsma

9

总的来说,Viola和Jones的论文中仍然存在一些混淆。

在他们CVPR'01 的论文中,清楚地说明:

"更具体地说,我们使用三种 种特征。一个二矩形特征的值是两个矩形区域内像素之和的差。 这些区域具有相同的大小和 形状,水平或垂直相邻(见图1)。 三个矩形特征计算外部两个矩形内的总和 与中心矩形中的总和相减。最后四个矩形特征。".

在IJCV'04的论文中,完全说了同样的事情。所以,总共4个特征。但奇怪的是,这次他们声明详细的特征集为45396!那似乎不是最终版本。我猜那里引入了一些额外的约束条件,比如min_width、min_height、width/height比例,甚至位置。

请注意,这两篇论文都可以在他的网页上下载。


3

我没有看完整篇论文,但你引用的措辞引起了我的注意。

考虑到检测器的基本分辨率为24x24,矩形特征的穷举集相当大,超过180,000个。请注意,与Haar基不同,矩形特征的集合是过完备的。

"矩形特征的集合是过完备的" "穷举集"

对我来说,这听起来像是一个设定,我期望论文作者会跟进并解释他们如何将搜索空间缩小到更有效的集合,例如通过排除表面积为零的矩形等微不足道的情况。

编辑:或者使用某种机器学习算法,正如摘要所暗示的那样。穷举集意味着所有可能性,而不仅仅是“合理”的可能性。


我应该在“过完备”之后包含脚注:“完备基底的基元之间没有线性依赖关系,并且具有与图像空间相同数量的元素,本例中为576。完整的180,000个特征集是多次过完备的。”他们没有明确消除没有表面分类器,而是使用AdaBoost来确定“少数这些特征可以组合成有效的分类器”。好的,所以零表面特征将立即被删除,但为什么要首先考虑它们呢? - Paul Lammertsma
听起来像是一个非常热衷于集合论的人的推理。 - Breton
我同意,详尽的集合将意味着所有可能性。但请注意,如果您将1到24作为x的取值,并且宽度<=x,则该特征将延伸1个像素到子框之外! - Paul Lammertsma
你确定你的代码没有“差一”的错误吗?我刚仔细看了一下,你写for循环的方式确实有点奇怪。 - Breton
我应该说明一下 - 我刚刚仔细考虑了一下,如果你有一个高度为1像素、2像素、3像素,一直到24像素的矩形,你就有了24种矩形,它们都适合于一个高度为24像素的子框架中。那么超出部分呢? - Breton
显示剩余3条评论

2

没有任何一篇论文的作者能够保证他们所有的假设和发现都是正确的。如果你认为第四个假设是正确的,那么请保留这个假设并尝试验证你的理论。你可能会比原作者更成功。


实验表明它表现出似乎完全相同的结果。我认为AdaBoost在第一轮中只是放弃了那些额外的零面特征,但我实际上没有深入研究过这个问题。 - Paul Lammertsma
Viola和Jones是计算机视觉领域非常响亮的名字。事实上,这篇论文被认为是开创性的。每个人都会犯错,但是这个特定的算法已经被证明非常有效。 - Dima
1
当然,我对他们的方法一点都不怀疑。它高效且运行非常好!理论是可靠的,但我认为他们可能错误地将探测器裁剪了一个像素,并包含了不必要的零表面特征。如果不是这样的话,我挑战你来证明有180k个特征! - Paul Lammertsma
事实是每个人都是人类,每个人都会犯错误。当一个大名人犯错时,往往会隐藏几代人,因为人们害怕质疑所接受的智慧。但真正的科学遵循科学方法,不崇拜任何人,无论他们的名字有多大。如果这是科学,那么普通人可以付出努力,理解它的工作原理,并将其适应于自己的情况。 - Michael Dillon
我们会看看的;我已经给作者发了一封电子邮件。 - Paul Lammertsma

1

在他们2001年的原始论文中,他们只声明使用了三种特征:

我们使用了三种特征

分别采用两个、三个和四个矩形。

由于每种特征都有两个方向(相差90度),也许为了计算总特征数,他们使用了2*3种特征:2种两个矩形特征、2种三个矩形特征和2种四个矩形特征。基于这个假设,确实有超过180,000种特征:

feature_types = [(1,2), (2,1), (1,3), (3,1), (2,2), (2,2)]
window_size = (24,24)

total_features = 0
for f_type in feature_types:
    for f_height in range(f_type[0], window_size[0] + 1, f_type[0]):
        for f_width in range(f_type[1], window_size[1] + 1, f_type[1]):
            total_features += (window_size[0] - f_height + 1) * (window_size[1] - f_width + 1)
            
print(total_features)
# 183072

第二个四矩形特征与第一个仅有符号不同,因此没有必要保留它,如果我们删除它,则特征的总数减少到162,336个。

1
非常好的观察,但是他们可能会隐式地在24x24帧上进行零填充,或者在旋转移位时“溢出”并开始使用第一个像素,或者像Breton所说,他们可能认为某些特征是“微不足道的特征”,然后用AdaBoost将它们丢弃。
此外,我编写了您的代码的Python和Matlab版本,以便我可以自己测试代码(对我来说更容易调试和跟踪),如果有人发现它们有用,我会在这里发布。
Python:
frameSize = 24;
features = 5;
# All five feature types:
feature = [[2,1], [1,2], [3,1], [1,3], [2,2]]

count = 0;
# Each feature:
for i in range(features):
    sizeX = feature[i][0]
    sizeY = feature[i][1]
    # Each position:
    for x in range(frameSize-sizeX+1):
        for y in range(frameSize-sizeY+1):
            # Each size fitting within the frameSize:
            for width in range(sizeX,frameSize-x+1,sizeX):
                for height in range(sizeY,frameSize-y+1,sizeY):
                    count=count+1
print (count)

Matlab:

frameSize = 24;
features = 5;
% All five feature types:
feature = [[2,1]; [1,2]; [3,1]; [1,3]; [2,2]];

count = 0;
% Each feature:
for ii = 1:features
    sizeX = feature(ii,1);
    sizeY = feature(ii,2);
    % Each position:
    for x = 0:frameSize-sizeX
        for y = 0:frameSize-sizeY
            % Each size fitting within the frameSize:
            for width = sizeX:sizeX:frameSize-x
                for height = sizeY:sizeY:frameSize-y
                    count=count+1;
                end
            end
        end
    end
end

display(count)

为什么你使用了5个特性,而主要问题中只发布了4个。不过还是感谢你提供Python版本。 - Kasparov92

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接