From e15fcbd39e9de7014f72dc1585f039f97ddf8460 Mon Sep 17 00:00:00 2001 From: Davis King Date: Sat, 19 May 2018 23:43:28 -0400 Subject: [PATCH] Added the following things to the python API: gaussian_blur(), label_connected_blobs(), randomly_color_image(), jet(), skeleton(), find_line_endpoints(), get_rect(), shrink_rect(), grow_rect(), and image_gradients. --- tools/python/src/image.cpp | 386 ++++++++++++++++++++++++++++++++ tools/python/src/rectangles.cpp | 11 + 2 files changed, 397 insertions(+) diff --git a/tools/python/src/image.cpp b/tools/python/src/image.cpp index 6709bc069..562830825 100644 --- a/tools/python/src/image.cpp +++ b/tools/python/src/image.cpp @@ -81,11 +81,99 @@ py::tuple py_partition_pixels2 ( DLIB_CASSERT(false, "This should never happen."); } +// ---------------------------------------------------------------------------------------- + +template +py::tuple py_gaussian_blur ( + const numpy_image& img, + double sigma = 1, + int max_size = 1001 +) +{ + numpy_image out; + auto rect = gaussian_blur(img, out, sigma, max_size); + return py::make_tuple(out, rect); +} + +template +py::tuple py_label_connected_blobs ( + const numpy_image& img, + bool zero_pixels_are_background, + int neighborhood_connectivity, + bool connected_if_both_not_zero +) +{ + DLIB_CASSERT(neighborhood_connectivity == 4 || + neighborhood_connectivity == 8 || + neighborhood_connectivity == 24); + + unsigned long num_blobs = 0; + + numpy_image labels; + + if (zero_pixels_are_background && neighborhood_connectivity == 4 && connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, ::zero_pixels_are_background(), neighbors_4(), ::connected_if_both_not_zero(), labels); + else if (zero_pixels_are_background && neighborhood_connectivity == 4 && !connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, ::zero_pixels_are_background(), neighbors_4(), connected_if_equal(), labels); + else if (!zero_pixels_are_background && neighborhood_connectivity == 4 && connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, nothing_is_background(), neighbors_4(), ::connected_if_both_not_zero(), labels); + else if (!zero_pixels_are_background && neighborhood_connectivity == 4 && !connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, nothing_is_background(), neighbors_4(), connected_if_equal(), labels); + + else if (zero_pixels_are_background && neighborhood_connectivity == 8 && connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, ::zero_pixels_are_background(), neighbors_8(), ::connected_if_both_not_zero(), labels); + else if (zero_pixels_are_background && neighborhood_connectivity == 8 && !connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, ::zero_pixels_are_background(), neighbors_8(), connected_if_equal(), labels); + else if (!zero_pixels_are_background && neighborhood_connectivity == 8 && connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, nothing_is_background(), neighbors_8(), ::connected_if_both_not_zero(), labels); + else if (!zero_pixels_are_background && neighborhood_connectivity == 8 && !connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, nothing_is_background(), neighbors_8(), connected_if_equal(), labels); + + else if (zero_pixels_are_background && neighborhood_connectivity == 24 && connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, ::zero_pixels_are_background(), neighbors_24(), ::connected_if_both_not_zero(), labels); + else if (zero_pixels_are_background && neighborhood_connectivity == 24 && !connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, ::zero_pixels_are_background(), neighbors_24(), connected_if_equal(), labels); + else if (!zero_pixels_are_background && neighborhood_connectivity == 24 && connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, nothing_is_background(), neighbors_24(), ::connected_if_both_not_zero(), labels); + else if (!zero_pixels_are_background && neighborhood_connectivity == 24 && !connected_if_both_not_zero ) + num_blobs = label_connected_blobs(img, nothing_is_background(), neighbors_24(), connected_if_equal(), labels); + else + DLIB_CASSERT(false, "this should never happen"); + + return py::make_tuple(labels, num_blobs); +} + +// ---------------------------------------------------------------------------------------- + +template +numpy_image py_randomly_color_image ( + const numpy_image& img +) +{ + numpy_image temp; + assign_image(temp, randomly_color_image(img)); + return temp; +} + +// ---------------------------------------------------------------------------------------- + +template +numpy_image py_jet ( + const numpy_image& img +) +{ + numpy_image temp; + assign_image(temp, jet(img)); + return temp; +} // ---------------------------------------------------------------------------------------- void bind_image_classes(py::module& m) { + + + py::class_(m, "rgb_pixel") .def(py::init(), py::arg("red"), py::arg("green"), py::arg("blue")) .def("__str__", &print_rgb_pixel_str) @@ -143,5 +231,303 @@ output is the same as if you did. For example, suppose you called \n\ m.def("partition_pixels", &py_partition_pixels2, py::arg("img"), py::arg("num_thresholds") ); m.def("partition_pixels", &py_partition_pixels2, py::arg("img"), py::arg("num_thresholds") ); m.def("partition_pixels", &py_partition_pixels2,docs, py::arg("img"), py::arg("num_thresholds") ); + + docs = +"requires \n\ + - sigma > 0 \n\ + - max_size > 0 \n\ + - max_size is an odd number \n\ +ensures \n\ + - Filters img with a Gaussian filter of sigma width. The actual spatial filter will \n\ + be applied to pixel blocks that are at most max_size wide and max_size tall (note that \n\ + this function will automatically select a smaller block size as appropriate). The \n\ + results are returned. We also return a rectangle which indicates what pixels \n\ + in the returned image are considered non-border pixels and therefore contain \n\ + output from the filter. E.g. \n\ + - filtered_img,rect = gaussian_blur(img) \n\ + would give you the filtered image and the rectangle in question. \n\ + - The filter is applied to each color channel independently. \n\ + - Pixels close enough to the edge of img to not have the filter still fit \n\ + inside the image are set to zero. \n\ + - The returned image has the same dimensions as the input image."; + /*! + requires + - sigma > 0 + - max_size > 0 + - max_size is an odd number + ensures + - Filters img with a Gaussian filter of sigma width. The actual spatial filter will + be applied to pixel blocks that are at most max_size wide and max_size tall (note that + this function will automatically select a smaller block size as appropriate). The + results are returned. We also return a rectangle which indicates what pixels + in the returned image are considered non-border pixels and therefore contain + output from the filter. E.g. + - filtered_img,rect = gaussian_blur(img) + would give you the filtered image and the rectangle in question. + - The filter is applied to each color channel independently. + - Pixels close enough to the edge of img to not have the filter still fit + inside the image are set to zero. + - The returned image has the same dimensions as the input image. + !*/ + m.def("gaussian_blur", &py_gaussian_blur,py::arg("img"), py::arg("sigma"), py::arg("max_size")=1000 ); + m.def("gaussian_blur", &py_gaussian_blur,py::arg("img"), py::arg("sigma"), py::arg("max_size")=1000 ); + m.def("gaussian_blur", &py_gaussian_blur,py::arg("img"), py::arg("sigma"), py::arg("max_size")=1000 ); + m.def("gaussian_blur", &py_gaussian_blur,py::arg("img"), py::arg("sigma"), py::arg("max_size")=1000 ); + m.def("gaussian_blur", &py_gaussian_blur, py::arg("img"), py::arg("sigma"), py::arg("max_size")=1000 ); + m.def("gaussian_blur", &py_gaussian_blur,docs, py::arg("img"), py::arg("sigma"), py::arg("max_size")=1000 ); + + + + docs = +"requires \n\ + - all pixels in img are set to either 255 or 0. \n\ +ensures \n\ + - This function computes the skeletonization of img and stores the result in \n\ + #img. That is, given a binary image, we progressively thin the binary blobs \n\ + (composed of on_pixel values) until only a single pixel wide skeleton of the \n\ + original blobs remains. \n\ + - Doesn't change the shape or size of img."; + /*! + requires + - all pixels in img are set to either 255 or 0. + ensures + - This function computes the skeletonization of img and stores the result in + #img. That is, given a binary image, we progressively thin the binary blobs + (composed of on_pixel values) until only a single pixel wide skeleton of the + original blobs remains. + - Doesn't change the shape or size of img. + !*/ + m.def("skeleton", skeleton>, docs, py::arg("img")); + + + + + docs = +"requires \n\ + - neighborhood_connectivity == 4, 8, or 24 \n\ +ensures \n\ + - This function labels each of the connected blobs in img with a unique integer \n\ + label. \n\ + - An image can be thought of as a graph where pixels A and B are connected if \n\ + they are close to each other and satisfy some criterion like having the same \n\ + value or both being non-zero. Then this function can be understood as \n\ + labeling all the connected components of this pixel graph such that all \n\ + pixels in a component get the same label while pixels in different components \n\ + get different labels. \n\ + - If zero_pixels_are_background==true then there is a special background component \n\ + and all pixels with value 0 are assigned to it. Moreover, all such background pixels \n\ + will always get a blob id of 0 regardless of any other considerations. \n\ + - This function returns a label image and a count of the number of blobs found. \n\ + I.e., if you ran this function like: \n\ + label_img, num_blobs = label_connected_blobs(img) \n\ + You would obtain the noted label image and number of blobs. \n\ + - The output label_img has the same dimensions as the input image. \n\ + - for all valid r and c: \n\ + - label_img[r][c] == the blob label number for pixel img[r][c]. \n\ + - label_img[r][c] >= 0 \n\ + - if (img[r][c]==0) then \n\ + - label_img[r][c] == 0 \n\ + - else \n\ + - label_img[r][c] != 0 \n\ + - if (len(img) != 0) then \n\ + - The returned num_blobs will be == label_img.max()+1 \n\ + (i.e. returns a number one greater than the maximum blob id number, \n\ + this is the number of blobs found.) \n\ + - else \n\ + - num_blobs will be 0. \n\ + - blob labels are contiguous, therefore, the number returned by this function is \n\ + the number of blobs in the image (including the background blob)."; + /*! + requires + - neighborhood_connectivity == 4, 8, or 24 + ensures + - This function labels each of the connected blobs in img with a unique integer + label. + - An image can be thought of as a graph where pixels A and B are connected if + they are close to each other and satisfy some criterion like having the same + value or both being non-zero. Then this function can be understood as + labeling all the connected components of this pixel graph such that all + pixels in a component get the same label while pixels in different components + get different labels. + - If zero_pixels_are_background==true then there is a special background component + and all pixels with value 0 are assigned to it. Moreover, all such background pixels + will always get a blob id of 0 regardless of any other considerations. + - This function returns a label image and a count of the number of blobs found. + I.e., if you ran this function like: + label_img, num_blobs = label_connected_blobs(img) + You would obtain the noted label image and number of blobs. + - The output label_img has the same dimensions as the input image. + - for all valid r and c: + - label_img[r][c] == the blob label number for pixel img[r][c]. + - label_img[r][c] >= 0 + - if (img[r][c]==0) then + - label_img[r][c] == 0 + - else + - label_img[r][c] != 0 + - if (len(img) != 0) then + - The returned num_blobs will be == label_img.max()+1 + (i.e. returns a number one greater than the maximum blob id number, + this is the number of blobs found.) + - else + - num_blobs will be 0. + - blob labels are contiguous, therefore, the number returned by this function is + the number of blobs in the image (including the background blob). + !*/ + m.def("label_connected_blobs", py_label_connected_blobs, py::arg("img"),py::arg("zero_pixels_are_background")=true,py::arg("neighborhood_connectivity")=8,py::arg("connected_if_both_not_zero")=false); + m.def("label_connected_blobs", py_label_connected_blobs, py::arg("img"),py::arg("zero_pixels_are_background")=true,py::arg("neighborhood_connectivity")=8,py::arg("connected_if_both_not_zero")=false); + m.def("label_connected_blobs", py_label_connected_blobs, docs, py::arg("img"),py::arg("zero_pixels_are_background")=true,py::arg("neighborhood_connectivity")=8,py::arg("connected_if_both_not_zero")=false); + + + + docs = +"Converts a grayscale image into a jet colored image. This is an image where dark \n\ +pixels are dark blue and larger values become light blue, then yellow, and then \n\ +finally red as they approach the maximum pixel values." ; + m.def("jet", py_jet, py::arg("img")); + m.def("jet", py_jet, py::arg("img")); + m.def("jet", py_jet, py::arg("img")); + m.def("jet", py_jet, py::arg("img")); + m.def("jet", py_jet, docs, py::arg("img")); + + docs = +"- randomly generates a mapping from gray level pixel values \n\ + to the RGB pixel space and then uses this mapping to create \n\ + a colored version of img. Returns an image which represents \n\ + this colored version of img. \n\ +- black pixels in img will remain black in the output image. "; + /*! + - randomly generates a mapping from gray level pixel values + to the RGB pixel space and then uses this mapping to create + a colored version of img. Returns an image which represents + this colored version of img. + - black pixels in img will remain black in the output image. + !*/ + m.def("randomly_color_image", py_randomly_color_image, py::arg("img")); + m.def("randomly_color_image", py_randomly_color_image, py::arg("img")); + m.def("randomly_color_image", py_randomly_color_image, docs, py::arg("img")); + + + + docs = +"requires \n\ + - all pixels in img are set to either 255 or 0. \n\ + (i.e. it must be a binary image) \n\ +ensures \n\ + - This routine finds endpoints of lines in a thinned binary image. For \n\ + example, if the image was produced by skeleton() or something like a Canny \n\ + edge detector then you can use find_line_endpoints() to find the pixels \n\ + sitting on the ends of lines."; + /*! + requires + - all pixels in img are set to either 255 or 0. + (i.e. it must be a binary image) + ensures + - This routine finds endpoints of lines in a thinned binary image. For + example, if the image was produced by skeleton() or something like a Canny + edge detector then you can use find_line_endpoints() to find the pixels + sitting on the ends of lines. + !*/ + m.def("find_line_endpoints", find_line_endpoints>, docs, py::arg("img")); + + + m.def("get_rect", [](const py::array& img){ return rectangle(0,0,(long)img.shape(1)-1,(long)img.shape(0)-1); }, + "returns a rectangle(0,0,img.shape(1)-1,img.shape(0)-1). Therefore, it is the rectangle that bounds the image.", + py::arg("img") ); + + + const char* grad_docs = +"- Let VALID_AREA = shrink_rect(get_rect(img),get_scale()). \n\ +- This routine computes the requested gradient of img at each location in VALID_AREA. \n\ + The gradients are returned in a new image of the same dimensions as img. All pixels \n\ + outside VALID_AREA are set to 0. VALID_AREA is also returned. I.e. we return a tuple \n\ + where the first element is the gradient image and the second is VALID_AREA."; + + const char* filt_docs = +"- Returns the filter used by the indicated derivative to compute the image gradient. \n\ + That is, the output gradients are found by cross correlating the returned filter with \n\ + the input image. \n\ +- The returned filter has get_scale()*2+1 rows and columns." ; + + py::class_(m, "image_gradients", +"This class is a tool for computing first and second derivatives of an \n\ +image. It does this by fitting a quadratic surface around each pixel and \n\ +then computing the gradients of that quadratic surface. For the details \n\ +see the paper: \n\ + Quadratic models for curved line detection in SAR CCD by Davis E. King \n\ + and Rhonda D. Phillips \n\ + \n\ +This technique gives very accurate gradient estimates and is also very fast \n\ +since the entire gradient estimation procedure, for each type of gradient, \n\ +is accomplished by cross-correlating the image with a single separable \n\ +filter. This means you can compute gradients at very large scales (e.g. by \n\ +fitting the quadratic to a large window, like a 99x99 window) and it still \n\ +runs very quickly." + ) + .def(py::init(), "Creates this class with the provided scale. i.e. get_scale()==scale. \nscale must be >= 1.", py::arg("scale")) + .def(py::init<>(), "Creates this class with a scale of 1. i.e. get_scale()==1") + .def("gradient_x", [](image_gradients& g, const numpy_image& img){ + numpy_image out; + auto rect=g.gradient_x(img,out); + return py::make_tuple(out,rect); + }, py::arg("img")) + .def("gradient_x", [](image_gradients& g, const numpy_image& img){ + numpy_image out; + auto rect=g.gradient_x(img,out); + return py::make_tuple(out,rect); + }, grad_docs, py::arg("img")) + .def("gradient_y", [](image_gradients& g, const numpy_image& img){ + numpy_image out; + auto rect=g.gradient_y(img,out); + return py::make_tuple(out,rect); + }, py::arg("img")) + .def("gradient_y", [](image_gradients& g, const numpy_image& img){ + numpy_image out; + auto rect=g.gradient_y(img,out); + return py::make_tuple(out,rect); + }, grad_docs, py::arg("img")) + .def("gradient_xx", [](image_gradients& g, const numpy_image& img){ + numpy_image out; + auto rect=g.gradient_xx(img,out); + return py::make_tuple(out,rect); + }, py::arg("img")) + .def("gradient_xx", [](image_gradients& g, const numpy_image& img){ + numpy_image out; + auto rect=g.gradient_xx(img,out); + return py::make_tuple(out,rect); + }, grad_docs, py::arg("img")) + .def("gradient_xy", [](image_gradients& g, const numpy_image& img){ + numpy_image out; + auto rect=g.gradient_xy(img,out); + return py::make_tuple(out,rect); + }, py::arg("img")) + .def("gradient_xy", [](image_gradients& g, const numpy_image& img){ + numpy_image out; + auto rect=g.gradient_xy(img,out); + return py::make_tuple(out,rect); + }, grad_docs, py::arg("img")) + .def("gradient_yy", [](image_gradients& g, const numpy_image& img){ + numpy_image out; + auto rect=g.gradient_yy(img,out); + return py::make_tuple(out,rect); + }, py::arg("img")) + .def("gradient_yy", [](image_gradients& g, const numpy_image& img){ + numpy_image out; + auto rect=g.gradient_yy(img,out); + return py::make_tuple(out,rect); + }, grad_docs, py::arg("img")) + .def("get_x_filter", [](image_gradients& g){ return numpy_image(g.get_x_filter()); }, filt_docs) + .def("get_y_filter", [](image_gradients& g){ return numpy_image(g.get_y_filter()); }, filt_docs) + .def("get_xx_filter", [](image_gradients& g){ return numpy_image(g.get_xx_filter()); }, filt_docs) + .def("get_xy_filter", [](image_gradients& g){ return numpy_image(g.get_xy_filter()); }, filt_docs) + .def("get_yy_filter", [](image_gradients& g){ return numpy_image(g.get_yy_filter()); }, filt_docs) + .def("get_scale", &image_gradients::get_scale, +"When we estimate a gradient we do so by fitting a quadratic filter so a window of size \n\ +get_scale()*2+1 centered on each pixel. Therefore, the scale parameter controls the size \n\ +of gradients we will find. For example, a very large scale will cause the gradient_xx() \n\ +to be insensitive to high frequency noise in the image while smaller scales would be more \n\ +sensitive to such fluctuations in the image." + ); + + } diff --git a/tools/python/src/rectangles.cpp b/tools/python/src/rectangles.cpp index d06ec591b..e9d89cc40 100644 --- a/tools/python/src/rectangles.cpp +++ b/tools/python/src/rectangles.cpp @@ -263,6 +263,17 @@ ensures \n\ .def("extend", extend_vector_with_python_list) .def(py::pickle(&getstate, &setstate)); } + + m.def("shrink_rect", [](const rectangle& rect, long num){return shrink_rect(rect,num);}, +" returns rectangle(rect.left()+num, rect.top()+num, rect.right()-num, rect.bottom()-num) \n\ + (i.e. shrinks the given rectangle by shrinking its border by num)", + py::arg("rect"), py::arg("num")); + + m.def("grow_rect", [](const rectangle& rect, long num){return grow_rect(rect,num);}, +"- return shrink_rect(rect, -num) \n\ + (i.e. grows the given rectangle by expanding its border by num)", + py::arg("rect"), py::arg("num")); + } // ----------------------------------------------------------------------------------------