// Copyright (C) 2011 Davis E. King (davis@dlib.net) // License: Boost Software License See LICENSE.txt for the full license. #ifndef DLIB_LABEL_CONNeCTED_BLOBS_H_ #define DLIB_LABEL_CONNeCTED_BLOBS_H_ #include "label_connected_blobs_abstract.h" #include "../geometry.h" #include <stack> #include <vector> #include "thresholding.h" #include "assign_image.h" #include <queue> namespace dlib { // ---------------------------------------------------------------------------------------- struct neighbors_24 { void operator() ( const point& p, std::vector<point>& neighbors ) const { for (long i = -2; i <= 2; ++i) for (long j = -2; j <= 2; ++j) if (i!=0||j!=0) neighbors.push_back(point(p.x()+i,p.y()+j)); } }; struct neighbors_8 { void operator() ( const point& p, std::vector<point>& neighbors ) const { neighbors.push_back(point(p.x()+1,p.y()+1)); neighbors.push_back(point(p.x()+1,p.y() )); neighbors.push_back(point(p.x()+1,p.y()-1)); neighbors.push_back(point(p.x(),p.y()+1)); neighbors.push_back(point(p.x(),p.y()-1)); neighbors.push_back(point(p.x()-1,p.y()+1)); neighbors.push_back(point(p.x()-1,p.y() )); neighbors.push_back(point(p.x()-1,p.y()-1)); } }; struct neighbors_4 { void operator() ( const point& p, std::vector<point>& neighbors ) const { neighbors.push_back(point(p.x()+1,p.y())); neighbors.push_back(point(p.x()-1,p.y())); neighbors.push_back(point(p.x(),p.y()+1)); neighbors.push_back(point(p.x(),p.y()-1)); } }; // ---------------------------------------------------------------------------------------- struct connected_if_both_not_zero { template <typename image_type> bool operator() ( const image_type& img, const point& a, const point& b ) const { return (img[a.y()][a.x()] != 0 && img[b.y()][b.x()] != 0); } }; struct connected_if_equal { template <typename image_type> bool operator() ( const image_type& img, const point& a, const point& b ) const { return (img[a.y()][a.x()] == img[b.y()][b.x()]); } }; // ---------------------------------------------------------------------------------------- struct zero_pixels_are_background { template <typename image_type> bool operator() ( const image_type& img, const point& p ) const { return img[p.y()][p.x()] == 0; } }; struct nothing_is_background { template <typename image_type> bool operator() ( const image_type&, const point& ) const { return false; } }; // ---------------------------------------------------------------------------------------- template < typename image_type, typename label_image_type, typename background_functor_type, typename neighbors_functor_type, typename connected_functor_type > unsigned long label_connected_blobs ( const image_type& img_, const background_functor_type& is_background, const neighbors_functor_type& get_neighbors, const connected_functor_type& is_connected, label_image_type& label_img_ ) { // make sure requires clause is not broken DLIB_ASSERT(is_same_object(img_, label_img_) == false, "\t unsigned long label_connected_blobs()" << "\n\t The input image and output label image can't be the same object." ); const_image_view<image_type> img(img_); image_view<label_image_type> label_img(label_img_); std::stack<point> neighbors; label_img.set_size(img.nr(), img.nc()); assign_all_pixels(label_img, 0); unsigned long next = 1; if (img.size() == 0) return 0; const rectangle area = get_rect(img); std::vector<point> window; for (long r = 0; r < img.nr(); ++r) { for (long c = 0; c < img.nc(); ++c) { // skip already labeled pixels or background pixels if (label_img[r][c] != 0 || is_background(img,point(c,r))) continue; label_img[r][c] = next; // label all the neighbors of this point neighbors.push(point(c,r)); while (neighbors.size() > 0) { const point p = neighbors.top(); neighbors.pop(); window.clear(); get_neighbors(p, window); for (unsigned long i = 0; i < window.size(); ++i) { if (area.contains(window[i]) && // point in image. !is_background(img,window[i]) && // isn't background. label_img[window[i].y()][window[i].x()] == 0 && // haven't already labeled it. is_connected(img, p, window[i])) // it's connected. { label_img[window[i].y()][window[i].x()] = next; neighbors.push(window[i]); } } } ++next; } } return next; } // ---------------------------------------------------------------------------------------- template < typename in_image_type, typename out_image_type > unsigned long label_connected_blobs_watershed ( const in_image_type& img_, out_image_type& labels_, typename pixel_traits<typename image_traits<in_image_type>::pixel_type>::basic_pixel_type background_thresh, const double smoothing = 0 ) { // make sure requires clause is not broken DLIB_ASSERT(is_same_object(img_, labels_) == false, "\t unsigned long segment_image_watersheds()" << "\n\t The input images can't be the same object." ); using label_pixel_type = typename image_traits<out_image_type>::pixel_type; DLIB_ASSERT(smoothing >= 0); COMPILE_TIME_ASSERT(is_unsigned_type<label_pixel_type>::value); struct watershed_points { watershed_points() = default; watershed_points(const point& p_, float score_, label_pixel_type label_): p(p_), score(score_), label(label_) {} point p; float score = 0; label_pixel_type label = std::numeric_limits<label_pixel_type>::max(); bool is_seed() const { return label == std::numeric_limits<label_pixel_type>::max(); } bool operator< (const watershed_points& rhs) const { // If two pixels have the same score then we take the one with the smallest // label out of the priority queue first. We do this so that seed points // that are downhill from some larger blob will be consumed by it if they // haven't grown before the larger blob's flooding reaches them. Doing // this helps a lot to avoid spuriously splitting blobs. if (score == rhs.score) { return label > rhs.label; } return score < rhs.score; } }; const_image_view<in_image_type> img(img_); image_view<out_image_type> labels(labels_); labels.set_size(img.nr(), img.nc()); // Initially, all pixels have the background label of 0. assign_all_pixels(labels, 0); std::priority_queue<watershed_points> next; // Note that we never blur the image values we use to check against the // background_thresh. We do however blur, if smoothing!=0, the pixel values used // to do the watershed. in_image_type img2_; if (smoothing != 0) gaussian_blur(img_, img2_, smoothing); const_image_view<in_image_type> img2view(img2_); // point us at img2 if we are doing smoothing, otherwise point us at the input // image. const auto& img2 = smoothing!=0?img2view:img; // first find all the local maxima for (long r = 1; r+1 < img.nr(); ++r) { for (long c = 1; c+1 < img.nc(); ++c) { if (img[r][c] < background_thresh) continue; auto val = img2[r][c]; // if img2[r][c] isn't a local maximum then skip it if (val < img2[r+1][c] || val < img2[r-1][c] || val < img2[r][c+1] || val < img2[r][c-1] ) { continue; } next.push(watershed_points(point(c,r), val, std::numeric_limits<label_pixel_type>::max())); } } const rectangle area = get_rect(img); label_pixel_type next_label = 1; std::vector<point> neighbors; neighbors_8 get_neighbors; while(next.size() > 0) { auto p = next.top(); next.pop(); label_pixel_type label; // If the next pixel is a seed of a new blob and is still labeled as a // background pixel (i.e. it hasn't been flooded over by a neighboring blob and // consumed by it) then we create a new label for this new blob. if (p.is_seed() && labels[p.p.y()][p.p.x()] == 0) { label = next_label++; labels[p.p.y()][p.p.x()] = label; } else { label = p.label; } neighbors.clear(); get_neighbors(p.p, neighbors); for (auto& n : neighbors) { if (!area.contains(n) || labels[n.y()][n.x()] != 0 || img[n.y()][n.x()] < background_thresh) continue; labels[n.y()][n.x()] = label; next.push(watershed_points(n, img2[n.y()][n.x()], label)); } } return next_label; } template < typename in_image_type, typename out_image_type > unsigned long label_connected_blobs_watershed ( const in_image_type& img, out_image_type& labels ) { return label_connected_blobs_watershed(img, labels, partition_pixels(img)); } // ---------------------------------------------------------------------------------------- } #endif // DLIB_LABEL_CONNeCTED_BLOBS_H_