Commit 0743a72c authored by Ross Girshick's avatar Ross Girshick
Browse files

initial checkin

parents
.nfs*
feat_cache/*
graveyard
*.mat
bin/*
selective_search/SelectiveSearchCodeIJCV
000084.jpg

37.6 KB

Copyright (c) 2014, Ross Girshick, Jeff Donahue, Trevor Darrell and Jitendra Malik
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## R-CNN: *Regions with Convolutional Neural Network Features*
Created by Ross Girshick, Jeff Donahue, Trevor Darrell and Jitendra Malik at UC Berkeley EECS.
### Introduction
R-CNN is a state-of-the-art visual object detection system that combines bottom-up region proposals with rich features computed by a convolutional neural network. At the time of its release, R-CNN improved the previous best detection performance on PASCAL VOC 2012 by 30% relative, going from 40.9% to 53.3% mean average precision. Unlike the previous best results, R-CNN achieves this performance without using contextual rescoring or an ensemble of feature types.
### Citing R-CNN
If you find R-CNN useful in your research, please consider citing:
@inproceedings{girshick14CVPR,
Author = {Girshick, Ross and Donahue, Jeff and Darrell, Trevor and Malik, Jitendra},
Title = {Rich feature hierarchies for accurate object detection and semantic segmentation},
Booktitle = {Computer Vision and Pattern Recognition},
Year = {2014}
}
### License
R-CNN is released under the Simplified BSD License (refer to the
LICENSE file for details).
### Installing R-CNN
0. **Prerequisites:** MATLAB (tested with 2012b on 64-bit Linux) and the Caffe [prerequisites](http://caffe.berkeleyvision.org/installation.html#prequequisites)
1. Download the R-CNN package, which includes a version of [Caffe](http://caffe.berkeleyvision.org) that is known to work with the precomputed models (see below)
* *Optional:* if you're feeling brave, you can clone the R-CNN git repo and the Caffe git repo and set them up manually.
2. Extract the R-CNN package: `$ tar zxvf r-cnn-release1.tgz` (this extracts safely into a folder called `rcnn`).
3. Build Caffe (this is the most complicated part)
1. Change directories `$ cd rcnn/caffe`.
2. Follow the instructions to build Caffe [here](http://caffe.berkeleyvision.org/installation.html).
3. **Important:** Make sure to compile the Caffe MATLAB wrapper, which is not built by default: `$ make matcaffe`.
4. Build R-CNN
1. Change directories back to the R-CNN root: `$ cd ..`
2. Start MATLAB: `$ matlab -nodesktop` (or however you like to start MATLAB in your environment)
3. Check that Caffe and MATLAB wrapper are setup correctly (this code should run without error):
`>> key = caffe('get_init_key'); assert(key == -2)`
3. Build R-CNN: `>> rcnn_build` (builds [liblinear](http://www.csie.ntu.edu.tw/~cjlin/liblinear/) and [Selective Search](http://www.science.uva.nl/research/publications/2013/UijlingsIJCV2013/))
4. Download the data package, which includes precompute models (see below).
### Downloading precomputed models (the data package)
The quickest way to get started is to [download precomputed R-CNN detectors](http://link/to/something). Currently we have detectors trained on PASCAL VOC 2007 and 2012 train+val. Unfortunately the download is large, so brew some coffee or take a walk while waiting.
After downloading the data package, you'll have a file called `r-cnn-release1-data.tgz`. Follow these instructions to install it:
1. Change to where you installed R-CNN: `$ cd rcnn`.
2. Extract the data package: `$ tar zxvf r-cnn-release1-data.tgz` (this will extract into a folder called `data`).
### Running an R-CNN detector on an image
Let's assume that you've downloaded the precomputed detectors. Now:
1. Change to where you installed R-CNN: `$ cd rcnn`.
2. Start MATLAB `$ matlab -desktop`.
* **Important:** if you don't see the message `R-CNN startup done` when MATLAB starts, then you probably didn't start MATLAB in `rcnn` directory.
3. Run the demo: `>> rcnn_demo`
### Training your own R-CNN detector on PASCAL VOC
Let's use PASCAL VOC 2007 as an example. The basic pipeline is: extract features to disk -> train SVMs -> test. You'll need about 200GB of disk space free for the feature cache (which is stored in `rcnn/feat_cache` by default; symlink `rcnn/feat_cache` elsewhere if needed). It's best if the features cache is on a fast, local disk. Before running the pipeline, we first need to install the PASCAL VOC 2007 dataset.
#### Installing PASCAL VOC 2007
1. Download the training and validation data [here](http://pascallin.ecs.soton.ac.uk/challenges/VOC/voc2007/VOCtrainval_06-Nov-2007.tar).
2. Download the test data [here](http://pascallin.ecs.soton.ac.uk/challenges/VOC/voc2007/VOCtest_06-Nov-2007.tar).
3. Download the VOCdevkit [here](http://pascallin.ecs.soton.ac.uk/challenges/VOC/voc2007/VOCdevkit_08-Jun-2007.tar).
4. Extract all of these tars into one directory, let's call it `VOCdevkit`. It should have this basic structure:
<pre>
VOCdevkit/ % development kit
VOCdevkit/VOCcode/ % VOC utility code
VOCdevkit/VOC2007 % image sets, annotations, etc.
... and several other directories ...
</pre>
I use a symlink to hook the R-CNN codebase to the PASCAL VOC dataset:
`$ cd rcnn/datasets` and then `$ ln -s /your/path/to/voc2007/VOCdevkit VOCdevkit2007`
#### Extracting features
<pre>
>> rcnn_exp_cache_features('train'); % chunk1
>> rcnn_exp_cache_features('val'); % chunk2
>> rcnn_exp_cache_features('test_1'); % chunk3
>> rcnn_exp_cache_features('test_2'); % chunk4
</pre>
**Pro tip:** on a machine with one hefty GPU (e.g., k20, k40, titan) and a six-core processor, I run start two MATLAB sessions each with a three worker matlabpool. I then run chunk1 and chunk2 in parallel on that machine. In this setup, completing chunk1 and chunk2 takes about 8-9 hours (depending on your CPU/GPU combo and disk) on a single machine. Obviously, if you have more machines you can hack this function to split the workload.
#### Training R-CNN models and testing
<pre>
>> test_results = rcnn_exp_train_and_test()
</pre>
### Training an R-CNN detector on another dataset
It should be easy to train an R-CNN detector using another detection dataset as long as that dataset has *complete* bounding box annotations (i.e., all instances of all classes are labeled).
To support a new dataset, you define three functions: (1) one that returns a structure that describes the class labels and list of images; (2) one that returns a region of interest (roi) structure that describes the bounding box annotations; and (3) one that provides an test evaluation function.
You can follow the PASCAL VOC implementation as your guide:
* `imdb/imdb_from_voc.m (list of images and classes)`
* `imdb/roidb_from_voc.m (region of interest database)`
* `imdb/imdb_eval_voc.m (evalutation)`
### Fine-tuning a CNN for detection with Caffe
**TODO:** write me
function pred_boxes = ...
rcnn_predict_bbox_regressor(model, feat, ex_boxes)
% rcnn_predict_bbox_regressor - compute predicted bounding box
% pred_boxes = rcnn_predict_bbox_regressor(model, feat, ex_boxes)
%
% Inputs
% model Bounding box regressor from rcnn_train_bbox_regressor.m
% feat Input feature vectors
% ex_boxes Input bounding boxes
%
% Outputs
% pred_boxes Modified (hopefully better) ex_boxes
if isempty(ex_boxes)
pred_boxes = [];
return;
end
% Predict regression targets
Y = bsxfun(@plus, feat*model.Beta(1:end-1, :), model.Beta(end, :));
% Invert whitening transformation
Y = bsxfun(@plus, Y*model.T_inv, model.mu);
% Read out predictions
dst_ctr_x = Y(:,1);
dst_ctr_y = Y(:,2);
dst_scl_x = Y(:,3);
dst_scl_y = Y(:,4);
src_w = ex_boxes(:,3) - ex_boxes(:,1) + eps;
src_h = ex_boxes(:,4) - ex_boxes(:,2) + eps;
src_ctr_x = ex_boxes(:,1) + 0.5*src_w;
src_ctr_y = ex_boxes(:,2) + 0.5*src_h;
pred_ctr_x = (dst_ctr_x .* src_w) + src_ctr_x;
pred_ctr_y = (dst_ctr_y .* src_h) + src_ctr_y;
pred_w = exp(dst_scl_x) .* src_w;
pred_h = exp(dst_scl_y) .* src_h;
pred_boxes = [pred_ctr_x - 0.5*pred_w, pred_ctr_y - 0.5*pred_h, ...
pred_ctr_x + 0.5*pred_w, pred_ctr_y + 0.5*pred_h];
function res = rcnn_test_bbox_regressor(imdb, rcnn_model, bbox_reg, suffix)
conf = rcnn_config('sub_dir', imdb.name);
image_ids = imdb.image_ids;
% assume they are all the same
feat_opts = bbox_reg.training_opts;
num_classes = length(rcnn_model.classes);
if ~exist('suffix', 'var') || isempty(suffix)
suffix = '_bbox_reg';
else
if suffix(1) ~= '_'
suffix = ['_' suffix];
end
end
aboxes = cell(num_classes, 1);
box_inds = cell(num_classes, 1);
for i = 1:num_classes
load([conf.cache_dir rcnn_model.classes{i} '_boxes_' imdb.name]);
aboxes{i} = boxes;
box_inds{i} = inds;
clear boxes inds;
end
for i = 1:length(image_ids)
fprintf('%s: bbox reg test (%s) %d/%d\n', procid(), imdb.name, i, length(image_ids));
d = rcnn_load_cached_pool5_features(feat_opts.cache_name, ...
imdb.name, image_ids{i});
if isempty(d.feat)
continue;
end
d.feat = rcnn_pool5_to_fcX(d.feat, feat_opts.layer, rcnn_model);
d.feat = rcnn_scale_features(d.feat, feat_opts.feat_norm_mean);
if feat_opts.binarize
d.feat = single(d.feat > 0);
end
for j = 1:num_classes
I = box_inds{j}{i};
boxes = aboxes{j}{i};
if ~isempty(boxes)
scores = boxes(:,end);
boxes = boxes(:,1:4);
assert(sum(sum(abs(d.boxes(I,:) - boxes))) == 0);
boxes = rcnn_predict_bbox_regressor(bbox_reg.models{j}, d.feat(I,:), boxes);
boxes(:,1) = max(boxes(:,1), 1);
boxes(:,2) = max(boxes(:,2), 1);
boxes(:,3) = min(boxes(:,3), imdb.sizes(i,2));
boxes(:,4) = min(boxes(:,4), imdb.sizes(i,1));
aboxes{j}{i} = cat(2, single(boxes), single(scores));
if 0
% debugging visualizations
im = imread(imdb.image_at(i));
keep = nms(aboxes{j}{i}, 0.3);
for k = 1:min(10, length(keep))
if aboxes{j}{i}(keep(k),end) > -0.9
showboxes(im, aboxes{j}{i}(keep(k),1:4));
title(sprintf('%s %d score: %.3f\n', rcnn_model.classes{j}, ...
k, aboxes{j}{i}(keep(k),end)));
pause;
end
end
end
end
end
end
for i = 1:num_classes
save_file = [conf.cache_dir rcnn_model.classes{i} '_boxes_' imdb.name suffix];
boxes = aboxes{i};
inds = box_inds{i};
save(save_file, 'boxes', 'inds');
clear boxes inds;
end
% ------------------------------------------------------------------------
% Peform AP evaluation
% ------------------------------------------------------------------------
for model_ind = 1:num_classes
cls = rcnn_model.classes{model_ind};
res(model_ind) = imdb.eval_func(cls, aboxes{model_ind}, imdb, suffix);
end
fprintf('\n~~~~~~~~~~~~~~~~~~~~\n');
fprintf('Results (bbox reg):\n');
aps = [res(:).ap]';
disp(aps);
disp(mean(aps));
fprintf('~~~~~~~~~~~~~~~~~~~~\n');
function bbox_reg = rcnn_train_bbox_regressor(imdb, rcnn_model, varargin)
ip = inputParser;
ip.addRequired('imdb', @isstruct);
ip.addRequired('rcnn_model', @isstruct);
ip.addParamValue('min_overlap', 0.6, @isscalar);
ip.addParamValue('layer', 5, @isscalar);
ip.addParamValue('lambda', 1000, @isscalar);
ip.addParamValue('robust', 0, @isscalar);
ip.addParamValue('binarize', false, @islogical);
ip.parse(imdb, rcnn_model, varargin{:});
opts = ip.Results;
opts = rmfield(opts, 'rcnn_model');
opts = rmfield(opts, 'imdb');
opts.cache_name = rcnn_model.cache_name;
fprintf('\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n');
fprintf('Training options:\n');
disp(opts);
fprintf('~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n');
conf = rcnn_config('sub_dir', imdb.name);
clss = rcnn_model.classes;
num_clss = length(clss);
% ------------------------------------------------------------------------
% Get the average norm of the features
opts.feat_norm_mean = rcnn_feature_stats(imdb, opts.layer, rcnn_model);
fprintf('average norm = %.3f\n', opts.feat_norm_mean);
% ------------------------------------------------------------------------
% ------------------------------------------------------------------------
% Get all positive examples
save_file = sprintf('./feat_cache/%s/%s/bbox_regressor_XY_layer_5_overlap_0.5.mat', ...
rcnn_model.cache_name, imdb.name);
try
load(save_file);
fprintf('Loaded saved positives from ground truth boxes\n');
catch
[X, Y, O, C] = get_examples(rcnn_model, imdb, opts);
save(save_file, 'X', 'Y', 'O', 'C', '-v7.3');
end
for i = 1:num_clss
fprintf('%14s has %6d samples\n', rcnn_model.classes{i}, length(find(C == i)));
end
X = rcnn_pool5_to_fcX(X, opts.layer, rcnn_model);
X = rcnn_scale_features(X, opts.feat_norm_mean);
% ------------------------------------------------------------------------
% use ridge regression solved by cholesky factorization
method = 'ridge_reg_chol';
models = cell(num_clss, 1);
for i = 1:num_clss
fprintf('Training regressors for class %s (%d/%d)\n', ...
rcnn_model.classes{i}, i, num_clss);
I = find(O > opts.min_overlap & C == i);
Xi = X(I,:);
if opts.binarize
Xi = single(Xi > 0);
end
Yi = Y(I,:);
Oi = O(I);
Ci = C(I);
% add bias feature
Xi = cat(2, Xi, ones(size(Xi,1), 1, class(Xi)));
% Center and decorrelate targets
mu = mean(Yi);
Yi = bsxfun(@minus, Yi, mu);
S = Yi'*Yi / size(Yi,1);
[V, D] = eig(S);
D = diag(D);
T = V*diag(1./sqrt(D+0.001))*V';
T_inv = V*diag(sqrt(D+0.001))*V';
Yi = Yi * T;
models{i}.mu = mu;
models{i}.T = T;
models{i}.T_inv = T_inv;
models{i}.Beta = [ ...
solve_robust(Xi, Yi(:,1), opts.lambda, method, opts.robust) ...
solve_robust(Xi, Yi(:,2), opts.lambda, method, opts.robust) ...
solve_robust(Xi, Yi(:,3), opts.lambda, method, opts.robust) ...
solve_robust(Xi, Yi(:,4), opts.lambda, method, opts.robust)];
end
bbox_reg.models = models;
bbox_reg.training_opts = opts;
save([conf.cache_dir 'bbox_regressor_final'], 'bbox_reg');
% ------------------------------------------------------------------------
function [X, Y, O, C] = get_examples(rcnn_model, imdb, opts)
% ------------------------------------------------------------------------
num_classes = length(rcnn_model.classes);
pool5 = 5;
roidb = imdb.roidb_func(imdb);
cls_counts = zeros(num_classes, 1);
for i = 1:length(imdb.image_ids)
tic_toc_print('%s: counting %d/%d\n', ...
procid(), i, length(imdb.image_ids));
d = roidb.rois(i);
[max_ov cls] = max(d.overlap, [], 2);
sel_ex = find(max_ov >= 0.5);
cls = cls(sel_ex);
for j = 1:length(sel_ex)
cls_counts(cls(j)) = cls_counts(cls(j)) + 1;
end
end
total = sum(cls_counts);
feat_dim = size(rcnn_model.cnn.layers(pool5+1).weights{1},1);
% features
X = zeros(total, feat_dim, 'single');
% target values
Y = zeros(total, 4, 'single');
% overlap amounts
O = zeros(total, 1, 'single');
% classes
C = zeros(total, 1, 'single');
cur = 1;
for i = 1:length(imdb.image_ids)
tic_toc_print('%s: pos features %d/%d\n', ...
procid(), i, length(imdb.image_ids));
d = rcnn_load_cached_pool5_features(rcnn_model.cache_name, ...
imdb.name, imdb.image_ids{i});
sel_gt = find(d.class > 0);
gt_boxes = d.boxes(sel_gt, :);
gt_classes = d.class(sel_gt);
max_ov = max(d.overlap, [], 2);
sel_ex = find(max_ov >= opts.min_overlap);
ex_boxes = d.boxes(sel_ex, :);
X(cur+(0:length(sel_ex)-1), :) = d.feat(sel_ex, :);
for j = 1:size(ex_boxes, 1)
ex_box = ex_boxes(j, :);
ov = boxoverlap(gt_boxes, ex_box);
[max_ov, assignment] = max(ov);
gt_box = gt_boxes(assignment, :);
cls = gt_classes(assignment);
src_w = ex_box(3) - ex_box(1) + eps;
src_h = ex_box(4) - ex_box(2) + eps;
src_ctr_x = ex_box(1) + 0.5*src_w;
src_ctr_y = ex_box(2) + 0.5*src_h;
gt_w = gt_box(3) - gt_box(1) + eps;
gt_h = gt_box(4) - gt_box(2) + eps;
gt_ctr_x = gt_box(1) + 0.5*gt_w;
gt_ctr_y = gt_box(2) + 0.5*gt_h;
dst_ctr_x = (gt_ctr_x - src_ctr_x) * 1/src_w;
dst_ctr_y = (gt_ctr_y - src_ctr_y) * 1/src_h;
dst_scl_w = log(gt_w / src_w);
dst_scl_h = log(gt_h / src_h);
target = [dst_ctr_x dst_ctr_y dst_scl_w dst_scl_h];
if 0
% debugging visualizations and checks
im = imread(imdb.image_at(i));
showboxesc(im, gt_box, 'g', '-');
showboxesc([], ex_box, 'r', '-');
hold on;
plot(gt_ctr_x, gt_ctr_y, 'gd');
plot(src_ctr_x, src_ctr_y, 'rd');
hold off;
fprintf('target = [%.3f %.3f %.3f %.3f]\n', target(1), target(2), target(3), target(4));
fprintf('cls = %s\n', rcnn_model.classes{cls});
% check that we can correctly reconstruct the gt_box from the
% gold-standard target
pred_ctr_x = (target(1) * src_w) + src_ctr_x;
pred_ctr_y = (target(2) * src_h) + src_ctr_y;
pred_w = exp(target(3)) * src_w;
pred_h = exp(target(4)) * src_h;
pred_box = [pred_ctr_x - 0.5*pred_w, pred_ctr_y - 0.5*pred_h, ...
pred_ctr_x + 0.5*pred_w, pred_ctr_y + 0.5*pred_h];
disp(pred_box);
disp(gt_box);
assert(sum(abs(pred_box - gt_box)) < 0.0001);
pause;
end
assert(cur <= total);
Y(cur, :) = target;
O(cur) = max_ov;
C(cur) = cls;
cur = cur + 1;
end
end
% ------------------------------------------------------------------------
function [x, losses] = solve_robust(A, y, lambda, method, qtile)
% ------------------------------------------------------------------------
[x, losses] = solve(A, y, lambda, method);
fprintf('loss = %.3f\n', mean(losses));
if qtile > 0
thresh = quantile(losses, 1-qtile);
I = find(losses < thresh);
[x, losses] = solve(A(I,:), y(I), lambda, method);
fprintf('loss (robust) = %.3f\n', mean(losses));
end
% ------------------------------------------------------------------------
function [x, losses] = solve(A, y, lambda, method)
% ------------------------------------------------------------------------
%tic;
switch method
case 'ridge_reg_chol'
% solve for x in min_x ||Ax - y||^2 + lambda*||x||^2
%
% solve (A'A + lambdaI)x = A'y for x using cholesky factorization
% R'R = (A'A + lambdaI)
% R'z = A'y : solve for z => R'Rx = R'z => Rx = z
% Rx = z : solve for x
R = chol(A'*A + lambda*eye(size(A,2)));
z = R' \ (A'*y);
x = R \ z;
case 'ridge_reg_inv'
% solve for x in min_x ||Ax - y||^2 + lambda*||x||^2
x = inv(A'*A + lambda*eye(size(A,2)))*A'*y;
case 'ls_mldivide'
% solve for x in min_x ||Ax - y||^2
if lambda > 0
warning('ignoring lambda; no regularization used');
end
x = A\y;
end
%toc;
losses = 0.5 * (A*x - y).^2;
function vis_debug_bbox_pred(imdb, rcnn_model, bbox_reg, cls)
image_ids = imdb.image_ids;
min_overlap = 0.3;
feat_opts = bbox_reg.training_opts;
roidb = imdb.roidb_func(imdb);
class_id = imdb.class_to_id(cls);
bbox_regressor = bbox_reg.models{class_id};
perm1 = randperm(length(image_ids));
%for i = 1:length(image_ids)
for i = perm1
fprintf('%s: debug (%s) %d/%d\n', procid(), imdb.name, i, length(image_ids));
if exist('cls','var')
if isempty(find(roidb.rois(i).class == class_id))
continue;
end
end
d = rcnn_load_cached_pool5_features(feat_opts.cache_name, ...
imdb.name, image_ids{i});
if isempty(d.feat)
continue;
end
d.feat = rcnn_pool5_to_fcX(d.feat, feat_opts.layer, rcnn_model);
d.feat = rcnn_scale_features(d.feat, feat_opts.feat_norm_mean);
if feat_opts.binarize
d.feat = (d.feat > 0);
end
im = imread(imdb.image_at(i));
if exist('cls','var')
sel_gt = find(d.class == class_id);
else
sel_gt = find(d.class > 0);
end
gt_boxes = d.boxes(sel_gt, :);
gt_classes = d.class(sel_gt);
if exist('cls','var')
max_ov = d.overlap(:, class_id);
else
max_ov = max(d.overlap, [], 2);
end
sel_ex = find(max_ov >= min_overlap);
ex_boxes = d.boxes(sel_ex, :);
ex_feat = d.feat(sel_ex, :);
%for j = 1:size(ex_boxes, 1)
perm = randperm(size(ex_boxes,1), min(10, size(ex_boxes,1)));
for j = perm
ex_box = ex_boxes(j, :);
ov = boxoverlap(gt_boxes, ex_box);
[max_ov, assignment] = max(ov);
gt_box = gt_boxes(assignment, :);
cls = gt_classes(assignment);
src_w = ex_box(3) - ex_box(1) + eps;
src_h = ex_box(4) - ex_box(2) + eps;