From bb37bf82248bd7515764630954a9bceff70e93b9 Mon Sep 17 00:00:00 2001 From: Souriya Trinh Date: Sun, 13 Oct 2024 11:55:40 +0200 Subject: [PATCH 1/5] Update brightness tutorial: - display and save both color and gray results - cycle across the different automatic gamma method --- .../tutorial-brightness-adjustment.cpp | 220 ++++++++++++------ 1 file changed, 146 insertions(+), 74 deletions(-) diff --git a/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp b/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp index f9e63c37c6..efa3b89593 100644 --- a/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp +++ b/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp @@ -4,9 +4,8 @@ #include #include #include -#include -#include -#include +#include +#include #include #if defined(VISP_HAVE_MODULE_IMGPROC) @@ -15,14 +14,49 @@ //! [Include] #endif +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) +#include +#endif + +namespace +{ +#ifdef ENABLE_VISP_NAMESPACE +using namespace VISP_NAMESPACE_NAME; +#endif + +void display(vpImage &I_display, vpImage &I_color_res, const vpImage &I_color_adjust, + vpImage &I_gray_res, vpImage &I_gray_adjust, vpImage &I_gray_display, + const std::string &title, const std::string &filename_color, const std::string &filename_gray, + const std::string &title_2 = "") +{ + I_color_res.insert(I_color_adjust, vpImagePoint(0, I_color_adjust.getWidth())); + I_display.insert(I_color_adjust, vpImagePoint(0, I_color_adjust.getWidth())); + + I_gray_res.insert(I_gray_adjust, vpImagePoint(0, I_gray_adjust.getWidth())); + vpImageConvert::convert(I_gray_adjust, I_gray_display); + I_display.insert(I_gray_display, vpImagePoint(I_color_adjust.getHeight(), I_color_adjust.getWidth())); + + vpImageIo::write(I_color_res, filename_color); + vpImageIo::write(I_gray_res, filename_gray); + + vpDisplay::display(I_display); + vpDisplay::displayText(I_display, 20, 20, title, vpColor::red); + if (!title_2.empty()) { + vpDisplay::displayText(I_display, 40, static_cast(I_color_adjust.getWidth()*0.85), + title_2, vpColor::green); + } + vpDisplay::flush(I_display); + vpDisplay::getClick(I_display); +} +} + int main(int argc, const char **argv) { -//! [Macro defined] -#if defined(VISP_HAVE_MODULE_IMGPROC) && \ - (defined(VISP_HAVE_X11) || defined(VISP_HAVE_GDI) || defined(VISP_HAVE_OPENCV)) && \ - (defined(VISP_HAVE_PNG) || defined(VISP_HAVE_OPENCV)) //! [Macro defined] - //! +#if defined(VISP_HAVE_MODULE_IMGPROC) && defined(VISP_HAVE_DISPLAY) && \ + (defined(VISP_HAVE_PNG) || defined(VISP_HAVE_OPENCV) || defined(VISP_HAVE_STBIMAGE) || defined(VISP_HAVE_SIMDLIB)) && \ + ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) + //! [Macro defined] #ifdef ENABLE_VISP_NAMESPACE using namespace VISP_NAMESPACE_NAME; #endif @@ -34,19 +68,24 @@ int main(int argc, const char **argv) VISP_NAMESPACE_NAME::vpGammaColorHandling colorHandling = VISP_NAMESPACE_NAME::GAMMA_HSV; int scale = 240, scaleDiv = 3, level = 0, kernelSize = -1; double dynamic = 3.0; + int scale_display = 2; for (int i = 1; i < argc; i++) { if (std::string(argv[i]) == "--input" && i + 1 < argc) { - input_filename = std::string(argv[i + 1]); + ++i; + input_filename = std::string(argv[i]); } else if (std::string(argv[i]) == "--alpha" && i + 1 < argc) { - alpha = atof(argv[i + 1]); + ++i; + alpha = atof(argv[i]); } else if (std::string(argv[i]) == "--beta" && i + 1 < argc) { - beta = atof(argv[i + 1]); + ++i; + beta = atof(argv[i]); } else if (std::string(argv[i]) == "--gamma" && i + 1 < argc) { - gamma = atof(argv[i + 1]); + ++i; + gamma = atof(argv[i]); } else if ((std::string(argv[i]) == "--gamma-color-handling") && ((i + 1) < argc)) { ++i; @@ -57,19 +96,28 @@ int main(int argc, const char **argv) method = VISP_NAMESPACE_NAME::vpGammaMethodFromString(argv[i]); } else if (std::string(argv[i]) == "--scale" && i + 1 < argc) { - scale = atoi(argv[i + 1]); + ++i; + scale = atoi(argv[i]); } else if (std::string(argv[i]) == "--scaleDiv" && i + 1 < argc) { - scaleDiv = atoi(argv[i + 1]); + ++i; + scaleDiv = atoi(argv[i]); } else if (std::string(argv[i]) == "--level" && i + 1 < argc) { - level = atoi(argv[i + 1]); + ++i; + level = atoi(argv[i]); } else if (std::string(argv[i]) == "--kernelSize" && i + 1 < argc) { - kernelSize = atoi(argv[i + 1]); + ++i; + kernelSize = atoi(argv[i]); } else if (std::string(argv[i]) == "--dynamic" && i + 1 < argc) { - dynamic = atof(argv[i + 1]); + ++i; + dynamic = atof(argv[i]); + } + else if (std::string(argv[i]) == "--scale-display" && i + 1 < argc) { + ++i; + scale_display = atoi(argv[i]); } else if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") { std::cout << "Usage: " << argv[0] @@ -83,45 +131,51 @@ int main(int argc, const char **argv) "VISP_NAMESPACE_NAME::retinex()]" " [--level [--kernelSize " "]" - " [--dynamic ] [--help]" + " [--dynamic ] " + " [--scale-display ] " + " [--help]" << std::endl; return EXIT_SUCCESS; } } + // Filename without extension to save the results + const std::string input_name = vpIoTools::getNameWE(input_filename); + vpImage I_color; vpImageIo::read(I_color, input_filename); vpImage I_gray; vpImageConvert::convert(I_color, I_gray); + vpImage I_gray_display; + vpImageConvert::convert(I_gray, I_gray_display); + // Side-by-side images 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 + + // Side-by-side display for color (top) and gray (bottom) images + vpImage I_display(2 * I_color.getHeight(), 2 * I_color.getWidth()); + I_display.insert(I_color, vpImagePoint()); + I_display.insert(I_gray_display, vpImagePoint(I_color.getHeight(), 0)); + std::shared_ptr d = vpDisplayFactory::createDisplay(); + d->setDownScalingFactor(static_cast(scale_display)); + d->init(I_display, 10, 10, "Brightness adjustment results"); //! [Brightness contrast adjustment] vpImage I_color_adjust; VISP_NAMESPACE_NAME::adjust(I_color, I_color_adjust, alpha, beta); + vpImage I_gray_adjust; + VISP_NAMESPACE_NAME::adjust(I_gray, I_gray_adjust, alpha, beta); //! [Brightness contrast adjustment] - I_color_res.insert(I_color_adjust, vpImagePoint(0, I_color.getWidth())); - std::stringstream ss; - ss << "Sample_low_brightness_alpha=" << alpha << "_beta=" << beta << ".png"; - vpImageIo::write(I_color_res, ss.str()); - vpDisplay::display(I_color_res); - vpDisplay::displayText(I_color_res, 20, 20, "Brightness and contrast adjustment. Click to continue.", vpColor::red); - vpDisplay::flush(I_color_res); - vpDisplay::getClick(I_color_res); + std::stringstream ss_color; + ss_color << input_name << "_adjust_alpha=" << alpha << "_beta=" << beta << ".png"; + std::stringstream ss_gray; + ss_gray << input_name << "_adjust_alpha=" << alpha << "_beta=" << beta << "_gray.png"; + display(I_display, I_color_res, I_color_adjust, I_gray_res, I_gray_adjust, I_gray_display, + "Brightness and contrast adjustment. Click to continue.", ss_color.str(), ss_gray.str()); //! [Gamma correction] if (method != VISP_NAMESPACE_NAME::GAMMA_MANUAL) { @@ -133,60 +187,78 @@ int main(int argc, const char **argv) // If the user wants to set a constant user-defined gamma factor, the method must be set to manual. method = VISP_NAMESPACE_NAME::GAMMA_MANUAL; } - vpImage I_gray_gamma_correction; - VISP_NAMESPACE_NAME::gammaCorrection(I_gray, I_gray_gamma_correction, static_cast(gamma), method); vpImage I_color_gamma_correction; VISP_NAMESPACE_NAME::gammaCorrection(I_color, I_color_gamma_correction, static_cast(gamma), colorHandling, method); + vpImage I_gray_gamma_correction; + VISP_NAMESPACE_NAME::gammaCorrection(I_gray, I_gray_gamma_correction, static_cast(gamma), 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"; - vpImageIo::write(I_color_res, ss.str()); - - vpDisplay::display(I_color_res); - vpDisplay::displayText(I_color_res, 20, 20, "Gamma correction. Click to continue.", vpColor::red); - vpDisplay::flush(I_color_res); - vpDisplay::getClick(I_color_res); + + ss_color.str(""); + ss_color << input_name << "_gamma=" << gamma << ".png"; + ss_gray.str(""); + ss_gray << input_name << "_gamma=" << gamma << "_gray.png"; + display(I_display, I_color_res, I_color_gamma_correction, I_gray_res, I_gray_gamma_correction, I_gray_display, + "Gamma correction. Click to continue.", ss_color.str(), ss_gray.str()); + + // Display results for the different Gamma correction method + for (int gamma_idx = 0; gamma_idx < VISP_NAMESPACE_NAME::GAMMA_METHOD_COUNT; ++gamma_idx) { + gamma = -1.; + VISP_NAMESPACE_NAME::vpGammaMethod gamma_method = static_cast(gamma_idx); + if (gamma_method == VISP_NAMESPACE_NAME::GAMMA_MANUAL) { + continue; + } + + vpImage I_color_gamma_correction; + VISP_NAMESPACE_NAME::gammaCorrection(I_color, I_color_gamma_correction, static_cast(gamma), colorHandling, + gamma_method); + vpImage I_gray_gamma_correction; + VISP_NAMESPACE_NAME::gammaCorrection(I_gray, I_gray_gamma_correction, static_cast(gamma), gamma_method); + + const std::string gamma_name = VISP_NAMESPACE_NAME::vpGammaMethodToString(gamma_method); + ss_color.str(""); + ss_color << input_name << "_" << gamma_name << ".png"; + ss_gray.str(""); + ss_gray << input_name << "_" << gamma_name << "_gray.png"; + display(I_display, I_color_res, I_color_gamma_correction, I_gray_res, I_gray_gamma_correction, I_gray_display, + "Gamma correction. Click to continue.", ss_color.str(), ss_gray.str(), gamma_name); + } //! [Histogram equalization] vpImage I_color_equalize_histogram; VISP_NAMESPACE_NAME::equalizeHistogram(I_color, I_color_equalize_histogram); + vpImage I_gray_equalize_histogram; + VISP_NAMESPACE_NAME::equalizeHistogram(I_gray, I_gray_equalize_histogram); //! [Histogram equalization] - I_color_res.insert(I_color_equalize_histogram, vpImagePoint(0, I_color.getWidth())); - ss.str(""); - ss << "Sample_low_brightness_eqHist.png"; - vpImageIo::write(I_color_res, ss.str()); - vpDisplay::display(I_color_res); - vpDisplay::displayText(I_color_res, 20, 20, "Histogram equalization. Click to continue.", vpColor::red); - vpDisplay::flush(I_color_res); - vpDisplay::getClick(I_color_res); + ss_color.str(""); + ss_color << input_name << "_eqHist.png"; + ss_gray.str(""); + ss_gray << input_name << "_eqHist_gray.png"; + display(I_display, I_color_res, I_color_equalize_histogram, I_gray_res, I_gray_equalize_histogram, I_gray_display, + "Histogram equalization. Click to continue.", ss_color.str(), ss_gray.str()); //! [Retinex] vpImage I_color_retinex; VISP_NAMESPACE_NAME::retinex(I_color, I_color_retinex, scale, scaleDiv, level, dynamic, kernelSize); + // Retinex uses color image as input + // Convert gray image into RGBa format for quick test + vpImage I_gray_color; + vpImageConvert::convert(I_gray, I_gray_color); + vpImage I_gray_color_retinex; + VISP_NAMESPACE_NAME::retinex(I_gray_color, I_gray_color_retinex, scale, scaleDiv, level, dynamic, kernelSize); + // Convert back to gray + vpImage I_gray_retinex; + vpImageConvert::convert(I_gray_color_retinex, I_gray_retinex); //! [Retinex] - I_color_res.insert(I_color_retinex, vpImagePoint(0, I_color.getWidth())); - ss.str(""); - ss << "Sample_low_brightness_scale=" << scale << "_scaleDiv=" << scaleDiv << "_level=" << level + ss_color.str(""); + ss_color << input_name << "_Retinex_scale=" << scale << "_scaleDiv=" << scaleDiv << "_level=" << level << "_dynamic=" << dynamic << "_kernelSize=" << kernelSize << ".png"; - vpImageIo::write(I_color_res, ss.str()); - - vpDisplay::display(I_color_res); - vpDisplay::displayText(I_color_res, 20, 20, "Retinex. Click to quit.", vpColor::red); - vpDisplay::flush(I_color_res); - vpDisplay::getClick(I_color_res); + ss_gray.str(""); + ss_gray << input_name << "_Retinex_scale=" << scale << "_scaleDiv=" << scaleDiv << "_level=" << level + << "_dynamic=" << dynamic << "_kernelSize=" << kernelSize << "_gray.png"; + display(I_display, I_color_res, I_color_retinex, I_gray_res, I_gray_retinex, I_gray_display, + "Retinex. Click to quit.", ss_color.str(), ss_gray.str()); #else (void)argc; (void)argv; From 6db51988ed4071a729c15f26b4d64019630ee6fc Mon Sep 17 00:00:00 2001 From: Souriya Trinh Date: Fri, 18 Oct 2024 22:05:48 +0200 Subject: [PATCH 2/5] Add sample code to compare the results across the different auto Gamma methods. --- tutorial/imgproc/brightness/CMakeLists.txt | 4 +- .../tutorial-brightness-adjustment.cpp | 20 +- .../tutorial-compare-auto-gamma.cpp | 266 ++++++++++++++++++ 3 files changed, 276 insertions(+), 14 deletions(-) create mode 100755 tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp diff --git a/tutorial/imgproc/brightness/CMakeLists.txt b/tutorial/imgproc/brightness/CMakeLists.txt index 625ad96907..283980c0f0 100644 --- a/tutorial/imgproc/brightness/CMakeLists.txt +++ b/tutorial/imgproc/brightness/CMakeLists.txt @@ -6,7 +6,9 @@ find_package(VISP REQUIRED visp_core visp_io visp_gui visp_imgproc) # set the list of source files set(tutorial_cpp - tutorial-brightness-adjustment.cpp) + tutorial-brightness-adjustment.cpp + tutorial-compare-auto-gamma.cpp +) list(APPEND tutorial_data "${CMAKE_CURRENT_SOURCE_DIR}/Sample_low_brightness.png") diff --git a/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp b/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp index efa3b89593..1645cef9ec 100644 --- a/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp +++ b/tutorial/imgproc/brightness/tutorial-brightness-adjustment.cpp @@ -18,12 +18,12 @@ #include #endif -namespace -{ #ifdef ENABLE_VISP_NAMESPACE using namespace VISP_NAMESPACE_NAME; #endif +namespace +{ void display(vpImage &I_display, vpImage &I_color_res, const vpImage &I_color_adjust, vpImage &I_gray_res, vpImage &I_gray_adjust, vpImage &I_gray_display, const std::string &title, const std::string &filename_color, const std::string &filename_gray, @@ -57,9 +57,6 @@ int main(int argc, const char **argv) (defined(VISP_HAVE_PNG) || defined(VISP_HAVE_OPENCV) || defined(VISP_HAVE_STBIMAGE) || defined(VISP_HAVE_SIMDLIB)) && \ ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) //! [Macro defined] -#ifdef ENABLE_VISP_NAMESPACE - using namespace VISP_NAMESPACE_NAME; -#endif std::string input_filename = "Sample_low_brightness.png"; double alpha = 10.0, beta = 50.0; @@ -122,16 +119,13 @@ int main(int argc, const char **argv) else if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") { std::cout << "Usage: " << argv[0] << " [--input ]" - " [--alpha ] [--beta ]" - " [--gamma ]" + " [--alpha ] [--beta ]" + " [--gamma ]" " [--gamma-color-handling " << VISP_NAMESPACE_NAME::vpGammaColorHandlingList() << "]" " [--gamma-method " << VISP_NAMESPACE_NAME::vpGammaMethodList() << "]" - " [--scale [--scaleDiv for " - "VISP_NAMESPACE_NAME::retinex()]" - " [--level [--kernelSize " - "]" - " [--dynamic ] " + " [--scale [--scaleDiv for retinex()]" + " [--level [--kernelSize ]" + " [--dynamic ] " " [--scale-display ] " " [--help]" << std::endl; diff --git a/tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp b/tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp new file mode 100755 index 0000000000..33911b649d --- /dev/null +++ b/tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp @@ -0,0 +1,266 @@ +//! \example tutorial-compare-auto-gamma + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// VISP_HAVE_SIMDLIB is required for INTERPOLATION_AREA +#if defined(VISP_HAVE_MODULE_IMGPROC) && defined(VISP_HAVE_SIMDLIB) && \ + ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) +#include +#include + +#ifdef ENABLE_VISP_NAMESPACE +using namespace VISP_NAMESPACE_NAME; +#endif + +namespace +{ +template +void computeMeanMaxStdev(const vpImage &I, float &mean, float &max, float &stdev) +{ + max = std::numeric_limits::epsilon(); + mean = 0.; + stdev = 0.; + unsigned int nbRows = I.getRows(); + unsigned int nbCols = I.getCols(); + float scale = 1.f / (static_cast(nbRows) * static_cast(nbCols)); + for (unsigned int r = 0; r < nbRows; r++) { + for (unsigned int c = 0; c < nbCols; c++) { + mean += I[r][c]; + max = std::max(max, static_cast(I[r][c])); + } + } + mean *= scale; + for (unsigned int r = 0; r < nbRows; r++) { + for (unsigned int c = 0; c < nbCols; c++) { + stdev += (I[r][c] - mean) * (I[r][c] - mean); + } + } + stdev *= scale; + stdev = std::sqrt(stdev); +} + +void computeCanny(const vpImage &I, vpCannyEdgeDetection &cannyDetector, int gaussianKernelSize, + float gaussianStdev, int apertureSize, vpImageFilter::vpCannyFilteringAndGradientType filteringType, + vpImage &dIxy_uchar, vpImage &I_canny_visp) +{ + vpImage dIx, dIy, dIxy(I.getHeight(), I.getWidth()); + vpImageFilter::computePartialDerivatives(I, dIx, dIy, true, true, true, gaussianKernelSize, gaussianStdev, + apertureSize, filteringType); + + for (unsigned int i = 0; i < dIx.getHeight(); i++) { + for (unsigned int j = 0; j < dIx.getWidth(); j++) { + dIxy[i][j] = std::sqrt(dIx[i][j]*dIx[i][j] + dIy[i][j]*dIy[i][j]); + } + } + + float mean, max, stdev; + computeMeanMaxStdev(dIxy, mean, max, stdev); + vpImageConvert::convert(dIx, dIxy_uchar); + + // Set the gradients of the vpCannyEdgeDetection + cannyDetector.setGradients(dIx, dIy); + + I_canny_visp = cannyDetector.detect(I); +} +} // namespace + +int main(int argc, const char **argv) +{ + std::string input = "Sample_low_brightness.png"; + std::string output = "Results"; + int gaussianKernelSize = 3; + float gaussianStdev = 1.; + int apertureSize = 3; + bool half = false; + vpImageFilter::vpCannyFilteringAndGradientType filteringType = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING; + VISP_NAMESPACE_NAME::vpGammaColorHandling gamma_colorspace = VISP_NAMESPACE_NAME::GAMMA_HSV; + bool jpeg = false; + + for (int i = 1; i < argc; i++) { + if (std::string(argv[i]) == "--input" && i + 1 < argc) { + ++i; + input = std::string(argv[i]); + } + else if (std::string(argv[i]) == "--half") { + half = true; + } + else if (std::string(argv[i]) == "--gaussian-kernel-size" && i + 1 < argc) { + ++i; + gaussianKernelSize = std::atoi(argv[i]); + } + else if (std::string(argv[i]) == "--gaussian-std" && i + 1 < argc) { + ++i; + gaussianStdev = std::atof(argv[i]); + } + else if (std::string(argv[i]) == "--aperture-size" && i + 1 < argc) { + ++i; + apertureSize = std::atoi(argv[i]); + } + else if (std::string(argv[i]) == "--canny-filtering-type" && i + 1 < argc) { + ++i; + int type = std::atoi(argv[i]); + if (type == 1) { + filteringType = vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING; + } + } + else if (std::string(argv[i]) == "--gamma-rgb") { + gamma_colorspace = VISP_NAMESPACE_NAME::GAMMA_RGB; + } + else if (std::string(argv[i]) == "--jpeg") { + jpeg = true; + } + else if (std::string(argv[i]) == "--output" && i + 1 < argc) { + ++i; + output = std::string(argv[i]); + } + else { + std::cout << "Usage: " << argv[0] + << " [--input ]" + " [--half (use half image resolution)]" + " [--gaussian-kernel-size ]" + " [--gaussian-std ]" + " [--aperture-size ]" + " [--canny-filtering-type <0=CANNY_GBLUR_SOBEL_FILTERING, 1=CANNY_GBLUR_SCHARR_FILTERING>]" + " [--gamma-rgb (RGB colorspace, else HSV]" + " [--jpeg (save in jpeg, otherwise png]" + " [--output (to save results)]" + << std::endl; + return EXIT_SUCCESS; + } + } + + std::cout << "Input: " << input << std::endl; + std::cout << "Process on half image resolution? " << half << std::endl; + std::cout << "Gaussian kernel size: " << gaussianKernelSize << std::endl; + std::cout << "Gaussian standard deviation: " << gaussianStdev << std::endl; + std::cout << "Aperture size: " << apertureSize << std::endl; + std::cout << "Canny filtering type: " << filteringType << std::endl; + std::cout << "RGB colorspace? " << (gamma_colorspace == VISP_NAMESPACE_NAME::GAMMA_RGB) << std::endl; + std::cout << "Save in jpeg? " << jpeg << std::endl; + std::cout << "Output result folder: " << output << std::endl; + + // Canny parameters + float lowerThresh = -1.; + float upperThresh = -1.; + float lowerThreshRatio = 0.6f; + float upperThreshRatio = 0.8f; + vpCannyEdgeDetection cannyDetector(gaussianKernelSize, gaussianStdev, apertureSize, + lowerThresh, upperThresh, lowerThreshRatio, upperThreshRatio, + filteringType); + + bool single_image = vpIoTools::checkFilename(input); + vpVideoReader reader; + vpImage I_color_ori, I_color; + if (single_image) { + vpImageIo::read(I_color_ori, input); + } + else { + reader.setFileName(input); + reader.open(I_color_ori); + } + if (half) { + vpImageTools::resize(I_color_ori, I_color, I_color_ori.getWidth()/2, I_color_ori.getHeight()/2, + vpImageTools::INTERPOLATION_AREA); + } + else { + I_color = I_color_ori; + } + + vpIoTools::makeDirectory(output); + + vpImage I_color_gamma_correction, I_res_stack; + vpImage I_gray_gamma_correction, dIxy_uchar, I_canny_visp; + vpImage dIxy_uchar_color, I_canny_visp_color; + vpFont font(32); + bool read_single_image = false; + while (!read_single_image && (single_image || !reader.end())) { + if (!single_image) { + reader.acquire(I_color_ori); + } + if (half) { + vpImageTools::resize(I_color_ori, I_color, I_color_ori.getWidth()/2, I_color_ori.getHeight()/2, + vpImageTools::INTERPOLATION_AREA); + } + else { + I_color = I_color_ori; + } + + const int nb_methods = VISP_NAMESPACE_NAME::GAMMA_METHOD_COUNT - 1; // all except GAMMA_MANUAL + I_res_stack.init(nb_methods*I_color.getHeight(), 4*I_color.getWidth()); + dIxy_uchar.init(I_color.getHeight(), I_color.getWidth()); + I_canny_visp.init(I_color.getHeight(), I_color.getWidth()); + + // Output results + int offset_text_start_y = 25; + int text_h = 40; + int offset_idx = 0; + double start_time = 0, end_time = 0; + char buffer[FILENAME_MAX]; + + for (int gamma_idx = 1; gamma_idx < VISP_NAMESPACE_NAME::GAMMA_METHOD_COUNT; ++gamma_idx, offset_idx++) { + VISP_NAMESPACE_NAME::vpGammaMethod gamma_method = static_cast(gamma_idx); + if (gamma_method == VISP_NAMESPACE_NAME::GAMMA_MANUAL) { + continue; + } + + const double gamma = -1; + start_time = vpTime::measureTimeMs(); + VISP_NAMESPACE_NAME::gammaCorrection(I_color, I_color_gamma_correction, static_cast(gamma), + gamma_colorspace, gamma_method); + end_time = vpTime::measureTimeMs(); + + vpImageConvert::convert(I_color_gamma_correction, I_gray_gamma_correction); + computeCanny(I_gray_gamma_correction, cannyDetector, gaussianKernelSize, gaussianStdev, apertureSize, + filteringType, dIxy_uchar, I_canny_visp); + vpImageConvert::convert(dIxy_uchar, dIxy_uchar_color); + vpImageConvert::convert(I_canny_visp, I_canny_visp_color); + I_res_stack.insert(I_color, vpImagePoint(offset_idx*I_color.getHeight(), 0)); + I_res_stack.insert(I_color_gamma_correction, vpImagePoint(offset_idx*I_color.getHeight(), I_color.getWidth())); + I_res_stack.insert(I_canny_visp_color, vpImagePoint(offset_idx*I_color.getHeight(), 2*I_color.getWidth())); + I_res_stack.insert(dIxy_uchar_color, vpImagePoint(offset_idx*I_color.getHeight(), 3*I_color.getWidth())); + std::ostringstream oss; + oss << VISP_NAMESPACE_NAME::vpGammaMethodToString(gamma_method) << " (%.2f ms)"; + snprintf(buffer, FILENAME_MAX, oss.str().c_str(), (end_time-start_time)); + font.drawText(I_res_stack, buffer, vpImagePoint(offset_idx*I_color.getHeight() + offset_text_start_y, + 0.35*I_res_stack.getWidth()), vpColor::red); + snprintf(buffer, FILENAME_MAX, "Canny mean: (%.2f)", I_canny_visp.getMeanValue()); + font.drawText(I_res_stack, buffer, vpImagePoint(offset_idx*I_color.getHeight() + offset_text_start_y+text_h, + 0.35*I_res_stack.getWidth()), vpColor::red); + } + + if (!output.empty()) { + std::stringstream output_filename; + const std::string extension = jpeg ? ".jpeg" : ".png"; + if (single_image) { + output_filename << vpIoTools::createFilePath(output, vpIoTools::getNameWE(input)) << extension; + } + else { + output_filename << vpIoTools::createFilePath(output, vpIoTools::getNameWE(reader.getFrameName())) << extension; + } + std::cout << "Write result to: " << output_filename.str() << std::endl; + vpImageIo::write(I_res_stack, output_filename.str()); + } + + if (single_image) { + read_single_image = true; + } + } + + return EXIT_SUCCESS; +} +#else +int main() +{ + std::cerr << "C++11 is required." << std::endl; + return EXIT_SUCCESS; +} +#endif From 0533dbfedafdec012d08de41a740ce179dfedacd Mon Sep 17 00:00:00 2001 From: Souriya Trinh Date: Fri, 18 Oct 2024 23:35:11 +0200 Subject: [PATCH 3/5] When testing on multiple images, looks like inverse gamma is not needed. --- modules/imgproc/src/vpImgproc.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/imgproc/src/vpImgproc.cpp b/modules/imgproc/src/vpImgproc.cpp index f6f092d0eb..8e74f2ca23 100644 --- a/modules/imgproc/src/vpImgproc.cpp +++ b/modules/imgproc/src/vpImgproc.cpp @@ -329,13 +329,12 @@ void gammaCorrectionLogMethod(vpImage &I, const vpImage *p_ unsigned char inputRange = inputMax - inputMin; float gamma_computed = static_cast((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); + lut[i] = vpMath::saturate(std::pow(static_cast(i - inputMin) / inputRangeAsFloat, gamma_computed) * 255.f); } I.performLut(lut); From d1f9401d8707fab535a76356d16add0b89e7067d Mon Sep 17 00:00:00 2001 From: Souriya Trinh Date: Sun, 27 Oct 2024 22:37:21 +0100 Subject: [PATCH 4/5] Add entropy measure and some computation time stats. --- .../tutorial-compare-auto-gamma.cpp | 75 ++++++++++++++++--- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp b/tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp index 33911b649d..b9d9c98055 100755 --- a/tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp +++ b/tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp @@ -71,6 +71,30 @@ void computeCanny(const vpImage &I, vpCannyEdgeDetection &cannyDe I_canny_visp = cannyDetector.detect(I); } + +double computeImageEntropy(const vpImage &I) +{ + // https://github.com/dengyueyun666/Image-Contrast-Enhancement/blob/cd2b1eb5bf6396e2fc3b94cd27f73933d5467147/src/Ying_2017_CAIP.cpp#L186-L207 + std::vector hist(256, 0); + for (unsigned int i = 0; i < I.getHeight(); i++) { + for (unsigned int j = 0; j < I.getWidth(); j++) { + int bin = I[i][j]; + hist[bin]++; + } + } + + double N = I.getSize(); + double cost = 0; + for (size_t i = 0; i < hist.size(); i++) { + if (hist[i] == 0) { + continue; + } + double p = hist[i] / N; + cost += -p * std::log2(p); + } + + return cost; +} } // namespace int main(int argc, const char **argv) @@ -78,12 +102,11 @@ int main(int argc, const char **argv) std::string input = "Sample_low_brightness.png"; std::string output = "Results"; int gaussianKernelSize = 3; - float gaussianStdev = 1.; + float gaussianStdev = 1.0f; int apertureSize = 3; bool half = false; vpImageFilter::vpCannyFilteringAndGradientType filteringType = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING; VISP_NAMESPACE_NAME::vpGammaColorHandling gamma_colorspace = VISP_NAMESPACE_NAME::GAMMA_HSV; - bool jpeg = false; for (int i = 1; i < argc; i++) { if (std::string(argv[i]) == "--input" && i + 1 < argc) { @@ -115,9 +138,6 @@ int main(int argc, const char **argv) else if (std::string(argv[i]) == "--gamma-rgb") { gamma_colorspace = VISP_NAMESPACE_NAME::GAMMA_RGB; } - else if (std::string(argv[i]) == "--jpeg") { - jpeg = true; - } else if (std::string(argv[i]) == "--output" && i + 1 < argc) { ++i; output = std::string(argv[i]); @@ -131,7 +151,6 @@ int main(int argc, const char **argv) " [--aperture-size ]" " [--canny-filtering-type <0=CANNY_GBLUR_SOBEL_FILTERING, 1=CANNY_GBLUR_SCHARR_FILTERING>]" " [--gamma-rgb (RGB colorspace, else HSV]" - " [--jpeg (save in jpeg, otherwise png]" " [--output (to save results)]" << std::endl; return EXIT_SUCCESS; @@ -145,7 +164,6 @@ int main(int argc, const char **argv) std::cout << "Aperture size: " << apertureSize << std::endl; std::cout << "Canny filtering type: " << filteringType << std::endl; std::cout << "RGB colorspace? " << (gamma_colorspace == VISP_NAMESPACE_NAME::GAMMA_RGB) << std::endl; - std::cout << "Save in jpeg? " << jpeg << std::endl; std::cout << "Output result folder: " << output << std::endl; // Canny parameters @@ -177,8 +195,12 @@ int main(int argc, const char **argv) vpIoTools::makeDirectory(output); + const int nb_methods = VISP_NAMESPACE_NAME::GAMMA_METHOD_COUNT - 1; // all except GAMMA_MANUAL + std::vector> computation_times(nb_methods); + int nb_images = 0; + vpImage I_color_gamma_correction, I_res_stack; - vpImage I_gray_gamma_correction, dIxy_uchar, I_canny_visp; + vpImage I_gray, I_gray_gamma_correction, dIxy_uchar, I_canny_visp; vpImage dIxy_uchar_color, I_canny_visp_color; vpFont font(32); bool read_single_image = false; @@ -193,6 +215,7 @@ int main(int argc, const char **argv) else { I_color = I_color_ori; } + nb_images++; const int nb_methods = VISP_NAMESPACE_NAME::GAMMA_METHOD_COUNT - 1; // all except GAMMA_MANUAL I_res_stack.init(nb_methods*I_color.getHeight(), 4*I_color.getWidth()); @@ -203,9 +226,14 @@ int main(int argc, const char **argv) int offset_text_start_y = 25; int text_h = 40; int offset_idx = 0; + double offset_text1 = 0.01; + double offset_text2 = 0.26; double start_time = 0, end_time = 0; char buffer[FILENAME_MAX]; + vpImageConvert::convert(I_color, I_gray); + const double img_ori_entropy = computeImageEntropy(I_gray); + for (int gamma_idx = 1; gamma_idx < VISP_NAMESPACE_NAME::GAMMA_METHOD_COUNT; ++gamma_idx, offset_idx++) { VISP_NAMESPACE_NAME::vpGammaMethod gamma_method = static_cast(gamma_idx); if (gamma_method == VISP_NAMESPACE_NAME::GAMMA_MANUAL) { @@ -217,8 +245,12 @@ int main(int argc, const char **argv) VISP_NAMESPACE_NAME::gammaCorrection(I_color, I_color_gamma_correction, static_cast(gamma), gamma_colorspace, gamma_method); end_time = vpTime::measureTimeMs(); + std::cout << "Computation time (" << VISP_NAMESPACE_NAME::vpGammaMethodToString(gamma_method) + << "): " << (end_time-start_time) << " ms" << std::endl; + computation_times[offset_idx].push_back(end_time-start_time); vpImageConvert::convert(I_color_gamma_correction, I_gray_gamma_correction); + const double img_corrected_entropy = computeImageEntropy(I_gray_gamma_correction); computeCanny(I_gray_gamma_correction, cannyDetector, gaussianKernelSize, gaussianStdev, apertureSize, filteringType, dIxy_uchar, I_canny_visp); vpImageConvert::convert(dIxy_uchar, dIxy_uchar_color); @@ -227,19 +259,27 @@ int main(int argc, const char **argv) I_res_stack.insert(I_color_gamma_correction, vpImagePoint(offset_idx*I_color.getHeight(), I_color.getWidth())); I_res_stack.insert(I_canny_visp_color, vpImagePoint(offset_idx*I_color.getHeight(), 2*I_color.getWidth())); I_res_stack.insert(dIxy_uchar_color, vpImagePoint(offset_idx*I_color.getHeight(), 3*I_color.getWidth())); + // Entropy original + snprintf(buffer, FILENAME_MAX, "Entropy: %.4f", img_ori_entropy); + font.drawText(I_res_stack, buffer, vpImagePoint(offset_idx*I_color.getHeight() + offset_text_start_y, offset_text1*I_res_stack.getWidth()), vpColor::red); + // Computation time std::ostringstream oss; oss << VISP_NAMESPACE_NAME::vpGammaMethodToString(gamma_method) << " (%.2f ms)"; snprintf(buffer, FILENAME_MAX, oss.str().c_str(), (end_time-start_time)); font.drawText(I_res_stack, buffer, vpImagePoint(offset_idx*I_color.getHeight() + offset_text_start_y, - 0.35*I_res_stack.getWidth()), vpColor::red); + offset_text2*I_res_stack.getWidth()), vpColor::red); + // Canny snprintf(buffer, FILENAME_MAX, "Canny mean: (%.2f)", I_canny_visp.getMeanValue()); font.drawText(I_res_stack, buffer, vpImagePoint(offset_idx*I_color.getHeight() + offset_text_start_y+text_h, - 0.35*I_res_stack.getWidth()), vpColor::red); + offset_text2*I_res_stack.getWidth()), vpColor::red); + // Entropy + snprintf(buffer, FILENAME_MAX, "Entropy: %.4f", img_corrected_entropy); + font.drawText(I_res_stack, buffer, vpImagePoint(offset_idx*I_color.getHeight() + offset_text_start_y+2*text_h, offset_text2*I_res_stack.getWidth()), vpColor::red); } if (!output.empty()) { std::stringstream output_filename; - const std::string extension = jpeg ? ".jpeg" : ".png"; + const std::string extension = ".jpeg"; if (single_image) { output_filename << vpIoTools::createFilePath(output, vpIoTools::getNameWE(input)) << extension; } @@ -255,6 +295,19 @@ int main(int argc, const char **argv) } } + std::cout << "\nStats:" << std::endl; + std::cout << "Nb images: " << nb_images << std::endl; + + for (int gamma_idx = 1; gamma_idx < VISP_NAMESPACE_NAME::GAMMA_METHOD_COUNT; ++gamma_idx) { + VISP_NAMESPACE_NAME::vpGammaMethod gamma_method = static_cast(gamma_idx); + if (gamma_method == VISP_NAMESPACE_NAME::GAMMA_MANUAL) { + continue; + } + std::cout << VISP_NAMESPACE_NAME::vpGammaMethodToString(gamma_method) << ": mean=" + << vpMath::getMean(computation_times[gamma_idx+1]) << " ms ; median=" + << vpMath::getMedian(computation_times[gamma_idx+1]) << " ms" << std::endl; + } + return EXIT_SUCCESS; } #else From db5850666d6961d2f274a1b8175c2f444cff27a5 Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Mon, 4 Nov 2024 10:07:36 +0100 Subject: [PATCH 5/5] Fix segfault when printings statistics. The following segfault was detected on Apple M1 Pro, while there is no segfault on CI! There was an out of range memory access to computation_times vector. % ./tutorial-compare-auto-gamma --input Sample_low_brightness.png --output Results/cube Input: Sample_low_brightness.png Process on half image resolution? 0 Gaussian kernel size: 3 Gaussian standard deviation: 1 Aperture size: 3 Canny filtering type: 0 RGB colorspace? 0 Output result folder: Results/cube Computation time (gamma_log): 2.30396 ms Computation time (gamma_nonlinear): 1.27197 ms Computation time (gamma_cdf): 2.1228 ms Computation time (gamma_classification): 2.62817 ms Computation time (gamma_spatial_variant): 19.5791 ms Write result to: Results/cube/Sample_low_brightness.jpeg Stats: Nb images: 1 gamma_log: mean=2.1228 ms ; median=2.1228 ms gamma_nonlinear: mean=2.62817 ms ; median=2.62817 ms gamma_cdf: mean=19.5791 ms ; median=19.5791 ms libc++abi: terminating due to uncaught exception of type vpException: Empty vector ! --- .../imgproc/brightness/tutorial-compare-auto-gamma.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp b/tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp index b9d9c98055..f25acdc3e8 100755 --- a/tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp +++ b/tutorial/imgproc/brightness/tutorial-compare-auto-gamma.cpp @@ -49,8 +49,8 @@ void computeMeanMaxStdev(const vpImage &I, float &mean, float &max, float &st } void computeCanny(const vpImage &I, vpCannyEdgeDetection &cannyDetector, int gaussianKernelSize, - float gaussianStdev, int apertureSize, vpImageFilter::vpCannyFilteringAndGradientType filteringType, - vpImage &dIxy_uchar, vpImage &I_canny_visp) + float gaussianStdev, int apertureSize, vpImageFilter::vpCannyFilteringAndGradientType filteringType, + vpImage &dIxy_uchar, vpImage &I_canny_visp) { vpImage dIx, dIy, dIxy(I.getHeight(), I.getWidth()); vpImageFilter::computePartialDerivatives(I, dIx, dIy, true, true, true, gaussianKernelSize, gaussianStdev, @@ -304,8 +304,8 @@ int main(int argc, const char **argv) continue; } std::cout << VISP_NAMESPACE_NAME::vpGammaMethodToString(gamma_method) << ": mean=" - << vpMath::getMean(computation_times[gamma_idx+1]) << " ms ; median=" - << vpMath::getMedian(computation_times[gamma_idx+1]) << " ms" << std::endl; + << vpMath::getMean(computation_times[gamma_idx-1]) << " ms ; median=" + << vpMath::getMedian(computation_times[gamma_idx-1]) << " ms" << std::endl; } return EXIT_SUCCESS;