// 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 < 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 ) { DLIB_ASSERT(!is_same_object(img, out_img)); rects.clear(); if (num_rows(img)*num_columns(img) == 0) { set_image_size(out_img,0,0); return; } const long min_height = 5; pyramid_type pyr; std::vector<matrix<rgb_pixel>> pyramid; matrix<rgb_pixel> temp; assign_image(temp, img); pyramid.push_back(std::move(temp)); // build the whole pyramid while(true) { matrix<rgb_pixel> temp; pyr(pyramid.back(), temp); if (temp.size() == 0 || temp.nr() < min_height) break; pyramid.push_back(std::move(temp)); } // figure out output image size long total_height = 0; for (auto&& i : pyramid) total_height += i.nr()+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 : pyramid) { // 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.nc() <= img.nc()-prev_width-(long)padding && (height-img.nr())*2 >= (total_height-img.nr())) { break; } height += i.nr() + padding; prev_width = i.nc(); } height -= padding; // don't add unnecessary padding to the very right side. set_image_size(out_img,height,img.nc()); assign_all_pixels(out_img, 0); long y = 0; size_t i = 0; while(y < height) { rectangle rect = translate_rect(get_rect(pyramid[i]),point(0,y)); DLIB_ASSERT(get_rect(out_img).contains(rect)); rects.push_back(rect); auto si = sub_image(out_img, rect); assign_image(si, pyramid[i]); y += pyramid[i].nr()+padding; ++i; } y -= padding; while (i < pyramid.size()) { point p1(img.nc()-1,y-1); point p2 = p1 - get_rect(pyramid[i]).br_corner(); rectangle rect(p1,p2); DLIB_ASSERT(get_rect(out_img).contains(rect)); // don't keep going on the last row if it would intersect the original image. if (!get_rect(img).intersect(rect).is_empty()) break; rects.push_back(rect); auto si = sub_image(out_img, rect); assign_image(si, pyramid[i]); y -= pyramid[i].nr()+padding; ++i; } } // ---------------------------------------------------------------------------------------- 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_