diff --git a/modules/core/include/visp3/core/vpImageMorphology.h b/modules/core/include/visp3/core/vpImageMorphology.h index 2fd2b6408b..cc83b2e251 100644 --- a/modules/core/include/visp3/core/vpImageMorphology.h +++ b/modules/core/include/visp3/core/vpImageMorphology.h @@ -76,19 +76,31 @@ class VISP_EXPORT vpImageMorphology private: /** - * @brief Modify the image by applying the \b operation on each of its elements on a 3x3 + * \brief Modify the image by applying the \b operation on each of its elements on a 3x3 * grid. * - * @param T Either a class such as vpRGBa or a type such as double, unsigned char ... - * @param I The image we want to modify. - * @param null_value The value that is padded to the input image to manage the borders. - * @param operation The operation to apply to its elements on a 3x3 grid. - * @param connexity Either a 4-connexity, if we want to take into account only the horizontal + * \tparam T Either a class such as vpRGBa or a type such as double, unsigned char ... + * \param[out] I The image we want to modify. + * \param[in] null_value The value that is padded to the input image to manage the borders. + * \param[in] operation The operation to apply to its elements on a 3x3 grid. + * \param[in] connexity Either a 4-connexity, if we want to take into account only the horizontal * and vertical neighbors, or a 8-connexity, if we want to also take into account the diagonal neighbors. */ template static void imageOperation(vpImage &I, const T &null_value, const T &(*operation)(const T &, const T &), const vpConnexityType &connexity = CONNEXITY_4); + /** + * \brief Modify the image by applying the \b operation on each of its elements on a \b size x \b size + * grid. The connexity that is used is a 8-connexity. + * + * \tparam T Any type such as double, unsigned char ... + * \param[out] I The image we want to modify. + * \param[in] size The size of the window with which we want to work. + * \param[in] operation The operation to apply to its elements. + */ + template + static void imageOperation(vpImage &I, const int &size, const T &(*operation)(const T &, const T &)); + public: template static void erosion(vpImage &I, Type value, Type value_out, vpConnexityType connexity = CONNEXITY_4); @@ -102,6 +114,12 @@ class VISP_EXPORT vpImageMorphology template static void dilatation(vpImage &I, const vpConnexityType &connexity = CONNEXITY_4); + template + static void erosion(vpImage &I, const int &size); + + template + static void dilatation(vpImage &I, const int &size); + #if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) /*! @name Deprecated functions @@ -351,7 +369,7 @@ void vpImageMorphology::imageOperation(vpImage &I, const T &null_value, const \param I : Image to process. \param connexity : Type of connexity: 4 or 8. - \sa dilatation(vpImage &, const vpConnexityType &) + \sa dilatation(vpImage &, const vpConnexityType &) */ template void vpImageMorphology::erosion(vpImage &I, const vpConnexityType &connexity) @@ -381,7 +399,7 @@ void vpImageMorphology::erosion(vpImage &I, const vpConnexityType &connexity) \param I : Image to process. \param connexity : Type of connexity: 4 or 8. - \sa erosion(vpImage &, const vpConnexityType &) + \sa erosion(vpImage &, const vpConnexityType &) */ template void vpImageMorphology::dilatation(vpImage &I, const vpConnexityType &connexity) @@ -389,6 +407,115 @@ void vpImageMorphology::dilatation(vpImage &I, const vpConnexityType &connexi const T &(*operation)(const T & a, const T & b) = std::max; vpImageMorphology::imageOperation(I, std::numeric_limits::min(), operation, connexity); } + +/** + * \brief Dilatation of \b size >=3 with 8-connectivity. + * + * \tparam T Any type of image, except vpRGBa . + * \param[out] I The image to which the dilatation must be applied, where the dilatation corresponds + * to a max operator on a window of size \b size. + * \param[in] size The size of the window on which is performed the max operator for each pixel. + */ +template +void vpImageMorphology::imageOperation(vpImage &I, const int &size, const T &(*operation)(const T &, const T &)) +{ + if (size % 2 != 1) { + throw(vpException(vpException::badValue, "Dilatation kernel must be odd.")); + } + + const int width_in = I.getWidth(); + const int height_in = I.getHeight(); + int halfKernelSize = size / 2; + vpImage J = I; + + for (int r = 0; r < height_in; r++) { + // Computing the rows we can explore without going outside the limits of the image + int r_iterator_start = -halfKernelSize, r_iterator_stop = halfKernelSize + 1; + if (r - halfKernelSize < 0) { + r_iterator_start = -r; + } + else if (r + halfKernelSize >= height_in) { + r_iterator_stop = height_in - r; + } + for (int c = 0; c < width_in; c++) { + T value = I[r][c]; + // Computing the columns we can explore without going outside the limits of the image + int c_iterator_start = -halfKernelSize, c_iterator_stop = halfKernelSize + 1; + if (c - halfKernelSize < 0) { + c_iterator_start = -c; + } + else if (c + halfKernelSize >= width_in) { + c_iterator_stop = width_in - c; + } + for (int r_iterator = r_iterator_start; r_iterator < r_iterator_stop; r_iterator++) { + for (int c_iterator = c_iterator_start; c_iterator < c_iterator_stop; c_iterator++) { + value = operation(value, J[r + r_iterator][c + c_iterator]); + } + } + I[r][c] = value; + } + } +} + +/*! + Erode an image using the given structuring element. + + The erosion of \f$ A \left( x, y \right) \f$ by \f$ B \left (x, y + \right) \f$ is defined as: \f[ \left ( A \ominus B \right ) \left( x,y + \right) = \textbf{min} \left \{ A \left ( x+x', y+y' \right ) - B \left ( + x', y'\right ) | \left ( x', y'\right ) \subseteq D_B \right \} \f] where + \f$ D_B \f$ is the domain of the structuring element \f$ B \f$ and \f$ A + \left( x,y \right) \f$ is assumed to be \f$ + \infty \f$ outside the domain + of the image. + + In our case, the erosion is performed with a flat structuring element + \f$ \left( B \left( x,y \right) = 0 \right) \f$. The erosion using + such a structuring element is equivalent to a local-minimum operator: \f[ + \left ( A \ominus B \right ) \left( x,y \right) = \textbf{min} \left \{ A + \left ( x+x', y+y' \right ) | \left ( x', y'\right ) \subseteq D_B \right \} + \f] + + \param I : Image to process. + \param size : The size of the kernel + + \sa dilatation(vpImage &, const int &) +*/ +template +void vpImageMorphology::erosion(vpImage &I, const int &size) +{ + const T &(*operation)(const T & a, const T & b) = std::min; + vpImageMorphology::imageOperation(I, size, operation); +} + +/*! + Dilate an image using the given structuring element. + + The dilatation of \f$ A \left( x, y \right) \f$ by \f$ B \left + (x, y \right) \f$ is defined as: \f[ \left ( A \oplus B \right ) \left( x,y + \right) = \textbf{max} \left \{ A \left ( x-x', y-y' \right ) + B \left ( + x', y'\right ) | \left ( x', y'\right ) \subseteq D_B \right \} \f] where + \f$ D_B \f$ is the domain of the structuring element \f$ B \f$ and \f$ A + \left( x,y \right) \f$ is assumed to be \f$ - \infty \f$ outside the domain + of the image. + + In our case, the dilatation is performed with a flat structuring element + \f$ \left( B \left( x,y \right) = 0 \right) \f$. The dilatation using + such a structuring element is equivalent to a local-maximum operator: \f[ + \left ( A \oplus B \right ) \left( x,y \right) = \textbf{max} \left \{ A + \left ( x-x', y-y' \right ) | \left ( x', y'\right ) \subseteq D_B \right \} + \f] + + \param I : Image to process. + \param size : The size of the kernel. + + \sa erosion(vpImage &, const int &) +*/ +template +void vpImageMorphology::dilatation(vpImage &I, const int &size) +{ + const T &(*operation)(const T & a, const T & b) = std::max; + vpImageMorphology::imageOperation(I, size, operation); +} #endif /* diff --git a/modules/core/test/image/testImageMorphology.cpp b/modules/core/test/image/testImageMorphology.cpp index 6be8c128a2..8e051b8330 100644 --- a/modules/core/test/image/testImageMorphology.cpp +++ b/modules/core/test/image/testImageMorphology.cpp @@ -124,6 +124,26 @@ TEST_CASE("Binary image morphology", "[image_morphology]") CHECK((I_morpho_ref == I_morpho_tpl)); CHECK((I_morpho_ref == I_morpho)); } + + SECTION("8-connexity-size5") + { + vpImage I_dilatation_ref(8, 16, 1); + I_dilatation_ref[0][0] = 0; + I_dilatation_ref[0][1] = 0; + I_dilatation_ref[0][2] = 0; + I_dilatation_ref[6][12] = 0; + I_dilatation_ref[7][12] = 0; + vpImage I_dilatation = I; + vpImage I_erosion_ref(8, 16, 0); + vpImage I_erosion = I; + + const int size = 5; + vpImageMorphology::dilatation(I_dilatation, size); + vpImageMorphology::erosion(I_erosion, size); + + CHECK((I_dilatation_ref == I_dilatation)); + CHECK((I_erosion_ref == I_erosion)); + } } SECTION("Matlab reference") @@ -211,6 +231,37 @@ TEST_CASE("Gray image morphology", "[image_morphology]") CHECK((I_morpho_ref == I_morpho)); } + + SECTION("8-connexity-size5") + { + const int size = 5; + vpImage I_morpho(12, 12); + unsigned char count = 1; + for (int r = 0; r < 12; r++) { + for (int c = 0; c < 12; c++) { + I_morpho[r][c] = count; + count++; + } + } + unsigned char image_data_dilatation[12 * 12] = { + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 36, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 48, 48, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 60, 60, + 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 72, 72, + 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 84, 84, + 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 96, 96, + 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 108, 108, + 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 120, 120, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 132, 132, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 144, 144, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 144, 144, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 144, 144 }; + vpImage I_dilatation_ref(image_data_dilatation, 12, 12, true); + + vpImageMorphology::dilatation(I_morpho, size); + + CHECK((I_dilatation_ref == I_morpho)); + } } SECTION("Erosion") @@ -238,6 +289,37 @@ TEST_CASE("Gray image morphology", "[image_morphology]") CHECK((I_morpho_ref == I_morpho)); } + + SECTION("8-connexity-size5") + { + const int size = 5; + vpImage I_morpho(12, 12); + unsigned char count = 1; + for (int r = 0; r < 12; r++) { + for (int c = 0; c < 12; c++) { + I_morpho[r][c] = count; + count++; + } + } + unsigned char image_data_erosion[12 * 12] = { + 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 13, 13, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 25, 25, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 37, 37, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 49, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 61, 61, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 73, 73, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 85, 85, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 97, 97, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 109, 109, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118 }; + vpImage I_erosion_ref(image_data_erosion, 12, 12, true); + + vpImageMorphology::erosion(I_morpho, size); + + CHECK((I_erosion_ref == I_morpho)); + } } SECTION("Matlab reference") @@ -336,7 +418,7 @@ TEST_CASE("Gray image morphology", "[image_morphology]") } } -int main(int argc, char *argv []) +int main(int argc, char *argv[]) { Catch::Session session; // There must be exactly one instance