// Copyright (C) 2010 Davis E. King (davis@dlib.net)
// License: Boost Software License See LICENSE.txt for the full license.
#ifndef DLIB_IMAGE_PYRaMID_Hh_
#define DLIB_IMAGE_PYRaMID_Hh_
#include "image_pyramid_abstract.h"
#include "../pixel.h"
#include "../array2d.h"
#include "../geometry.h"
#include "spatial_filtering.h"
namespace dlib
{
// ----------------------------------------------------------------------------------------
class pyramid_disable : noncopyable
{
public:
template <typename T>
vector<double,2> point_down (
const vector<T,2>&
) const
{
return vector<double,2>(0,0);
}
template <typename T>
vector<double,2> point_up (
const vector<T,2>&
) const
{
return vector<double,2>(0,0);
}
// -----------------------------
template <typename T>
vector<double,2> point_down (
const vector<T,2>& p,
unsigned int levels
) const
{
if (levels == 0)
return p;
else
return vector<double,2>(0,0);
}
template <typename T>
vector<double,2> point_up (
const vector<T,2>& p,
unsigned int levels
) const
{
if (levels == 0)
return p;
else
return vector<double,2>(0,0);
}
// -----------------------------
drectangle rect_up (
const drectangle& rect
) const
{
return drectangle(point_up(rect.tl_corner()), point_up(rect.br_corner()));
}
drectangle rect_up (
const drectangle& rect,
unsigned int levels
) const
{
return drectangle(point_up(rect.tl_corner(),levels), point_up(rect.br_corner(),levels));
}
// -----------------------------
drectangle rect_down (
const drectangle& rect
) const
{
return drectangle(point_down(rect.tl_corner()), point_down(rect.br_corner()));
}
drectangle rect_down (
const drectangle& rect,
unsigned int levels
) const
{
return drectangle(point_down(rect.tl_corner(),levels), point_down(rect.br_corner(),levels));
}
// -----------------------------
public:
template <
typename in_image_type,
typename out_image_type
>
void operator() (
// we do this #ifdef stuff to avoid compiler warnings about unused variables.
#ifdef ENABLE_ASSERTS
const in_image_type& original,
#else
const in_image_type& ,
#endif
out_image_type& down
) const
{
// make sure requires clause is not broken
DLIB_ASSERT(is_same_object(original, down) == false,
"\t void pyramid_disable::operator()"
<< "\n\t is_same_object(original, down): " << is_same_object(original, down)
<< "\n\t this: " << this
);
typedef typename image_traits<in_image_type>::pixel_type in_pixel_type;
typedef typename image_traits<out_image_type>::pixel_type out_pixel_type;
COMPILE_TIME_ASSERT( pixel_traits<in_pixel_type>::has_alpha == false );
COMPILE_TIME_ASSERT( pixel_traits<out_pixel_type>::has_alpha == false );
set_image_size(down, 0, 0);
}
template <
typename image_type
>
void operator() (
image_type& img
) const
{
typedef typename image_traits<image_type>::pixel_type pixel_type;
COMPILE_TIME_ASSERT( pixel_traits<pixel_type>::has_alpha == false );
set_image_size(img, 0, 0);
}
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
namespace impl
{
class pyramid_down_2_1 : noncopyable
{
public:
template <typename T>
vector<double,2> point_down (
const vector<T,2>& p
) const
{
return p/2.0 - vector<double,2>(1.25,0.75);
}
template <typename T>
vector<double,2> point_up (
const vector<T,2>& p
) const
{
return (p + vector<T,2>(1.25,0.75))*2;
}
// -----------------------------
template <typename T>
vector<double,2> point_down (
const vector<T,2>& p,
unsigned int levels
) const
{
vector<double,2> temp = p;
for (unsigned int i = 0; i < levels; ++i)
temp = point_down(temp);
return temp;
}
template <typename T>
vector<double,2> point_up (
const vector<T,2>& p,
unsigned int levels
) const
{
vector<double,2> temp = p;
for (unsigned int i = 0; i < levels; ++i)
temp = point_up(temp);
return temp;
}
// -----------------------------
drectangle rect_up (
const drectangle& rect
) const
{
return drectangle(point_up(rect.tl_corner()), point_up(rect.br_corner()));
}
drectangle rect_up (
const drectangle& rect,
unsigned int levels
) const
{
return drectangle(point_up(rect.tl_corner(),levels), point_up(rect.br_corner(),levels));
}
// -----------------------------
drectangle rect_down (
const drectangle& rect
) const
{
return drectangle(point_down(rect.tl_corner()), point_down(rect.br_corner()));
}
drectangle rect_down (
const drectangle& rect,
unsigned int levels
) const
{
return drectangle(point_down(rect.tl_corner(),levels), point_down(rect.br_corner(),levels));
}
// -----------------------------
private:
template <typename T, typename U>
struct both_images_rgb
{
typedef typename image_traits<T>::pixel_type T_pix;
typedef typename image_traits<U>::pixel_type U_pix;
const static bool value = pixel_traits<T_pix>::rgb && pixel_traits<U_pix>::rgb;
};
public:
template <
typename in_image_type,
typename out_image_type
>
typename disable_if<both_images_rgb<in_image_type,out_image_type> >::type operator() (
const in_image_type& original_,
out_image_type& down_
) const
{
// make sure requires clause is not broken
DLIB_ASSERT( is_same_object(original_, down_) == false,
"\t void pyramid_down_2_1::operator()"
<< "\n\t is_same_object(original_, down_): " << is_same_object(original_, down_)
<< "\n\t this: " << this
);
typedef typename image_traits<in_image_type>::pixel_type in_pixel_type;
typedef typename image_traits<out_image_type>::pixel_type out_pixel_type;
COMPILE_TIME_ASSERT( pixel_traits<in_pixel_type>::has_alpha == false );
COMPILE_TIME_ASSERT( pixel_traits<out_pixel_type>::has_alpha == false );
const_image_view<in_image_type> original(original_);
image_view<out_image_type> down(down_);
if (original.nr() <= 8 || original.nc() <= 8)
{
down.clear();
return;
}
typedef typename pixel_traits<in_pixel_type>::basic_pixel_type bp_type;
typedef typename promote<bp_type>::type ptype;
array2d<ptype> temp_img;
temp_img.set_size(original.nr(), (original.nc()-3)/2);
down.set_size((original.nr()-3)/2, (original.nc()-3)/2);
// This function applies a 5x5 Gaussian filter to the image. It
// does this by separating the filter into its horizontal and vertical
// components and then downsamples the image by dropping every other
// row and column. Note that we can do these things all together in
// one step.
// apply row filter
for (long r = 0; r < temp_img.nr(); ++r)
{
long oc = 0;
for (long c = 0; c < temp_img.nc(); ++c)
{
ptype pix1;
ptype pix2;
ptype pix3;
ptype pix4;
ptype pix5;
assign_pixel(pix1, original[r][oc]);
assign_pixel(pix2, original[r][oc+1]);
assign_pixel(pix3, original[r][oc+2]);
assign_pixel(pix4, original[r][oc+3]);
assign_pixel(pix5, original[r][oc+4]);
pix2 *= 4;
pix3 *= 6;
pix4 *= 4;
assign_pixel(temp_img[r][c], pix1 + pix2 + pix3 + pix4 + pix5);
oc += 2;
}
}
// apply column filter
long dr = 0;
for (long r = 2; r < temp_img.nr()-2; r += 2)
{
for (long c = 0; c < temp_img.nc(); ++c)
{
ptype temp = temp_img[r-2][c] +
temp_img[r-1][c]*4 +
temp_img[r ][c]*6 +
temp_img[r+1][c]*4 +
temp_img[r+2][c];
assign_pixel(down[dr][c],temp/256);
}
++dr;
}
}
private:
struct rgbptype
{
uint16 red;
uint16 green;
uint16 blue;
};
public:
// ------------------------------------------
// OVERLOAD FOR RGB TO RGB IMAGES
// ------------------------------------------
template <
typename in_image_type,
typename out_image_type
>
typename enable_if<both_images_rgb<in_image_type,out_image_type> >::type operator() (
const in_image_type& original_,
out_image_type& down_
) const
{
// make sure requires clause is not broken
DLIB_ASSERT( is_same_object(original_, down_) == false,
"\t void pyramid_down_2_1::operator()"
<< "\n\t is_same_object(original_, down_): " << is_same_object(original_, down_)
<< "\n\t this: " << this
);
typedef typename image_traits<in_image_type>::pixel_type in_pixel_type;
typedef typename image_traits<out_image_type>::pixel_type out_pixel_type;
COMPILE_TIME_ASSERT( pixel_traits<in_pixel_type>::has_alpha == false );
COMPILE_TIME_ASSERT( pixel_traits<out_pixel_type>::has_alpha == false );
const_image_view<in_image_type> original(original_);
image_view<out_image_type> down(down_);
if (original.nr() <= 8 || original.nc() <= 8)
{
down.clear();
return;
}
array2d<rgbptype> temp_img;
temp_img.set_size(original.nr(), (original.nc()-3)/2);
down.set_size((original.nr()-3)/2, (original.nc()-3)/2);
// This function applies a 5x5 Gaussian filter to the image. It
// does this by separating the filter into its horizontal and vertical
// components and then downsamples the image by dropping every other
// row and column. Note that we can do these things all together in
// one step.
// apply row filter
for (long r = 0; r < temp_img.nr(); ++r)
{
long oc = 0;
for (long c = 0; c < temp_img.nc(); ++c)
{
rgbptype pix1;
rgbptype pix2;
rgbptype pix3;
rgbptype pix4;
rgbptype pix5;
pix1.red = original[r][oc].red;
pix2.red = original[r][oc+1].red;
pix3.red = original[r][oc+2].red;
pix4.red = original[r][oc+3].red;
pix5.red = original[r][oc+4].red;
pix1.green = original[r][oc].green;
pix2.green = original[r][oc+1].green;
pix3.green = original[r][oc+2].green;
pix4.green = original[r][oc+3].green;
pix5.green = original[r][oc+4].green;
pix1.blue = original[r][oc].blue;
pix2.blue = original[r][oc+1].blue;
pix3.blue = original[r][oc+2].blue;
pix4.blue = original[r][oc+3].blue;
pix5.blue = original[r][oc+4].blue;
pix2.red *= 4;
pix3.red *= 6;
pix4.red *= 4;
pix2.green *= 4;
pix3.green *= 6;
pix4.green *= 4;
pix2.blue *= 4;
pix3.blue *= 6;
pix4.blue *= 4;
rgbptype temp;
temp.red = pix1.red + pix2.red + pix3.red + pix4.red + pix5.red;
temp.green = pix1.green + pix2.green + pix3.green + pix4.green + pix5.green;
temp.blue = pix1.blue + pix2.blue + pix3.blue + pix4.blue + pix5.blue;
temp_img[r][c] = temp;
oc += 2;
}
}
// apply column filter
long dr = 0;
for (long r = 2; r < temp_img.nr()-2; r += 2)
{
for (long c = 0; c < temp_img.nc(); ++c)
{
rgbptype temp;
temp.red = temp_img[r-2][c].red +
temp_img[r-1][c].red*4 +
temp_img[r ][c].red*6 +
temp_img[r+1][c].red*4 +
temp_img[r+2][c].red;
temp.green = temp_img[r-2][c].green +
temp_img[r-1][c].green*4 +
temp_img[r ][c].green*6 +
temp_img[r+1][c].green*4 +
temp_img[r+2][c].green;
temp.blue = temp_img[r-2][c].blue +
temp_img[r-1][c].blue*4 +
temp_img[r ][c].blue*6 +
temp_img[r+1][c].blue*4 +
temp_img[r+2][c].blue;
down[dr][c].red = temp.red/256;
down[dr][c].green = temp.green/256;
down[dr][c].blue = temp.blue/256;
}
++dr;
}
}
template <
typename image_type
>
void operator() (
image_type& img
) const
{
image_type temp;
(*this)(img, temp);
swap(temp, img);
}
private:
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
class pyramid_down_3_2 : noncopyable
{
public:
template <typename T>
vector<double,2> point_down (
const vector<T,2>& p
) const
{
const double ratio = 2.0/3.0;
return p*ratio - vector<double,2>(1,1);
}
template <typename T>
vector<double,2> point_up (
const vector<T,2>& p
) const
{
const double ratio = 3.0/2.0;
return p*ratio + vector<T,2>(ratio,ratio);
}
// -----------------------------
template <typename T>
vector<double,2> point_down (
const vector<T,2>& p,
unsigned int levels
) const
{
vector<double,2> temp = p;
for (unsigned int i = 0; i < levels; ++i)
temp = point_down(temp);
return temp;
}
template <typename T>
vector<double,2> point_up (
const vector<T,2>& p,
unsigned int levels
) const
{
vector<double,2> temp = p;
for (unsigned int i = 0; i < levels; ++i)
temp = point_up(temp);
return temp;
}
// -----------------------------
drectangle rect_up (
const drectangle& rect
) const
{
return drectangle(point_up(rect.tl_corner()), point_up(rect.br_corner()));
}
drectangle rect_up (
const drectangle& rect,
unsigned int levels
) const
{
return drectangle(point_up(rect.tl_corner(),levels), point_up(rect.br_corner(),levels));
}
// -----------------------------
drectangle rect_down (
const drectangle& rect
) const
{
return drectangle(point_down(rect.tl_corner()), point_down(rect.br_corner()));
}
drectangle rect_down (
const drectangle& rect,
unsigned int levels
) const
{
return drectangle(point_down(rect.tl_corner(),levels), point_down(rect.br_corner(),levels));
}
// -----------------------------
private:
template <typename T, typename U>
struct both_images_rgb
{
typedef typename image_traits<T>::pixel_type T_pix;
typedef typename image_traits<U>::pixel_type U_pix;
const static bool value = pixel_traits<T_pix>::rgb && pixel_traits<U_pix>::rgb;
};
public:
template <
typename in_image_type,
typename out_image_type
>
typename disable_if<both_images_rgb<in_image_type,out_image_type> >::type operator() (
const in_image_type& original_,
out_image_type& down_
) const
{
// make sure requires clause is not broken
DLIB_ASSERT(is_same_object(original_, down_) == false,
"\t void pyramid_down_3_2::operator()"
<< "\n\t is_same_object(original_, down_): " << is_same_object(original_, down_)
<< "\n\t this: " << this
);
typedef typename image_traits<in_image_type>::pixel_type in_pixel_type;
typedef typename image_traits<out_image_type>::pixel_type out_pixel_type;
COMPILE_TIME_ASSERT( pixel_traits<in_pixel_type>::has_alpha == false );
COMPILE_TIME_ASSERT( pixel_traits<out_pixel_type>::has_alpha == false );
const_image_view<in_image_type> original(original_);
image_view<out_image_type> down(down_);
if (original.nr() <= 8 || original.nc() <= 8)
{
down.clear();
return;
}
const long size_in = 3;
const long size_out = 2;
typedef typename pixel_traits<in_pixel_type>::basic_pixel_type bp_type;
typedef typename promote<bp_type>::type ptype;
const long full_nr = size_out*((original.nr()-2)/size_in);
const long part_nr = (size_out*(original.nr()-2))/size_in;
const long full_nc = size_out*((original.nc()-2)/size_in);
const long part_nc = (size_out*(original.nc()-2))/size_in;
down.set_size(part_nr, part_nc);
long rr = 1;
long r;
for (r = 0; r < full_nr; r+=size_out)
{
long cc = 1;
long c;
for (c = 0; c < full_nc; c+=size_out)
{
ptype block[size_in][size_in];
separable_3x3_filter_block_grayscale(block, original_, rr, cc, 2, 12, 2);
// bi-linearly interpolate block
assign_pixel(down[r][c] , (block[0][0]*9 + block[1][0]*3 + block[0][1]*3 + block[1][1])/(16*256));
assign_pixel(down[r][c+1] , (block[0][2]*9 + block[1][2]*3 + block[0][1]*3 + block[1][1])/(16*256));
assign_pixel(down[r+1][c] , (block[2][0]*9 + block[1][0]*3 + block[2][1]*3 + block[1][1])/(16*256));
assign_pixel(down[r+1][c+1] , (block[2][2]*9 + block[1][2]*3 + block[2][1]*3 + block[1][1])/(16*256));
cc += size_in;
}
if (part_nc - full_nc == 1)
{
ptype block[size_in][2];
separable_3x3_filter_block_grayscale(block, original_, rr, cc, 2, 12, 2);
// bi-linearly interpolate partial block
assign_pixel(down[r][c] , (block[0][0]*9 + block[1][0]*3 + block[0][1]*3 + block[1][1])/(16*256));
assign_pixel(down[r+1][c] , (block[2][0]*9 + block[1][0]*3 + block[2][1]*3 + block[1][1])/(16*256));
}
rr += size_in;
}
if (part_nr - full_nr == 1)
{
long cc = 1;
long c;
for (c = 0; c < full_nc; c+=size_out)
{
ptype block[2][size_in];
separable_3x3_filter_block_grayscale(block, original_, rr, cc, 2, 12, 2);
// bi-linearly interpolate partial block
assign_pixel(down[r][c] , (block[0][0]*9 + block[1][0]*3 + block[0][1]*3 + block[1][1])/(16*256));
assign_pixel(down[r][c+1] , (block[0][2]*9 + block[1][2]*3 + block[0][1]*3 + block[1][1])/(16*256));
cc += size_in;
}
if (part_nc - full_nc == 1)
{
ptype block[2][2];
separable_3x3_filter_block_grayscale(block, original_, rr, cc, 2, 12, 2);
// bi-linearly interpolate partial block
assign_pixel(down[r][c] , (block[0][0]*9 + block[1][0]*3 + block[0][1]*3 + block[1][1])/(16*256));
}
}
}
private:
struct rgbptype
{
uint32 red;
uint32 green;
uint32 blue;
};
public:
// ------------------------------------------
// OVERLOAD FOR RGB TO RGB IMAGES
// ------------------------------------------
template <
typename in_image_type,
typename out_image_type
>
typename enable_if<both_images_rgb<in_image_type,out_image_type> >::type operator() (
const in_image_type& original_,
out_image_type& down_
) const
{
// make sure requires clause is not broken
DLIB_ASSERT( is_same_object(original_, down_) == false,
"\t void pyramid_down_3_2::operator()"
<< "\n\t is_same_object(original_, down_): " << is_same_object(original_, down_)
<< "\n\t this: " << this
);
typedef typename image_traits<in_image_type>::pixel_type in_pixel_type;
typedef typename image_traits<out_image_type>::pixel_type out_pixel_type;
COMPILE_TIME_ASSERT( pixel_traits<in_pixel_type>::has_alpha == false );
COMPILE_TIME_ASSERT( pixel_traits<out_pixel_type>::has_alpha == false );
const_image_view<in_image_type> original(original_);
image_view<out_image_type> down(down_);
if (original.nr() <= 8 || original.nc() <= 8)
{
down.clear();
return;
}
const long size_in = 3;
const long size_out = 2;
const long full_nr = size_out*((original.nr()-2)/size_in);
const long part_nr = (size_out*(original.nr()-2))/size_in;
const long full_nc = size_out*((original.nc()-2)/size_in);
const long part_nc = (size_out*(original.nc()-2))/size_in;
down.set_size(part_nr, part_nc);
long rr = 1;
long r;
for (r = 0; r < full_nr; r+=size_out)
{
long cc = 1;
long c;
for (c = 0; c < full_nc; c+=size_out)
{
rgbptype block[size_in][size_in];
separable_3x3_filter_block_rgb(block, original_, rr, cc, 2, 12, 2);
// bi-linearly interpolate block
down[r][c].red = (block[0][0].red*9 + block[1][0].red*3 + block[0][1].red*3 + block[1][1].red)/(16*256);
down[r][c].green = (block[0][0].green*9 + block[1][0].green*3 + block[0][1].green*3 + block[1][1].green)/(16*256);
down[r][c].blue = (block[0][0].blue*9 + block[1][0].blue*3 + block[0][1].blue*3 + block[1][1].blue)/(16*256);
down[r][c+1].red = (block[0][2].red*9 + block[1][2].red*3 + block[0][1].red*3 + block[1][1].red)/(16*256);
down[r][c+1].green = (block[0][2].green*9 + block[1][2].green*3 + block[0][1].green*3 + block[1][1].green)/(16*256);
down[r][c+1].blue = (block[0][2].blue*9 + block[1][2].blue*3 + block[0][1].blue*3 + block[1][1].blue)/(16*256);
down[r+1][c].red = (block[2][0].red*9 + block[1][0].red*3 + block[2][1].red*3 + block[1][1].red)/(16*256);
down[r+1][c].green = (block[2][0].green*9 + block[1][0].green*3 + block[2][1].green*3 + block[1][1].green)/(16*256);
down[r+1][c].blue = (block[2][0].blue*9 + block[1][0].blue*3 + block[2][1].blue*3 + block[1][1].blue)/(16*256);
down[r+1][c+1].red = (block[2][2].red*9 + block[1][2].red*3 + block[2][1].red*3 + block[1][1].red)/(16*256);
down[r+1][c+1].green = (block[2][2].green*9 + block[1][2].green*3 + block[2][1].green*3 + block[1][1].green)/(16*256);
down[r+1][c+1].blue = (block[2][2].blue*9 + block[1][2].blue*3 + block[2][1].blue*3 + block[1][1].blue)/(16*256);
cc += size_in;
}
if (part_nc - full_nc == 1)
{
rgbptype block[size_in][2];
separable_3x3_filter_block_rgb(block, original_, rr, cc, 2, 12, 2);
// bi-linearly interpolate partial block
down[r][c].red = (block[0][0].red*9 + block[1][0].red*3 + block[0][1].red*3 + block[1][1].red)/(16*256);
down[r][c].green = (block[0][0].green*9 + block[1][0].green*3 + block[0][1].green*3 + block[1][1].green)/(16*256);
down[r][c].blue = (block[0][0].blue*9 + block[1][0].blue*3 + block[0][1].blue*3 + block[1][1].blue)/(16*256);
down[r+1][c].red = (block[2][0].red*9 + block[1][0].red*3 + block[2][1].red*3 + block[1][1].red)/(16*256);
down[r+1][c].green = (block[2][0].green*9 + block[1][0].green*3 + block[2][1].green*3 + block[1][1].green)/(16*256);
down[r+1][c].blue = (block[2][0].blue*9 + block[1][0].blue*3 + block[2][1].blue*3 + block[1][1].blue)/(16*256);
}
rr += size_in;
}
if (part_nr - full_nr == 1)
{
long cc = 1;
long c;
for (c = 0; c < full_nc; c+=size_out)
{
rgbptype block[2][size_in];
separable_3x3_filter_block_rgb(block, original_, rr, cc, 2, 12, 2);
// bi-linearly interpolate partial block
down[r][c].red = (block[0][0].red*9 + block[1][0].red*3 + block[0][1].red*3 + block[1][1].red)/(16*256);
down[r][c].green = (block[0][0].green*9 + block[1][0].green*3 + block[0][1].green*3 + block[1][1].green)/(16*256);
down[r][c].blue = (block[0][0].blue*9 + block[1][0].blue*3 + block[0][1].blue*3 + block[1][1].blue)/(16*256);
down[r][c+1].red = (block[0][2].red*9 + block[1][2].red*3 + block[0][1].red*3 + block[1][1].red)/(16*256);
down[r][c+1].green = (block[0][2].green*9 + block[1][2].green*3 + block[0][1].green*3 + block[1][1].green)/(16*256);
down[r][c+1].blue = (block[0][2].blue*9 + block[1][2].blue*3 + block[0][1].blue*3 + block[1][1].blue)/(16*256);
cc += size_in;
}
if (part_nc - full_nc == 1)
{
rgbptype block[2][2];
separable_3x3_filter_block_rgb(block, original_, rr, cc, 2, 12, 2);
// bi-linearly interpolate partial block
down[r][c].red = (block[0][0].red*9 + block[1][0].red*3 + block[0][1].red*3 + block[1][1].red)/(16*256);
down[r][c].green = (block[0][0].green*9 + block[1][0].green*3 + block[0][1].green*3 + block[1][1].green)/(16*256);
down[r][c].blue = (block[0][0].blue*9 + block[1][0].blue*3 + block[0][1].blue*3 + block[1][1].blue)/(16*256);
}
}
}
template <
typename image_type
>
void operator() (
image_type& img
) const
{
image_type temp;
(*this)(img, temp);
swap(temp, img);
}
private:
};
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <
unsigned int N
>
class pyramid_down : noncopyable
{
public:
COMPILE_TIME_ASSERT(N > 0);
template <typename T>
vector<double,2> point_down (
const vector<T,2>& p
) const
{
const double ratio = (N-1.0)/N;
return (p - 0.3)*ratio;
}
template <typename T>
vector<double,2> point_up (
const vector<T,2>& p
) const
{
const double ratio = N/(N-1.0);
return p*ratio + 0.3;
}
// -----------------------------
template <typename T>
vector<double,2> point_down (
const vector<T,2>& p,
unsigned int levels
) const
{
vector<double,2> temp = p;
for (unsigned int i = 0; i < levels; ++i)
temp = point_down(temp);
return temp;
}
template <typename T>
vector<double,2> point_up (
const vector<T,2>& p,
unsigned int levels
) const
{
vector<double,2> temp = p;
for (unsigned int i = 0; i < levels; ++i)
temp = point_up(temp);
return temp;
}
// -----------------------------
drectangle rect_up (
const drectangle& rect
) const
{
return drectangle(point_up(rect.tl_corner()), point_up(rect.br_corner()));
}
drectangle rect_up (
const drectangle& rect,
unsigned int levels
) const
{
return drectangle(point_up(rect.tl_corner(),levels), point_up(rect.br_corner(),levels));
}
// -----------------------------
drectangle rect_down (
const drectangle& rect
) const
{
return drectangle(point_down(rect.tl_corner()), point_down(rect.br_corner()));
}
drectangle rect_down (
const drectangle& rect,
unsigned int levels
) const
{
return drectangle(point_down(rect.tl_corner(),levels), point_down(rect.br_corner(),levels));
}
template <
typename in_image_type,
typename out_image_type
>
void operator() (
const in_image_type& original,
out_image_type& down
) const
{
// make sure requires clause is not broken
DLIB_ASSERT(is_same_object(original, down) == false,
"\t void pyramid_down::operator()"
<< "\n\t is_same_object(original, down): " << is_same_object(original, down)
<< "\n\t this: " << this
);
typedef typename image_traits<in_image_type>::pixel_type in_pixel_type;
typedef typename image_traits<out_image_type>::pixel_type out_pixel_type;
COMPILE_TIME_ASSERT( pixel_traits<in_pixel_type>::has_alpha == false );
COMPILE_TIME_ASSERT( pixel_traits<out_pixel_type>::has_alpha == false );
set_image_size(down, ((N-1)*num_rows(original))/N+0.5, ((N-1)*num_columns(original))/N+0.5);
resize_image(original, down);
}
template <
typename image_type
>
void operator() (
image_type& img
) const
{
image_type temp;
(*this)(img, temp);
swap(temp, img);
}
};
template <>
class pyramid_down<1> : public pyramid_disable {};
template <>
class pyramid_down<2> : public dlib::impl::pyramid_down_2_1 {};
template <>
class pyramid_down<3> : public dlib::impl::pyramid_down_3_2 {};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <unsigned int N>
double pyramid_rate(const pyramid_down<N>&)
{
return (N-1.0)/N;
}
// ----------------------------------------------------------------------------------------
template <unsigned int N>
void find_pyramid_down_output_image_size(
const pyramid_down<N>& pyr,
long& nr,
long& nc
)
{
const double rate = pyramid_rate(pyr);
nr = std::floor(rate*nr);
nc = std::floor(rate*nc);
}
inline void find_pyramid_down_output_image_size(
const pyramid_down<3>& /*pyr*/,
long& nr,
long& nc
)
{
nr = 2*(nr-2)/3;
nc = 2*(nc-2)/3;
}
inline void find_pyramid_down_output_image_size(
const pyramid_down<2>& /*pyr*/,
long& nr,
long& nc
)
{
nr = (nr-3)/2;
nc = (nc-3)/2;
}
inline void find_pyramid_down_output_image_size(
const pyramid_down<1>& /*pyr*/,
long& nr,
long& nc
)
{
nr = 0;
nc = 0;
}
// ----------------------------------------------------------------------------------------
namespace impl
{
template <typename pyramid_type>
void compute_tiled_image_pyramid_details (
const pyramid_type& pyr,
long nr,
long nc,
const unsigned long padding,
const unsigned long outer_padding,
std::vector<rectangle>& rects,
long& pyramid_image_nr,
long& pyramid_image_nc
)
{
rects.clear();
if (nr*nc == 0)
{
pyramid_image_nr = 0;
pyramid_image_nc = 0;
return;
}
const long min_height = 5;
rects.reserve(100);
rects.push_back(rectangle(nc,nr));
// build the whole pyramid
while(true)
{
find_pyramid_down_output_image_size(pyr, nr, nc);
if (nr*nc == 0 || nr < min_height)
break;
rects.push_back(rectangle(nc,nr));
}
// figure out output image size
long total_height = 0;
for (auto&& i : rects)
total_height += i.height()+padding;
total_height -= padding*2; // don't add unnecessary padding to the very right side.
long height = 0;
long prev_width = 0;
for (auto&& i : rects)
{
// Figure out how far we go on the first column. We go until the next image can
// fit next to the previous one, which means we can double back for the second
// column of images.
if (i.width() <= rects[0].width()-prev_width-(long)padding &&
(height-rects[0].height())*2 >= (total_height-rects[0].height()))
{
break;
}
height += i.height() + padding;
prev_width = i.width();
}
height -= padding; // don't add unnecessary padding to the very right side.
const long width = rects[0].width();
pyramid_image_nr = height+outer_padding*2;
pyramid_image_nc = width+outer_padding*2;
long y = outer_padding;
size_t i = 0;
while(y < height+(long)outer_padding && i < rects.size())
{
rects[i] = translate_rect(rects[i],point(outer_padding,y));
DLIB_ASSERT(rectangle(pyramid_image_nc,pyramid_image_nr).contains(rects[i]));
y += rects[i].height()+padding;
++i;
}
y -= padding;
while (i < rects.size())
{
point p1(outer_padding+width-1,y-1);
point p2 = p1 - rects[i].br_corner();
rectangle rect(p1,p2);
DLIB_ASSERT(rectangle(pyramid_image_nc,pyramid_image_nr).contains(rect));
// don't keep going on the last row if it would intersect the original image.
if (!rects[0].intersect(rect).is_empty())
break;
rects[i] = rect;
y -= rects[i].height()+padding;
++i;
}
// Delete any extraneous rectangles if we broke out of the above loop early due to
// intersection with the original image.
rects.resize(i);
}
}
// ----------------------------------------------------------------------------------------
template <
typename pyramid_type,
typename image_type1,
typename image_type2
>
void create_tiled_pyramid (
const image_type1& img,
image_type2& out_img,
std::vector<rectangle>& rects,
const unsigned long padding = 10,
const unsigned long outer_padding = 0
)
{
DLIB_ASSERT(!is_same_object(img, out_img));
long out_nr, out_nc;
pyramid_type pyr;
impl::compute_tiled_image_pyramid_details(pyr, img.nr(), img.nc(), padding, outer_padding, rects, out_nr, out_nc);
set_image_size(out_img, out_nr, out_nc);
assign_all_pixels(out_img, 0);
if (rects.size() == 0)
return;
// now build the image pyramid into out_img
auto si = sub_image(out_img, rects[0]);
assign_image(si, img);
for (size_t i = 1; i < rects.size(); ++i)
{
auto s1 = sub_image(out_img, rects[i-1]);
auto s2 = sub_image(out_img, rects[i]);
pyr(s1,s2);
}
}
// ----------------------------------------------------------------------------------------
template <
typename pyramid_type
>
dpoint image_to_tiled_pyramid (
const std::vector<rectangle>& rects,
double scale,
dpoint p
)
{
DLIB_CASSERT(rects.size() > 0);
DLIB_CASSERT(0 < scale && scale <= 1);
pyramid_type pyr;
// This scale factor maps this many levels down the pyramid
long pyramid_down_iter = static_cast<long>(std::log(scale)/std::log(pyramid_rate(pyr))+0.5);
pyramid_down_iter = put_in_range(0, (long)rects.size()-1, pyramid_down_iter);
return rects[pyramid_down_iter].tl_corner() + pyr.point_down(p, pyramid_down_iter);
}
// ----------------------------------------------------------------------------------------
template <
typename pyramid_type
>
drectangle image_to_tiled_pyramid (
const std::vector<rectangle>& rects,
double scale,
drectangle r
)
{
DLIB_ASSERT(rects.size() > 0);
DLIB_ASSERT(0 < scale && scale <= 1);
return drectangle(image_to_tiled_pyramid<pyramid_type>(rects, scale, r.tl_corner()),
image_to_tiled_pyramid<pyramid_type>(rects, scale, r.br_corner()));
}
// ----------------------------------------------------------------------------------------
template <
typename pyramid_type
>
dpoint tiled_pyramid_to_image (
const std::vector<rectangle>& rects,
dpoint p
)
{
DLIB_CASSERT(rects.size() > 0);
size_t pyramid_down_iter = nearest_rect(rects, p);
p -= rects[pyramid_down_iter].tl_corner();
pyramid_type pyr;
return pyr.point_up(p, pyramid_down_iter);
}
// ----------------------------------------------------------------------------------------
template <
typename pyramid_type
>
drectangle tiled_pyramid_to_image (
const std::vector<rectangle>& rects,
drectangle r
)
{
DLIB_CASSERT(rects.size() > 0);
size_t pyramid_down_iter = nearest_rect(rects, dcenter(r));
dpoint origin = rects[pyramid_down_iter].tl_corner();
r = drectangle(r.tl_corner()-origin, r.br_corner()-origin);
pyramid_type pyr;
return pyr.rect_up(r, pyramid_down_iter);
}
// ----------------------------------------------------------------------------------------
}
#endif // DLIB_IMAGE_PYRaMID_Hh_