Visual Servoing Platform  version 3.6.1 under development (2025-02-01)
tutorial-compare-auto-gamma.cpp
#include <iostream>
#include <visp3/core/vpConfig.h>
#include <visp3/core/vpImage.h>
#include <visp3/core/vpIoTools.h>
#include <visp3/core/vpImageTools.h>
#include <visp3/core/vpCannyEdgeDetection.h>
#include <visp3/core/vpImageFilter.h>
#include <visp3/core/vpFont.h>
#include <visp3/io/vpImageIo.h>
#include <visp3/io/vpVideoReader.h>
// 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 <visp3/imgproc/vpImgproc.h>
#include <memory>
#ifdef ENABLE_VISP_NAMESPACE
using namespace VISP_NAMESPACE_NAME;
#endif
namespace
{
template <class T>
void computeMeanMaxStdev(const vpImage<T> &I, float &mean, float &max, float &stdev)
{
max = std::numeric_limits<float>::epsilon();
mean = 0.;
stdev = 0.;
unsigned int nbRows = I.getRows();
unsigned int nbCols = I.getCols();
float scale = 1.f / (static_cast<float>(nbRows) * static_cast<float>(nbCols));
for (unsigned int r = 0; r < nbRows; r++) {
for (unsigned int c = 0; c < nbCols; c++) {
mean += I[r][c];
max = std::max<float>(max, static_cast<float>(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<unsigned char> &I, vpCannyEdgeDetection &cannyDetector, int gaussianKernelSize,
float gaussianStdev, int apertureSize, vpImageFilter::vpCannyFilteringAndGradientType filteringType,
vpImage<unsigned char> &dIxy_uchar, vpImage<unsigned char> &I_canny_visp)
{
vpImage<float> 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);
}
double computeImageEntropy(const vpImage<unsigned char> &I)
{
// https://github.com/dengyueyun666/Image-Contrast-Enhancement/blob/cd2b1eb5bf6396e2fc3b94cd27f73933d5467147/src/Ying_2017_CAIP.cpp#L186-L207
std::vector<int> 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)
{
std::string input = "Sample_low_brightness.png";
std::string output = "Results";
int gaussianKernelSize = 3;
float gaussianStdev = 1.0f;
int apertureSize = 3;
bool half = 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 = static_cast<float>(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) {
}
}
else if (std::string(argv[i]) == "--gamma-rgb") {
gamma_colorspace = VISP_NAMESPACE_NAME::GAMMA_RGB;
}
else if (std::string(argv[i]) == "--output" && i + 1 < argc) {
++i;
output = std::string(argv[i]);
}
else {
std::cout << "Usage: " << argv[0]
<< " [--input <input path or image sequence pattern>]"
" [--half (use half image resolution)]"
" [--gaussian-kernel-size <e.g. 3, 5, 7>]"
" [--gaussian-std <e.g. 1>]"
" [--aperture-size <e.g. 3>]"
" [--canny-filtering-type <0=CANNY_GBLUR_SOBEL_FILTERING, 1=CANNY_GBLUR_SCHARR_FILTERING>]"
" [--gamma-rgb (RGB colorspace, else HSV]"
" [--output <folder path> (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 << "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<vpRGBa> 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,
}
else {
I_color = I_color_ori;
}
const int nb_methods = VISP_NAMESPACE_NAME::GAMMA_METHOD_COUNT - 1; // all except GAMMA_MANUAL
std::vector<std::vector<double>> computation_times(nb_methods);
int nb_images = 0;
vpImage<vpRGBa> I_color_gamma_correction, I_res_stack;
vpImage<unsigned char> I_gray, I_gray_gamma_correction, dIxy_uchar, I_canny_visp;
vpImage<vpRGBa> 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,
}
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());
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 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++) {
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<float>(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);
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()));
// 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,
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,
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";
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;
}
}
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) {
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
int main()
{
std::cerr << "C++11 is required." << std::endl;
return EXIT_SUCCESS;
}
#endif
Class that implements the Canny's edge detector. It is possible to use a boolean mask to ignore some ...
vpImage< unsigned char > detect(const vpImage< vpRGBa > &I_color)
Detect the edges in an image. Convert the color image into a gray-scale image.
void setGradients(const vpImage< float > &dIx, const vpImage< float > &dIy)
Set the Gradients of the image that will be processed.
static const vpColor red
Definition: vpColor.h:198
Font drawing functions for image.
Definition: vpFont.h:55
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
vpCannyFilteringAndGradientType
Canny filter and gradient operators to apply on the image before the edge detection stage.
Definition: vpImageFilter.h:92
@ CANNY_GBLUR_SOBEL_FILTERING
Apply Gaussian blur + Sobel operator on the input image.
Definition: vpImageFilter.h:93
@ CANNY_GBLUR_SCHARR_FILTERING
Apply Gaussian blur + Scharr operator on the input image.
Definition: vpImageFilter.h:94
static void computePartialDerivatives(const cv::Mat &cv_I, cv::Mat &cv_dIx, cv::Mat &cv_dIy, const bool &computeDx=true, const bool &computeDy=true, const bool &normalize=true, const unsigned int &gaussianKernelSize=5, const float &gaussianStdev=2.f, const unsigned int &apertureGradient=3, const vpCannyFilteringAndGradientType &filteringType=CANNY_GBLUR_SOBEL_FILTERING)
Compute the partial derivatives (i.e. horizontal and vertical gradients) of the input image.
static void read(vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
Definition: vpImageIo.cpp:147
static void write(const vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
Definition: vpImageIo.cpp:291
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
Definition: vpImagePoint.h:82
static void resize(const vpImage< Type > &I, vpImage< Type > &Ires, unsigned int width, unsigned int height, const vpImageInterpolationType &method=INTERPOLATION_NEAREST, unsigned int nThreads=0)
Definition of the vpImage class member functions.
Definition: vpImage.h:131
void init(unsigned int height, unsigned int width)
Set the size of the image.
Definition: vpImage.h:387
unsigned int getWidth() const
Definition: vpImage.h:242
void insert(const vpImage< Type > &src, const vpImagePoint &topLeft)
Definition: vpImage.h:639
unsigned int getSize() const
Definition: vpImage.h:221
unsigned int getCols() const
Definition: vpImage.h:171
unsigned int getHeight() const
Definition: vpImage.h:181
unsigned int getRows() const
Definition: vpImage.h:212
double getMeanValue(const vpImage< bool > *p_mask=nullptr, unsigned int *nbValidPoints=nullptr) const
Return the mean value of the bitmap.
static bool checkFilename(const std::string &filename)
Definition: vpIoTools.cpp:786
static std::string createFilePath(const std::string &parent, const std::string &child)
Definition: vpIoTools.cpp:1427
static void makeDirectory(const std::string &dirname)
Definition: vpIoTools.cpp:550
static std::string getNameWE(const std::string &pathname)
Definition: vpIoTools.cpp:1227
static double getMedian(const std::vector< double > &v)
Definition: vpMath.cpp:322
static double getMean(const std::vector< double > &v)
Definition: vpMath.cpp:302
Class that enables to manipulate easily a video file or a sequence of images. As it inherits from the...
void acquire(vpImage< vpRGBa > &I)
void open(vpImage< vpRGBa > &I)
void setFileName(const std::string &filename)
std::string getFrameName() const
VISP_EXPORT void gammaCorrection(VISP_NAMESPACE_ADDRESSING vpImage< unsigned char > &I, const float &gamma, const vpGammaMethod &method=GAMMA_MANUAL, const VISP_NAMESPACE_ADDRESSING vpImage< bool > *p_mask=nullptr)
vpGammaColorHandling
How to handle color images when applying Gamma Correction.
Definition: vpImgproc.h:149
vpGammaMethod
Gamma Correction automatic methods.
Definition: vpImgproc.h:100
VISP_EXPORT std::string vpGammaMethodToString(const vpGammaMethod &type)
Cast a vpGammaMethod into a string, to know its name.
Definition: vpImgproc.cpp:87
VISP_EXPORT double measureTimeMs()