diff --git a/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown b/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown index bb0ffd8978..05dd4db13f 100644 --- a/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown +++ b/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown @@ -10,6 +10,7 @@ In this tutorial you will learn how to: - Initialize a matrix with zeros - Learn what @ref cv::saturate_cast does and why it is useful - Get some cool info about pixel transformations +- Improve the brightness of an image on a practical example Theory ------ @@ -53,87 +54,29 @@ Code ---- - The following code performs the operation \f$g(i,j) = \alpha \cdot f(i,j) + \beta\f$ : -@code{.cpp} -#include -#include - -using namespace cv; - -double alpha; /*< Simple contrast control */ -int beta; /*< Simple brightness control */ - -int main( int argc, char** argv ) -{ - /// Read image given by user - Mat image = imread( argv[1] ); - Mat new_image = Mat::zeros( image.size(), image.type() ); - - /// Initialize values - std::cout<<" Basic Linear Transforms "<>alpha; - std::cout<<"* Enter the beta value [0-100]: "; std::cin>>beta; - - /// Do the operation new_image(i,j) = alpha*image(i,j) + beta - for( int y = 0; y < image.rows; y++ ) { - for( int x = 0; x < image.cols; x++ ) { - for( int c = 0; c < 3; c++ ) { - new_image.at(y,x)[c] = - saturate_cast( alpha*( image.at(y,x)[c] ) + beta ); - } - } - } - - /// Create Windows - namedWindow("Original Image", 1); - namedWindow("New Image", 1); - - /// Show stuff - imshow("Original Image", image); - imshow("New Image", new_image); - - /// Wait until user press some key - waitKey(); - return 0; -} -@endcode +@include BasicLinearTransforms.cpp Explanation ----------- -# We begin by creating parameters to save \f$\alpha\f$ and \f$\beta\f$ to be entered by the user: - @code{.cpp} - double alpha; - int beta; - @endcode + @snippet BasicLinearTransforms.cpp basic-linear-transform-parameters + -# We load an image using @ref cv::imread and save it in a Mat object: - @code{.cpp} - Mat image = imread( argv[1] ); - @endcode + @snippet BasicLinearTransforms.cpp basic-linear-transform-load -# Now, since we will make some transformations to this image, we need a new Mat object to store it. Also, we want this to have the following features: - Initial pixel values equal to zero - Same size and type as the original image - @code{.cpp} - Mat new_image = Mat::zeros( image.size(), image.type() ); - @endcode + @snippet BasicLinearTransforms.cpp basic-linear-transform-output We observe that @ref cv::Mat::zeros returns a Matlab-style zero initializer based on *image.size()* and *image.type()* -# Now, to perform the operation \f$g(i,j) = \alpha \cdot f(i,j) + \beta\f$ we will access to each pixel in image. Since we are operating with BGR images, we will have three values per pixel (B, G and R), so we will also access them separately. Here is the piece of code: - @code{.cpp} - for( int y = 0; y < image.rows; y++ ) { - for( int x = 0; x < image.cols; x++ ) { - for( int c = 0; c < 3; c++ ) { - new_image.at(y,x)[c] = - saturate_cast( alpha*( image.at(y,x)[c] ) + beta ); - } - } - } - @endcode + @snippet BasicLinearTransforms.cpp basic-linear-transform-operation Notice the following: - To access each pixel in the images we are using this syntax: *image.at\(y,x)[c]* where *y* is the row, *x* is the column and *c* is R, G or B (0, 1 or 2). @@ -142,15 +85,7 @@ Explanation values are valid. -# Finally, we create windows and show the images, the usual way. - @code{.cpp} - namedWindow("Original Image", 1); - namedWindow("New Image", 1); - - imshow("Original Image", image); - imshow("New Image", new_image); - - waitKey(0); - @endcode + @snippet BasicLinearTransforms.cpp basic-linear-transform-display @note Instead of using the **for** loops to access each pixel, we could have simply used this command: @@ -176,3 +111,82 @@ Result - We get this: ![](images/Basic_Linear_Transform_Tutorial_Result_big.jpg) + +Practical example +---- + +In this paragraph, we will put into practice what we have learned to correct an underexposed image by adjusting the brightness +and the contrast of the image. We will also see another technique to correct the brightness of an image called +gamma correction. + +### Brightness and contrast adjustments + +Increasing (/ decreasing) the \f$\beta\f$ value will add (/ subtract) a constant value to every pixel. Pixel values outside of the [0 ; 255] +range will be saturated (i.e. a pixel value higher (/ lesser) than 255 (/ 0) will be clamp to 255 (/ 0)). + +![In light gray, histogram of the original image, in dark gray when brightness = 80 in Gimp](images/Basic_Linear_Transform_Tutorial_hist_beta.png) + +The histogram represents for each color level the number of pixels with that color level. A dark image will have many pixels with +low color value and thus the histogram will present a peak in his left part. When adding a constant bias, the histogram is shifted to the +right as we have added a constant bias to all the pixels. + +The \f$\alpha\f$ parameter will modify how the levels spread. If \f$ \alpha < 1 \f$, the color levels will be compressed and the result +will be an image with less contrast. + +![In light gray, histogram of the original image, in dark gray when contrast < 0 in Gimp](images/Basic_Linear_Transform_Tutorial_hist_alpha.png) + +Note that these histograms have been obtained using the Brightness-Contrast tool in the Gimp software. The brightness tool should be +identical to the \f$\beta\f$ bias parameters but the contrast tool seems to differ to the \f$\alpha\f$ gain where the output range +seems to be centered with Gimp (as you can notice in the previous histogram). + +It can occur that playing with the \f$\beta\f$ bias will improve the brightness but in the same time the image will appear with a +slight veil as the contrast will be reduced. The \f$\alpha\f$ gain can be used to diminue this effect but due to the saturation, +we will lose some details in the original bright regions. + +### Gamma correction + +[Gamma correction](https://en.wikipedia.org/wiki/Gamma_correction) can be used to correct the brightness of an image by using a non +linear transformation between the input values and the mapped output values: + +\f[O = \left( \frac{I}{255} \right)^{\gamma} \times 255\f] + +As this relation is non linear, the effect will not be the same for all the pixels and will depend to their original value. + +![Plot for different values of gamma](images/Basic_Linear_Transform_Tutorial_gamma.png) + +When \f$ \gamma < 1 \f$, the original dark regions will be brighter and the histogram will be shifted to the right whereas it will +be the opposite with \f$ \gamma > 1 \f$. + +### Correct an underexposed image + +The following image has been corrected with: \f$ \alpha = 1.3 \f$ and \f$ \beta = 40 \f$. + +![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_linear_transform_correction.jpg) + +The overall brightness has been improved but you can notice that the clouds are now greatly saturated due to the numerical saturation +of the implementation used. A custom method that preserves the original color range can of course be implemented instead. + +The following image has been corrected with: \f$ \gamma = 0.4 \f$. + +![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg) + +The gamma correction should tend to add less saturation effect but should introduce some other type of color artifacts instead. + +![Left: histogram after alpha, beta correction ; Center: histogram of the original image ; Right: histogram after the gamma correction](images/Basic_Linear_Transform_Tutorial_histogram_compare.png) + +The previous figure compares the histograms for the three images (the y-ranges are not the same between the three histograms). +You can notice that most of the pixel values are in the lower part of the histogram for the original image. After \f$ \alpha \f$, +\f$ \beta \f$ correction, we can observe a big peak at 255 due to the saturation as well as a shift in the right. +After gamma correction, the histogram is shifted to the right but the pixels in the dark regions are more shifted +(see the gamma curves [figure](Basic_Linear_Transform_Tutorial_gamma.png)) than those in the bright regions. + +In this tutorial, you have seen two simple methods to adjust the contrast and the brightness of an image. **They are basic techniques +and are not intended to be used as a replacement of a raster graphics editor!** + +### Code + +Code for the tutorial is [here](changing_contrast_brightness_image.cpp). Code for the gamma correction: + +@snippet changing_contrast_brightness_image.cpp changing-contrast-brightness-gamma-correction + +A look-up table is used to improve the performance of the computation as only 256 values needs to be calculated once. diff --git a/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma.png b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma.png new file mode 100644 index 0000000000..50cd5860ac Binary files /dev/null and b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma.png differ diff --git a/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg new file mode 100644 index 0000000000..7ade82d85e Binary files /dev/null and b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg differ diff --git a/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_hist_alpha.png b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_hist_alpha.png new file mode 100644 index 0000000000..21185a16d3 Binary files /dev/null and b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_hist_alpha.png differ diff --git a/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_hist_beta.png b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_hist_beta.png new file mode 100644 index 0000000000..d8ef844691 Binary files /dev/null and b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_hist_beta.png differ diff --git a/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_histogram_compare.png b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_histogram_compare.png new file mode 100644 index 0000000000..c2494dc22e Binary files /dev/null and b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_histogram_compare.png differ diff --git a/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_linear_transform_correction.jpg b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_linear_transform_correction.jpg new file mode 100644 index 0000000000..2cad6558ce Binary files /dev/null and b/doc/tutorials/core/basic_linear_transform/images/Basic_Linear_Transform_Tutorial_linear_transform_correction.jpg differ diff --git a/samples/cpp/tutorial_code/ImgProc/BasicLinearTransforms.cpp b/samples/cpp/tutorial_code/ImgProc/BasicLinearTransforms.cpp index b24451de35..2535782087 100644 --- a/samples/cpp/tutorial_code/ImgProc/BasicLinearTransforms.cpp +++ b/samples/cpp/tutorial_code/ImgProc/BasicLinearTransforms.cpp @@ -8,51 +8,60 @@ #include "opencv2/highgui.hpp" #include +using namespace std; using namespace cv; -double alpha; /**< Simple contrast control */ -int beta; /**< Simple brightness control */ - /** * @function main * @brief Main function */ int main( int, char** argv ) { - /// Read image given by user - Mat image = imread( argv[1] ); - Mat new_image = Mat::zeros( image.size(), image.type() ); - - /// Initialize values - std::cout<<" Basic Linear Transforms "<>alpha; - std::cout<<"* Enter the beta value [0-100]: "; std::cin>>beta; - - - /// Do the operation new_image(i,j) = alpha*image(i,j) + beta - /// Instead of these 'for' loops we could have used simply: - /// image.convertTo(new_image, -1, alpha, beta); - /// but we wanted to show you how to access the pixels :) - for( int y = 0; y < image.rows; y++ ) - { for( int x = 0; x < image.cols; x++ ) - { for( int c = 0; c < 3; c++ ) - { - new_image.at(y,x)[c] = saturate_cast( alpha*( image.at(y,x)[c] ) + beta ); - } - } - } - - /// Create Windows - namedWindow("Original Image", 1); - namedWindow("New Image", 1); - - /// Show stuff - imshow("Original Image", image); - imshow("New Image", new_image); - - - /// Wait until user press some key - waitKey(); - return 0; + //! [basic-linear-transform-parameters] + double alpha = 1.0; /*< Simple contrast control */ + int beta = 0; /*< Simple brightness control */ + //! [basic-linear-transform-parameters] + + /// Read image given by user + //! [basic-linear-transform-load] + Mat image = imread( argv[1] ); + //! [basic-linear-transform-load] + //! [basic-linear-transform-output] + Mat new_image = Mat::zeros( image.size(), image.type() ); + //! [basic-linear-transform-output] + + /// Initialize values + cout << " Basic Linear Transforms " << endl; + cout << "-------------------------" << endl; + cout << "* Enter the alpha value [1.0-3.0]: "; cin >> alpha; + cout << "* Enter the beta value [0-100]: "; cin >> beta; + + /// Do the operation new_image(i,j) = alpha*image(i,j) + beta + /// Instead of these 'for' loops we could have used simply: + /// image.convertTo(new_image, -1, alpha, beta); + /// but we wanted to show you how to access the pixels :) + //! [basic-linear-transform-operation] + for( int y = 0; y < image.rows; y++ ) { + for( int x = 0; x < image.cols; x++ ) { + for( int c = 0; c < 3; c++ ) { + new_image.at(y,x)[c] = + saturate_cast( alpha*( image.at(y,x)[c] ) + beta ); + } + } + } + //! [basic-linear-transform-operation] + + //! [basic-linear-transform-display] + /// Create Windows + namedWindow("Original Image", WINDOW_AUTOSIZE); + namedWindow("New Image", WINDOW_AUTOSIZE); + + /// Show stuff + imshow("Original Image", image); + imshow("New Image", new_image); + + /// Wait until user press some key + waitKey(); + //! [basic-linear-transform-display] + return 0; } diff --git a/samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpp b/samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpp new file mode 100644 index 0000000000..7fc28f03d1 --- /dev/null +++ b/samples/cpp/tutorial_code/ImgProc/changing_contrast_brightness_image/changing_contrast_brightness_image.cpp @@ -0,0 +1,91 @@ +#include +#include "opencv2/imgcodecs.hpp" +#include "opencv2/highgui.hpp" + +using namespace std; +using namespace cv; + +namespace +{ +/** Global Variables */ +int alpha = 100; +int beta = 100; +int gamma_cor = 100; +Mat img_original, img_corrected, img_gamma_corrected; + +void basicLinearTransform(const Mat &img, const double alpha_, const int beta_) +{ + Mat res; + img.convertTo(res, -1, alpha_, beta_); + + hconcat(img, res, img_corrected); +} + +void gammaCorrection(const Mat &img, const double gamma_) +{ + CV_Assert(gamma_ >= 0); + //![changing-contrast-brightness-gamma-correction] + Mat lookUpTable(1, 256, CV_8U); + uchar* p = lookUpTable.ptr(); + for( int i = 0; i < 256; ++i) + p[i] = saturate_cast(pow(i / 255.0, gamma_) * 255.0); + + Mat res = img.clone(); + LUT(img, lookUpTable, res); + //![changing-contrast-brightness-gamma-correction] + + hconcat(img, res, img_gamma_corrected); +} + +void on_linear_transform_alpha_trackbar(int, void *) +{ + double alpha_value = alpha / 100.0; + int beta_value = beta - 100; + basicLinearTransform(img_original, alpha_value, beta_value); +} + +void on_linear_transform_beta_trackbar(int, void *) +{ + double alpha_value = alpha / 100.0; + int beta_value = beta - 100; + basicLinearTransform(img_original, alpha_value, beta_value); +} + +void on_gamma_correction_trackbar(int, void *) +{ + double gamma_value = gamma_cor / 100.0; + gammaCorrection(img_original, gamma_value); +} +} + +int main( int, char** argv ) +{ + img_original = imread( argv[1] ); + img_corrected = Mat(img_original.rows, img_original.cols*2, img_original.type()); + img_gamma_corrected = Mat(img_original.rows, img_original.cols*2, img_original.type()); + + hconcat(img_original, img_original, img_corrected); + hconcat(img_original, img_original, img_gamma_corrected); + + namedWindow("Brightness and contrast adjustments", WINDOW_AUTOSIZE); + namedWindow("Gamma correction", WINDOW_AUTOSIZE); + + createTrackbar("Alpha gain (contrast)", "Brightness and contrast adjustments", &alpha, 500, on_linear_transform_alpha_trackbar); + createTrackbar("Beta bias (brightness)", "Brightness and contrast adjustments", &beta, 200, on_linear_transform_beta_trackbar); + createTrackbar("Gamma correction", "Gamma correction", &gamma_cor, 200, on_gamma_correction_trackbar); + + while (true) + { + imshow("Brightness and contrast adjustments", img_corrected); + imshow("Gamma correction", img_gamma_corrected); + + int c = waitKey(30); + if (c == 27) + break; + } + + imwrite("linear_transform_correction.png", img_corrected); + imwrite("gamma_correction.png", img_gamma_corrected); + + return 0; +}