diff --git a/doc/tutorial/imgproc/tutorial-imgproc-brightness.dox b/doc/tutorial/imgproc/tutorial-imgproc-brightness.dox index 1797cf6fb1..eafd02e649 100644 --- a/doc/tutorial/imgproc/tutorial-imgproc-brightness.dox +++ b/doc/tutorial/imgproc/tutorial-imgproc-brightness.dox @@ -49,6 +49,13 @@ The result image is the following: \image html img-tutorial-brighness-gamma-correction-3.5.png "Left: underexposed image - Right: image corrected with gamma=3.5" +ViSP proposes the implementation of several automatic computation of the gamma factor. +Most of these methods are designed for gray-shade images, so ViSP proposes different way +of handling the colors. + +You can test the different methods using the `--gamma-method` option of the tutorial program +and the different way of handling the colors using the `--gamma-color-handling` option. + \section imgproc_brightness_histogram_equalization Histogram equalization Histogram equalization is an image processing method that will adjust the contrast of an image by stretching or shrinking the intensity distribution in order to have a linear cumulative histogram distribution. diff --git a/modules/core/include/visp3/core/vpImage.h b/modules/core/include/visp3/core/vpImage.h index 1184627d29..b3f82d0794 100644 --- a/modules/core/include/visp3/core/vpImage.h +++ b/modules/core/include/visp3/core/vpImage.h @@ -186,7 +186,13 @@ template class vpImage // Return the maximum value within the bitmap Type getMaxValue(bool onlyFiniteVal = true) const; // Return the mean value of the bitmap - Type getMeanValue() const; + double getMeanValue() const; + double getMeanValue(const vpImage *p_mask) const; + double getMeanValue(const vpImage *p_mask, unsigned int &nbValidPoints) const; + double getStdev() const; + double getStdev(const vpImage *p_mask) const; + double getStdev(const double &mean) const; + double getStdev(const double &mean, const unsigned int &nbValidPoints, const vpImage *p_mask) const; // Return the minumum value within the bitmap Type getMinValue(bool onlyFiniteVal = true) const; // Look for the minumum and the maximum value within the bitmap @@ -231,6 +237,7 @@ template class vpImage // Get image pixels sum double getSum() const; + double getSum(const vpImage *p_mask, unsigned int &nbValidPoints) const; /*! Get the image width. @@ -939,14 +946,103 @@ template <> inline float vpImage::getMaxValue(bool onlyFiniteVal) const /*! \brief Return the mean value of the bitmap */ -template Type vpImage::getMeanValue() const +template double vpImage::getMeanValue() const { - if ((height == 0) || (width == 0)) + if ((height == 0) || (width == 0)) { return 0.0; + } return getSum() / (height * width); } +/*! + \brief Return the mean value of the bitmap + + \param[in] p_mask A boolean mask that indicates which points must be considered, if set. +*/ +template double vpImage::getMeanValue(const vpImage *p_mask) const +{ + unsigned int nbValidPoints = 0; + return getMeanValue(p_mask, nbValidPoints); +} + +/*! + \brief Return the mean value of the bitmap + + \param[in] p_mask A boolean mask that indicates which points must be considered, if set. + \param[out] nbValidPoints Number of points that are valid according to the boolean mask. +*/ +template double vpImage::getMeanValue(const vpImage *p_mask, unsigned int &nbValidPoints) const +{ + nbValidPoints = 0; + if ((height == 0) || (width == 0)) { + return 0.0; + } + + double sum = getSum(p_mask, nbValidPoints); + return sum / nbValidPoints; +} + +/*! +* \brief Return the standard deviation of the bitmap +*/ +template double vpImage::getStdev() const +{ + double mean = getMeanValue(); + return getStdev(mean); +} + +/*! +* \brief Return the standard deviation of the bitmap +* +* \param[in] p_mask A boolean mask that indicates which points must be considered, if set. +*/ +template double vpImage::getStdev(const vpImage *p_mask) const +{ + unsigned int nbValidPoints = 0; + double mean = getMeanValue(p_mask, nbValidPoints); + return getStdev(mean, nbValidPoints, p_mask); +} + +/*! +* \brief Return the standard deviation of the bitmap +* +* \param[in] mean The mean of the image. +*/ +template double vpImage::getStdev(const double &mean) const +{ + const unsigned int size = width * height; + double sum = 0.; + for (unsigned int i = 0; i < size; ++i) { + sum += (bitmap[i] - mean) * (bitmap[i] - mean); + } + sum /= static_cast(size); + return std::sqrt(sum); +} + +/*! +* \brief Return the standard deviation of the bitmap +* +* \param[in] mean The mean of the image. +* \param[in] nbValidPoints Number of points that are valid according to the boolean mask. +* \param[in] p_mask A boolean mask that indicates which points must be considered, if set. +*/ +template double vpImage::getStdev(const double &mean, const unsigned int &nbValidPoints, const vpImage *p_mask) const +{ + if (p_mask == nullptr) { + return getStdev(mean); + } + const unsigned int size = width * height; + double sum = 0.; + for (unsigned int i = 0; i < size; ++i) { + if (p_mask->bitmap[i]) { + sum += (bitmap[i] - mean) * (bitmap[i] - mean); + } + } + sum /= static_cast(nbValidPoints); + return std::sqrt(sum); +} + /*! * \brief Return the minimum value within the bitmap * \param onlyFiniteVal : This parameter is ignored for non double or non float bitmap. @@ -1869,6 +1965,34 @@ template inline double vpImage::getSum() const return res; } +/** + * Compute the sum of image intensities. + * For vpRGBa image type, compute the sum (R+G+B) of image intensities. + * + * \param[in] p_mask Boolean mask that indicates the valid points by a true flag. + * \param[out] nbValidPoints The number of valid points according to the \b p_mask. + */ +template inline double vpImage::getSum(const vpImage *p_mask, unsigned int &nbValidPoints) const +{ + if ((height == 0) || (width == 0)) + return 0.0; + if (p_mask == nullptr) { + nbValidPoints = height * width; + return getSum(); + } + + double res = 0.0; + nbValidPoints = 0; + unsigned int size = height * width; + for (unsigned int i = 0; i < size; ++i) { + if (p_mask->bitmap[i]) { + res += static_cast(bitmap[i]); + ++nbValidPoints; + } + } + return res; +} + /** * \relates vpImage */ @@ -1884,6 +2008,32 @@ template <> inline double vpImage::getSum() const return res; } +/** + * \relates vpImage + */ +template <> inline double vpImage::getSum(const vpImage *p_mask, unsigned int &nbValidPoints) const +{ + if ((height == 0) || (width == 0)) { + return 0.0; + } + + if (p_mask == nullptr) { + nbValidPoints = height * width; + return getSum(); + } + + double res = 0.0; + nbValidPoints = 0; + unsigned int size = height * width; + for (unsigned int i = 0; i < size; ++i) { + if (p_mask->bitmap[i]) { + res += static_cast(bitmap[i].R) + static_cast(bitmap[i].G) + static_cast(bitmap[i].B); + ++nbValidPoints; + } + } + return res; +} + /** * \relates vpImage */ @@ -1899,6 +2049,31 @@ template <> inline double vpImage::getSum() const return res; } +/** + * \relates vpImage + */ +template <> inline double vpImage::getSum(const vpImage *p_mask, unsigned int &nbValidPoints) const +{ + if ((height == 0) || (width == 0)) { + return 0.0; + } + if (p_mask == nullptr) { + nbValidPoints = height * width; + return getSum(); + } + + double res = 0.0; + nbValidPoints = 0; + unsigned int size = height * width; + for (unsigned int i = 0; i < size; ++i) { + if (p_mask->bitmap[i]) { + res += static_cast(bitmap[i].R) + static_cast(bitmap[i].G) + static_cast(bitmap[i].B); + ++nbValidPoints; + } + } + return res; +} + /*! Operation C = *this - B. diff --git a/modules/imgproc/include/visp3/imgproc/vpImgproc.h b/modules/imgproc/include/visp3/imgproc/vpImgproc.h index bd7f7cfdd9..e6b5ad16da 100644 --- a/modules/imgproc/include/visp3/imgproc/vpImgproc.h +++ b/modules/imgproc/include/visp3/imgproc/vpImgproc.h @@ -40,6 +40,7 @@ #ifndef _vpImgproc_h_ #define _vpImgproc_h_ +#include #include #include #include @@ -91,6 +92,93 @@ typedef enum */ } vpAutoThresholdMethod; +/** + * \brief Gamma Correction automatic methods. + */ +typedef enum vpGammaMethod +{ + GAMMA_MANUAL = 0, /*!< User-defined constant positive gamma factor.*/ + GAMMA_LOG_BASED = 1, /*!< Scott, J & Pusateri M (2009)"Towards Real-time Hardware + Gamma Correction for Dynamic Contrast Enhancement" + IEEE Applied Imagery Pattern Recognition Workshop (AIPR 2009)*/ + GAMMA_NONLINEAR_BASED = 2, /*!< Shi, Y et al. (2007), "Reducing Illumination Based On Nonlinear Gamma Correction", + International Conference on Image Processing */ + GAMMA_CDF_BASED = 3, /*!< Huang, SC et al. (2013),"Efficient Contrast Enhancement Using Adaptive + Gamma Correction With Weighting Distribution", + IEEE Trans. on Image Processing, VOL. 22, NO. 3, MARCH 2013. */ + GAMMA_CLASSIFICATION_BASED = 4, /*!< Rahman, S et al. (2016), "An adaptive gamma correction for image + enhancement", EURASIP Journal on Image and Video Processing*/ + GAMMA_SPATIAL_VARIANT_BASED = 5, /*!< Lee, S et al. (2010), "A Space-Variant Luminance Map based + Color Image Enhancement", + IEEE Trans. on Consumer Electronics, Vol. 56, No. 4, November 2010.*/ + GAMMA_METHOD_COUNT = 6 +} vpGammaMethod; + +/** + * \brief Get the list of available vpGammaMethod. + * + * \param[in] pref The prefix of the list. + * \param[in] sep The separator between two elements of the list. + * \param[in] suf The suffix of the list. + * \return std::string The list of available items. + */ +VISP_EXPORT std::string vpGammaMethodList(const std::string &pref = "<", const std::string &sep = " , ", + const std::string &suf = ">"); + +/** + * \brief Cast a \b vp::vpGammaMethod into a string, to know its name. + * + * \param[in] type The type that must be casted into a string. + * \return std::string The corresponding name. + */ +VISP_EXPORT std::string vpGammaMethodToString(const vpGammaMethod &type); + +/** + * \brief Cast a string into a \b vp::vpGammaMethod. + * + * \param[in] name The name of the backend. + * \return vp::vpGammaMethod The corresponding enumeration value. + */ +VISP_EXPORT vpGammaMethod vpGammaMethodFromString(const std::string &name); + +/** + * \brief How to handle color images when applying Gamma Correction. + */ +typedef enum vpGammaColorHandling +{ + GAMMA_RGB = 0, /*!< Gamma correction is apply to Red, Blue and Green channels individually.*/ + GAMMA_HSV = 1, /*!< The input image is converted into HSV space, Gamma Correction is applied to Value channel and + then the image is converted back into RGBa space.*/ + GAMMA_COLOR_HANDLING_COUNT = 2 +} vpGammaColorHandling; + +/** + * \brief Get the list of available vpGammaColorHandling. + * + * \param[in] pref The prefix of the list. + * \param[in] sep The separator between two elements of the list. + * \param[in] suf The suffix of the list. + * \return std::string The list of available items. + */ +VISP_EXPORT std::string vpGammaColorHandlingList(const std::string &pref = "<", const std::string &sep = " , ", + const std::string &suf = ">"); + +/** + * \brief Cast a \b vp::vpGammaColorHandling into a string, to know its name. + * + * \param[in] type The type that must be casted into a string. + * \return std::string The corresponding name. + */ +VISP_EXPORT std::string vpGammaColorHandlingToString(const vpGammaColorHandling &type); + +/** + * \brief Cast a string into a \b vp::vpGammaColorHandling. + * + * \param[in] name The name of the backend. + * \return vp::vpGammaColorHandling The corresponding enumeration value. + */ +VISP_EXPORT vpGammaColorHandling vpGammaColorHandlingFromString(const std::string &name); + /*! * \ingroup group_imgproc_brightness * @@ -262,42 +350,71 @@ VISP_EXPORT void equalizeHistogram(const vpImage &I1, vpImage &I * * Perform a gamma correction on a grayscale image. * - * \param I : The grayscale image to apply gamma correction. - * \param gamma : Gamma value. + * \param[out] I : The grayscale image to apply gamma correction. + * \param[in] gamma : Gamma value. If equals to -1, use automatic Gamma correction based on a non-linear + * technique. If equals to -2, use automatic Gamma correction based on a logarithmic technique. + * If equals to -3, uses automatic Gamma correction based on classification. If equals to -4, uses automatic Gamma + * correction based on probalistics. + * \param[in] method: The method to use: either \b GAMMA_MANUAL if the user wants to use a positive constant \b gamma + * factor, or one of the automatic method if \b gamma is negative. + * \param[in] p_mask:if different from nullptr, permits to indicate which points must be taken into account by setting + * them to true. */ -VISP_EXPORT void gammaCorrection(vpImage &I, double gamma); +VISP_EXPORT void gammaCorrection(vpImage &I, const float &gamma, const vpGammaMethod &method = vp::GAMMA_MANUAL, + const vpImage *p_mask = nullptr); /*! * \ingroup group_imgproc_gamma * * Perform a gamma correction on a grayscale image. * - * \param I1 : The first grayscale image. - * \param I2 : The second grayscale image after gamma correction. - * \param gamma : Gamma value. + * \param[in] I1 : The first grayscale image. + * \param[out] I2 : The second grayscale image after gamma correction. + * \param[in] gamma : Gamma value. If equals to -1, use automatic Gamma correction based on a non-linear + * technique. If equals to -2, use automatic Gamma correction based on a logarithmic technique. + * If equals to -3, uses automatic Gamma correction based on classification. If equals to -4, uses automatic Gamma + * correction based on probalistics. + * \param[in] method: The method to use: either \b GAMMA_MANUAL if the user wants to use a positive constant \b gamma + * factor, or one of the automatic method if \b gamma is negative. + * \param[in] p_mask:if different from nullptr, permits to indicate which points must be taken into account by setting + * them to true. */ -VISP_EXPORT void gammaCorrection(const vpImage &I1, vpImage &I2, double gamma); +VISP_EXPORT void gammaCorrection(const vpImage &I1, vpImage &I2, const float &gamma, + const vpGammaMethod &method = vp::GAMMA_MANUAL, const vpImage *p_mask = nullptr); /*! * \ingroup group_imgproc_gamma * * Perform a gamma correction on a color image. * - * \param I : The color image to apply gamma correction. - * \param gamma : Gamma value. + * \param[out] I : The color image to apply gamma correction. + * \param[in] gamma : Gamma value. + * \param[in] colorHandling : How to handle the colors of the image. + * \param[in] method : The method to use: either \b GAMMA_MANUAL if the user wants to use a positive constant \b gamma factor, + * or one of the automatic method if \b gamma is negative. + * \param[in] p_mask : if different from nullptr, permits to indicate which points must be taken into account by setting + * them to true. */ -VISP_EXPORT void gammaCorrection(vpImage &I, double gamma); +VISP_EXPORT void gammaCorrection(vpImage &I, const float &gamma, const vpGammaColorHandling &colorHandling = vp::GAMMA_RGB, + const vpGammaMethod &method = vp::GAMMA_MANUAL, const vpImage *p_mask = nullptr); /*! * \ingroup group_imgproc_gamma * * Perform a gamma correction on a color image. * - * \param I1 : The first color image. - * \param I2 : The second color image after gamma correction. - * \param gamma : Gamma value. + * \param[in] I1 : The first color image. + * \param[out] I2 : The second color image after gamma correction. + * \param[in] gamma : Gamma value. + * \param[in] colorHandling : How to handle the colors of the image. + * \param[in] method: The method to use: either \b GAMMA_MANUAL if the user wants to use a positive constant \b gamma factor, + * or one of the automatic method if \b gamma is negative. + * \param[in] p_mask:if different from nullptr, permits to indicate which points must be taken into account by setting + * them to true. */ -VISP_EXPORT void gammaCorrection(const vpImage &I1, vpImage &I2, double gamma); +VISP_EXPORT void gammaCorrection(const vpImage &I1, vpImage &I2, const float &gamma, + const vpGammaColorHandling &colorHandling = vp::GAMMA_RGB, + const vpGammaMethod &method = vp::GAMMA_MANUAL, const vpImage *p_mask = nullptr); /*! * \ingroup group_imgproc_retinex diff --git a/modules/imgproc/src/vpImgproc.cpp b/modules/imgproc/src/vpImgproc.cpp index f3adc5ddc3..8496c6ebd2 100644 --- a/modules/imgproc/src/vpImgproc.cpp +++ b/modules/imgproc/src/vpImgproc.cpp @@ -60,11 +60,120 @@ #include #include #include +#include #include #include namespace vp { +std::string vpGammaMethodList(const std::string &pref, const std::string &sep, const std::string &suf) +{ + std::string list(pref); + for (unsigned int i = 0; i < (GAMMA_METHOD_COUNT - 1); ++i) { + vpGammaMethod type = static_cast(i); + list += vpGammaMethodToString(type); + list += sep; + } + vpGammaMethod type = static_cast(GAMMA_METHOD_COUNT - 1); + list += vpGammaMethodToString(type); + list += suf; + return list; +} + +std::string vpGammaMethodToString(const vpGammaMethod &type) +{ + std::string name; + switch (type) { + case GAMMA_MANUAL: + name = "gamma_manual"; + break; + case GAMMA_LOG_BASED: + name = "gamma_log"; + break; + case GAMMA_NONLINEAR_BASED: + name = "gamma_nonlinear"; + break; + case GAMMA_CDF_BASED: + name = "gamma_cdf"; + break; + case GAMMA_CLASSIFICATION_BASED: + name = "gamma_classification"; + break; + case GAMMA_SPATIAL_VARIANT_BASED: + name = "gamma_spatial_variant"; + break; + case GAMMA_METHOD_COUNT: + default: + name = "gamma_method_unknown"; + } + return name; +} + +vpGammaMethod vpGammaMethodFromString(const std::string &name) +{ + vpGammaMethod type(GAMMA_METHOD_COUNT); + unsigned int count = static_cast(GAMMA_METHOD_COUNT); + bool notFound = true; + unsigned int i = 0; + while ((i < count) && notFound) { + vpGammaMethod temp = static_cast(i); + if (name == vpGammaMethodToString(temp)) { + type = temp; + notFound = false; + } + ++i; + } + return type; +} + +std::string vpGammaColorHandlingList(const std::string &pref, const std::string &sep, const std::string &suf) +{ + std::string list(pref); + for (unsigned int i = 0; i < (GAMMA_COLOR_HANDLING_COUNT - 1); ++i) { + vpGammaColorHandling type = static_cast(i); + list += vpGammaColorHandlingToString(type); + list += sep; + } + vpGammaColorHandling type = static_cast(GAMMA_COLOR_HANDLING_COUNT - 1); + list += vpGammaColorHandlingToString(type); + list += suf; + return list; +} + +std::string vpGammaColorHandlingToString(const vpGammaColorHandling &type) +{ + std::string name; + switch (type) { + case GAMMA_RGB: + name = "gamma_color_rgb"; + break; + case GAMMA_HSV: + name = "gamma_color_hsv"; + break; + case GAMMA_COLOR_HANDLING_COUNT: + default: + name = "gamma_color_unknown"; + } + return name; +} + +vpGammaColorHandling vpGammaColorHandlingFromString(const std::string &name) +{ + vpGammaColorHandling type(GAMMA_COLOR_HANDLING_COUNT); + unsigned int count = static_cast(GAMMA_COLOR_HANDLING_COUNT); + bool notFound = true; + unsigned int i = 0; + while ((i < count) && notFound) { + vpGammaColorHandling temp = static_cast(i); + if (name == vpGammaColorHandlingToString(temp)) { + type = temp; + notFound = false; + } + ++i; + } + return type; +} + void adjust(vpImage &I, double alpha, double beta) { // Construct the look-up table @@ -193,57 +302,403 @@ void equalizeHistogram(const vpImage &I1, vpImage &I2, bool useH vp::equalizeHistogram(I2, useHSV); } -void gammaCorrection(vpImage &I, double gamma) +namespace { - double inverse_gamma = 1.0; - if (gamma > 0) { - inverse_gamma = 1.0 / gamma; - } - else { - throw vpException(vpException::badValue, "The gamma value must be positive !"); - } +/** + * \brief This method is an implementation of the article "Towards Real-time Hardware Gamma Correction + * for Dynamic Contrast Enhancement" by Jesse Scott, Michael Pusateri, IEEE Applied Imagery Pattern Recognition + * Workshop (AIPR 2009), 2009 + * + * The gamma factor depends on the mean of the original image and its intensity range. + * + * \param[out] I The image on which gamma correction must be applied. + * \param[in] p_mask Boolean that indicates which points must be taken into account (true value) + * or must be ignored (false value). + */ +void gammaCorrectionLogMethod(vpImage &I, const vpImage *p_mask) +{ + float mean = static_cast(I.getMeanValue(p_mask)); + unsigned char inputMin = 0, inputMax = 0; + I.getMinMaxValue(inputMin, inputMax); + unsigned char inputRange = inputMax - inputMin; + + float gamma_computed = (std::log(128.f) - std::log(256.f)) / (std::log(mean) - std::log(inputRange)); + float inverse_gamma = 1.f / gamma_computed; // Construct the look-up table unsigned char lut[256]; + float inputRangeAsFloat = static_cast(inputRange); + for (unsigned int i = inputMin; i <= inputMax; i++) { + lut[i] = vpMath::saturate(std::pow(static_cast(i - inputMin) / inputRangeAsFloat, inverse_gamma) * 255.f); + } + + I.performLut(lut); +} + +/** + * \brief This method is an implementation of the article "REDUCING ILLUMINATION BASED ON NONLINEAR GAMMA CORRECTION" + * by Yihua Shi, Jinfeng Yang, Renbiao Wu, International Conference on Image Processing ยท September 2007 + * + * The gamma factor is the result of the sum of non-linear functions whose values depend on + * the pixel intensity. + * + * \param[out] I The image on which gamma correction must be applied. + * \param[in] p_mask Boolean that indicates which points must be taken into account (true value) + * or must be ignored (false value). + */ +void gammaCorrectionNonLinearMethod(vpImage &I, const vpImage *p_mask) +{ + (void)p_mask; + const float a = 0.2f; + const float b = 0.3f; + const float c = 0.3f; + const float x_m = 127.5f; + const float alpha = std::atan2(-b, x_m); + const float rho = 0.1f; + unsigned char lut[256]; for (unsigned int i = 0; i < 256; i++) { - lut[i] = vpMath::saturate(pow((double)i / 255.0, inverse_gamma) * 255.0); + float x = static_cast(i); + float phi = M_PIf * x / (2.f * x_m); + float f1 = a * std::cos(phi); + float k = rho * std::sin(4 * M_PIf * x / 255.f); + float f2 = (k + b)*std::cos(alpha) + x * std::sin(alpha); + float r = c * std::abs(x / x_m - 1.f); + float f3 = r * std::cos(3.f * M_PIf * x / 255.f); + float g = f1 + f2 + f3; + float gamma = 1 + g; + float inverse_gamma = 1.f / gamma; + lut[i] = vpMath::saturate(std::pow(static_cast(i) / 255.f, inverse_gamma) * 255.f); } + I.performLut(lut); +} +/** + * \brief This method is an implementation of the article "An adaptive gamma correction for image + * enhancement", Shanto Rahman, Md Mostafijur Rahman, M. Abdullah-Al-Wadud, Golam Dastegir Al-Quaderi and + * Mohammad Shoyaib, EURASIP Journal on Image and Video Processing (2016) + * + * The gamma factor depends of the contrast of the image. The constant depends on the brightness of + * the image. + * + * \param[out] I The image on which gamma correction must be applied. + * \param[in] p_mask Boolean that indicates which points must be taken into account (true value) + * or must be ignored (false value). + */ +void gammaCorrectionClassificationBasedMethod(vpImage &I, const vpImage *p_mask) +{ + unsigned int nbValidPoints = 0; + double mean = I.getMeanValue(p_mask, nbValidPoints); + double stdev = I.getStdev(mean, nbValidPoints, p_mask); + double meanNormalized = mean / 255.; + double stdevNormalized = stdev / 255.; + const float tau = 3.f; + bool isAlreadyHighContrast = (4. * stdevNormalized) > (1./tau); + unsigned char lut[256]; + float gamma = 0.f; + if (isAlreadyHighContrast) { + // Case medium to high contrast image + gamma = std::exp((1.f - (meanNormalized + stdevNormalized))/2.f); + } + else { + // Case low contrast image +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + gamma = -std::log2(stdevNormalized); +#else + gamma = -std::log(stdevNormalized) / std::log(2); +#endif + } + if (meanNormalized < 0.5) { + // Case dark image + float meanPowerGamma = std::pow(meanNormalized, gamma); + for (unsigned int i = 0; i <= 255; i++) { + float iNormalized = static_cast(i)/255.f; + float iPowerGamma = std::pow(iNormalized, gamma); + lut[i] = vpMath::saturate(255.f * (iPowerGamma / (iPowerGamma + (1.f - iPowerGamma) * meanPowerGamma))); + } + } + else { + // Case bright image + for (unsigned int i = 0; i <= 255; i++) { + float iNormalized = static_cast(i)/255.f; + lut[i] = vpMath::saturate(std::pow(iNormalized, gamma) * 255.f); + } + } I.performLut(lut); } -void gammaCorrection(const vpImage &I1, vpImage &I2, double gamma) +/** + * \brief This technique comes from the article "Efficient Contrast Enhancement Using Adaptive + * Gamma Correction With Weighting Distribution" by Shih-Chia Huang, Fan-Chieh Cheng, and Yi-Sheng Chiu, + * IEEE TRANSACTIONS ON IMAGE PROCESSING, VOL. 22, NO. 3, MARCH 2013. + * + * It works well on globally dark images that must be brightened, but overcompensate + * images that are already "bright enough". + * + * \param[out] I The image on which gamma correction must be applied. + * \param[in] p_mask Boolean that indicates which points must be taken into account (true value) + * or must be ignored (false value). + */ +void gammaCorrectionProbabilisticBased(vpImage &I, const vpImage *p_mask) { - I2 = I1; - vp::gammaCorrection(I2, gamma); + const unsigned int nbBins = 256; + vpHistogram histo; + histo.setMask(p_mask); + histo.calculate(I, nbBins); + unsigned int totalNbPoints = histo.getTotal(); + unsigned int minHisto = histo[0]; + unsigned int maxHisto = histo[0]; + for (unsigned int i = 1; i < nbBins; ++i) { + minHisto = std::min(minHisto, histo[i]); + maxHisto = std::max(maxHisto, histo[i]); + } + float pdfMin = static_cast(minHisto) / static_cast(totalNbPoints); + float pdfMax = static_cast(maxHisto) / static_cast(totalNbPoints); + float pdf_w[nbBins]; + float sum_pdf_w = 0.f; + for (unsigned int i = 0; i < nbBins; ++i) { + float pdf = static_cast(histo[i])/static_cast(totalNbPoints); + pdf_w[i] = pdfMax * std::sqrt((pdf - pdfMin)/(pdfMax - pdfMin)); // alpha = 0.5 + sum_pdf_w += pdf_w[i]; + } + unsigned char lut[256]; + float cdf_w = 0; + for (unsigned int i = 0; i <= 255; i++) { + cdf_w += pdf_w[i] / sum_pdf_w; + float gamma = 1.f - cdf_w; + float iNormalized = static_cast(i)/255.f; + lut[i] = vpMath::saturate(std::pow(iNormalized, gamma) * 255.f); + } + I.performLut(lut); +} + +/** + * \brief This technique comes from the article "A Space-Variant Luminance Map based Color Image Enhancement" by Sungmok + * Lee, Homin Kwon, Hagyong Han, Gidong Lee, and Bongsoon Kang, + * IEEE Transactions on Consumer Electronics, Vol. 56, No. 4, November 2010. + * + * \param[out] I The image on which gamma correction must be applied. + * \param[in] p_mask Boolean that indicates which points must be taken into account (true value) + * or must be ignored (false value). + */ +void gammaCorrectionSpatialBased(vpImage &I, const vpImage *p_mask) +{ + unsigned int width = I.getWidth(), height = I.getHeight(); + vpImage I_2, I_4, I_8; + I.subsample(2, 2, I_2); + I.subsample(4, 4, I_4); + I.subsample(8, 8, I_8); + vpImage I_blur, I_2_blur, I_4_blur, I_8_blur; + const bool normalize = true; + vpImageFilter::gaussianBlur(I, I_blur, 3, 0.f, normalize, p_mask); + vpImageFilter::gaussianBlur(I_2, I_2_blur, 3, 0.f, normalize, p_mask); + vpImageFilter::gaussianBlur(I_4, I_4_blur, 3, 0.f, normalize, p_mask); + vpImageFilter::gaussianBlur(I_8, I_8_blur, 3, 0.f, normalize, p_mask); + vpImage L, L_2, L_4, L_8; + vpImageTools::resize(I_blur, L, width, height, vpImageTools::INTERPOLATION_CUBIC); + vpImageTools::resize(I_2_blur, L_2, width, height, vpImageTools::INTERPOLATION_CUBIC); + vpImageTools::resize(I_4_blur, L_4, width, height, vpImageTools::INTERPOLATION_CUBIC); + vpImageTools::resize(I_8_blur, L_8, width, height, vpImageTools::INTERPOLATION_CUBIC); + const float alpha = 0.5f; + unsigned int size = height * width; + float stdev = I.getStdev(p_mask); + float p; + if (stdev <= 40) { + p = 2.f; + } + else if (stdev <= 80) { + p = -0.025f * stdev + 3.f; + } + else { + p = 1.f; + } + + for (unsigned int i = 0; i < size; ++i) { + bool hasToCompute = true; + if (p_mask != nullptr) { + hasToCompute = p_mask->bitmap[i]; + } + if (hasToCompute) { + float svlm = (L.bitmap[i] + L_2.bitmap[i] + L_4.bitmap[i] + L_8.bitmap[i]) / 4.f; // Computation of the space-variant luminance map + float gamma = std::pow(alpha, (128.f - svlm)/128.f); + float iNormalized = static_cast(I.bitmap[i])/255.f; + float o = std::pow(iNormalized, gamma) * 255.f; // Computation of the luminance + float r = svlm / o; + float e = std::pow(r, p); + float s = 255.f * std::pow(o / 255.f, e); + I.bitmap[i] = vpMath::saturate((s * static_cast(I.bitmap[i])) / o); + } + } +} + +/** + * \brief This technique comes from the article "A Space-Variant Luminance Map based Color Image Enhancement" by Sungmok + * Lee, Homin Kwon, Hagyong Han, Gidong Lee, and Bongsoon Kang, + * IEEE Transactions on Consumer Electronics, Vol. 56, No. 4, November 2010. + * + * \param[out] I The image on which gamma correction must be applied. + * \param[in] p_mask Boolean that indicates which points must be taken into account (true value) + * or must be ignored (false value). + */ +void gammaCorrectionSpatialBased(vpImage &I, const vpImage *p_mask) +{ + unsigned int width = I.getWidth(), height = I.getHeight(); + unsigned int size = height * width; + vpImage I_gray(height, width); + for (unsigned int i = 0; i < size; ++i) { + vpRGBa rgb = I.bitmap[i]; + I_gray.bitmap[i] = 0.299 * rgb.R + 0.587 * rgb.G + 0.114 *rgb.B; + } + vpImage I_2, I_4, I_8; + I_gray.subsample(2, 2, I_2); + I_gray.subsample(4, 4, I_4); + I_gray.subsample(8, 8, I_8); + vpImage I_blur, I_2_blur, I_4_blur, I_8_blur; + const bool normalize = true; + vpImageFilter::gaussianBlur(I_gray, I_blur, 3, 0.f, normalize, p_mask); + vpImageFilter::gaussianBlur(I_2, I_2_blur, 3, 0.f, normalize, p_mask); + vpImageFilter::gaussianBlur(I_4, I_4_blur, 3, 0.f, normalize, p_mask); + vpImageFilter::gaussianBlur(I_8, I_8_blur, 3, 0.f, normalize, p_mask); + vpImage L, L_2, L_4, L_8; + vpImageTools::resize(I_blur, L, width, height, vpImageTools::INTERPOLATION_CUBIC); + vpImageTools::resize(I_2_blur, L_2, width, height, vpImageTools::INTERPOLATION_CUBIC); + vpImageTools::resize(I_4_blur, L_4, width, height, vpImageTools::INTERPOLATION_CUBIC); + vpImageTools::resize(I_8_blur, L_8, width, height, vpImageTools::INTERPOLATION_CUBIC); + const float alpha = 0.5f; + + float stdev = I.getStdev(p_mask); + float p; + if (stdev <= 40) { + p = 2.f; + } + else if (stdev <= 80) { + p = -0.025f * stdev + 3.f; + } + else { + p = 1.f; + } + for (unsigned int i = 0; i < size; ++i) { + bool hasToCompute = true; + if (p_mask != nullptr) { + hasToCompute = p_mask->bitmap[i]; + } + if (hasToCompute) { + float svlm = (L.bitmap[i] + L_2.bitmap[i] + L_4.bitmap[i] + L_8.bitmap[i]) / 4.f; // Computation of the space-variant luminance map + float gamma = std::pow(alpha, (128.f - svlm)/128.f); + float iNormalized = static_cast(I_gray.bitmap[i])/255.f; + float o = std::pow(iNormalized, gamma) * 255.f; // Computation of the luminance + float r = svlm / o; + float e = std::pow(r, p); + float s = 255.f * std::pow(o / 255.f, e); + I.bitmap[i].R = vpMath::saturate((s * static_cast(I.bitmap[i].R)) / o); + I.bitmap[i].G = vpMath::saturate((s * static_cast(I.bitmap[i].G)) / o); + I.bitmap[i].B = vpMath::saturate((s * static_cast(I.bitmap[i].B)) / o); + } + } +} } -void gammaCorrection(vpImage &I, double gamma) +void gammaCorrection(vpImage &I, const float &gamma, const vpGammaMethod &method, const vpImage *p_mask) { - double inverse_gamma = 1.0; - if (gamma > 0) { + float inverse_gamma = 1.0; + if ((gamma > 0) && (method == GAMMA_MANUAL)) { inverse_gamma = 1.0 / gamma; + // Construct the look-up table + unsigned char lut[256]; + for (unsigned int i = 0; i < 256; i++) { + lut[i] = vpMath::saturate(std::pow(static_cast(i) / 255.0, inverse_gamma) * 255.0); + } + + I.performLut(lut); + } + else if (method == GAMMA_MANUAL) { + std::stringstream errMsg; + errMsg << "ERROR: gamma correction factor ("; + errMsg << gamma << ") cannot be negative when using a constant user-defined factor." << std::endl; + throw(vpException(vpException::badValue, errMsg.str())); + } + else if (gamma > 0) { + std::stringstream errMsg; + errMsg << "ERROR: asking for automatic gamma correction but setting a user-defined factor (" << gamma << ")." << std::endl; + throw(vpException(vpException::badValue, errMsg.str())); } else { - throw vpException(vpException::badValue, "The gamma value must be positive !"); + if (method == GAMMA_NONLINEAR_BASED) { + gammaCorrectionNonLinearMethod(I, p_mask); + } + else if (method == GAMMA_LOG_BASED) { + gammaCorrectionLogMethod(I, p_mask); + } + else if (method == GAMMA_CLASSIFICATION_BASED) { + gammaCorrectionClassificationBasedMethod(I, p_mask); + } + else if (method == GAMMA_CDF_BASED) { + gammaCorrectionProbabilisticBased(I, p_mask); + } + else if (method == GAMMA_SPATIAL_VARIANT_BASED) { + gammaCorrectionSpatialBased(I, p_mask); + } + else { + std::stringstream errMsg; + errMsg << "Gamma automatic method \"" << vpGammaMethodToString(method) << "\" is not handled." << std::endl; + throw(vpException(vpException::badValue, errMsg.str())); + } } +} - // Construct the look-up table - vpRGBa lut[256]; - for (unsigned int i = 0; i < 256; i++) { - lut[i].R = vpMath::saturate(pow((double)i / 255.0, inverse_gamma) * 255.0); - lut[i].G = vpMath::saturate(pow((double)i / 255.0, inverse_gamma) * 255.0); - lut[i].B = vpMath::saturate(pow((double)i / 255.0, inverse_gamma) * 255.0); - lut[i].A = vpMath::saturate(pow((double)i / 255.0, inverse_gamma) * 255.0); +void gammaCorrection(const vpImage &I1, vpImage &I2, const float &gamma, + const vpGammaMethod &method, const vpImage *p_mask) +{ + I2 = I1; + vp::gammaCorrection(I2, gamma, method, p_mask); +} + +void gammaCorrection(vpImage &I, const float &gamma, const vpGammaColorHandling &colorHandling, + const vpGammaMethod &method, const vpImage *p_mask) +{ + if ((method == GAMMA_SPATIAL_VARIANT_BASED)) { + gammaCorrectionSpatialBased(I, p_mask); } + else { + if (colorHandling == GAMMA_HSV) { + const unsigned int height = I.getHeight(), width = I.getWidth(); + unsigned int size = height * width; + std::vector hue(size); + std::vector saturation(size); + std::vector value(size); - I.performLut(lut); + vpImageConvert::RGBaToHSV((unsigned char *)I.bitmap, &hue.front(), &saturation.front(), &value.front(), size); + vpImage I_hue(&hue.front(), height, width); + vpImage I_saturation(&saturation.front(), height, width); + vpImage I_value(&value.front(), height, width); + + gammaCorrection(I_value, gamma, method, p_mask); + + vpImageConvert::HSVToRGBa(I_hue.bitmap, I_saturation.bitmap, I_value.bitmap, (unsigned char *)I.bitmap, size); + } + else if (colorHandling == GAMMA_RGB) { + vpImage pR, pG, pB, pa; + vpImageConvert::split(I, &pR, &pG, &pB, &pa); + gammaCorrection(pR, gamma, method, p_mask); + gammaCorrection(pG, gamma, method, p_mask); + gammaCorrection(pB, gamma, method, p_mask); + gammaCorrection(pa, gamma, method, p_mask); + vpImageConvert::merge(&pR, &pG, &pB, &pa, I); + } + else { + std::stringstream errMsg; + errMsg << "Gamma color handling mode \"" << vpGammaColorHandlingToString(colorHandling); + errMsg << "\" is not handled." << std::endl; + throw(vpException(vpException::badValue, errMsg.str())); + } + } } -void gammaCorrection(const vpImage &I1, vpImage &I2, double gamma) +void gammaCorrection(const vpImage &I1, vpImage &I2, const float &gamma, + const vpGammaColorHandling &colorHandling, const vpGammaMethod &method, + const vpImage *p_mask) { I2 = I1; - vp::gammaCorrection(I2, gamma); + vp::gammaCorrection(I2, gamma, colorHandling, method, p_mask); } void stretchContrast(vpImage &I) diff --git a/modules/java/misc/imgproc/gen_dict.json b/modules/java/misc/imgproc/gen_dict.json index c7a00a2fc2..e2f14d5906 100644 --- a/modules/java/misc/imgproc/gen_dict.json +++ b/modules/java/misc/imgproc/gen_dict.json @@ -124,6 +124,188 @@ " return;", "}\n" ] + }, + "gammaCorrection" : { + "j_code" : [ + "//", + "// C++: static void fillHoles(vpImage_char I1, vpImage_char I2, float gamma vp_vpGammaMethod method = vp::GAMMA_MANUAL, vpImage_bool * I_mask = 0)", + "//", + "\n", + "//javadoc: Vp::gammaCorrection(I1, I2, gamma)", + "\n", + "public static void gammaCorrection(VpImageUChar I1, VpImageUChar I2, float gamma)", + "{", + " gammaCorrection(I1.nativeObj, I2.nativeObj, gamma);", + " return;", + "}\n", + "//", + "// C++: static void fillHoles(vpImage_char I, float gamma vp_vpGammaMethod method = vp::GAMMA_MANUAL, vpImage_bool * I_mask = 0)", + "//", + "\n", + "//javadoc: Vp::gammaCorrection(I1, gamma)", + "\n", + "public static void gammaCorrection(VpImageUChar I, float gamma)", + "{", + " gammaCorrection(I.nativeObj, gamma);", + " return;", + "}", + "\n" + ], + "jn_code" : [ + "// C++: static void gammaCorrection(vpImage_char I1, vpImage_char I2, float gamma, vpGammaMethod method = vp::GAMMA_MANUAL, vpImage_bool* p_mask = 0)", + "private static native void gammaCorrection(long I1_nativeobj, long I2_nativeobj, float gamma);\n", + "// C++: static void gammaCorrection(vpImage_char I, float gamma, vpGammaMethod method = vp::GAMMA_MANUAL, vpImage_bool* p_mask = 0)", + "private static native void gammaCorrection(long I_nativeobj, float gamma);\n" + ], + "cpp_code" : [ + "//", + "// manual port", + "// C++: static void gammaCorrection(vpImage_char I1, vpImage_char I2, float gamma, vpGammaMethod method = vp::GAMMA_MANUAL, vpImage_bool* p_mask = 0)", + "//", + "//javadoc: Vp::gammaCorrection(I1, I2, gamma)", + "JNIEXPORT void JNICALL Java_org_visp_imgproc_VpImgproc_gammaCorrection_10 (JNIEnv*, jclass, jlong, jlong, jfloat);", + "JNIEXPORT void JNICALL Java_org_visp_imgproc_VpImgproc_gammaCorrection_10 (JNIEnv* env, jclass , jlong I1_nativeObj, jlong I2_nativeObj, jfloat gamma)", + "{", + " static const char method_name[] = \"imgproc::gammaCorrection_10()\";", + " try {", + " LOGD(\"%s\", method_name);", + " vpImage& I1 = *((vpImage*)I1_nativeObj);", + " vpImage& I2 = *((vpImage*)I2_nativeObj);", + " vp::gammaCorrection( I1, I2, (float)gamma, vp::GAMMA_MANUAL, 0 );", + " return;", + " } catch(const std::exception &e) {", + " throwJavaException(env, &e, method_name);", + " } catch (...) {", + " throwJavaException(env, 0, method_name);", + " }", + " return;", + "}\n", + "//", + "// manual port", + "// C++: static void gammaCorrection(vpImage_char I, float gamma, vpGammaMethod method = vp::GAMMA_MANUAL, vpImage_bool* p_mask = 0)", + "//", + "//javadoc: Vp::gammaCorrection(I, gamma)\n", + "JNIEXPORT void JNICALL Java_org_visp_imgproc_VpImgproc_gammaCorrection_13 (JNIEnv*, jclass, jlong, jfloat);\n", + "JNIEXPORT void JNICALL Java_org_visp_imgproc_VpImgproc_gammaCorrection_13 (JNIEnv* env, jclass , jlong I_nativeObj, jfloat gamma)", + "{", + " static const char method_name[] = \"imgproc::gammaCorrection_13()\";", + " try {", + " LOGD(\"%s\", method_name);", + " vpImage& I = *((vpImage*)I_nativeObj);", + " vp::gammaCorrection( I, (float)gamma, vp::GAMMA_MANUAL, 0 );", + " return;", + " } catch(const std::exception &e) {", + " throwJavaException(env, &e, method_name);", + " } catch (...) {", + " throwJavaException(env, 0, method_name);", + " }", + " return;", + "}\n\n" + ] + }, + "vpGammaColorHandlingList" : { + "j_code" : [ + "\n//", + "// manual port", + "// C++: std::string vpGammaColorHandlingList(const std::string &pref = \"<\", const std::string &sep = \" , \", const std::string &suf = \">\")\n", + "//javadoc: Vp::vpGammaColorHandlingList(pref, sep, suf)\n", + "public static String vpGammaColorHandlingList(String pref, String sep, String suf)", + "{", + " return vpGammaColorHandlingList(pref, sep, suf);", + "}" + ], + "jn_code" : [ + "\n// C++: std::string vpGammaColorHandlingList(const std::string &pref = \"<\", const std::string &sep = \" , \", const std::string &suf = \">\")\n" + ], + "cpp_code" : [ + "//", + "// manual port", + "// C++: std::string vpGammaColorHandlingList(const std::string &pref = \"<\", const std::string &sep = \" , \", const std::string &suf = \">\")\n", + "//", + "JNIEXPORT jstring JNICALL Java_org_visp_imgproc_VpImgproc_vpGammaColorHandlingList_10 (JNIEnv*, jclass, jstring);", + "JNIEXPORT jstring JNICALL Java_org_visp_imgproc_VpImgproc_vpGammaColorHandlingList_10 (JNIEnv* env, jclass , jstring pref)", + "{", + " static const char method_name[] = \"imgproc::vpGammaColorHandlingList_10()\";", + " try {", + " LOGD(\"%s\", method_name);", + " const char* utf_pref = env->GetStringUTFChars(pref, 0); string n_pref( utf_pref ? utf_pref : \"\" ); env->ReleaseStringUTFChars(pref, utf_pref);", + " string _retval_ = vp::vpGammaColorHandlingList( n_pref );", + " return env->NewStringUTF(_retval_.c_str());", + " } catch(const std::exception &e) {", + " throwJavaException(env, &e, method_name);", + " } catch (...) {", + " throwJavaException(env, 0, method_name);", + " }", + " return 0;", + "}\n", + "JNIEXPORT jstring JNICALL Java_org_visp_imgproc_VpImgproc_vpGammaColorHandlingList_11 (JNIEnv*, jclass);", + "JNIEXPORT jstring JNICALL Java_org_visp_imgproc_VpImgproc_vpGammaColorHandlingList_11 (JNIEnv* env, jclass )", + "{", + " static const char method_name[] = \"imgproc::vpGammaColorHandlingList_11()\";", + " try {", + " LOGD(\"%s\", method_name);", + " string _retval_ = vp::vpGammaColorHandlingList( );", + " return env->NewStringUTF(_retval_.c_str());", + " } catch(const std::exception &e) {", + " throwJavaException(env, &e, method_name);", + " } catch (...) {", + " throwJavaException(env, 0, method_name);", + " }", + " return 0;", + "}\n" + ] + }, + "vpGammaMethodList" : { + "j_code" : [ + "\n//", + "// manual port", + "// C++: std::string vpGammaMethodList(const std::string &pref = \"<\", const std::string &sep = \" , \", const std::string &suf = \">\")\n", + "//javadoc: Vp::vpGammaMethodList(pref, sep, suf)\n", + "public static String vpGammaMethodList(String pref, String sep, String suf)", + "{", + " return vpGammaMethodList(pref, sep, suf);", + "}" + ], + "jn_code" : [ + "\n// C++: std::string vpGammaMethodList(const std::string &pref = \"<\", const std::string &sep = \" , \", const std::string &suf = \">\")\n" + ], + "cpp_code" : [ + "//", + "// manual port", + "// C++: std::string vpGammaMethodList(const std::string &pref = \"<\", const std::string &sep = \" , \", const std::string &suf = \">\")\n", + "//", + "JNIEXPORT jstring JNICALL Java_org_visp_imgproc_VpImgproc_vpGammaMethodList_10 (JNIEnv*, jclass, jstring);", + "JNIEXPORT jstring JNICALL Java_org_visp_imgproc_VpImgproc_vpGammaMethodList_10 (JNIEnv* env, jclass , jstring pref)", + "{", + " static const char method_name[] = \"imgproc::vpGammaMethodList_10()\";", + " try {", + " LOGD(\"%s\", method_name);", + " const char* utf_pref = env->GetStringUTFChars(pref, 0); string n_pref( utf_pref ? utf_pref : \"\" ); env->ReleaseStringUTFChars(pref, utf_pref);", + " string _retval_ = vp::vpGammaMethodList( n_pref );", + " return env->NewStringUTF(_retval_.c_str());", + " } catch(const std::exception &e) {", + " throwJavaException(env, &e, method_name);", + " } catch (...) {", + " throwJavaException(env, 0, method_name);", + " }", + " return 0;", + "}\n", + "JNIEXPORT jstring JNICALL Java_org_visp_imgproc_VpImgproc_vpGammaMethodList_11 (JNIEnv*, jclass);", + "JNIEXPORT jstring JNICALL Java_org_visp_imgproc_VpImgproc_vpGammaMethodList_11 (JNIEnv* env, jclass )", + "{", + " static const char method_name[] = \"imgproc::vpGammaMethodList_11()\";", + " try {", + " LOGD(\"%s\", method_name);", + " string _retval_ = vp::vpGammaMethodList( );", + " return env->NewStringUTF(_retval_.c_str());", + " } catch(const std::exception &e) {", + " throwJavaException(env, &e, method_name);", + " } catch (...) {", + " throwJavaException(env, 0, method_name);", + " }", + " return 0;", + "}\n" + ] } } }, diff --git a/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp b/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp index d62440741d..90ff929689 100644 --- a/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp +++ b/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp @@ -25,54 +25,82 @@ int main(int argc, const char **argv) std::string input_filename = "Sample_low_brightness.png"; double alpha = 10.0, beta = 50.0; double gamma = 3.5; + vp::vpGammaMethod method = vp::GAMMA_MANUAL; + vp::vpGammaColorHandling colorHandling = vp::GAMMA_HSV; int scale = 240, scaleDiv = 3, level = 0, kernelSize = -1; double dynamic = 3.0; for (int i = 1; i < argc; i++) { if (std::string(argv[i]) == "--input" && i + 1 < argc) { input_filename = std::string(argv[i + 1]); - } else if (std::string(argv[i]) == "--alpha" && i + 1 < argc) { + } + else if (std::string(argv[i]) == "--alpha" && i + 1 < argc) { alpha = atof(argv[i + 1]); - } else if (std::string(argv[i]) == "--beta" && i + 1 < argc) { + } + else if (std::string(argv[i]) == "--beta" && i + 1 < argc) { beta = atof(argv[i + 1]); - } else if (std::string(argv[i]) == "--gamma" && i + 1 < argc) { + } + else if (std::string(argv[i]) == "--gamma" && i + 1 < argc) { gamma = atof(argv[i + 1]); - } else if (std::string(argv[i]) == "--scale" && i + 1 < argc) { + } + else if ((std::string(argv[i]) == "--gamma-color-handling") && ((i + 1) < argc)) { + ++i; + colorHandling = vp::vpGammaColorHandlingFromString(argv[i]); + } + else if ((std::string(argv[i]) == "--gamma-method") && ((i + 1) < argc)) { + ++i; + method = vp::vpGammaMethodFromString(argv[i]); + } + else if (std::string(argv[i]) == "--scale" && i + 1 < argc) { scale = atoi(argv[i + 1]); - } else if (std::string(argv[i]) == "--scaleDiv" && i + 1 < argc) { + } + else if (std::string(argv[i]) == "--scaleDiv" && i + 1 < argc) { scaleDiv = atoi(argv[i + 1]); - } else if (std::string(argv[i]) == "--level" && i + 1 < argc) { + } + else if (std::string(argv[i]) == "--level" && i + 1 < argc) { level = atoi(argv[i + 1]); - } else if (std::string(argv[i]) == "--kernelSize" && i + 1 < argc) { + } + else if (std::string(argv[i]) == "--kernelSize" && i + 1 < argc) { kernelSize = atoi(argv[i + 1]); - } else if (std::string(argv[i]) == "--dynamic" && i + 1 < argc) { + } + else if (std::string(argv[i]) == "--dynamic" && i + 1 < argc) { dynamic = atof(argv[i + 1]); - } else if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") { + } + else if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") { std::cout << "Usage: " << argv[0] - << " [--input ]" - " [--alpha ] [--beta ]" - " [--gamma ]" - " [--scale [--scaleDiv for " - "vp::retinex()]" - " [--level [--kernelSize " - "]" - " [--dynamic ] [--help]" - << std::endl; + << " [--input ]" + " [--alpha ] [--beta ]" + " [--gamma ]" + " [--gamma-color-handling " << vp::vpGammaColorHandlingList() << "]" + " [--gamma-method " << vp::vpGammaMethodList() << "]" + " [--scale [--scaleDiv for " + "vp::retinex()]" + " [--level [--kernelSize " + "]" + " [--dynamic ] [--help]" + << std::endl; return EXIT_SUCCESS; } } vpImage I_color; vpImageIo::read(I_color, input_filename); + vpImage I_gray; + vpImageConvert::convert(I_color, I_gray); vpImage I_color_res(I_color.getHeight(), 2 * I_color.getWidth()); I_color_res.insert(I_color, vpImagePoint()); + vpImage I_gray_res(I_gray.getHeight(), 2 * I_gray.getWidth()); + I_gray_res.insert(I_gray, vpImagePoint()); #ifdef VISP_HAVE_X11 + vpDisplayX d_gray(I_gray_res); vpDisplayX d(I_color_res); #elif defined(VISP_HAVE_GDI) + vpDisplayGDI d_gray(I_gray_res); vpDisplayGDI d(I_color_res); #elif defined(HAVE_OPENCV_HIGHGUI) + vpDisplayOpenCV d_gray(I_gray_res); vpDisplayOpenCV d(I_color_res); #endif @@ -91,9 +119,30 @@ int main(int argc, const char **argv) vpDisplay::getClick(I_color_res); //! [Gamma correction] + if (method != vp::GAMMA_MANUAL) { + // If the user wants to use an automatic method, the gamma factor must be negative. + gamma = -1.; + } + + if (gamma > 0.) { + // If the user wants to set a constant user-defined gamma factor, the method must be set to manual. + method = vp::GAMMA_MANUAL; + } + vpImage I_gray_gamma_correction; + vp::gammaCorrection(I_gray, I_gray_gamma_correction, gamma, method); vpImage I_color_gamma_correction; - vp::gammaCorrection(I_color, I_color_gamma_correction, gamma); + vp::gammaCorrection(I_color, I_color_gamma_correction, gamma, colorHandling, method); //! [Gamma correction] + I_gray_res.insert(I_gray_gamma_correction, vpImagePoint(0, I_gray.getWidth())); + ss.str(""); + ss << "Sample_low_brightness_gray.png"; + vpImageIo::write(I_gray_res, ss.str()); + + vpDisplay::display(I_gray_res); + vpDisplay::displayText(I_gray_res, 20, 20, "Gamma correction on gray image. Click to continue.", vpColor::red); + vpDisplay::flush(I_gray_res); + vpDisplay::getClick(I_gray_res); + I_color_res.insert(I_color_gamma_correction, vpImagePoint(0, I_color.getWidth())); ss.str(""); ss << "Sample_low_brightness_gamma=" << gamma << ".png"; @@ -126,7 +175,7 @@ int main(int argc, const char **argv) ss.str(""); ss << "Sample_low_brightness_scale=" << scale << "_scaleDiv=" << scaleDiv << "_level=" << level - << "_dynamic=" << dynamic << "_kernelSize=" << kernelSize << ".png"; + << "_dynamic=" << dynamic << "_kernelSize=" << kernelSize << ".png"; vpImageIo::write(I_color_res, ss.str()); vpDisplay::display(I_color_res);