Merge remote-tracking branch 'origin/master' into merge-2.4
Conflicts: doc/tutorials/bioinspired/retina_model/retina_model.rst~
This commit is contained in:
commit
30d7e1c33d
@ -196,6 +196,7 @@ OCV_OPTION(ENABLE_SSSE3 "Enable SSSE3 instructions"
|
||||
OCV_OPTION(ENABLE_SSE41 "Enable SSE4.1 instructions" OFF IF ((CV_ICC OR CMAKE_COMPILER_IS_GNUCXX) AND (X86 OR X86_64)) )
|
||||
OCV_OPTION(ENABLE_SSE42 "Enable SSE4.2 instructions" OFF IF (CMAKE_COMPILER_IS_GNUCXX AND (X86 OR X86_64)) )
|
||||
OCV_OPTION(ENABLE_AVX "Enable AVX instructions" OFF IF ((MSVC OR CMAKE_COMPILER_IS_GNUCXX) AND (X86 OR X86_64)) )
|
||||
OCV_OPTION(ENABLE_NEON "Enable NEON instructions" OFF IF (CMAKE_COMPILER_IS_GNUCXX AND ARM) )
|
||||
OCV_OPTION(ENABLE_NOISY_WARNINGS "Show all warnings even if they are too noisy" OFF )
|
||||
OCV_OPTION(OPENCV_WARNINGS_ARE_ERRORS "Treat warnings as errors" OFF )
|
||||
OCV_OPTION(ENABLE_WINRT_MODE "Build with Windows Runtime support" OFF IF WIN32 )
|
||||
|
@ -161,6 +161,10 @@ if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_NEON)
|
||||
add_extra_compiler_option(-mfpu=neon)
|
||||
endif()
|
||||
|
||||
# Profiling?
|
||||
if(ENABLE_PROFILING)
|
||||
add_extra_compiler_option("-pg -g")
|
||||
|
@ -1,418 +0,0 @@
|
||||
.. _Retina_Model:
|
||||
|
||||
Discovering the human retina and its use for image processing
|
||||
*************************************************************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
I present here a model of human retina that shows some interesting properties for image preprocessing and enhancement.
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ discover the main two channels outing from your retina
|
||||
|
||||
+ see the basics to use the retina model
|
||||
|
||||
+ discover some parameters tweaks
|
||||
|
||||
|
||||
General overview
|
||||
================
|
||||
|
||||
The proposed model originates from Jeanny Herault's research [herault2010]_ at `Gipsa <http://www.gipsa-lab.inpg.fr>`_. It is involved in image processing applications with `Listic <http://www.listic.univ-savoie.fr>`_ (code maintainer and user) lab. This is not a complete model but it already present interesting properties that can be involved for enhanced image processing experience. The model allows the following human retina properties to be used :
|
||||
|
||||
* spectral whitening that has 3 important effects: high spatio-temporal frequency signals canceling (noise), mid-frequencies details enhancement and low frequencies luminance energy reduction. This *all in one* property directly allows visual signals cleaning of classical undesired distortions introduced by image sensors and input luminance range.
|
||||
|
||||
* local logarithmic luminance compression allows details to be enhanced even in low light conditions.
|
||||
|
||||
* decorrelation of the details information (Parvocellular output channel) and transient information (events, motion made available at the Magnocellular output channel).
|
||||
|
||||
The first two points are illustrated below :
|
||||
|
||||
In the figure below, the OpenEXR image sample *CrissyField.exr*, a High Dynamic Range image is shown. In order to make it visible on this web-page, the original input image is linearly rescaled to the classical image luminance range [0-255] and is converted to 8bit/channel format. Such strong conversion hides many details because of too strong local contrasts. Furthermore, noise energy is also strong and pollutes visual information.
|
||||
|
||||
.. image:: images/retina_TreeHdr_small.jpg
|
||||
:alt: A High dynamic range image linearly rescaled within range [0-255].
|
||||
:align: center
|
||||
|
||||
In the following image, applying the ideas proposed in [benoit2010]_, as your retina does, local luminance adaptation, spatial noise removal and spectral whitening work together and transmit accurate information on lower range 8bit data channels. On this picture, noise in significantly removed, local details hidden by strong luminance contrasts are enhanced. Output image keeps its naturalness and visual content is enhanced. Color processing is based on the color multiplexing/demultiplexing method proposed in [chaix2007]_.
|
||||
|
||||
.. image:: images/retina_TreeHdr_retina.jpg
|
||||
:alt: A High dynamic range image compressed within range [0-255] using the retina.
|
||||
:align: center
|
||||
|
||||
|
||||
*Note :* image sample can be downloaded from the `OpenEXR website <http://www.openexr.com>`_. Regarding this demonstration, before retina processing, input image has been linearly rescaled within 0-255 keeping its channels float format. 5% of its histogram ends has been cut (mostly removes wrong HDR pixels). Check out the sample *opencv/samples/cpp/OpenEXRimages_HDR_Retina_toneMapping.cpp* for similar processing. The following demonstration will only consider classical 8bit/channel images.
|
||||
|
||||
The retina model output channels
|
||||
================================
|
||||
|
||||
The retina model presents two outputs that benefit from the above cited behaviors.
|
||||
|
||||
* The first one is called the Parvocellular channel. It is mainly active in the foveal retina area (high resolution central vision with color sensitive photo-receptors), its aim is to provide accurate color vision for visual details remaining static on the retina. On the other hand objects moving on the retina projection are blurred.
|
||||
|
||||
* The second well known channel is the Magnocellular channel. It is mainly active in the retina peripheral vision and send signals related to change events (motion, transient events, etc.). These outing signals also help visual system to focus/center retina on 'transient'/moving areas for more detailed analysis thus improving visual scene context and object classification.
|
||||
|
||||
**NOTE :** regarding the proposed model, contrary to the real retina, we apply these two channels on the entire input images using the same resolution. This allows enhanced visual details and motion information to be extracted on all the considered images... but remember, that these two channels are complementary. For example, if Magnocellular channel gives strong energy in an area, then, the Parvocellular channel is certainly blurred there since there is a transient event.
|
||||
|
||||
As an illustration, we apply in the following the retina model on a webcam video stream of a dark visual scene. In this visual scene, captured in an amphitheater of the university, some students are moving while talking to the teacher.
|
||||
|
||||
In this video sequence, because of the dark ambiance, signal to noise ratio is low and color artifacts are present on visual features edges because of the low quality image capture tool-chain.
|
||||
|
||||
.. image:: images/studentsSample_input.jpg
|
||||
:alt: an input video stream extract sample
|
||||
:align: center
|
||||
|
||||
Below is shown the retina foveal vision applied on the entire image. In the used retina configuration, global luminance is preserved and local contrasts are enhanced. Also, signal to noise ratio is improved : since high frequency spatio-temporal noise is reduced, enhanced details are not corrupted by any enhanced noise.
|
||||
|
||||
.. image:: images/studentsSample_parvo.jpg
|
||||
:alt: the retina Parvocellular output. Enhanced details, luminance adaptation and noise removal. A processing tool for image analysis.
|
||||
:align: center
|
||||
|
||||
Below is the output of the Magnocellular output of the retina model. Its signals are strong where transient events occur. Here, a student is moving at the bottom of the image thus generating high energy. The remaining of the image is static however, it is corrupted by a strong noise. Here, the retina filters out most of the noise thus generating low false motion area 'alarms'. This channel can be used as a transient/moving areas detector : it would provide relevant information for a low cost segmentation tool that would highlight areas in which an event is occurring.
|
||||
|
||||
.. image:: images/studentsSample_magno.jpg
|
||||
:alt: the retina Magnocellular output. Enhanced transient signals (motion, etc.). A preprocessing tool for event detection.
|
||||
:align: center
|
||||
|
||||
Retina use case
|
||||
===============
|
||||
|
||||
This model can be used basically for spatio-temporal video effects but also in the aim of :
|
||||
|
||||
* performing texture analysis with enhanced signal to noise ratio and enhanced details robust against input images luminance ranges (check out the Parvocellular retina channel output)
|
||||
|
||||
* performing motion analysis also taking benefit of the previously cited properties.
|
||||
|
||||
Literature
|
||||
==========
|
||||
For more information, refer to the following papers :
|
||||
|
||||
.. [benoit2010] Benoit A., Caplier A., Durette B., Herault, J., "Using Human Visual System Modeling For Bio-Inspired Low Level Image Processing", Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773. DOI <http://dx.doi.org/10.1016/j.cviu.2010.01.011>
|
||||
|
||||
* Please have a look at the reference work of Jeanny Herault that you can read in his book :
|
||||
|
||||
.. [herault2010] Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891.
|
||||
|
||||
This retina filter code includes the research contributions of phd/research collegues from which code has been redrawn by the author :
|
||||
|
||||
* take a look at the *retinacolor.hpp* module to discover Brice Chaix de Lavarene phD color mosaicing/demosaicing and his reference paper:
|
||||
|
||||
.. [chaix2007] B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). "Efficient demosaicing through recursive filtering", IEEE International Conference on Image Processing ICIP 2007
|
||||
|
||||
* take a look at *imagelogpolprojection.hpp* to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny's discussions. More informations in the above cited Jeanny Heraults's book.
|
||||
|
||||
Code tutorial
|
||||
=============
|
||||
|
||||
Please refer to the original tutorial source code in file *opencv_folder/samples/cpp/tutorial_code/bioinspired/retina_tutorial.cpp*.
|
||||
|
||||
**Note :** do not forget that the retina model is included in the following namespace : *cv::bioinspired*.
|
||||
|
||||
To compile it, assuming OpenCV is correctly installed, use the following command. It requires the opencv_core *(cv::Mat and friends objects management)*, opencv_highgui *(display and image/video read)* and opencv_bioinspired *(Retina description)* libraries to compile.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// compile
|
||||
gcc retina_tutorial.cpp -o Retina_tuto -lopencv_core -lopencv_highgui -lopencv_bioinspired
|
||||
|
||||
// Run commands : add 'log' as a last parameter to apply a spatial log sampling (simulates retina sampling)
|
||||
// run on webcam
|
||||
./Retina_tuto -video
|
||||
// run on video file
|
||||
./Retina_tuto -video myVideo.avi
|
||||
// run on an image
|
||||
./Retina_tuto -image myPicture.jpg
|
||||
// run on an image with log sampling
|
||||
./Retina_tuto -image myPicture.jpg log
|
||||
|
||||
Here is a code explanation :
|
||||
|
||||
Retina definition is present in the bioinspired package and a simple include allows to use it. You can rather use the specific header : *opencv2/bioinspired.hpp* if you prefer but then include the other required openv modules : *opencv2/core.hpp* and *opencv2/highgui.hpp*
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/opencv.hpp"
|
||||
|
||||
Provide user some hints to run the program with a help function
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// the help procedure
|
||||
static void help(std::string errorMessage)
|
||||
{
|
||||
std::cout<<"Program init error : "<<errorMessage<<std::endl;
|
||||
std::cout<<"\nProgram call procedure : retinaDemo [processing mode] [Optional : media target] [Optional LAST parameter: \"log\" to activate retina log sampling]"<<std::endl;
|
||||
std::cout<<"\t[processing mode] :"<<std::endl;
|
||||
std::cout<<"\t -image : for still image processing"<<std::endl;
|
||||
std::cout<<"\t -video : for video stream processing"<<std::endl;
|
||||
std::cout<<"\t[Optional : media target] :"<<std::endl;
|
||||
std::cout<<"\t if processing an image or video file, then, specify the path and filename of the target to process"<<std::endl;
|
||||
std::cout<<"\t leave empty if processing video stream coming from a connected video device"<<std::endl;
|
||||
std::cout<<"\t[Optional : activate retina log sampling] : an optional last parameter can be specified for retina spatial log sampling"<<std::endl;
|
||||
std::cout<<"\t set \"log\" without quotes to activate this sampling, output frame size will be divided by 4"<<std::endl;
|
||||
std::cout<<"\nExamples:"<<std::endl;
|
||||
std::cout<<"\t-Image processing : ./retinaDemo -image lena.jpg"<<std::endl;
|
||||
std::cout<<"\t-Image processing with log sampling : ./retinaDemo -image lena.jpg log"<<std::endl;
|
||||
std::cout<<"\t-Video processing : ./retinaDemo -video myMovie.mp4"<<std::endl;
|
||||
std::cout<<"\t-Live video processing : ./retinaDemo -video"<<std::endl;
|
||||
std::cout<<"\nPlease start again with new parameters"<<std::endl;
|
||||
std::cout<<"****************************************************"<<std::endl;
|
||||
std::cout<<" NOTE : this program generates the default retina parameters file 'RetinaDefaultParameters.xml'"<<std::endl;
|
||||
std::cout<<" => you can use this to fine tune parameters and load them if you save to file 'RetinaSpecificParameters.xml'"<<std::endl;
|
||||
}
|
||||
|
||||
Then, start the main program and first declare a *cv::Mat* matrix in which input images will be loaded. Also allocate a *cv::VideoCapture* object ready to load video streams (if necessary)
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// declare the retina input buffer... that will be fed differently in regard of the input media
|
||||
cv::Mat inputFrame;
|
||||
cv::VideoCapture videoCapture; // in case a video media is used, its manager is declared here
|
||||
|
||||
|
||||
In the main program, before processing, first check input command parameters. Here it loads a first input image coming from a single loaded image (if user chose command *-image*) or from a video stream (if user chose command *-video*). Also, if the user added *log* command at the end of its program call, the spatial logarithmic image sampling performed by the retina is taken into account by the Boolean flag *useLogSampling*.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// welcome message
|
||||
std::cout<<"****************************************************"<<std::endl;
|
||||
std::cout<<"* Retina demonstration : demonstrates the use of is a wrapper class of the Gipsa/Listic Labs retina model."<<std::endl;
|
||||
std::cout<<"* This demo will try to load the file 'RetinaSpecificParameters.xml' (if exists).\nTo create it, copy the autogenerated template 'RetinaDefaultParameters.xml'.\nThen twaek it with your own retina parameters."<<std::endl;
|
||||
// basic input arguments checking
|
||||
if (argc<2)
|
||||
{
|
||||
help("bad number of parameter");
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool useLogSampling = !strcmp(argv[argc-1], "log"); // check if user wants retina log sampling processing
|
||||
|
||||
std::string inputMediaType=argv[1];
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// checking input media type (still image, video file, live video acquisition)
|
||||
if (!strcmp(inputMediaType.c_str(), "-image") && argc >= 3)
|
||||
{
|
||||
std::cout<<"RetinaDemo: processing image "<<argv[2]<<std::endl;
|
||||
// image processing case
|
||||
inputFrame = cv::imread(std::string(argv[2]), 1); // load image in RGB mode
|
||||
}else
|
||||
if (!strcmp(inputMediaType.c_str(), "-video"))
|
||||
{
|
||||
if (argc == 2 || (argc == 3 && useLogSampling)) // attempt to grab images from a video capture device
|
||||
{
|
||||
videoCapture.open(0);
|
||||
}else// attempt to grab images from a video filestream
|
||||
{
|
||||
std::cout<<"RetinaDemo: processing video stream "<<argv[2]<<std::endl;
|
||||
videoCapture.open(argv[2]);
|
||||
}
|
||||
|
||||
// grab a first frame to check if everything is ok
|
||||
videoCapture>>inputFrame;
|
||||
}else
|
||||
{
|
||||
// bad command parameter
|
||||
help("bad command parameter");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Once all input parameters are processed, a first image should have been loaded, if not, display error and stop program :
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
if (inputFrame.empty())
|
||||
{
|
||||
help("Input media could not be loaded, aborting");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Now, everything is ready to run the retina model. I propose here to allocate a retina instance and to manage the eventual log sampling option. The Retina constructor expects at least a cv::Size object that shows the input data size that will have to be managed. One can activate other options such as color and its related color multiplexing strategy (here Bayer multiplexing is chosen using *enum cv::bioinspired::RETINA_COLOR_BAYER*). If using log sampling, the image reduction factor (smaller output images) and log sampling strengh can be adjusted.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// pointer to a retina object
|
||||
cv::Ptr<Retina> myRetina;
|
||||
|
||||
// if the last parameter is 'log', then activate log sampling (favour foveal vision and subsamples peripheral vision)
|
||||
if (useLogSampling)
|
||||
{
|
||||
myRetina = cv::bioinspired::createRetina(inputFrame.size(), true, cv::bioinspired::RETINA_COLOR_BAYER, true, 2.0, 10.0);
|
||||
}
|
||||
else// -> else allocate "classical" retina :
|
||||
myRetina = cv::bioinspired::createRetina(inputFrame.size());
|
||||
|
||||
Once done, the proposed code writes a default xml file that contains the default parameters of the retina. This is useful to make your own config using this template. Here generated template xml file is called *RetinaDefaultParameters.xml*.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// save default retina parameters file in order to let you see this and maybe modify it and reload using method "setup"
|
||||
myRetina->write("RetinaDefaultParameters.xml");
|
||||
|
||||
In the following line, the retina attempts to load another xml file called *RetinaSpecificParameters.xml*. If you created it and introduced your own setup, it will be loaded, in the other case, default retina parameters are used.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// load parameters if file exists
|
||||
myRetina->setup("RetinaSpecificParameters.xml");
|
||||
|
||||
It is not required here but just to show it is possible, you can reset the retina buffers to zero to force it to forget past events.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// reset all retina buffers (imagine you close your eyes for a long time)
|
||||
myRetina->clearBuffers();
|
||||
|
||||
Now, it is time to run the retina ! First create some output buffers ready to receive the two retina channels outputs
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// declare retina output buffers
|
||||
cv::Mat retinaOutput_parvo;
|
||||
cv::Mat retinaOutput_magno;
|
||||
|
||||
Then, run retina in a loop, load new frames from video sequence if necessary and get retina outputs back to dedicated buffers.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// processing loop with no stop condition
|
||||
while(true)
|
||||
{
|
||||
// if using video stream, then, grabbing a new frame, else, input remains the same
|
||||
if (videoCapture.isOpened())
|
||||
videoCapture>>inputFrame;
|
||||
|
||||
// run retina filter on the loaded input frame
|
||||
myRetina->run(inputFrame);
|
||||
// Retrieve and display retina output
|
||||
myRetina->getParvo(retinaOutput_parvo);
|
||||
myRetina->getMagno(retinaOutput_magno);
|
||||
cv::imshow("retina input", inputFrame);
|
||||
cv::imshow("Retina Parvo", retinaOutput_parvo);
|
||||
cv::imshow("Retina Magno", retinaOutput_magno);
|
||||
cv::waitKey(10);
|
||||
}
|
||||
|
||||
That's done ! But if you want to secure the system, take care and manage Exceptions. The retina can throw some when it sees irrelevant data (no input frame, wrong setup, etc.).
|
||||
Then, i recommend to surround all the retina code by a try/catch system like this :
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
try{
|
||||
// pointer to a retina object
|
||||
cv::Ptr<cv::Retina> myRetina;
|
||||
[---]
|
||||
// processing loop with no stop condition
|
||||
while(true)
|
||||
{
|
||||
[---]
|
||||
}
|
||||
|
||||
}catch(cv::Exception e)
|
||||
{
|
||||
std::cerr<<"Error using Retina : "<<e.what()<<std::endl;
|
||||
}
|
||||
|
||||
Retina parameters, what to do ?
|
||||
===============================
|
||||
|
||||
First, it is recommended to read the reference paper :
|
||||
|
||||
* Benoit A., Caplier A., Durette B., Herault, J., *"Using Human Visual System Modeling For Bio-Inspired Low Level Image Processing"*, Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773. DOI <http://dx.doi.org/10.1016/j.cviu.2010.01.011>
|
||||
|
||||
Once done open the configuration file *RetinaDefaultParameters.xml* generated by the demo and let's have a look at it.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<opencv_storage>
|
||||
<OPLandIPLparvo>
|
||||
<colorMode>1</colorMode>
|
||||
<normaliseOutput>1</normaliseOutput>
|
||||
<photoreceptorsLocalAdaptationSensitivity>7.5e-01</photoreceptorsLocalAdaptationSensitivity>
|
||||
<photoreceptorsTemporalConstant>9.0e-01</photoreceptorsTemporalConstant>
|
||||
<photoreceptorsSpatialConstant>5.7e-01</photoreceptorsSpatialConstant>
|
||||
<horizontalCellsGain>0.01</horizontalCellsGain>
|
||||
<hcellsTemporalConstant>0.5</hcellsTemporalConstant>
|
||||
<hcellsSpatialConstant>7.</hcellsSpatialConstant>
|
||||
<ganglionCellsSensitivity>7.5e-01</ganglionCellsSensitivity></OPLandIPLparvo>
|
||||
<IPLmagno>
|
||||
<normaliseOutput>1</normaliseOutput>
|
||||
<parasolCells_beta>0.</parasolCells_beta>
|
||||
<parasolCells_tau>0.</parasolCells_tau>
|
||||
<parasolCells_k>7.</parasolCells_k>
|
||||
<amacrinCellsTemporalCutFrequency>2.0e+00</amacrinCellsTemporalCutFrequency>
|
||||
<V0CompressionParameter>9.5e-01</V0CompressionParameter>
|
||||
<localAdaptintegration_tau>0.</localAdaptintegration_tau>
|
||||
<localAdaptintegration_k>7.</localAdaptintegration_k></IPLmagno>
|
||||
</opencv_storage>
|
||||
|
||||
Here are some hints but actually, the best parameter setup depends more on what you want to do with the retina rather than the images input that you give to retina. Apart from the more specific case of High Dynamic Range images (HDR) that require more specific setup for specific luminance compression objective, the retina behaviors should be rather stable from content to content. Note that OpenCV is able to manage such HDR format thanks to the OpenEXR images compatibility.
|
||||
|
||||
Then, if the application target requires details enhancement prior to specific image processing, you need to know if mean luminance information is required or not. If not, the the retina can cancel or significantly reduce its energy thus giving more visibility to higher spatial frequency details.
|
||||
|
||||
|
||||
Basic parameters
|
||||
----------------
|
||||
|
||||
The most simple parameters are the following :
|
||||
|
||||
* **colorMode** : let the retina process color information (if 1) or gray scale images (if 0). In this last case, only the first channel of the input will be processed.
|
||||
|
||||
* **normaliseOutput** : each channel has this parameter, if value is 1, then the considered channel output is rescaled between 0 and 255. Take care in this case at the Magnocellular output level (motion/transient channel detection). Residual noise will also be rescaled !
|
||||
|
||||
**Note :** using color requires color channels multiplexing/demultipexing which requires more processing. You can expect much faster processing using gray levels : it would require around 30 product per pixel for all the retina processes and it has recently been parallelized for multicore architectures.
|
||||
|
||||
Photo-receptors parameters
|
||||
--------------------------
|
||||
|
||||
The following parameters act on the entry point of the retina - photo-receptors - and impact all the following processes. These sensors are low pass spatio-temporal filters that smooth temporal and spatial data and also adjust there sensitivity to local luminance thus improving details extraction and high frequency noise canceling.
|
||||
|
||||
* **photoreceptorsLocalAdaptationSensitivity** between 0 and 1. Values close to 1 allow high luminance log compression effect at the photo-receptors level. Values closer to 0 give a more linear sensitivity. Increased alone, it can burn the *Parvo (details channel)* output image. If adjusted in collaboration with **ganglionCellsSensitivity** images can be very contrasted whatever the local luminance there is... at the price of a naturalness decrease.
|
||||
|
||||
* **photoreceptorsTemporalConstant** this setups the temporal constant of the low pass filter effect at the entry of the retina. High value lead to strong temporal smoothing effect : moving objects are blurred and can disappear while static object are favored. But when starting the retina processing, stable state is reached lately.
|
||||
|
||||
* **photoreceptorsSpatialConstant** specifies the spatial constant related to photo-receptors low pass filter effect. This parameters specify the minimum allowed spatial signal period allowed in the following. Typically, this filter should cut high frequency noise. Then a 0 value doesn't cut anything noise while higher values start to cut high spatial frequencies and more and more lower frequencies... Then, do not go to high if you wanna see some details of the input images ! A good compromise for color images is 0.53 since this won't affect too much the color spectrum. Higher values would lead to gray and blurred output images.
|
||||
|
||||
Horizontal cells parameters
|
||||
---------------------------
|
||||
|
||||
This parameter set tunes the neural network connected to the photo-receptors, the horizontal cells. It modulates photo-receptors sensitivity and completes the processing for final spectral whitening (part of the spatial band pass effect thus favoring visual details enhancement).
|
||||
|
||||
* **horizontalCellsGain** here is a critical parameter ! If you are not interested by the mean luminance and focus on details enhancement, then, set to zero. But if you want to keep some environment luminance data, let some low spatial frequencies pass into the system and set a higher value (<1).
|
||||
|
||||
* **hcellsTemporalConstant** similar to photo-receptors, this acts on the temporal constant of a low pass temporal filter that smooths input data. Here, a high value generates a high retina after effect while a lower value makes the retina more reactive. This value should be lower than **photoreceptorsTemporalConstant** to limit strong retina after effects.
|
||||
|
||||
* **hcellsSpatialConstant** is the spatial constant of the low pass filter of these cells filter. It specifies the lowest spatial frequency allowed in the following. Visually, a high value leads to very low spatial frequencies processing and leads to salient halo effects. Lower values reduce this effect but the limit is : do not go lower than the value of **photoreceptorsSpatialConstant**. Those 2 parameters actually specify the spatial band-pass of the retina.
|
||||
|
||||
**NOTE** after the processing managed by the previous parameters, input data is cleaned from noise and luminance in already partly enhanced. The following parameters act on the last processing stages of the two outing retina signals.
|
||||
|
||||
Parvo (details channel) dedicated parameter
|
||||
-------------------------------------------
|
||||
|
||||
* **ganglionCellsSensitivity** specifies the strength of the final local adaptation occurring at the output of this details dedicated channel. Parameter values remain between 0 and 1. Low value tend to give a linear response while higher values enforces the remaining low contrasted areas.
|
||||
|
||||
**Note :** this parameter can correct eventual burned images by favoring low energetic details of the visual scene, even in bright areas.
|
||||
|
||||
IPL Magno (motion/transient channel) parameters
|
||||
-----------------------------------------------
|
||||
|
||||
Once image information is cleaned, this channel acts as a high pass temporal filter that only selects signals related to transient signals (events, motion, etc.). A low pass spatial filter smooths extracted transient data and a final logarithmic compression enhances low transient events thus enhancing event sensitivity.
|
||||
|
||||
* **parasolCells_beta** generally set to zero, can be considered as an amplifier gain at the entry point of this processing stage. Generally set to 0.
|
||||
|
||||
* **parasolCells_tau** the temporal smoothing effect that can be added
|
||||
|
||||
* **parasolCells_k** the spatial constant of the spatial filtering effect, set it at a high value to favor low spatial frequency signals that are lower subject to residual noise.
|
||||
|
||||
* **amacrinCellsTemporalCutFrequency** specifies the temporal constant of the high pass filter. High values let slow transient events to be selected.
|
||||
|
||||
* **V0CompressionParameter** specifies the strength of the log compression. Similar behaviors to previous description but here it enforces sensitivity of transient events.
|
||||
|
||||
* **localAdaptintegration_tau** generally set to 0, no real use here actually
|
||||
|
||||
* **localAdaptintegration_k** specifies the size of the area on which local adaptation is performed. Low values lead to short range local adaptation (higher sensitivity to noise), high values secure log compression.
|
BIN
doc/tutorials/images/photo.png
Normal file
BIN
doc/tutorials/images/photo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
122
doc/tutorials/photo/hdr_imaging/hdr_imaging.rst
Normal file
122
doc/tutorials/photo/hdr_imaging/hdr_imaging.rst
Normal file
@ -0,0 +1,122 @@
|
||||
.. _hdrimaging:
|
||||
|
||||
High Dynamic Range Imaging
|
||||
***************************************
|
||||
|
||||
Introduction
|
||||
------------------
|
||||
Today most digital images and imaging devices use 8 bits per channel thus limiting the dynamic range of the device to two orders of magnitude (actually 256 levels), while human eye can adapt to lighting conditions varying by ten orders of magnitude. When we take photographs of a real world scene bright regions may be overexposed, while the dark ones may be underexposed, so we can’t capture all details using a single exposure. HDR imaging works with images that use more that 8 bits per channel (usually 32-bit float values), allowing much wider dynamic range.
|
||||
|
||||
There are different ways to obtain HDR images, but the most common one is to use photographs of the scene taken with different exposure values. To combine this exposures it is useful to know your camera’s response function and there are algorithms to estimate it. After the HDR image has been blended it has to be converted back to 8-bit to view it on usual displays. This process is called tonemapping. Additional complexities arise when objects of the scene or camera move between shots, since images with different exposures should be registered and aligned.
|
||||
|
||||
In this tutorial we show how to generate and display HDR image from an exposure sequence. In our case images are already aligned and there are no moving objects. We also demonstrate an alternative approach called exposure fusion that produces low dynamic range image. Each step of HDR pipeline can be implemented using different algorithms so take a look at the reference manual to see them all.
|
||||
|
||||
Exposure sequence
|
||||
------------------
|
||||
|
||||
.. image:: images/memorial.png
|
||||
:height: 357pt
|
||||
:width: 242pt
|
||||
:alt: Exposure sequence
|
||||
:align: center
|
||||
|
||||
Source Code
|
||||
===========
|
||||
|
||||
.. literalinclude:: ../../../../samples/cpp/tutorial_code/photo/hdr_imaging/hdr_imaging.cpp
|
||||
:language: cpp
|
||||
:linenos:
|
||||
:tab-width: 4
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
1. **Load images and exposure times**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
vector<Mat> images;
|
||||
vector<float> times;
|
||||
loadExposureSeq(argv[1], images, times);
|
||||
|
||||
Firstly we load input images and exposure times from user-defined folder. The folder should contain images and *list.txt* - file that contains file names and inverse exposure times.
|
||||
|
||||
For our image sequence the list is following:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
memorial00.png 0.03125
|
||||
memorial01.png 0.0625
|
||||
...
|
||||
memorial15.png 1024
|
||||
|
||||
2. **Estimate camera response**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat response;
|
||||
Ptr<CalibrateDebevec> calibrate = createCalibrateDebevec();
|
||||
calibrate->process(images, response, times);
|
||||
|
||||
It is necessary to know camera response function (CRF) for a lot of HDR construction algorithms. We use one of the calibration algorithms to estimate inverse CRF for all 256 pixel values.
|
||||
|
||||
3. **Make HDR image**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat hdr;
|
||||
Ptr<MergeDebevec> merge_debevec = createMergeDebevec();
|
||||
merge_debevec->process(images, hdr, times, response);
|
||||
|
||||
We use Debevec's weighting scheme to construct HDR image using response calculated in the previous item.
|
||||
|
||||
4. **Tonemap HDR image**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat ldr;
|
||||
Ptr<TonemapDurand> tonemap = createTonemapDurand(2.2f);
|
||||
tonemap->process(hdr, ldr);
|
||||
|
||||
Since we want to see our results on common LDR display we have to map our HDR image to 8-bit range preserving most details. It is the main goal of tonemapping methods. We use tonemapper with bilateral filtering and set 2.2 as the value for gamma correction.
|
||||
|
||||
5. **Perform exposure fusion**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat fusion;
|
||||
Ptr<MergeMertens> merge_mertens = createMergeMertens();
|
||||
merge_mertens->process(images, fusion);
|
||||
|
||||
There is an alternative way to merge our exposures in case when we don't need HDR image. This process is called exposure fusion and produces LDR image that doesn't require gamma correction. It also doesn't use exposure values of the photographs.
|
||||
|
||||
6. **Write results**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
imwrite("fusion.png", fusion * 255);
|
||||
imwrite("ldr.png", ldr * 255);
|
||||
imwrite("hdr.hdr", hdr);
|
||||
|
||||
Now it's time to look at the results. Note that HDR image can't be stored in one of common image formats, so we save it to Radiance image (.hdr). Also all HDR imaging functions return results in [0, 1] range so we should multiply result by 255.
|
||||
|
||||
Results
|
||||
=======
|
||||
|
||||
Tonemapped image
|
||||
------------------
|
||||
|
||||
.. image:: images/ldr.png
|
||||
:height: 357pt
|
||||
:width: 242pt
|
||||
:alt: Tonemapped image
|
||||
:align: center
|
||||
|
||||
Exposure fusion
|
||||
------------------
|
||||
|
||||
.. image:: images/fusion.png
|
||||
:height: 357pt
|
||||
:width: 242pt
|
||||
:alt: Exposure fusion
|
||||
:align: center
|
BIN
doc/tutorials/photo/hdr_imaging/images/fusion.png
Normal file
BIN
doc/tutorials/photo/hdr_imaging/images/fusion.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 687 KiB |
BIN
doc/tutorials/photo/hdr_imaging/images/ldr.png
Normal file
BIN
doc/tutorials/photo/hdr_imaging/images/ldr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 560 KiB |
BIN
doc/tutorials/photo/hdr_imaging/images/memorial.png
Normal file
BIN
doc/tutorials/photo/hdr_imaging/images/memorial.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 490 KiB |
BIN
doc/tutorials/photo/table_of_content_photo/images/hdr.png
Normal file
BIN
doc/tutorials/photo/table_of_content_photo/images/hdr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 603 KiB |
@ -0,0 +1,36 @@
|
||||
.. _Table-Of-Content-Photo:
|
||||
|
||||
*photo* module. Computational photography
|
||||
-----------------------------------------------------------
|
||||
|
||||
Use OpenCV for advanced photo processing.
|
||||
|
||||
.. include:: ../../definitions/tocDefinitions.rst
|
||||
|
||||
+
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
============ ==============================================
|
||||
|HDR| **Title:** :ref:`hdrimaging`
|
||||
|
||||
*Compatibility:* > OpenCV 3.0
|
||||
|
||||
*Author:* Fedor Morozov
|
||||
|
||||
Learn how to create and process high dynamic range images.
|
||||
|
||||
============ ==============================================
|
||||
|
||||
.. |HDR| image:: images/hdr.png
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\pagebreak
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
../hdr_imaging/hdr_imaging
|
@ -132,7 +132,7 @@ As always, we would be happy to hear your comments and receive your contribution
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
=========== =======================================================
|
||||
|ml| Use the powerfull machine learning classes for statistical classification, regression and clustering of data.
|
||||
|ml| Use the powerful machine learning classes for statistical classification, regression and clustering of data.
|
||||
|
||||
=========== =======================================================
|
||||
|
||||
@ -141,6 +141,21 @@ As always, we would be happy to hear your comments and receive your contribution
|
||||
:width: 80pt
|
||||
:alt: ml Icon
|
||||
|
||||
* :ref:`Table-Of-Content-Photo`
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
=========== =======================================================
|
||||
|photo| Use OpenCV for advanced photo processing.
|
||||
|
||||
=========== =======================================================
|
||||
|
||||
.. |photo| image:: images/photo.png
|
||||
:height: 80pt
|
||||
:width: 80pt
|
||||
:alt: photo Icon
|
||||
|
||||
* :ref:`Table-Of-Content-GPU`
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
@ -233,6 +248,7 @@ As always, we would be happy to hear your comments and receive your contribution
|
||||
video/table_of_content_video/table_of_content_video
|
||||
objdetect/table_of_content_objdetect/table_of_content_objdetect
|
||||
ml/table_of_content_ml/table_of_content_ml
|
||||
photo/table_of_content_photo/table_of_content_photo
|
||||
gpu/table_of_content_gpu/table_of_content_gpu
|
||||
bioinspired/table_of_content_bioinspired/table_of_content_bioinspired
|
||||
ios/table_of_content_ios/table_of_content_ios
|
||||
|
@ -47,26 +47,37 @@
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
typedef double polyfit_type;
|
||||
|
||||
void cv::polyfit(const Mat& src_x, const Mat& src_y, Mat& dst, int order)
|
||||
{
|
||||
CV_Assert((src_x.rows>0)&&(src_y.rows>0)&&(src_x.cols==1)&&(src_y.cols==1)
|
||||
&&(dst.cols==1)&&(dst.rows==(order+1))&&(order>=1));
|
||||
Mat X;
|
||||
X = Mat::zeros(src_x.rows, order+1,CV_32FC1);
|
||||
Mat copy;
|
||||
for(int i = 0; i <=order;i++)
|
||||
const int wdepth = DataType<polyfit_type>::depth;
|
||||
int npoints = src_x.checkVector(1);
|
||||
int nypoints = src_y.checkVector(1);
|
||||
|
||||
CV_Assert(npoints == nypoints && npoints >= order+1);
|
||||
|
||||
Mat srcX = Mat_<polyfit_type>(src_x), srcY = Mat_<polyfit_type>(src_y);
|
||||
|
||||
Mat X = Mat::zeros(order + 1, npoints, wdepth);
|
||||
polyfit_type* pSrcX = (polyfit_type*)srcX.data;
|
||||
polyfit_type* pXData = (polyfit_type*)X.data;
|
||||
int stepX = (int)(X.step/X.elemSize1());
|
||||
for (int y = 0; y < order + 1; ++y)
|
||||
{
|
||||
copy = src_x.clone();
|
||||
pow(copy,i,copy);
|
||||
Mat M1 = X.col(i);
|
||||
copy.col(0).copyTo(M1);
|
||||
for (int x = 0; x < npoints; ++x)
|
||||
{
|
||||
if (y == 0)
|
||||
pXData[x] = 1;
|
||||
else if (y == 1)
|
||||
pXData[x + stepX] = pSrcX[x];
|
||||
else pXData[x + y*stepX] = pSrcX[x]* pXData[x + (y-1)*stepX];
|
||||
}
|
||||
}
|
||||
Mat X_t, X_inv;
|
||||
transpose(X,X_t);
|
||||
Mat temp = X_t*X;
|
||||
Mat temp2;
|
||||
invert (temp,temp2);
|
||||
Mat temp3 = temp2*X_t;
|
||||
Mat W = temp3*src_y;
|
||||
W.copyTo(dst);
|
||||
|
||||
Mat A, b, w;
|
||||
mulTransposed(X, A, false);
|
||||
b = X*srcY;
|
||||
solve(A, b, w, DECOMP_SVD);
|
||||
w.convertTo(dst, std::max(std::max(src_x.depth(), src_y.depth()), CV_32F));
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ file(GLOB grfmt_hdrs src/grfmt*.hpp)
|
||||
file(GLOB grfmt_srcs src/grfmt*.cpp)
|
||||
list(APPEND grfmt_hdrs src/bitstrm.hpp)
|
||||
list(APPEND grfmt_srcs src/bitstrm.cpp)
|
||||
list(APPEND grfmt_hdrs src/rgbe.hpp)
|
||||
list(APPEND grfmt_srcs src/rgbe.cpp)
|
||||
|
||||
source_group("Src\\grfmts" FILES ${grfmt_hdrs} ${grfmt_srcs})
|
||||
|
||||
|
164
modules/highgui/src/grfmt_hdr.cpp
Normal file
164
modules/highgui/src/grfmt_hdr.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "grfmt_hdr.hpp"
|
||||
#include "rgbe.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
HdrDecoder::HdrDecoder()
|
||||
{
|
||||
m_signature = "#?RGBE";
|
||||
m_signature_alt = "#?RADIANCE";
|
||||
file = NULL;
|
||||
m_type = CV_32FC3;
|
||||
}
|
||||
|
||||
HdrDecoder::~HdrDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
size_t HdrDecoder::signatureLength() const
|
||||
{
|
||||
return m_signature.size() > m_signature_alt.size() ?
|
||||
m_signature.size() : m_signature_alt.size();
|
||||
}
|
||||
|
||||
bool HdrDecoder::readHeader()
|
||||
{
|
||||
file = fopen(m_filename.c_str(), "rb");
|
||||
if(!file) {
|
||||
return false;
|
||||
}
|
||||
RGBE_ReadHeader(file, &m_width, &m_height, NULL);
|
||||
if(m_width <= 0 || m_height <= 0) {
|
||||
fclose(file);
|
||||
file = NULL;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HdrDecoder::readData(Mat& _img)
|
||||
{
|
||||
Mat img(m_height, m_width, CV_32FC3);
|
||||
if(!file) {
|
||||
if(!readHeader()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
RGBE_ReadPixels_RLE(file, const_cast<float*>(img.ptr<float>()), img.cols, img.rows);
|
||||
fclose(file); file = NULL;
|
||||
|
||||
if(_img.depth() == img.depth()) {
|
||||
img.convertTo(_img, _img.type());
|
||||
} else {
|
||||
img.convertTo(_img, _img.type(), 255);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HdrDecoder::checkSignature( const String& signature ) const
|
||||
{
|
||||
if(signature.size() >= m_signature.size() &&
|
||||
(!memcmp(signature.c_str(), m_signature.c_str(), m_signature.size()) ||
|
||||
!memcmp(signature.c_str(), m_signature_alt.c_str(), m_signature_alt.size())))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageDecoder HdrDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<HdrDecoder>();
|
||||
}
|
||||
|
||||
HdrEncoder::HdrEncoder()
|
||||
{
|
||||
m_description = "Radiance HDR (*.hdr;*.pic)";
|
||||
}
|
||||
|
||||
HdrEncoder::~HdrEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
bool HdrEncoder::write( const Mat& input_img, const std::vector<int>& params )
|
||||
{
|
||||
Mat img;
|
||||
CV_Assert(input_img.channels() == 3 || input_img.channels() == 1);
|
||||
if(input_img.channels() == 1) {
|
||||
std::vector<Mat> splitted(3, input_img);
|
||||
merge(splitted, img);
|
||||
} else {
|
||||
input_img.copyTo(img);
|
||||
}
|
||||
if(img.depth() != CV_32F) {
|
||||
img.convertTo(img, CV_32FC3, 1/255.0f);
|
||||
}
|
||||
CV_Assert(params.empty() || params[0] == HDR_NONE || params[0] == HDR_RLE);
|
||||
FILE *fout = fopen(m_filename.c_str(), "wb");
|
||||
if(!fout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RGBE_WriteHeader(fout, img.cols, img.rows, NULL);
|
||||
if(params.empty() || params[0] == HDR_RLE) {
|
||||
RGBE_WritePixels_RLE(fout, const_cast<float*>(img.ptr<float>()), img.cols, img.rows);
|
||||
} else {
|
||||
RGBE_WritePixels(fout, const_cast<float*>(img.ptr<float>()), img.cols * img.rows);
|
||||
}
|
||||
|
||||
fclose(fout);
|
||||
return true;
|
||||
}
|
||||
|
||||
ImageEncoder HdrEncoder::newEncoder() const
|
||||
{
|
||||
return makePtr<HdrEncoder>();
|
||||
}
|
||||
|
||||
bool HdrEncoder::isFormatSupported( int depth ) const {
|
||||
return depth != CV_64F;
|
||||
}
|
||||
|
||||
}
|
88
modules/highgui/src/grfmt_hdr.hpp
Normal file
88
modules/highgui/src/grfmt_hdr.hpp
Normal file
@ -0,0 +1,88 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMT_HDR_H_
|
||||
#define _GRFMT_HDR_H_
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
enum HdrCompression
|
||||
{
|
||||
HDR_NONE = 0,
|
||||
HDR_RLE = 1
|
||||
};
|
||||
|
||||
// Radiance rgbe (.hdr) reader
|
||||
class HdrDecoder : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
HdrDecoder();
|
||||
~HdrDecoder();
|
||||
bool readHeader();
|
||||
bool readData( Mat& img );
|
||||
bool checkSignature( const String& signature ) const;
|
||||
ImageDecoder newDecoder() const;
|
||||
size_t signatureLength() const;
|
||||
protected:
|
||||
String m_signature_alt;
|
||||
FILE *file;
|
||||
};
|
||||
|
||||
// ... writer
|
||||
class HdrEncoder : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
HdrEncoder();
|
||||
~HdrEncoder();
|
||||
bool write( const Mat& img, const std::vector<int>& params );
|
||||
ImageEncoder newEncoder() const;
|
||||
bool isFormatSupported( int depth ) const;
|
||||
protected:
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif/*_GRFMT_HDR_H_*/
|
@ -47,6 +47,7 @@
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "grfmt_tiff.hpp"
|
||||
#include <opencv2/imgproc.hpp>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
@ -71,6 +72,7 @@ TiffDecoder::TiffDecoder()
|
||||
TIFFSetErrorHandler( GrFmtSilentTIFFErrorHandler );
|
||||
TIFFSetWarningHandler( GrFmtSilentTIFFErrorHandler );
|
||||
}
|
||||
m_hdr = false;
|
||||
}
|
||||
|
||||
|
||||
@ -133,6 +135,14 @@ bool TiffDecoder::readHeader()
|
||||
|
||||
m_width = wdth;
|
||||
m_height = hght;
|
||||
if((bpp == 32 && ncn == 3) || photometric == PHOTOMETRIC_LOGLUV)
|
||||
{
|
||||
m_type = CV_32FC3;
|
||||
m_hdr = true;
|
||||
return true;
|
||||
}
|
||||
m_hdr = false;
|
||||
|
||||
if( bpp > 8 &&
|
||||
((photometric != 2 && photometric != 1) ||
|
||||
(ncn != 1 && ncn != 3 && ncn != 4)))
|
||||
@ -171,6 +181,10 @@ bool TiffDecoder::readHeader()
|
||||
|
||||
bool TiffDecoder::readData( Mat& img )
|
||||
{
|
||||
if(m_hdr && img.type() == CV_32FC3)
|
||||
{
|
||||
return readHdrData(img);
|
||||
}
|
||||
bool result = false;
|
||||
bool color = img.channels() > 1;
|
||||
uchar* data = img.data;
|
||||
@ -380,6 +394,37 @@ bool TiffDecoder::readData( Mat& img )
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TiffDecoder::readHdrData(Mat& img)
|
||||
{
|
||||
int rows_per_strip = 0, photometric = 0;
|
||||
if(!m_tif)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
TIFF *tif = static_cast<TIFF*>(m_tif);
|
||||
TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
|
||||
TIFFGetField( tif, TIFFTAG_PHOTOMETRIC, &photometric );
|
||||
TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT);
|
||||
int size = 3 * m_width * m_height * sizeof (float);
|
||||
int strip_size = 3 * m_width * rows_per_strip;
|
||||
float *ptr = img.ptr<float>();
|
||||
for (size_t i = 0; i < TIFFNumberOfStrips(tif); i++, ptr += strip_size)
|
||||
{
|
||||
TIFFReadEncodedStrip(tif, i, ptr, size);
|
||||
size -= strip_size * sizeof(float);
|
||||
}
|
||||
close();
|
||||
if(photometric == PHOTOMETRIC_LOGLUV)
|
||||
{
|
||||
cvtColor(img, img, COLOR_XYZ2BGR);
|
||||
}
|
||||
else
|
||||
{
|
||||
cvtColor(img, img, COLOR_RGB2BGR);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -405,7 +450,11 @@ ImageEncoder TiffEncoder::newEncoder() const
|
||||
|
||||
bool TiffEncoder::isFormatSupported( int depth ) const
|
||||
{
|
||||
#ifdef HAVE_TIFF
|
||||
return depth == CV_8U || depth == CV_16U || depth == CV_32F;
|
||||
#else
|
||||
return depth == CV_8U || depth == CV_16U;
|
||||
#endif
|
||||
}
|
||||
|
||||
void TiffEncoder::writeTag( WLByteStream& strm, TiffTag tag,
|
||||
@ -557,6 +606,33 @@ bool TiffEncoder::writeLibTiff( const Mat& img, const std::vector<int>& params)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TiffEncoder::writeHdr(const Mat& _img)
|
||||
{
|
||||
Mat img;
|
||||
cvtColor(_img, img, COLOR_BGR2XYZ);
|
||||
TIFF* tif = TIFFOpen(m_filename.c_str(), "w");
|
||||
if (!tif)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, img.cols);
|
||||
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, img.rows);
|
||||
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
|
||||
TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_SGILOG);
|
||||
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_LOGLUV);
|
||||
TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
|
||||
TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT);
|
||||
TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1);
|
||||
int strip_size = 3 * img.cols;
|
||||
float *ptr = const_cast<float*>(img.ptr<float>());
|
||||
for (int i = 0; i < img.rows; i++, ptr += strip_size)
|
||||
{
|
||||
TIFFWriteEncodedStrip(tif, i, ptr, strip_size * sizeof(float));
|
||||
}
|
||||
TIFFClose(tif);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TIFF
|
||||
@ -568,6 +644,12 @@ bool TiffEncoder::write( const Mat& img, const std::vector<int>& /*params*/)
|
||||
int channels = img.channels();
|
||||
int width = img.cols, height = img.rows;
|
||||
int depth = img.depth();
|
||||
#ifdef HAVE_TIFF
|
||||
if(img.type() == CV_32FC3)
|
||||
{
|
||||
return writeHdr(img);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (depth != CV_8U && depth != CV_16U)
|
||||
return false;
|
||||
|
@ -108,6 +108,8 @@ public:
|
||||
protected:
|
||||
void* m_tif;
|
||||
int normalizeChannelsNumber(int channels) const;
|
||||
bool readHdrData(Mat& img);
|
||||
bool m_hdr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -130,6 +132,7 @@ protected:
|
||||
int count, int value );
|
||||
|
||||
bool writeLibTiff( const Mat& img, const std::vector<int>& params );
|
||||
bool writeHdr( const Mat& img );
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -52,5 +52,6 @@
|
||||
#include "grfmt_jpeg2000.hpp"
|
||||
#include "grfmt_exr.hpp"
|
||||
#include "grfmt_webp.hpp"
|
||||
#include "grfmt_hdr.hpp"
|
||||
|
||||
#endif/*_GRFMTS_H_*/
|
||||
|
@ -47,6 +47,7 @@
|
||||
#include "grfmts.hpp"
|
||||
#undef min
|
||||
#undef max
|
||||
#include <iostream>
|
||||
|
||||
/****************************************************************************************\
|
||||
* Image Codecs *
|
||||
@ -60,6 +61,8 @@ struct ImageCodecInitializer
|
||||
{
|
||||
decoders.push_back( makePtr<BmpDecoder>() );
|
||||
encoders.push_back( makePtr<BmpEncoder>() );
|
||||
decoders.push_back( makePtr<HdrDecoder>() );
|
||||
encoders.push_back( makePtr<HdrEncoder>() );
|
||||
#ifdef HAVE_JPEG
|
||||
decoders.push_back( makePtr<JpegDecoder>() );
|
||||
encoders.push_back( makePtr<JpegEncoder>() );
|
||||
@ -203,7 +206,6 @@ imread_( const String& filename, int flags, int hdrtype, Mat* mat=0 )
|
||||
decoder->setSource(filename);
|
||||
if( !decoder->readHeader() )
|
||||
return 0;
|
||||
|
||||
CvSize size;
|
||||
size.width = decoder->width();
|
||||
size.height = decoder->height();
|
||||
@ -271,7 +273,6 @@ static bool imwrite_( const String& filename, const Mat& image,
|
||||
ImageEncoder encoder = findEncoder( filename );
|
||||
if( !encoder )
|
||||
CV_Error( CV_StsError, "could not find a writer for the specified extension" );
|
||||
|
||||
if( !encoder->isFormatSupported(image.depth()) )
|
||||
{
|
||||
CV_Assert( encoder->isFormatSupported(CV_8U) );
|
||||
|
450
modules/highgui/src/rgbe.cpp
Normal file
450
modules/highgui/src/rgbe.cpp
Normal file
@ -0,0 +1,450 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "rgbe.hpp"
|
||||
#include <math.h>
|
||||
#if !defined(__APPLE__)
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
// This file contains code to read and write four byte rgbe file format
|
||||
// developed by Greg Ward. It handles the conversions between rgbe and
|
||||
// pixels consisting of floats. The data is assumed to be an array of floats.
|
||||
// By default there are three floats per pixel in the order red, green, blue.
|
||||
// (RGBE_DATA_??? values control this.) Only the mimimal header reading and
|
||||
// writing is implemented. Each routine does error checking and will return
|
||||
// a status value as defined below. This code is intended as a skeleton so
|
||||
// feel free to modify it to suit your needs.
|
||||
|
||||
// Some opencv specific changes have been added:
|
||||
// inline define specified, error handler uses CV_Error,
|
||||
// defines changed to work in bgr color space.
|
||||
//
|
||||
// posted to http://www.graphics.cornell.edu/~bjw/
|
||||
// written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95
|
||||
// based on code written by Greg Ward
|
||||
|
||||
#define INLINE inline
|
||||
|
||||
/* offsets to red, green, and blue components in a data (float) pixel */
|
||||
#define RGBE_DATA_RED 2
|
||||
#define RGBE_DATA_GREEN 1
|
||||
#define RGBE_DATA_BLUE 0
|
||||
/* number of floats per pixel */
|
||||
#define RGBE_DATA_SIZE 3
|
||||
|
||||
enum rgbe_error_codes {
|
||||
rgbe_read_error,
|
||||
rgbe_write_error,
|
||||
rgbe_format_error,
|
||||
rgbe_memory_error,
|
||||
};
|
||||
|
||||
/* default error routine. change this to change error handling */
|
||||
static int rgbe_error(int rgbe_error_code, const char *msg)
|
||||
{
|
||||
switch (rgbe_error_code) {
|
||||
case rgbe_read_error:
|
||||
CV_Error(cv::Error::StsError, "RGBE read error");
|
||||
break;
|
||||
case rgbe_write_error:
|
||||
CV_Error(cv::Error::StsError, "RGBE write error");
|
||||
break;
|
||||
case rgbe_format_error:
|
||||
CV_Error(cv::Error::StsError, cv::String("RGBE bad file format: ") +
|
||||
cv::String(msg));
|
||||
break;
|
||||
default:
|
||||
case rgbe_memory_error:
|
||||
CV_Error(cv::Error::StsError, cv::String("RGBE error: \n") +
|
||||
cv::String(msg));
|
||||
}
|
||||
return RGBE_RETURN_FAILURE;
|
||||
}
|
||||
|
||||
/* standard conversion from float pixels to rgbe pixels */
|
||||
/* note: you can remove the "inline"s if your compiler complains about it */
|
||||
static INLINE void
|
||||
float2rgbe(unsigned char rgbe[4], float red, float green, float blue)
|
||||
{
|
||||
float v;
|
||||
int e;
|
||||
|
||||
v = red;
|
||||
if (green > v) v = green;
|
||||
if (blue > v) v = blue;
|
||||
if (v < 1e-32) {
|
||||
rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
|
||||
}
|
||||
else {
|
||||
v = static_cast<float>(frexp(v,&e) * 256.0/v);
|
||||
rgbe[0] = (unsigned char) (red * v);
|
||||
rgbe[1] = (unsigned char) (green * v);
|
||||
rgbe[2] = (unsigned char) (blue * v);
|
||||
rgbe[3] = (unsigned char) (e + 128);
|
||||
}
|
||||
}
|
||||
|
||||
/* standard conversion from rgbe to float pixels */
|
||||
/* note: Ward uses ldexp(col+0.5,exp-(128+8)). However we wanted pixels */
|
||||
/* in the range [0,1] to map back into the range [0,1]. */
|
||||
static INLINE void
|
||||
rgbe2float(float *red, float *green, float *blue, unsigned char rgbe[4])
|
||||
{
|
||||
float f;
|
||||
|
||||
if (rgbe[3]) { /*nonzero pixel*/
|
||||
f = static_cast<float>(ldexp(1.0,rgbe[3]-(int)(128+8)));
|
||||
*red = rgbe[0] * f;
|
||||
*green = rgbe[1] * f;
|
||||
*blue = rgbe[2] * f;
|
||||
}
|
||||
else
|
||||
*red = *green = *blue = 0.0;
|
||||
}
|
||||
|
||||
/* default minimal header. modify if you want more information in header */
|
||||
int RGBE_WriteHeader(FILE *fp, int width, int height, rgbe_header_info *info)
|
||||
{
|
||||
const char *programtype = "RGBE";
|
||||
|
||||
if (info && (info->valid & RGBE_VALID_PROGRAMTYPE))
|
||||
programtype = info->programtype;
|
||||
if (fprintf(fp,"#?%s\n",programtype) < 0)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
/* The #? is to identify file type, the programtype is optional. */
|
||||
if (info && (info->valid & RGBE_VALID_GAMMA)) {
|
||||
if (fprintf(fp,"GAMMA=%g\n",info->gamma) < 0)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
}
|
||||
if (info && (info->valid & RGBE_VALID_EXPOSURE)) {
|
||||
if (fprintf(fp,"EXPOSURE=%g\n",info->exposure) < 0)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
}
|
||||
if (fprintf(fp,"FORMAT=32-bit_rle_rgbe\n\n") < 0)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
if (fprintf(fp, "-Y %d +X %d\n", height, width) < 0)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
/* minimal header reading. modify if you want to parse more information */
|
||||
int RGBE_ReadHeader(FILE *fp, int *width, int *height, rgbe_header_info *info)
|
||||
{
|
||||
char buf[128];
|
||||
float tempf;
|
||||
int i;
|
||||
|
||||
if (info) {
|
||||
info->valid = 0;
|
||||
info->programtype[0] = 0;
|
||||
info->gamma = info->exposure = 1.0;
|
||||
}
|
||||
if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == NULL)
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
if ((buf[0] != '#')||(buf[1] != '?')) {
|
||||
/* if you want to require the magic token then uncomment the next line */
|
||||
/*return rgbe_error(rgbe_format_error,"bad initial token"); */
|
||||
}
|
||||
else if (info) {
|
||||
info->valid |= RGBE_VALID_PROGRAMTYPE;
|
||||
for(i=0;i<static_cast<int>(sizeof(info->programtype)-1);i++) {
|
||||
if ((buf[i+2] == 0) || isspace(buf[i+2]))
|
||||
break;
|
||||
info->programtype[i] = buf[i+2];
|
||||
}
|
||||
info->programtype[i] = 0;
|
||||
if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
}
|
||||
for(;;) {
|
||||
if ((buf[0] == 0)||(buf[0] == '\n'))
|
||||
return rgbe_error(rgbe_format_error,"no FORMAT specifier found");
|
||||
else if (strcmp(buf,"FORMAT=32-bit_rle_rgbe\n") == 0)
|
||||
break; /* format found so break out of loop */
|
||||
else if (info && (sscanf(buf,"GAMMA=%g",&tempf) == 1)) {
|
||||
info->gamma = tempf;
|
||||
info->valid |= RGBE_VALID_GAMMA;
|
||||
}
|
||||
else if (info && (sscanf(buf,"EXPOSURE=%g",&tempf) == 1)) {
|
||||
info->exposure = tempf;
|
||||
info->valid |= RGBE_VALID_EXPOSURE;
|
||||
}
|
||||
if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
}
|
||||
if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
if (strcmp(buf,"\n") != 0)
|
||||
return rgbe_error(rgbe_format_error,
|
||||
"missing blank line after FORMAT specifier");
|
||||
if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
if (sscanf(buf,"-Y %d +X %d",height,width) < 2)
|
||||
return rgbe_error(rgbe_format_error,"missing image size specifier");
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
/* simple write routine that does not use run length encoding */
|
||||
/* These routines can be made faster by allocating a larger buffer and
|
||||
fread-ing and fwrite-ing the data in larger chunks */
|
||||
int RGBE_WritePixels(FILE *fp, float *data, int numpixels)
|
||||
{
|
||||
unsigned char rgbe[4];
|
||||
|
||||
while (numpixels-- > 0) {
|
||||
float2rgbe(rgbe,data[RGBE_DATA_RED],
|
||||
data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
|
||||
data += RGBE_DATA_SIZE;
|
||||
if (fwrite(rgbe, sizeof(rgbe), 1, fp) < 1)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
}
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
/* simple read routine. will not correctly handle run length encoding */
|
||||
int RGBE_ReadPixels(FILE *fp, float *data, int numpixels)
|
||||
{
|
||||
unsigned char rgbe[4];
|
||||
|
||||
while(numpixels-- > 0) {
|
||||
if (fread(rgbe, sizeof(rgbe), 1, fp) < 1)
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
rgbe2float(&data[RGBE_DATA_RED],&data[RGBE_DATA_GREEN],
|
||||
&data[RGBE_DATA_BLUE],rgbe);
|
||||
data += RGBE_DATA_SIZE;
|
||||
}
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
/* The code below is only needed for the run-length encoded files. */
|
||||
/* Run length encoding adds considerable complexity but does */
|
||||
/* save some space. For each scanline, each channel (r,g,b,e) is */
|
||||
/* encoded separately for better compression. */
|
||||
|
||||
static int RGBE_WriteBytes_RLE(FILE *fp, unsigned char *data, int numbytes)
|
||||
{
|
||||
#define MINRUNLENGTH 4
|
||||
int cur, beg_run, run_count, old_run_count, nonrun_count;
|
||||
unsigned char buf[2];
|
||||
|
||||
cur = 0;
|
||||
while(cur < numbytes) {
|
||||
beg_run = cur;
|
||||
/* find next run of length at least 4 if one exists */
|
||||
run_count = old_run_count = 0;
|
||||
while((run_count < MINRUNLENGTH) && (beg_run < numbytes)) {
|
||||
beg_run += run_count;
|
||||
old_run_count = run_count;
|
||||
run_count = 1;
|
||||
while( (beg_run + run_count < numbytes) && (run_count < 127)
|
||||
&& (data[beg_run] == data[beg_run + run_count]))
|
||||
run_count++;
|
||||
}
|
||||
/* if data before next big run is a short run then write it as such */
|
||||
if ((old_run_count > 1)&&(old_run_count == beg_run - cur)) {
|
||||
buf[0] = static_cast<unsigned char>(128 + old_run_count); /*write short run*/
|
||||
buf[1] = data[cur];
|
||||
if (fwrite(buf,sizeof(buf[0])*2,1,fp) < 1)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
cur = beg_run;
|
||||
}
|
||||
/* write out bytes until we reach the start of the next run */
|
||||
while(cur < beg_run) {
|
||||
nonrun_count = beg_run - cur;
|
||||
if (nonrun_count > 128)
|
||||
nonrun_count = 128;
|
||||
buf[0] = static_cast<unsigned char>(nonrun_count);
|
||||
if (fwrite(buf,sizeof(buf[0]),1,fp) < 1)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
if (fwrite(&data[cur],sizeof(data[0])*nonrun_count,1,fp) < 1)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
cur += nonrun_count;
|
||||
}
|
||||
/* write out next run if one was found */
|
||||
if (run_count >= MINRUNLENGTH) {
|
||||
buf[0] = static_cast<unsigned char>(128 + run_count);
|
||||
buf[1] = data[beg_run];
|
||||
if (fwrite(buf,sizeof(buf[0])*2,1,fp) < 1)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
cur += run_count;
|
||||
}
|
||||
}
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
#undef MINRUNLENGTH
|
||||
}
|
||||
|
||||
int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
|
||||
int num_scanlines)
|
||||
{
|
||||
unsigned char rgbe[4];
|
||||
unsigned char *buffer;
|
||||
int i, err;
|
||||
|
||||
if ((scanline_width < 8)||(scanline_width > 0x7fff))
|
||||
/* run length encoding is not allowed so write flat*/
|
||||
return RGBE_WritePixels(fp,data,scanline_width*num_scanlines);
|
||||
buffer = (unsigned char *)malloc(sizeof(unsigned char)*4*scanline_width);
|
||||
if (buffer == NULL)
|
||||
/* no buffer space so write flat */
|
||||
return RGBE_WritePixels(fp,data,scanline_width*num_scanlines);
|
||||
while(num_scanlines-- > 0) {
|
||||
rgbe[0] = 2;
|
||||
rgbe[1] = 2;
|
||||
rgbe[2] = static_cast<unsigned char>(scanline_width >> 8);
|
||||
rgbe[3] = scanline_width & 0xFF;
|
||||
if (fwrite(rgbe, sizeof(rgbe), 1, fp) < 1) {
|
||||
free(buffer);
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
}
|
||||
for(i=0;i<scanline_width;i++) {
|
||||
float2rgbe(rgbe,data[RGBE_DATA_RED],
|
||||
data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
|
||||
buffer[i] = rgbe[0];
|
||||
buffer[i+scanline_width] = rgbe[1];
|
||||
buffer[i+2*scanline_width] = rgbe[2];
|
||||
buffer[i+3*scanline_width] = rgbe[3];
|
||||
data += RGBE_DATA_SIZE;
|
||||
}
|
||||
/* write out each of the four channels separately run length encoded */
|
||||
/* first red, then green, then blue, then exponent */
|
||||
for(i=0;i<4;i++) {
|
||||
if ((err = RGBE_WriteBytes_RLE(fp,&buffer[i*scanline_width],
|
||||
scanline_width)) != RGBE_RETURN_SUCCESS) {
|
||||
free(buffer);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(buffer);
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
int RGBE_ReadPixels_RLE(FILE *fp, float *data, int scanline_width,
|
||||
int num_scanlines)
|
||||
{
|
||||
unsigned char rgbe[4], *scanline_buffer, *ptr, *ptr_end;
|
||||
int i, count;
|
||||
unsigned char buf[2];
|
||||
|
||||
if ((scanline_width < 8)||(scanline_width > 0x7fff))
|
||||
/* run length encoding is not allowed so read flat*/
|
||||
return RGBE_ReadPixels(fp,data,scanline_width*num_scanlines);
|
||||
scanline_buffer = NULL;
|
||||
/* read in each successive scanline */
|
||||
while(num_scanlines > 0) {
|
||||
if (fread(rgbe,sizeof(rgbe),1,fp) < 1) {
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
}
|
||||
if ((rgbe[0] != 2)||(rgbe[1] != 2)||(rgbe[2] & 0x80)) {
|
||||
/* this file is not run length encoded */
|
||||
rgbe2float(&data[RGBE_DATA_RED],&data[RGBE_DATA_GREEN],&data[RGBE_DATA_BLUE],rgbe);
|
||||
data += RGBE_DATA_SIZE;
|
||||
free(scanline_buffer);
|
||||
return RGBE_ReadPixels(fp,data,scanline_width*num_scanlines-1);
|
||||
}
|
||||
if ((((int)rgbe[2])<<8 | rgbe[3]) != scanline_width) {
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_format_error,"wrong scanline width");
|
||||
}
|
||||
if (scanline_buffer == NULL)
|
||||
scanline_buffer = (unsigned char *)
|
||||
malloc(sizeof(unsigned char)*4*scanline_width);
|
||||
if (scanline_buffer == NULL)
|
||||
return rgbe_error(rgbe_memory_error,"unable to allocate buffer space");
|
||||
|
||||
ptr = &scanline_buffer[0];
|
||||
/* read each of the four channels for the scanline into the buffer */
|
||||
for(i=0;i<4;i++) {
|
||||
ptr_end = &scanline_buffer[(i+1)*scanline_width];
|
||||
while(ptr < ptr_end) {
|
||||
if (fread(buf,sizeof(buf[0])*2,1,fp) < 1) {
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
}
|
||||
if (buf[0] > 128) {
|
||||
/* a run of the same value */
|
||||
count = buf[0]-128;
|
||||
if ((count == 0)||(count > ptr_end - ptr)) {
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_format_error,"bad scanline data");
|
||||
}
|
||||
while(count-- > 0)
|
||||
*ptr++ = buf[1];
|
||||
}
|
||||
else {
|
||||
/* a non-run */
|
||||
count = buf[0];
|
||||
if ((count == 0)||(count > ptr_end - ptr)) {
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_format_error,"bad scanline data");
|
||||
}
|
||||
*ptr++ = buf[1];
|
||||
if (--count > 0) {
|
||||
if (fread(ptr,sizeof(*ptr)*count,1,fp) < 1) {
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
}
|
||||
ptr += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* now convert data from buffer into floats */
|
||||
for(i=0;i<scanline_width;i++) {
|
||||
rgbe[0] = scanline_buffer[i];
|
||||
rgbe[1] = scanline_buffer[i+scanline_width];
|
||||
rgbe[2] = scanline_buffer[i+2*scanline_width];
|
||||
rgbe[3] = scanline_buffer[i+3*scanline_width];
|
||||
rgbe2float(&data[RGBE_DATA_RED],&data[RGBE_DATA_GREEN],
|
||||
&data[RGBE_DATA_BLUE],rgbe);
|
||||
data += RGBE_DATA_SIZE;
|
||||
}
|
||||
num_scanlines--;
|
||||
}
|
||||
free(scanline_buffer);
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
89
modules/highgui/src/rgbe.hpp
Normal file
89
modules/highgui/src/rgbe.hpp
Normal file
@ -0,0 +1,89 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _RGBE_HDR_H_
|
||||
#define _RGBE_HDR_H_
|
||||
|
||||
// posted to http://www.graphics.cornell.edu/~bjw/
|
||||
// written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95
|
||||
// based on code written by Greg Ward
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct {
|
||||
int valid; /* indicate which fields are valid */
|
||||
char programtype[16]; /* listed at beginning of file to identify it
|
||||
* after "#?". defaults to "RGBE" */
|
||||
float gamma; /* image has already been gamma corrected with
|
||||
* given gamma. defaults to 1.0 (no correction) */
|
||||
float exposure; /* a value of 1.0 in an image corresponds to
|
||||
* <exposure> watts/steradian/m^2.
|
||||
* defaults to 1.0 */
|
||||
} rgbe_header_info;
|
||||
|
||||
/* flags indicating which fields in an rgbe_header_info are valid */
|
||||
#define RGBE_VALID_PROGRAMTYPE 0x01
|
||||
#define RGBE_VALID_GAMMA 0x02
|
||||
#define RGBE_VALID_EXPOSURE 0x04
|
||||
|
||||
/* return codes for rgbe routines */
|
||||
#define RGBE_RETURN_SUCCESS 0
|
||||
#define RGBE_RETURN_FAILURE -1
|
||||
|
||||
/* read or write headers */
|
||||
/* you may set rgbe_header_info to null if you want to */
|
||||
int RGBE_WriteHeader(FILE *fp, int width, int height, rgbe_header_info *info);
|
||||
int RGBE_ReadHeader(FILE *fp, int *width, int *height, rgbe_header_info *info);
|
||||
|
||||
/* read or write pixels */
|
||||
/* can read or write pixels in chunks of any size including single pixels*/
|
||||
int RGBE_WritePixels(FILE *fp, float *data, int numpixels);
|
||||
int RGBE_ReadPixels(FILE *fp, float *data, int numpixels);
|
||||
|
||||
/* read or write run length encoded files */
|
||||
/* must be called to read or write whole scanlines */
|
||||
int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
|
||||
int num_scanlines);
|
||||
int RGBE_ReadPixels_RLE(FILE *fp, float *data, int scanline_width,
|
||||
int num_scanlines);
|
||||
|
||||
#endif/*_RGBE_HDR_H_*/
|
@ -430,11 +430,11 @@ TEST(Highgui_Tiff, decode_tile16384x16384)
|
||||
TEST(Highgui_WebP, encode_decode_lossless_webp)
|
||||
{
|
||||
cvtest::TS& ts = *cvtest::TS::ptr();
|
||||
std::string input = std::string(ts.get_data_path()) + "../cv/shared/lena.png";
|
||||
string input = string(ts.get_data_path()) + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(input);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
std::string output = cv::tempfile(".webp");
|
||||
string output = cv::tempfile(".webp");
|
||||
EXPECT_NO_THROW(cv::imwrite(output, img)); // lossless
|
||||
|
||||
cv::Mat img_webp = cv::imread(output);
|
||||
@ -525,3 +525,28 @@ TEST(Highgui_WebP, encode_decode_with_alpha_webp)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST(Highgui_Hdr, regression)
|
||||
{
|
||||
string folder = string(cvtest::TS::ptr()->get_data_path()) + "/readwrite/";
|
||||
string name_rle = folder + "rle.hdr";
|
||||
string name_no_rle = folder + "no_rle.hdr";
|
||||
Mat img_rle = imread(name_rle, -1);
|
||||
ASSERT_FALSE(img_rle.empty()) << "Could not open " << name_rle;
|
||||
Mat img_no_rle = imread(name_no_rle, -1);
|
||||
ASSERT_FALSE(img_no_rle.empty()) << "Could not open " << name_no_rle;
|
||||
|
||||
double min = 0.0, max = 1.0;
|
||||
minMaxLoc(abs(img_rle - img_no_rle), &min, &max);
|
||||
ASSERT_FALSE(max > DBL_EPSILON);
|
||||
string tmp_file_name = tempfile(".hdr");
|
||||
vector<int>param(1);
|
||||
for(int i = 0; i < 2; i++) {
|
||||
param[0] = i;
|
||||
imwrite(tmp_file_name, img_rle, param);
|
||||
Mat written_img = imread(tmp_file_name, -1);
|
||||
ASSERT_FALSE(written_img.empty()) << "Could not open " << tmp_file_name;
|
||||
minMaxLoc(abs(img_rle - written_img), &min, &max);
|
||||
ASSERT_FALSE(max > DBL_EPSILON);
|
||||
}
|
||||
}
|
||||
|
@ -509,11 +509,11 @@ Line segment detector class, following the algorithm described at [Rafael12]_.
|
||||
.. ocv:class:: LineSegmentDetector : public Algorithm
|
||||
|
||||
|
||||
createLineSegmentDetectorPtr
|
||||
----------------------------
|
||||
createLineSegmentDetector
|
||||
-------------------------
|
||||
Creates a smart pointer to a LineSegmentDetector object and initializes it.
|
||||
|
||||
.. ocv:function:: Ptr<LineSegmentDetector> createLineSegmentDetectorPtr(int _refine = LSD_REFINE_STD, double _scale = 0.8, double _sigma_scale = 0.6, double _quant = 2.0, double _ang_th = 22.5, double _log_eps = 0, double _density_th = 0.7, int _n_bins = 1024)
|
||||
.. ocv:function:: Ptr<LineSegmentDetector> createLineSegmentDetector(int _refine = LSD_REFINE_STD, double _scale = 0.8, double _sigma_scale = 0.6, double _quant = 2.0, double _ang_th = 22.5, double _log_eps = 0, double _density_th = 0.7, int _n_bins = 1024)
|
||||
|
||||
:param _refine: The way found lines will be refined:
|
||||
|
||||
|
@ -904,7 +904,7 @@ protected:
|
||||
Point2f bottomRight;
|
||||
};
|
||||
|
||||
class LineSegmentDetector : public Algorithm
|
||||
class CV_EXPORTS_W LineSegmentDetector : public Algorithm
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@ -926,7 +926,7 @@ public:
|
||||
* * 1 corresponds to 0.1 mean false alarms
|
||||
* This vector will be calculated _only_ when the objects type is REFINE_ADV
|
||||
*/
|
||||
virtual void detect(InputArray _image, OutputArray _lines,
|
||||
CV_WRAP virtual void detect(InputArray _image, OutputArray _lines,
|
||||
OutputArray width = noArray(), OutputArray prec = noArray(),
|
||||
OutputArray nfa = noArray()) = 0;
|
||||
|
||||
@ -937,7 +937,7 @@ public:
|
||||
* Should have the size of the image, where the lines were found
|
||||
* @param lines The lines that need to be drawn
|
||||
*/
|
||||
virtual void drawSegments(InputOutputArray _image, InputArray lines) = 0;
|
||||
CV_WRAP virtual void drawSegments(InputOutputArray _image, InputArray lines) = 0;
|
||||
|
||||
/**
|
||||
* Draw both vectors on the image canvas. Uses blue for lines 1 and red for lines 2.
|
||||
@ -949,13 +949,13 @@ public:
|
||||
* Should have the size of the image, where the lines were found
|
||||
* @return The number of mismatching pixels between lines1 and lines2.
|
||||
*/
|
||||
virtual int compareSegments(const Size& size, InputArray lines1, InputArray lines2, InputOutputArray _image = noArray()) = 0;
|
||||
CV_WRAP virtual int compareSegments(const Size& size, InputArray lines1, InputArray lines2, InputOutputArray _image = noArray()) = 0;
|
||||
|
||||
virtual ~LineSegmentDetector() {};
|
||||
};
|
||||
|
||||
//! Returns a pointer to a LineSegmentDetector class.
|
||||
CV_EXPORTS Ptr<LineSegmentDetector> createLineSegmentDetectorPtr(
|
||||
CV_EXPORTS_W Ptr<LineSegmentDetector> createLineSegmentDetector(
|
||||
int _refine = LSD_REFINE_STD, double _scale = 0.8,
|
||||
double _sigma_scale = 0.6, double _quant = 2.0, double _ang_th = 22.5,
|
||||
double _log_eps = 0, double _density_th = 0.7, int _n_bins = 1024);
|
||||
|
@ -388,7 +388,7 @@ private:
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CV_EXPORTS Ptr<LineSegmentDetector> createLineSegmentDetectorPtr(
|
||||
CV_EXPORTS Ptr<LineSegmentDetector> createLineSegmentDetector(
|
||||
int _refine, double _scale, double _sigma_scale, double _quant, double _ang_th,
|
||||
double _log_eps, double _density_th, int _n_bins)
|
||||
{
|
||||
|
@ -110,7 +110,7 @@ TEST_F(Imgproc_LSD_ADV, whiteNoise)
|
||||
for (int i = 0; i < EPOCHS; ++i)
|
||||
{
|
||||
GenerateWhiteNoise(test_image);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_ADV);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_ADV);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(40u >= lines.size()) ++passedtests;
|
||||
@ -123,7 +123,7 @@ TEST_F(Imgproc_LSD_ADV, constColor)
|
||||
for (int i = 0; i < EPOCHS; ++i)
|
||||
{
|
||||
GenerateConstColor(test_image);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_ADV);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_ADV);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(0u == lines.size()) ++passedtests;
|
||||
@ -137,7 +137,7 @@ TEST_F(Imgproc_LSD_ADV, lines)
|
||||
{
|
||||
const unsigned int numOfLines = 1;
|
||||
GenerateLines(test_image, numOfLines);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_ADV);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_ADV);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(numOfLines * 2 == lines.size()) ++passedtests; // * 2 because of Gibbs effect
|
||||
@ -150,7 +150,7 @@ TEST_F(Imgproc_LSD_ADV, rotatedRect)
|
||||
for (int i = 0; i < EPOCHS; ++i)
|
||||
{
|
||||
GenerateRotatedRect(test_image);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_ADV);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_ADV);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(2u <= lines.size()) ++passedtests;
|
||||
@ -163,7 +163,7 @@ TEST_F(Imgproc_LSD_STD, whiteNoise)
|
||||
for (int i = 0; i < EPOCHS; ++i)
|
||||
{
|
||||
GenerateWhiteNoise(test_image);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_STD);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_STD);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(50u >= lines.size()) ++passedtests;
|
||||
@ -176,7 +176,7 @@ TEST_F(Imgproc_LSD_STD, constColor)
|
||||
for (int i = 0; i < EPOCHS; ++i)
|
||||
{
|
||||
GenerateConstColor(test_image);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_STD);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_STD);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(0u == lines.size()) ++passedtests;
|
||||
@ -190,7 +190,7 @@ TEST_F(Imgproc_LSD_STD, lines)
|
||||
{
|
||||
const unsigned int numOfLines = 1;
|
||||
GenerateLines(test_image, numOfLines);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_STD);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_STD);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(numOfLines * 2 == lines.size()) ++passedtests; // * 2 because of Gibbs effect
|
||||
@ -203,7 +203,7 @@ TEST_F(Imgproc_LSD_STD, rotatedRect)
|
||||
for (int i = 0; i < EPOCHS; ++i)
|
||||
{
|
||||
GenerateRotatedRect(test_image);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_STD);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_STD);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(4u <= lines.size()) ++passedtests;
|
||||
@ -216,7 +216,7 @@ TEST_F(Imgproc_LSD_NONE, whiteNoise)
|
||||
for (int i = 0; i < EPOCHS; ++i)
|
||||
{
|
||||
GenerateWhiteNoise(test_image);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_STD);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_STD);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(50u >= lines.size()) ++passedtests;
|
||||
@ -229,7 +229,7 @@ TEST_F(Imgproc_LSD_NONE, constColor)
|
||||
for (int i = 0; i < EPOCHS; ++i)
|
||||
{
|
||||
GenerateConstColor(test_image);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_NONE);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_NONE);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(0u == lines.size()) ++passedtests;
|
||||
@ -243,7 +243,7 @@ TEST_F(Imgproc_LSD_NONE, lines)
|
||||
{
|
||||
const unsigned int numOfLines = 1;
|
||||
GenerateLines(test_image, numOfLines);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_NONE);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_NONE);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(numOfLines * 2 == lines.size()) ++passedtests; // * 2 because of Gibbs effect
|
||||
@ -256,7 +256,7 @@ TEST_F(Imgproc_LSD_NONE, rotatedRect)
|
||||
for (int i = 0; i < EPOCHS; ++i)
|
||||
{
|
||||
GenerateRotatedRect(test_image);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetectorPtr(LSD_REFINE_NONE);
|
||||
Ptr<LineSegmentDetector> detector = createLineSegmentDetector(LSD_REFINE_NONE);
|
||||
detector->detect(test_image, lines);
|
||||
|
||||
if(8u <= lines.size()) ++passedtests;
|
||||
|
377
modules/photo/doc/hdr_imaging.rst
Normal file
377
modules/photo/doc/hdr_imaging.rst
Normal file
@ -0,0 +1,377 @@
|
||||
HDR imaging
|
||||
=============
|
||||
|
||||
.. highlight:: cpp
|
||||
|
||||
This section describes high dynamic range imaging algorithms namely tonemapping, exposure alignment, camera calibration with multiple exposures and exposure fusion.
|
||||
|
||||
Tonemap
|
||||
---------------------------
|
||||
.. ocv:class:: Tonemap : public Algorithm
|
||||
|
||||
Base class for tonemapping algorithms - tools that are used to map HDR image to 8-bit range.
|
||||
|
||||
Tonemap::process
|
||||
---------------------------
|
||||
Tonemaps image
|
||||
|
||||
.. ocv:function:: void Tonemap::process(InputArray src, OutputArray dst)
|
||||
|
||||
:param src: source image - 32-bit 3-channel Mat
|
||||
:param dst: destination image - 32-bit 3-channel Mat with values in [0, 1] range
|
||||
|
||||
createTonemap
|
||||
---------------------------
|
||||
Creates simple linear mapper with gamma correction
|
||||
|
||||
.. ocv:function:: Ptr<Tonemap> createTonemap(float gamma = 1.0f)
|
||||
|
||||
:param gamma: positive value for gamma correction. Gamma value of 1.0 implies no correction, gamma equal to 2.2f is suitable for most displays.
|
||||
|
||||
Generally gamma > 1 brightens the image and gamma < 1 darkens it.
|
||||
|
||||
TonemapDrago
|
||||
---------------------------
|
||||
.. ocv:class:: TonemapDrago : public Tonemap
|
||||
|
||||
Adaptive logarithmic mapping is a fast global tonemapping algorithm that scales the image in logarithmic domain.
|
||||
|
||||
Since it's a global operator the same function is applied to all the pixels, it is controlled by the bias parameter.
|
||||
|
||||
Optional saturation enhancement is possible as described in [FL02]_.
|
||||
|
||||
For more information see [DM03]_.
|
||||
|
||||
createTonemapDrago
|
||||
---------------------------
|
||||
Creates TonemapDrago object
|
||||
|
||||
.. ocv:function:: Ptr<TonemapDrago> createTonemapDrago(float gamma = 1.0f, float saturation = 1.0f, float bias = 0.85f)
|
||||
|
||||
:param gamma: gamma value for gamma correction. See :ocv:func:`createTonemap`
|
||||
|
||||
:param saturation: positive saturation enhancement value. 1.0 preserves saturation, values greater than 1 increase saturation and values less than 1 decrease it.
|
||||
|
||||
:param bias: value for bias function in [0, 1] range. Values from 0.7 to 0.9 usually give best results, default value is 0.85.
|
||||
|
||||
TonemapDurand
|
||||
---------------------------
|
||||
.. ocv:class:: TonemapDurand : public Tonemap
|
||||
|
||||
This algorithm decomposes image into two layers: base layer and detail layer using bilateral filter and compresses contrast of the base layer thus preserving all the details.
|
||||
|
||||
This implementation uses regular bilateral filter from opencv.
|
||||
|
||||
Saturation enhancement is possible as in ocv:class:`TonemapDrago`.
|
||||
|
||||
For more information see [DD02]_.
|
||||
|
||||
createTonemapDurand
|
||||
---------------------------
|
||||
Creates TonemapDurand object
|
||||
|
||||
.. ocv:function:: Ptr<TonemapDurand> createTonemapDurand(float gamma = 1.0f, float contrast = 4.0f, float saturation = 1.0f, float sigma_space = 2.0f, float sigma_color = 2.0f)
|
||||
|
||||
:param gamma: gamma value for gamma correction. See :ocv:func:`createTonemap`
|
||||
|
||||
:param contrast: resulting contrast on logarithmic scale, i. e. log(max / min), where max and min are maximum and minimum luminance values of the resulting image.
|
||||
|
||||
:param saturation: saturation enhancement value. See :ocv:func:`createTonemapDrago`
|
||||
|
||||
:param sigma_space: bilateral filter sigma in color space
|
||||
|
||||
:param sigma_color: bilateral filter sigma in coordinate space
|
||||
|
||||
TonemapReinhard
|
||||
---------------------------
|
||||
.. ocv:class:: TonemapReinhard : public Tonemap
|
||||
|
||||
This is a global tonemapping operator that models human visual system.
|
||||
|
||||
Mapping function is controlled by adaptation parameter, that is computed using light adaptation and color adaptation.
|
||||
|
||||
For more information see [RD05]_.
|
||||
|
||||
createTonemapReinhard
|
||||
---------------------------
|
||||
Creates TonemapReinhard object
|
||||
|
||||
.. ocv:function:: Ptr<TonemapReinhard> createTonemapReinhard(float gamma = 1.0f, float intensity = 0.0f, float light_adapt = 1.0f, float color_adapt = 0.0f)
|
||||
|
||||
:param gamma: gamma value for gamma correction. See :ocv:func:`createTonemap`
|
||||
|
||||
:param intensity: result intensity in [-8, 8] range. Greater intensity produces brighter results.
|
||||
|
||||
:param light_adapt: light adaptation in [0, 1] range. If 1 adaptation is based only on pixel value, if 0 it's global, otherwise it's a weighted mean of this two cases.
|
||||
|
||||
:param color_adapt: chromatic adaptation in [0, 1] range. If 1 channels are treated independently, if 0 adaptation level is the same for each channel.
|
||||
|
||||
TonemapMantiuk
|
||||
---------------------------
|
||||
.. ocv:class:: TonemapMantiuk : public Tonemap
|
||||
|
||||
This algorithm transforms image to contrast using gradients on all levels of gaussian pyramid, transforms contrast values to HVS response and scales the response.
|
||||
After this the image is reconstructed from new contrast values.
|
||||
|
||||
For more information see [MM06]_.
|
||||
|
||||
createTonemapMantiuk
|
||||
---------------------------
|
||||
Creates TonemapMantiuk object
|
||||
|
||||
.. ocv:function:: Ptr<TonemapMantiuk> createTonemapMantiuk(float gamma = 1.0f, float scale = 0.7f, float saturation = 1.0f)
|
||||
|
||||
:param gamma: gamma value for gamma correction. See :ocv:func:`createTonemap`
|
||||
|
||||
:param scale: contrast scale factor. HVS response is multiplied by this parameter, thus compressing dynamic range. Values from 0.6 to 0.9 produce best results.
|
||||
|
||||
:param saturation: saturation enhancement value. See :ocv:func:`createTonemapDrago`
|
||||
|
||||
AlignExposures
|
||||
---------------------------
|
||||
.. ocv:class:: AlignExposures : public Algorithm
|
||||
|
||||
The base class for algorithms that align images of the same scene with different exposures
|
||||
|
||||
AlignExposures::process
|
||||
---------------------------
|
||||
Aligns images
|
||||
|
||||
.. ocv:function:: void AlignExposures::process(InputArrayOfArrays src, std::vector<Mat>& dst, InputArray times, InputArray response)
|
||||
|
||||
:param src: vector of input images
|
||||
|
||||
:param dst: vector of aligned images
|
||||
|
||||
:param times: vector of exposure time values for each image
|
||||
|
||||
:param response: 256x1 matrix with inverse camera response function for each pixel value, it should have the same number of channels as images.
|
||||
|
||||
AlignMTB
|
||||
---------------------------
|
||||
.. ocv:class:: AlignMTB : public AlignExposures
|
||||
|
||||
This algorithm converts images to median threshold bitmaps (1 for pixels brighter than median luminance and 0 otherwise) and than aligns the resulting bitmaps using bit operations.
|
||||
|
||||
It is invariant to exposure, so exposure values and camera response are not necessary.
|
||||
|
||||
In this implementation new image regions are filled with zeros.
|
||||
|
||||
For more information see [GW03]_.
|
||||
|
||||
AlignMTB::process
|
||||
---------------------------
|
||||
Short version of process, that doesn't take extra arguments.
|
||||
|
||||
.. ocv:function:: void AlignMTB::process(InputArrayOfArrays src, std::vector<Mat>& dst)
|
||||
|
||||
:param src: vector of input images
|
||||
|
||||
:param dst: vector of aligned images
|
||||
|
||||
AlignMTB::calculateShift
|
||||
---------------------------
|
||||
Calculates shift between two images, i. e. how to shift the second image to correspond it with the first.
|
||||
|
||||
.. ocv:function:: Point AlignMTB::calculateShift(InputArray img0, InputArray img1)
|
||||
|
||||
:param img0: first image
|
||||
|
||||
:param img1: second image
|
||||
|
||||
AlignMTB::shiftMat
|
||||
---------------------------
|
||||
Helper function, that shift Mat filling new regions with zeros.
|
||||
|
||||
.. ocv:function:: void AlignMTB::shiftMat(InputArray src, OutputArray dst, const Point shift)
|
||||
|
||||
:param src: input image
|
||||
|
||||
:param dst: result image
|
||||
|
||||
:param shift: shift value
|
||||
|
||||
AlignMTB::computeBitmaps
|
||||
---------------------------
|
||||
Computes median threshold and exclude bitmaps of given image.
|
||||
|
||||
.. ocv:function:: void AlignMTB::computeBitmaps(InputArray img, OutputArray tb, OutputArray eb)
|
||||
|
||||
:param img: input image
|
||||
|
||||
:param tb: median threshold bitmap
|
||||
|
||||
:param eb: exclude bitmap
|
||||
|
||||
createAlignMTB
|
||||
---------------------------
|
||||
Creates AlignMTB object
|
||||
|
||||
.. ocv:function:: Ptr<AlignMTB> createAlignMTB(int max_bits = 6, int exclude_range = 4, bool cut = true)
|
||||
|
||||
:param max_bits: logarithm to the base 2 of maximal shift in each dimension. Values of 5 and 6 are usually good enough (31 and 63 pixels shift respectively).
|
||||
|
||||
:param exclude_range: range for exclusion bitmap that is constructed to suppress noise around the median value.
|
||||
|
||||
:param cut: if true cuts images, otherwise fills the new regions with zeros.
|
||||
|
||||
CalibrateCRF
|
||||
---------------------------
|
||||
.. ocv:class:: CalibrateCRF : public Algorithm
|
||||
|
||||
The base class for camera response calibration algorithms.
|
||||
|
||||
CalibrateCRF::process
|
||||
---------------------------
|
||||
Recovers inverse camera response.
|
||||
|
||||
.. ocv:function:: void CalibrateCRF::process(InputArrayOfArrays src, OutputArray dst, InputArray times)
|
||||
|
||||
:param src: vector of input images
|
||||
|
||||
:param dst: 256x1 matrix with inverse camera response function
|
||||
|
||||
:param times: vector of exposure time values for each image
|
||||
|
||||
CalibrateDebevec
|
||||
---------------------------
|
||||
.. ocv:class:: CalibrateDebevec : public CalibrateCRF
|
||||
|
||||
Inverse camera response function is extracted for each brightness value by minimizing an objective function as linear system.
|
||||
Objective function is constructed using pixel values on the same position in all images, extra term is added to make the result smoother.
|
||||
|
||||
For more information see [DM97]_.
|
||||
|
||||
createCalibrateDebevec
|
||||
---------------------------
|
||||
Creates CalibrateDebevec object
|
||||
|
||||
.. ocv:function:: createCalibrateDebevec(int samples = 70, float lambda = 10.0f, bool random = false)
|
||||
|
||||
:param samples: number of pixel locations to use
|
||||
|
||||
:param lambda: smoothness term weight. Greater values produce smoother results, but can alter the response.
|
||||
|
||||
:param random: if true sample pixel locations are chosen at random, otherwise the form a rectangular grid.
|
||||
|
||||
CalibrateRobertson
|
||||
---------------------------
|
||||
.. ocv:class:: CalibrateRobertson : public CalibrateCRF
|
||||
|
||||
Inverse camera response function is extracted for each brightness value by minimizing an objective function as linear system.
|
||||
This algorithm uses all image pixels.
|
||||
|
||||
For more information see [RB99]_.
|
||||
|
||||
createCalibrateRobertson
|
||||
---------------------------
|
||||
Creates CalibrateRobertson object
|
||||
|
||||
.. ocv:function:: createCalibrateRobertson(int max_iter = 30, float threshold = 0.01f)
|
||||
|
||||
:param max_iter: maximal number of Gauss-Seidel solver iterations.
|
||||
|
||||
:param threshold: target difference between results of two successive steps of the minimization.
|
||||
|
||||
MergeExposures
|
||||
---------------------------
|
||||
.. ocv:class:: MergeExposures : public Algorithm
|
||||
|
||||
The base class algorithms that can merge exposure sequence to a single image.
|
||||
|
||||
MergeExposures::process
|
||||
---------------------------
|
||||
Merges images.
|
||||
|
||||
.. ocv:function:: void MergeExposures::process(InputArrayOfArrays src, OutputArray dst, InputArray times, InputArray response)
|
||||
|
||||
:param src: vector of input images
|
||||
|
||||
:param dst: result image
|
||||
|
||||
:param times: vector of exposure time values for each image
|
||||
|
||||
:param response: 256x1 matrix with inverse camera response function for each pixel value, it should have the same number of channels as images.
|
||||
|
||||
MergeDebevec
|
||||
---------------------------
|
||||
.. ocv:class:: MergeDebevec : public MergeExposures
|
||||
|
||||
The resulting HDR image is calculated as weighted average of the exposures considering exposure values and camera response.
|
||||
|
||||
For more information see [DM97]_.
|
||||
|
||||
createMergeDebevec
|
||||
---------------------------
|
||||
Creates MergeDebevec object
|
||||
|
||||
.. ocv:function:: Ptr<MergeDebevec> createMergeDebevec()
|
||||
|
||||
MergeMertens
|
||||
---------------------------
|
||||
.. ocv:class:: MergeMertens : public MergeExposures
|
||||
|
||||
Pixels are weighted using contrast, saturation and well-exposedness measures, than images are combined using laplacian pyramids.
|
||||
|
||||
The resulting image weight is constructed as weighted average of contrast, saturation and well-exposedness measures.
|
||||
|
||||
The resulting image doesn't require tonemapping and can be converted to 8-bit image by multiplying by 255, but it's recommended to apply gamma correction and/or linear tonemapping.
|
||||
|
||||
For more information see [MK07]_.
|
||||
|
||||
MergeMertens::process
|
||||
---------------------------
|
||||
Short version of process, that doesn't take extra arguments.
|
||||
|
||||
.. ocv:function:: void MergeMertens::process(InputArrayOfArrays src, OutputArray dst)
|
||||
|
||||
:param src: vector of input images
|
||||
|
||||
:param dst: result image
|
||||
|
||||
createMergeMertens
|
||||
---------------------------
|
||||
Creates MergeMertens object
|
||||
|
||||
.. ocv:function:: Ptr<MergeMertens> createMergeMertens(float contrast_weight = 1.0f, float saturation_weight = 1.0f, float exposure_weight = 0.0f)
|
||||
|
||||
:param contrast_weight: contrast measure weight. See :ocv:class:`MergeMertens`.
|
||||
|
||||
:param saturation_weight: saturation measure weight
|
||||
|
||||
:param exposure_weight: well-exposedness measure weight
|
||||
|
||||
MergeRobertson
|
||||
---------------------------
|
||||
.. ocv:class:: MergeRobertson : public MergeExposures
|
||||
|
||||
The resulting HDR image is calculated as weighted average of the exposures considering exposure values and camera response.
|
||||
|
||||
For more information see [RB99]_.
|
||||
|
||||
createMergeRobertson
|
||||
---------------------------
|
||||
Creates MergeRobertson object
|
||||
|
||||
.. ocv:function:: Ptr<MergeRobertson> createMergeRobertson()
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [DM03] F. Drago, K. Myszkowski, T. Annen, N. Chiba, "Adaptive Logarithmic Mapping For Displaying High Contrast Scenes", Computer Graphics Forum, 2003, 22, 419 - 426.
|
||||
|
||||
.. [FL02] R. Fattal, D. Lischinski, M. Werman, "Gradient Domain High Dynamic Range Compression", Proceedings OF ACM SIGGRAPH, 2002, 249 - 256.
|
||||
|
||||
.. [DD02] F. Durand and Julie Dorsey, "Fast Bilateral Filtering for the Display of High-Dynamic-Range Images", ACM Transactions on Graphics, 2002, 21, 3, 257 - 266.
|
||||
|
||||
.. [RD05] E. Reinhard, K. Devlin, "Dynamic Range Reduction Inspired by Photoreceptor Physiology", IEEE Transactions on Visualization and Computer Graphics, 2005, 11, 13 - 24.
|
||||
|
||||
.. [MM06] R. Mantiuk, K. Myszkowski, H.-P. Seidel, "Perceptual Framework for Contrast Processing of High Dynamic Range Images", ACM Transactions on Applied Perception, 2006, 3, 3, 286 - 308.
|
||||
|
||||
.. [GW03] G. Ward, "Fast, Robust Image Registration for Compositing High Dynamic Range Photographs from Handheld Exposures", Journal of Graphics Tools, 2003, 8, 17 - 30.
|
||||
|
||||
.. [DM97] P. Debevec, J. Malik, "Recovering High Dynamic Range Radiance Maps from Photographs", Proceedings OF ACM SIGGRAPH, 1997, 369 - 378.
|
||||
|
||||
.. [MK07] T. Mertens, J. Kautz, F. Van Reeth, "Exposure Fusion", Proceedings of the 15th Pacific Conference on Computer Graphics and Applications, 2007, 382 - 390.
|
||||
|
||||
.. [RB99] M. Robertson , S. Borman , R. Stevenson , "Dynamic range improvement through multiple exposures ", Proceedings of the Int. Conf. on Image Processing , 1999, 159 - 163.
|
@ -9,3 +9,4 @@ photo. Computational Photography
|
||||
|
||||
inpainting
|
||||
denoising
|
||||
hdr_imaging
|
@ -80,6 +80,214 @@ CV_EXPORTS_W void fastNlMeansDenoisingColoredMulti( InputArrayOfArrays srcImgs,
|
||||
float h = 3, float hColor = 3,
|
||||
int templateWindowSize = 7, int searchWindowSize = 21);
|
||||
|
||||
enum { LDR_SIZE = 256 };
|
||||
|
||||
class CV_EXPORTS_W Tonemap : public Algorithm
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual void process(InputArray src, OutputArray dst) = 0;
|
||||
|
||||
CV_WRAP virtual float getGamma() const = 0;
|
||||
CV_WRAP virtual void setGamma(float gamma) = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS_W Ptr<Tonemap> createTonemap(float gamma = 1.0f);
|
||||
|
||||
// "Adaptive Logarithmic Mapping For Displaying HighContrast Scenes", Drago et al., 2003
|
||||
|
||||
class CV_EXPORTS_W TonemapDrago : public Tonemap
|
||||
{
|
||||
public:
|
||||
|
||||
CV_WRAP virtual float getSaturation() const = 0;
|
||||
CV_WRAP virtual void setSaturation(float saturation) = 0;
|
||||
|
||||
CV_WRAP virtual float getBias() const = 0;
|
||||
CV_WRAP virtual void setBias(float bias) = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS_W Ptr<TonemapDrago> createTonemapDrago(float gamma = 1.0f, float saturation = 1.0f, float bias = 0.85f);
|
||||
|
||||
// "Fast Bilateral Filtering for the Display of High-Dynamic-Range Images", Durand, Dorsey, 2002
|
||||
|
||||
class CV_EXPORTS_W TonemapDurand : public Tonemap
|
||||
{
|
||||
public:
|
||||
|
||||
CV_WRAP virtual float getSaturation() const = 0;
|
||||
CV_WRAP virtual void setSaturation(float saturation) = 0;
|
||||
|
||||
CV_WRAP virtual float getContrast() const = 0;
|
||||
CV_WRAP virtual void setContrast(float contrast) = 0;
|
||||
|
||||
CV_WRAP virtual float getSigmaSpace() const = 0;
|
||||
CV_WRAP virtual void setSigmaSpace(float sigma_space) = 0;
|
||||
|
||||
CV_WRAP virtual float getSigmaColor() const = 0;
|
||||
CV_WRAP virtual void setSigmaColor(float sigma_color) = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS_W Ptr<TonemapDurand>
|
||||
createTonemapDurand(float gamma = 1.0f, float contrast = 4.0f, float saturation = 1.0f, float sigma_space = 2.0f, float sigma_color = 2.0f);
|
||||
|
||||
// "Dynamic Range Reduction Inspired by Photoreceptor Physiology", Reinhard, Devlin, 2005
|
||||
|
||||
class CV_EXPORTS_W TonemapReinhard : public Tonemap
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual float getIntensity() const = 0;
|
||||
CV_WRAP virtual void setIntensity(float intensity) = 0;
|
||||
|
||||
CV_WRAP virtual float getLightAdaptation() const = 0;
|
||||
CV_WRAP virtual void setLightAdaptation(float light_adapt) = 0;
|
||||
|
||||
CV_WRAP virtual float getColorAdaptation() const = 0;
|
||||
CV_WRAP virtual void setColorAdaptation(float color_adapt) = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS_W Ptr<TonemapReinhard>
|
||||
createTonemapReinhard(float gamma = 1.0f, float intensity = 0.0f, float light_adapt = 1.0f, float color_adapt = 0.0f);
|
||||
|
||||
// "Perceptual Framework for Contrast Processing of High Dynamic Range Images", Mantiuk et al., 2006
|
||||
|
||||
class CV_EXPORTS_W TonemapMantiuk : public Tonemap
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual float getScale() const = 0;
|
||||
CV_WRAP virtual void setScale(float scale) = 0;
|
||||
|
||||
CV_WRAP virtual float getSaturation() const = 0;
|
||||
CV_WRAP virtual void setSaturation(float saturation) = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS_W Ptr<TonemapMantiuk>
|
||||
createTonemapMantiuk(float gamma = 1.0f, float scale = 0.7f, float saturation = 1.0f);
|
||||
|
||||
class CV_EXPORTS_W AlignExposures : public Algorithm
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual void process(InputArrayOfArrays src, std::vector<Mat>& dst,
|
||||
InputArray times, InputArray response) = 0;
|
||||
};
|
||||
|
||||
// "Fast, Robust Image Registration for Compositing High Dynamic Range Photographs from Handheld Exposures", Ward, 2003
|
||||
|
||||
class CV_EXPORTS_W AlignMTB : public AlignExposures
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual void process(InputArrayOfArrays src, std::vector<Mat>& dst,
|
||||
InputArray times, InputArray response) = 0;
|
||||
|
||||
CV_WRAP virtual void process(InputArrayOfArrays src, std::vector<Mat>& dst) = 0;
|
||||
|
||||
CV_WRAP virtual Point calculateShift(InputArray img0, InputArray img1) = 0;
|
||||
CV_WRAP virtual void shiftMat(InputArray src, OutputArray dst, const Point shift) = 0;
|
||||
CV_WRAP virtual void computeBitmaps(InputArray img, OutputArray tb, OutputArray eb) = 0;
|
||||
|
||||
CV_WRAP virtual int getMaxBits() const = 0;
|
||||
CV_WRAP virtual void setMaxBits(int max_bits) = 0;
|
||||
|
||||
CV_WRAP virtual int getExcludeRange() const = 0;
|
||||
CV_WRAP virtual void setExcludeRange(int exclude_range) = 0;
|
||||
|
||||
CV_WRAP virtual bool getCut() const = 0;
|
||||
CV_WRAP virtual void setCut(bool value) = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS_W Ptr<AlignMTB> createAlignMTB(int max_bits = 6, int exclude_range = 4, bool cut = true);
|
||||
|
||||
class CV_EXPORTS_W CalibrateCRF : public Algorithm
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, InputArray times) = 0;
|
||||
};
|
||||
|
||||
// "Recovering High Dynamic Range Radiance Maps from Photographs", Debevec, Malik, 1997
|
||||
|
||||
class CV_EXPORTS_W CalibrateDebevec : public CalibrateCRF
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual float getLambda() const = 0;
|
||||
CV_WRAP virtual void setLambda(float lambda) = 0;
|
||||
|
||||
CV_WRAP virtual int getSamples() const = 0;
|
||||
CV_WRAP virtual void setSamples(int samples) = 0;
|
||||
|
||||
CV_WRAP virtual bool getRandom() const = 0;
|
||||
CV_WRAP virtual void setRandom(bool random) = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS_W Ptr<CalibrateDebevec> createCalibrateDebevec(int samples = 70, float lambda = 10.0f, bool random = false);
|
||||
|
||||
// "Dynamic range improvement through multiple exposures", Robertson et al., 1999
|
||||
|
||||
class CV_EXPORTS_W CalibrateRobertson : public CalibrateCRF
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual int getMaxIter() const = 0;
|
||||
CV_WRAP virtual void setMaxIter(int max_iter) = 0;
|
||||
|
||||
CV_WRAP virtual float getThreshold() const = 0;
|
||||
CV_WRAP virtual void setThreshold(float threshold) = 0;
|
||||
|
||||
CV_WRAP virtual Mat getRadiance() const = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS_W Ptr<CalibrateRobertson> createCalibrateRobertson(int max_iter = 30, float threshold = 0.01f);
|
||||
|
||||
class CV_EXPORTS_W MergeExposures : public Algorithm
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst,
|
||||
InputArray times, InputArray response) = 0;
|
||||
};
|
||||
|
||||
// "Recovering High Dynamic Range Radiance Maps from Photographs", Debevec, Malik, 1997
|
||||
|
||||
class CV_EXPORTS_W MergeDebevec : public MergeExposures
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst,
|
||||
InputArray times, InputArray response) = 0;
|
||||
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, InputArray times) = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS_W Ptr<MergeDebevec> createMergeDebevec();
|
||||
|
||||
// "Exposure Fusion", Mertens et al., 2007
|
||||
|
||||
class CV_EXPORTS_W MergeMertens : public MergeExposures
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst,
|
||||
InputArray times, InputArray response) = 0;
|
||||
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst) = 0;
|
||||
|
||||
CV_WRAP virtual float getContrastWeight() const = 0;
|
||||
CV_WRAP virtual void setContrastWeight(float contrast_weiht) = 0;
|
||||
|
||||
CV_WRAP virtual float getSaturationWeight() const = 0;
|
||||
CV_WRAP virtual void setSaturationWeight(float saturation_weight) = 0;
|
||||
|
||||
CV_WRAP virtual float getExposureWeight() const = 0;
|
||||
CV_WRAP virtual void setExposureWeight(float exposure_weight) = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS_W Ptr<MergeMertens>
|
||||
createMergeMertens(float contrast_weight = 1.0f, float saturation_weight = 1.0f, float exposure_weight = 0.0f);
|
||||
|
||||
// "Dynamic range improvement through multiple exposures", Robertson et al., 1999
|
||||
|
||||
class CV_EXPORTS_W MergeRobertson : public MergeExposures
|
||||
{
|
||||
public:
|
||||
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst,
|
||||
InputArray times, InputArray response) = 0;
|
||||
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, InputArray times) = 0;
|
||||
};
|
||||
|
||||
CV_EXPORTS_W Ptr<MergeRobertson> createMergeRobertson();
|
||||
|
||||
} // cv
|
||||
|
||||
#endif
|
||||
|
270
modules/photo/src/align.cpp
Normal file
270
modules/photo/src/align.cpp
Normal file
@ -0,0 +1,270 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/photo.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "hdr_common.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class AlignMTBImpl : public AlignMTB
|
||||
{
|
||||
public:
|
||||
AlignMTBImpl(int _max_bits, int _exclude_range, bool _cut) :
|
||||
name("AlignMTB"),
|
||||
max_bits(_max_bits),
|
||||
exclude_range(_exclude_range),
|
||||
cut(_cut)
|
||||
{
|
||||
}
|
||||
|
||||
void process(InputArrayOfArrays src, std::vector<Mat>& dst,
|
||||
InputArray, InputArray)
|
||||
{
|
||||
process(src, dst);
|
||||
}
|
||||
|
||||
void process(InputArrayOfArrays _src, std::vector<Mat>& dst)
|
||||
{
|
||||
std::vector<Mat> src;
|
||||
_src.getMatVector(src);
|
||||
|
||||
checkImageDimensions(src);
|
||||
dst.resize(src.size());
|
||||
|
||||
size_t pivot = src.size() / 2;
|
||||
dst[pivot] = src[pivot];
|
||||
Mat gray_base;
|
||||
cvtColor(src[pivot], gray_base, COLOR_RGB2GRAY);
|
||||
std::vector<Point> shifts;
|
||||
|
||||
for(size_t i = 0; i < src.size(); i++) {
|
||||
if(i == pivot) {
|
||||
shifts.push_back(Point(0, 0));
|
||||
continue;
|
||||
}
|
||||
Mat gray;
|
||||
cvtColor(src[i], gray, COLOR_RGB2GRAY);
|
||||
Point shift = calculateShift(gray_base, gray);
|
||||
shifts.push_back(shift);
|
||||
shiftMat(src[i], dst[i], shift);
|
||||
}
|
||||
if(cut) {
|
||||
Point max(0, 0), min(0, 0);
|
||||
for(size_t i = 0; i < shifts.size(); i++) {
|
||||
if(shifts[i].x > max.x) {
|
||||
max.x = shifts[i].x;
|
||||
}
|
||||
if(shifts[i].y > max.y) {
|
||||
max.y = shifts[i].y;
|
||||
}
|
||||
if(shifts[i].x < min.x) {
|
||||
min.x = shifts[i].x;
|
||||
}
|
||||
if(shifts[i].y < min.y) {
|
||||
min.y = shifts[i].y;
|
||||
}
|
||||
}
|
||||
Point size = dst[0].size();
|
||||
for(size_t i = 0; i < dst.size(); i++) {
|
||||
dst[i] = dst[i](Rect(max, min + size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Point calculateShift(InputArray _img0, InputArray _img1)
|
||||
{
|
||||
Mat img0 = _img0.getMat();
|
||||
Mat img1 = _img1.getMat();
|
||||
CV_Assert(img0.channels() == 1 && img0.type() == img1.type());
|
||||
CV_Assert(img0.size() == img0.size());
|
||||
|
||||
int maxlevel = static_cast<int>(log((double)max(img0.rows, img0.cols)) / log(2.0)) - 1;
|
||||
maxlevel = min(maxlevel, max_bits - 1);
|
||||
|
||||
std::vector<Mat> pyr0;
|
||||
std::vector<Mat> pyr1;
|
||||
buildPyr(img0, pyr0, maxlevel);
|
||||
buildPyr(img1, pyr1, maxlevel);
|
||||
|
||||
Point shift(0, 0);
|
||||
for(int level = maxlevel; level >= 0; level--) {
|
||||
|
||||
shift *= 2;
|
||||
Mat tb1, tb2, eb1, eb2;
|
||||
computeBitmaps(pyr0[level], tb1, eb1);
|
||||
computeBitmaps(pyr1[level], tb2, eb2);
|
||||
|
||||
int min_err = pyr0[level].total();
|
||||
Point new_shift(shift);
|
||||
for(int i = -1; i <= 1; i++) {
|
||||
for(int j = -1; j <= 1; j++) {
|
||||
Point test_shift = shift + Point(i, j);
|
||||
Mat shifted_tb2, shifted_eb2, diff;
|
||||
shiftMat(tb2, shifted_tb2, test_shift);
|
||||
shiftMat(eb2, shifted_eb2, test_shift);
|
||||
bitwise_xor(tb1, shifted_tb2, diff);
|
||||
bitwise_and(diff, eb1, diff);
|
||||
bitwise_and(diff, shifted_eb2, diff);
|
||||
int err = countNonZero(diff);
|
||||
if(err < min_err) {
|
||||
new_shift = test_shift;
|
||||
min_err = err;
|
||||
}
|
||||
}
|
||||
}
|
||||
shift = new_shift;
|
||||
}
|
||||
return shift;
|
||||
}
|
||||
|
||||
void shiftMat(InputArray _src, OutputArray _dst, const Point shift)
|
||||
{
|
||||
Mat src = _src.getMat();
|
||||
_dst.create(src.size(), src.type());
|
||||
Mat dst = _dst.getMat();
|
||||
|
||||
Mat res = Mat::zeros(src.size(), src.type());
|
||||
int width = src.cols - abs(shift.x);
|
||||
int height = src.rows - abs(shift.y);
|
||||
Rect dst_rect(max(shift.x, 0), max(shift.y, 0), width, height);
|
||||
Rect src_rect(max(-shift.x, 0), max(-shift.y, 0), width, height);
|
||||
src(src_rect).copyTo(res(dst_rect));
|
||||
res.copyTo(dst);
|
||||
}
|
||||
|
||||
int getMaxBits() const { return max_bits; }
|
||||
void setMaxBits(int val) { max_bits = val; }
|
||||
|
||||
int getExcludeRange() const { return exclude_range; }
|
||||
void setExcludeRange(int val) { exclude_range = val; }
|
||||
|
||||
bool getCut() const { return cut; }
|
||||
void setCut(bool val) { cut = val; }
|
||||
|
||||
void write(FileStorage& fs) const
|
||||
{
|
||||
fs << "name" << name
|
||||
<< "max_bits" << max_bits
|
||||
<< "exclude_range" << exclude_range
|
||||
<< "cut" << static_cast<int>(cut);
|
||||
}
|
||||
|
||||
void read(const FileNode& fn)
|
||||
{
|
||||
FileNode n = fn["name"];
|
||||
CV_Assert(n.isString() && String(n) == name);
|
||||
max_bits = fn["max_bits"];
|
||||
exclude_range = fn["exclude_range"];
|
||||
int cut_val = fn["cut"];
|
||||
cut = (cut_val != 0);
|
||||
}
|
||||
|
||||
void computeBitmaps(InputArray _img, OutputArray _tb, OutputArray _eb)
|
||||
{
|
||||
Mat img = _img.getMat();
|
||||
_tb.create(img.size(), CV_8U);
|
||||
_eb.create(img.size(), CV_8U);
|
||||
Mat tb = _tb.getMat(), eb = _eb.getMat();
|
||||
int median = getMedian(img);
|
||||
compare(img, median, tb, CMP_GT);
|
||||
compare(abs(img - median), exclude_range, eb, CMP_GT);
|
||||
}
|
||||
|
||||
protected:
|
||||
String name;
|
||||
int max_bits, exclude_range;
|
||||
bool cut;
|
||||
|
||||
void downsample(Mat& src, Mat& dst)
|
||||
{
|
||||
dst = Mat(src.rows / 2, src.cols / 2, CV_8UC1);
|
||||
|
||||
int offset = src.cols * 2;
|
||||
uchar *src_ptr = src.ptr();
|
||||
uchar *dst_ptr = dst.ptr();
|
||||
for(int y = 0; y < dst.rows; y ++) {
|
||||
uchar *ptr = src_ptr;
|
||||
for(int x = 0; x < dst.cols; x++) {
|
||||
dst_ptr[0] = ptr[0];
|
||||
dst_ptr++;
|
||||
ptr += 2;
|
||||
}
|
||||
src_ptr += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void buildPyr(Mat& img, std::vector<Mat>& pyr, int maxlevel)
|
||||
{
|
||||
pyr.resize(maxlevel + 1);
|
||||
pyr[0] = img.clone();
|
||||
for(int level = 0; level < maxlevel; level++) {
|
||||
downsample(pyr[level], pyr[level + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
int getMedian(Mat& img)
|
||||
{
|
||||
int channels = 0;
|
||||
Mat hist;
|
||||
int hist_size = LDR_SIZE;
|
||||
float range[] = {0, LDR_SIZE} ;
|
||||
const float* ranges[] = {range};
|
||||
calcHist(&img, 1, &channels, Mat(), hist, 1, &hist_size, ranges);
|
||||
float *ptr = hist.ptr<float>();
|
||||
int median = 0, sum = 0;
|
||||
int thresh = img.total() / 2;
|
||||
while(sum < thresh && median < LDR_SIZE) {
|
||||
sum += static_cast<int>(ptr[median]);
|
||||
median++;
|
||||
}
|
||||
return median;
|
||||
}
|
||||
};
|
||||
|
||||
Ptr<AlignMTB> createAlignMTB(int max_bits, int exclude_range, bool cut)
|
||||
{
|
||||
return makePtr<AlignMTBImpl>(max_bits, exclude_range, cut);
|
||||
}
|
||||
|
||||
}
|
276
modules/photo/src/calibrate.cpp
Normal file
276
modules/photo/src/calibrate.cpp
Normal file
@ -0,0 +1,276 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/photo.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
//#include "opencv2/highgui.hpp"
|
||||
#include "hdr_common.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class CalibrateDebevecImpl : public CalibrateDebevec
|
||||
{
|
||||
public:
|
||||
CalibrateDebevecImpl(int _samples, float _lambda, bool _random) :
|
||||
name("CalibrateDebevec"),
|
||||
samples(_samples),
|
||||
lambda(_lambda),
|
||||
random(_random),
|
||||
w(tringleWeights())
|
||||
{
|
||||
}
|
||||
|
||||
void process(InputArrayOfArrays src, OutputArray dst, InputArray _times)
|
||||
{
|
||||
std::vector<Mat> images;
|
||||
src.getMatVector(images);
|
||||
Mat times = _times.getMat();
|
||||
|
||||
CV_Assert(images.size() == times.total());
|
||||
checkImageDimensions(images);
|
||||
CV_Assert(images[0].depth() == CV_8U);
|
||||
|
||||
int channels = images[0].channels();
|
||||
int CV_32FCC = CV_MAKETYPE(CV_32F, channels);
|
||||
|
||||
dst.create(LDR_SIZE, 1, CV_32FCC);
|
||||
Mat result = dst.getMat();
|
||||
|
||||
std::vector<Point> sample_points;
|
||||
if(random) {
|
||||
for(int i = 0; i < samples; i++) {
|
||||
sample_points.push_back(Point(rand() % images[0].cols, rand() % images[0].rows));
|
||||
}
|
||||
} else {
|
||||
int x_points = static_cast<int>(sqrt(static_cast<double>(samples) * images[0].cols / images[0].rows));
|
||||
int y_points = samples / x_points;
|
||||
int step_x = images[0].cols / x_points;
|
||||
int step_y = images[0].rows / y_points;
|
||||
|
||||
for(int i = 0, x = step_x / 2; i < x_points; i++, x += step_x) {
|
||||
for(int j = 0, y = step_y; j < y_points; j++, y += step_y) {
|
||||
sample_points.push_back(Point(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Mat> result_split(channels);
|
||||
for(int channel = 0; channel < channels; channel++) {
|
||||
Mat A = Mat::zeros(sample_points.size() * images.size() + LDR_SIZE + 1, LDR_SIZE + sample_points.size(), CV_32F);
|
||||
Mat B = Mat::zeros(A.rows, 1, CV_32F);
|
||||
|
||||
int eq = 0;
|
||||
for(size_t i = 0; i < sample_points.size(); i++) {
|
||||
for(size_t j = 0; j < images.size(); j++) {
|
||||
|
||||
int val = images[j].ptr()[3*(sample_points[i].y * images[j].cols + sample_points[j].x) + channel];
|
||||
A.at<float>(eq, val) = w.at<float>(val);
|
||||
A.at<float>(eq, LDR_SIZE + i) = -w.at<float>(val);
|
||||
B.at<float>(eq, 0) = w.at<float>(val) * log(times.at<float>(j));
|
||||
eq++;
|
||||
}
|
||||
}
|
||||
A.at<float>(eq, LDR_SIZE / 2) = 1;
|
||||
eq++;
|
||||
|
||||
for(int i = 0; i < 254; i++) {
|
||||
A.at<float>(eq, i) = lambda * w.at<float>(i + 1);
|
||||
A.at<float>(eq, i + 1) = -2 * lambda * w.at<float>(i + 1);
|
||||
A.at<float>(eq, i + 2) = lambda * w.at<float>(i + 1);
|
||||
eq++;
|
||||
}
|
||||
Mat solution;
|
||||
solve(A, B, solution, DECOMP_SVD);
|
||||
solution.rowRange(0, LDR_SIZE).copyTo(result_split[channel]);
|
||||
}
|
||||
merge(result_split, result);
|
||||
exp(result, result);
|
||||
}
|
||||
|
||||
int getSamples() const { return samples; }
|
||||
void setSamples(int val) { samples = val; }
|
||||
|
||||
float getLambda() const { return lambda; }
|
||||
void setLambda(float val) { lambda = val; }
|
||||
|
||||
bool getRandom() const { return random; }
|
||||
void setRandom(bool val) { random = val; }
|
||||
|
||||
void write(FileStorage& fs) const
|
||||
{
|
||||
fs << "name" << name
|
||||
<< "samples" << samples
|
||||
<< "lambda" << lambda
|
||||
<< "random" << static_cast<int>(random);
|
||||
}
|
||||
|
||||
void read(const FileNode& fn)
|
||||
{
|
||||
FileNode n = fn["name"];
|
||||
CV_Assert(n.isString() && String(n) == name);
|
||||
samples = fn["samples"];
|
||||
lambda = fn["lambda"];
|
||||
int random_val = fn["random"];
|
||||
random = (random_val != 0);
|
||||
}
|
||||
|
||||
protected:
|
||||
String name;
|
||||
int samples;
|
||||
float lambda;
|
||||
bool random;
|
||||
Mat w;
|
||||
};
|
||||
|
||||
Ptr<CalibrateDebevec> createCalibrateDebevec(int samples, float lambda, bool random)
|
||||
{
|
||||
return makePtr<CalibrateDebevecImpl>(samples, lambda, random);
|
||||
}
|
||||
|
||||
class CalibrateRobertsonImpl : public CalibrateRobertson
|
||||
{
|
||||
public:
|
||||
CalibrateRobertsonImpl(int _max_iter, float _threshold) :
|
||||
name("CalibrateRobertson"),
|
||||
max_iter(_max_iter),
|
||||
threshold(_threshold),
|
||||
weight(RobertsonWeights())
|
||||
{
|
||||
}
|
||||
|
||||
void process(InputArrayOfArrays src, OutputArray dst, InputArray _times)
|
||||
{
|
||||
std::vector<Mat> images;
|
||||
src.getMatVector(images);
|
||||
Mat times = _times.getMat();
|
||||
|
||||
CV_Assert(images.size() == times.total());
|
||||
checkImageDimensions(images);
|
||||
CV_Assert(images[0].depth() == CV_8U);
|
||||
|
||||
int channels = images[0].channels();
|
||||
int CV_32FCC = CV_MAKETYPE(CV_32F, channels);
|
||||
|
||||
dst.create(LDR_SIZE, 1, CV_32FCC);
|
||||
Mat response = dst.getMat();
|
||||
response = linearResponse(3) / (LDR_SIZE / 2.0f);
|
||||
|
||||
Mat card = Mat::zeros(LDR_SIZE, 1, CV_32FCC);
|
||||
for(size_t i = 0; i < images.size(); i++) {
|
||||
uchar *ptr = images[i].ptr();
|
||||
for(size_t pos = 0; pos < images[i].total(); pos++) {
|
||||
for(int c = 0; c < channels; c++, ptr++) {
|
||||
card.at<Vec3f>(*ptr)[c] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
card = 1.0 / card;
|
||||
|
||||
Ptr<MergeRobertson> merge = createMergeRobertson();
|
||||
for(int iter = 0; iter < max_iter; iter++) {
|
||||
|
||||
radiance = Mat::zeros(images[0].size(), CV_32FCC);
|
||||
merge->process(images, radiance, times, response);
|
||||
|
||||
Mat new_response = Mat::zeros(LDR_SIZE, 1, CV_32FC3);
|
||||
for(size_t i = 0; i < images.size(); i++) {
|
||||
uchar *ptr = images[i].ptr();
|
||||
float* rad_ptr = radiance.ptr<float>();
|
||||
for(size_t pos = 0; pos < images[i].total(); pos++) {
|
||||
for(int c = 0; c < channels; c++, ptr++, rad_ptr++) {
|
||||
new_response.at<Vec3f>(*ptr)[c] += times.at<float>(i) * *rad_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
new_response = new_response.mul(card);
|
||||
for(int c = 0; c < 3; c++) {
|
||||
float middle = new_response.at<Vec3f>(LDR_SIZE / 2)[c];
|
||||
for(int i = 0; i < LDR_SIZE; i++) {
|
||||
new_response.at<Vec3f>(i)[c] /= middle;
|
||||
}
|
||||
}
|
||||
float diff = static_cast<float>(sum(sum(abs(new_response - response)))[0] / channels);
|
||||
new_response.copyTo(response);
|
||||
if(diff < threshold) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getMaxIter() const { return max_iter; }
|
||||
void setMaxIter(int val) { max_iter = val; }
|
||||
|
||||
float getThreshold() const { return threshold; }
|
||||
void setThreshold(float val) { threshold = val; }
|
||||
|
||||
Mat getRadiance() const { return radiance; }
|
||||
|
||||
void write(FileStorage& fs) const
|
||||
{
|
||||
fs << "name" << name
|
||||
<< "max_iter" << max_iter
|
||||
<< "threshold" << threshold;
|
||||
}
|
||||
|
||||
void read(const FileNode& fn)
|
||||
{
|
||||
FileNode n = fn["name"];
|
||||
CV_Assert(n.isString() && String(n) == name);
|
||||
max_iter = fn["max_iter"];
|
||||
threshold = fn["threshold"];
|
||||
}
|
||||
|
||||
protected:
|
||||
String name;
|
||||
int max_iter;
|
||||
float threshold;
|
||||
Mat weight, radiance;
|
||||
};
|
||||
|
||||
Ptr<CalibrateRobertson> createCalibrateRobertson(int max_iter, float threshold)
|
||||
{
|
||||
return makePtr<CalibrateRobertsonImpl>(max_iter, threshold);
|
||||
}
|
||||
|
||||
}
|
@ -116,7 +116,7 @@ static void fastNlMeansDenoisingMultiCheckPreconditions(
|
||||
int imgToDenoiseIndex, int temporalWindowSize,
|
||||
int templateWindowSize, int searchWindowSize)
|
||||
{
|
||||
int src_imgs_size = (int)srcImgs.size();
|
||||
int src_imgs_size = static_cast<int>(srcImgs.size());
|
||||
if (src_imgs_size == 0) {
|
||||
CV_Error(Error::StsBadArg, "Input images vector should not be empty!");
|
||||
}
|
||||
@ -198,7 +198,7 @@ void cv::fastNlMeansDenoisingColoredMulti( InputArrayOfArrays _srcImgs, OutputAr
|
||||
_dst.create(srcImgs[0].size(), srcImgs[0].type());
|
||||
Mat dst = _dst.getMat();
|
||||
|
||||
int src_imgs_size = (int)srcImgs.size();
|
||||
int src_imgs_size = static_cast<int>(srcImgs.size());
|
||||
|
||||
if (srcImgs[0].type() != CV_8UC3) {
|
||||
CV_Error(Error::StsBadArg, "Type of input images should be CV_8UC3!");
|
||||
|
105
modules/photo/src/hdr_common.cpp
Normal file
105
modules/photo/src/hdr_common.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/photo.hpp"
|
||||
#include "hdr_common.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
void checkImageDimensions(const std::vector<Mat>& images)
|
||||
{
|
||||
CV_Assert(!images.empty());
|
||||
int width = images[0].cols;
|
||||
int height = images[0].rows;
|
||||
int type = images[0].type();
|
||||
|
||||
for(size_t i = 0; i < images.size(); i++) {
|
||||
CV_Assert(images[i].cols == width && images[i].rows == height);
|
||||
CV_Assert(images[i].type() == type);
|
||||
}
|
||||
}
|
||||
|
||||
Mat tringleWeights()
|
||||
{
|
||||
Mat w(LDR_SIZE, 1, CV_32F);
|
||||
int half = LDR_SIZE / 2;
|
||||
for(int i = 0; i < LDR_SIZE; i++) {
|
||||
w.at<float>(i) = i < half ? i + 1.0f : LDR_SIZE - i;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
Mat RobertsonWeights()
|
||||
{
|
||||
Mat weight(LDR_SIZE, 1, CV_32FC3);
|
||||
float q = (LDR_SIZE - 1) / 4.0f;
|
||||
for(int i = 0; i < LDR_SIZE; i++) {
|
||||
float value = i / q - 2.0f;
|
||||
value = exp(-value * value);
|
||||
weight.at<Vec3f>(i) = Vec3f::all(value);
|
||||
}
|
||||
return weight;
|
||||
}
|
||||
|
||||
void mapLuminance(Mat src, Mat dst, Mat lum, Mat new_lum, float saturation)
|
||||
{
|
||||
std::vector<Mat> channels(3);
|
||||
split(src, channels);
|
||||
for(int i = 0; i < 3; i++) {
|
||||
channels[i] = channels[i].mul(1.0f / lum);
|
||||
pow(channels[i], saturation, channels[i]);
|
||||
channels[i] = channels[i].mul(new_lum);
|
||||
}
|
||||
merge(channels, dst);
|
||||
}
|
||||
|
||||
Mat linearResponse(int channels)
|
||||
{
|
||||
Mat response = Mat(LDR_SIZE, 1, CV_MAKETYPE(CV_32F, channels));
|
||||
for(int i = 0; i < LDR_SIZE; i++) {
|
||||
response.at<Vec3f>(i) = Vec3f::all(static_cast<float>(i));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
};
|
62
modules/photo/src/hdr_common.hpp
Normal file
62
modules/photo/src/hdr_common.hpp
Normal file
@ -0,0 +1,62 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef __OPENCV_HDR_COMMON_HPP__
|
||||
#define __OPENCV_HDR_COMMON_HPP__
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/photo.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
void checkImageDimensions(const std::vector<Mat>& images);
|
||||
|
||||
Mat tringleWeights();
|
||||
|
||||
void mapLuminance(Mat src, Mat dst, Mat lum, Mat new_lum, float saturation);
|
||||
|
||||
Mat RobertsonWeights();
|
||||
|
||||
Mat linearResponse(int channels);
|
||||
};
|
||||
|
||||
#endif
|
351
modules/photo/src/merge.cpp
Normal file
351
modules/photo/src/merge.cpp
Normal file
@ -0,0 +1,351 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/photo.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "hdr_common.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class MergeDebevecImpl : public MergeDebevec
|
||||
{
|
||||
public:
|
||||
MergeDebevecImpl() :
|
||||
name("MergeDebevec"),
|
||||
weights(tringleWeights())
|
||||
{
|
||||
}
|
||||
|
||||
void process(InputArrayOfArrays src, OutputArray dst, InputArray _times, InputArray input_response)
|
||||
{
|
||||
std::vector<Mat> images;
|
||||
src.getMatVector(images);
|
||||
Mat times = _times.getMat();
|
||||
|
||||
CV_Assert(images.size() == times.total());
|
||||
checkImageDimensions(images);
|
||||
CV_Assert(images[0].depth() == CV_8U);
|
||||
|
||||
int channels = images[0].channels();
|
||||
Size size = images[0].size();
|
||||
int CV_32FCC = CV_MAKETYPE(CV_32F, channels);
|
||||
|
||||
dst.create(images[0].size(), CV_32FCC);
|
||||
Mat result = dst.getMat();
|
||||
|
||||
Mat response = input_response.getMat();
|
||||
|
||||
if(response.empty()) {
|
||||
response = linearResponse(channels);
|
||||
response.at<Vec3f>(0) = response.at<Vec3f>(1);
|
||||
}
|
||||
log(response, response);
|
||||
CV_Assert(response.rows == LDR_SIZE && response.cols == 1 &&
|
||||
response.channels() == channels);
|
||||
|
||||
Mat exp_values(times);
|
||||
log(exp_values, exp_values);
|
||||
|
||||
result = Mat::zeros(size, CV_32FCC);
|
||||
std::vector<Mat> result_split;
|
||||
split(result, result_split);
|
||||
Mat weight_sum = Mat::zeros(size, CV_32F);
|
||||
|
||||
for(size_t i = 0; i < images.size(); i++) {
|
||||
std::vector<Mat> splitted;
|
||||
split(images[i], splitted);
|
||||
|
||||
Mat w = Mat::zeros(size, CV_32F);
|
||||
for(int c = 0; c < channels; c++) {
|
||||
LUT(splitted[c], weights, splitted[c]);
|
||||
w += splitted[c];
|
||||
}
|
||||
w /= channels;
|
||||
|
||||
Mat response_img;
|
||||
LUT(images[i], response, response_img);
|
||||
split(response_img, splitted);
|
||||
for(int c = 0; c < channels; c++) {
|
||||
result_split[c] += w.mul(splitted[c] - exp_values.at<float>(i));
|
||||
}
|
||||
weight_sum += w;
|
||||
}
|
||||
weight_sum = 1.0f / weight_sum;
|
||||
for(int c = 0; c < channels; c++) {
|
||||
result_split[c] = result_split[c].mul(weight_sum);
|
||||
}
|
||||
merge(result_split, result);
|
||||
exp(result, result);
|
||||
}
|
||||
|
||||
void process(InputArrayOfArrays src, OutputArray dst, InputArray times)
|
||||
{
|
||||
process(src, dst, times, Mat());
|
||||
}
|
||||
|
||||
protected:
|
||||
String name;
|
||||
Mat weights;
|
||||
};
|
||||
|
||||
Ptr<MergeDebevec> createMergeDebevec()
|
||||
{
|
||||
return makePtr<MergeDebevecImpl>();
|
||||
}
|
||||
|
||||
class MergeMertensImpl : public MergeMertens
|
||||
{
|
||||
public:
|
||||
MergeMertensImpl(float _wcon, float _wsat, float _wexp) :
|
||||
name("MergeMertens"),
|
||||
wcon(_wcon),
|
||||
wsat(_wsat),
|
||||
wexp(_wexp)
|
||||
{
|
||||
}
|
||||
|
||||
void process(InputArrayOfArrays src, OutputArrayOfArrays dst, InputArray, InputArray)
|
||||
{
|
||||
process(src, dst);
|
||||
}
|
||||
|
||||
void process(InputArrayOfArrays src, OutputArray dst)
|
||||
{
|
||||
std::vector<Mat> images;
|
||||
src.getMatVector(images);
|
||||
checkImageDimensions(images);
|
||||
|
||||
int channels = images[0].channels();
|
||||
CV_Assert(channels == 1 || channels == 3);
|
||||
Size size = images[0].size();
|
||||
int CV_32FCC = CV_MAKETYPE(CV_32F, channels);
|
||||
|
||||
std::vector<Mat> weights(images.size());
|
||||
Mat weight_sum = Mat::zeros(size, CV_32F);
|
||||
|
||||
for(size_t i = 0; i < images.size(); i++) {
|
||||
Mat img, gray, contrast, saturation, wellexp;
|
||||
std::vector<Mat> splitted(channels);
|
||||
|
||||
images[i].convertTo(img, CV_32F, 1.0f/255.0f);
|
||||
if(channels == 3) {
|
||||
cvtColor(img, gray, COLOR_RGB2GRAY);
|
||||
} else {
|
||||
img.copyTo(gray);
|
||||
}
|
||||
split(img, splitted);
|
||||
|
||||
Laplacian(gray, contrast, CV_32F);
|
||||
contrast = abs(contrast);
|
||||
|
||||
Mat mean = Mat::zeros(size, CV_32F);
|
||||
for(int c = 0; c < channels; c++) {
|
||||
mean += splitted[c];
|
||||
}
|
||||
mean /= channels;
|
||||
|
||||
saturation = Mat::zeros(size, CV_32F);
|
||||
for(int c = 0; c < channels; c++) {
|
||||
Mat deviation = splitted[c] - mean;
|
||||
pow(deviation, 2.0f, deviation);
|
||||
saturation += deviation;
|
||||
}
|
||||
sqrt(saturation, saturation);
|
||||
|
||||
wellexp = Mat::ones(size, CV_32F);
|
||||
for(int c = 0; c < channels; c++) {
|
||||
Mat exp = splitted[c] - 0.5f;
|
||||
pow(exp, 2.0f, exp);
|
||||
exp = -exp / 0.08f;
|
||||
wellexp = wellexp.mul(exp);
|
||||
}
|
||||
|
||||
pow(contrast, wcon, contrast);
|
||||
pow(saturation, wsat, saturation);
|
||||
pow(wellexp, wexp, wellexp);
|
||||
|
||||
weights[i] = contrast;
|
||||
if(channels == 3) {
|
||||
weights[i] = weights[i].mul(saturation);
|
||||
}
|
||||
weights[i] = weights[i].mul(wellexp);
|
||||
weight_sum += weights[i];
|
||||
}
|
||||
int maxlevel = static_cast<int>(logf(static_cast<float>(min(size.width, size.height))) / logf(2.0f));
|
||||
std::vector<Mat> res_pyr(maxlevel + 1);
|
||||
|
||||
for(size_t i = 0; i < images.size(); i++) {
|
||||
weights[i] /= weight_sum;
|
||||
Mat img;
|
||||
images[i].convertTo(img, CV_32F, 1.0f/255.0f);
|
||||
|
||||
std::vector<Mat> img_pyr, weight_pyr;
|
||||
buildPyramid(img, img_pyr, maxlevel);
|
||||
buildPyramid(weights[i], weight_pyr, maxlevel);
|
||||
|
||||
for(int lvl = 0; lvl < maxlevel; lvl++) {
|
||||
Mat up;
|
||||
pyrUp(img_pyr[lvl + 1], up, img_pyr[lvl].size());
|
||||
img_pyr[lvl] -= up;
|
||||
}
|
||||
for(int lvl = 0; lvl <= maxlevel; lvl++) {
|
||||
std::vector<Mat> splitted(channels);
|
||||
split(img_pyr[lvl], splitted);
|
||||
for(int c = 0; c < channels; c++) {
|
||||
splitted[c] = splitted[c].mul(weight_pyr[lvl]);
|
||||
}
|
||||
merge(splitted, img_pyr[lvl]);
|
||||
if(res_pyr[lvl].empty()) {
|
||||
res_pyr[lvl] = img_pyr[lvl];
|
||||
} else {
|
||||
res_pyr[lvl] += img_pyr[lvl];
|
||||
}
|
||||
}
|
||||
}
|
||||
for(int lvl = maxlevel; lvl > 0; lvl--) {
|
||||
Mat up;
|
||||
pyrUp(res_pyr[lvl], up, res_pyr[lvl - 1].size());
|
||||
res_pyr[lvl - 1] += up;
|
||||
}
|
||||
dst.create(size, CV_32FCC);
|
||||
res_pyr[0].copyTo(dst.getMat());
|
||||
}
|
||||
|
||||
float getContrastWeight() const { return wcon; }
|
||||
void setContrastWeight(float val) { wcon = val; }
|
||||
|
||||
float getSaturationWeight() const { return wsat; }
|
||||
void setSaturationWeight(float val) { wsat = val; }
|
||||
|
||||
float getExposureWeight() const { return wexp; }
|
||||
void setExposureWeight(float val) { wexp = val; }
|
||||
|
||||
void write(FileStorage& fs) const
|
||||
{
|
||||
fs << "name" << name
|
||||
<< "contrast_weight" << wcon
|
||||
<< "saturation_weight" << wsat
|
||||
<< "exposure_weight" << wexp;
|
||||
}
|
||||
|
||||
void read(const FileNode& fn)
|
||||
{
|
||||
FileNode n = fn["name"];
|
||||
CV_Assert(n.isString() && String(n) == name);
|
||||
wcon = fn["contrast_weight"];
|
||||
wsat = fn["saturation_weight"];
|
||||
wexp = fn["exposure_weight"];
|
||||
}
|
||||
|
||||
protected:
|
||||
String name;
|
||||
float wcon, wsat, wexp;
|
||||
};
|
||||
|
||||
Ptr<MergeMertens> createMergeMertens(float wcon, float wsat, float wexp)
|
||||
{
|
||||
return makePtr<MergeMertensImpl>(wcon, wsat, wexp);
|
||||
}
|
||||
|
||||
class MergeRobertsonImpl : public MergeRobertson
|
||||
{
|
||||
public:
|
||||
MergeRobertsonImpl() :
|
||||
name("MergeRobertson"),
|
||||
weight(RobertsonWeights())
|
||||
{
|
||||
}
|
||||
|
||||
void process(InputArrayOfArrays src, OutputArray dst, InputArray _times, InputArray input_response)
|
||||
{
|
||||
std::vector<Mat> images;
|
||||
src.getMatVector(images);
|
||||
Mat times = _times.getMat();
|
||||
|
||||
CV_Assert(images.size() == times.total());
|
||||
checkImageDimensions(images);
|
||||
CV_Assert(images[0].depth() == CV_8U);
|
||||
|
||||
int channels = images[0].channels();
|
||||
int CV_32FCC = CV_MAKETYPE(CV_32F, channels);
|
||||
|
||||
dst.create(images[0].size(), CV_32FCC);
|
||||
Mat result = dst.getMat();
|
||||
|
||||
Mat response = input_response.getMat();
|
||||
if(response.empty()) {
|
||||
float middle = LDR_SIZE / 2.0f;
|
||||
response = linearResponse(channels) / middle;
|
||||
}
|
||||
CV_Assert(response.rows == LDR_SIZE && response.cols == 1 &&
|
||||
response.channels() == channels);
|
||||
|
||||
result = Mat::zeros(images[0].size(), CV_32FCC);
|
||||
Mat wsum = Mat::zeros(images[0].size(), CV_32FCC);
|
||||
for(size_t i = 0; i < images.size(); i++) {
|
||||
Mat im, w;
|
||||
LUT(images[i], weight, w);
|
||||
LUT(images[i], response, im);
|
||||
|
||||
result += times.at<float>(i) * w.mul(im);
|
||||
wsum += times.at<float>(i) * times.at<float>(i) * w;
|
||||
}
|
||||
result = result.mul(1 / wsum);
|
||||
}
|
||||
|
||||
void process(InputArrayOfArrays src, OutputArray dst, InputArray times)
|
||||
{
|
||||
process(src, dst, times, Mat());
|
||||
}
|
||||
|
||||
protected:
|
||||
String name;
|
||||
Mat weight;
|
||||
};
|
||||
|
||||
Ptr<MergeRobertson> createMergeRobertson()
|
||||
{
|
||||
return makePtr<MergeRobertsonImpl>();
|
||||
}
|
||||
|
||||
}
|
531
modules/photo/src/tonemap.cpp
Normal file
531
modules/photo/src/tonemap.cpp
Normal file
@ -0,0 +1,531 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/photo.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "hdr_common.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class TonemapImpl : public Tonemap
|
||||
{
|
||||
public:
|
||||
TonemapImpl(float _gamma) : name("Tonemap"), gamma(_gamma)
|
||||
{
|
||||
}
|
||||
|
||||
void process(InputArray _src, OutputArray _dst)
|
||||
{
|
||||
Mat src = _src.getMat();
|
||||
CV_Assert(!src.empty());
|
||||
_dst.create(src.size(), CV_32FC3);
|
||||
Mat dst = _dst.getMat();
|
||||
|
||||
double min, max;
|
||||
minMaxLoc(src, &min, &max);
|
||||
if(max - min > DBL_EPSILON) {
|
||||
dst = (src - min) / (max - min);
|
||||
} else {
|
||||
src.copyTo(dst);
|
||||
}
|
||||
|
||||
pow(dst, 1.0f / gamma, dst);
|
||||
}
|
||||
|
||||
float getGamma() const { return gamma; }
|
||||
void setGamma(float val) { gamma = val; }
|
||||
|
||||
void write(FileStorage& fs) const
|
||||
{
|
||||
fs << "name" << name
|
||||
<< "gamma" << gamma;
|
||||
}
|
||||
|
||||
void read(const FileNode& fn)
|
||||
{
|
||||
FileNode n = fn["name"];
|
||||
CV_Assert(n.isString() && String(n) == name);
|
||||
gamma = fn["gamma"];
|
||||
}
|
||||
|
||||
protected:
|
||||
String name;
|
||||
float gamma;
|
||||
};
|
||||
|
||||
Ptr<Tonemap> createTonemap(float gamma)
|
||||
{
|
||||
return makePtr<TonemapImpl>(gamma);
|
||||
}
|
||||
|
||||
class TonemapDragoImpl : public TonemapDrago
|
||||
{
|
||||
public:
|
||||
TonemapDragoImpl(float _gamma, float _saturation, float _bias) :
|
||||
name("TonemapDrago"),
|
||||
gamma(_gamma),
|
||||
saturation(_saturation),
|
||||
bias(_bias)
|
||||
{
|
||||
}
|
||||
|
||||
void process(InputArray _src, OutputArray _dst)
|
||||
{
|
||||
Mat src = _src.getMat();
|
||||
CV_Assert(!src.empty());
|
||||
_dst.create(src.size(), CV_32FC3);
|
||||
Mat img = _dst.getMat();
|
||||
|
||||
Ptr<Tonemap> linear = createTonemap(1.0f);
|
||||
linear->process(src, img);
|
||||
|
||||
Mat gray_img;
|
||||
cvtColor(img, gray_img, COLOR_RGB2GRAY);
|
||||
Mat log_img;
|
||||
log(gray_img, log_img);
|
||||
float mean = expf(static_cast<float>(sum(log_img)[0]) / log_img.total());
|
||||
gray_img /= mean;
|
||||
log_img.release();
|
||||
|
||||
double max;
|
||||
minMaxLoc(gray_img, NULL, &max);
|
||||
|
||||
Mat map;
|
||||
log(gray_img + 1.0f, map);
|
||||
Mat div;
|
||||
pow(gray_img / static_cast<float>(max), logf(bias) / logf(0.5f), div);
|
||||
log(2.0f + 8.0f * div, div);
|
||||
map = map.mul(1.0f / div);
|
||||
div.release();
|
||||
|
||||
mapLuminance(img, img, gray_img, map, saturation);
|
||||
|
||||
linear->setGamma(gamma);
|
||||
linear->process(img, img);
|
||||
}
|
||||
|
||||
float getGamma() const { return gamma; }
|
||||
void setGamma(float val) { gamma = val; }
|
||||
|
||||
float getSaturation() const { return saturation; }
|
||||
void setSaturation(float val) { saturation = val; }
|
||||
|
||||
float getBias() const { return bias; }
|
||||
void setBias(float val) { bias = val; }
|
||||
|
||||
void write(FileStorage& fs) const
|
||||
{
|
||||
fs << "name" << name
|
||||
<< "gamma" << gamma
|
||||
<< "bias" << bias
|
||||
<< "saturation" << saturation;
|
||||
}
|
||||
|
||||
void read(const FileNode& fn)
|
||||
{
|
||||
FileNode n = fn["name"];
|
||||
CV_Assert(n.isString() && String(n) == name);
|
||||
gamma = fn["gamma"];
|
||||
bias = fn["bias"];
|
||||
saturation = fn["saturation"];
|
||||
}
|
||||
|
||||
protected:
|
||||
String name;
|
||||
float gamma, saturation, bias;
|
||||
};
|
||||
|
||||
Ptr<TonemapDrago> createTonemapDrago(float gamma, float saturation, float bias)
|
||||
{
|
||||
return makePtr<TonemapDragoImpl>(gamma, saturation, bias);
|
||||
}
|
||||
|
||||
class TonemapDurandImpl : public TonemapDurand
|
||||
{
|
||||
public:
|
||||
TonemapDurandImpl(float _gamma, float _contrast, float _saturation, float _sigma_color, float _sigma_space) :
|
||||
name("TonemapDurand"),
|
||||
gamma(_gamma),
|
||||
contrast(_contrast),
|
||||
saturation(_saturation),
|
||||
sigma_color(_sigma_color),
|
||||
sigma_space(_sigma_space)
|
||||
{
|
||||
}
|
||||
|
||||
void process(InputArray _src, OutputArray _dst)
|
||||
{
|
||||
Mat src = _src.getMat();
|
||||
CV_Assert(!src.empty());
|
||||
_dst.create(src.size(), CV_32FC3);
|
||||
Mat img = _dst.getMat();
|
||||
Ptr<Tonemap> linear = createTonemap(1.0f);
|
||||
linear->process(src, img);
|
||||
|
||||
Mat gray_img;
|
||||
cvtColor(img, gray_img, COLOR_RGB2GRAY);
|
||||
Mat log_img;
|
||||
log(gray_img, log_img);
|
||||
Mat map_img;
|
||||
bilateralFilter(log_img, map_img, -1, sigma_color, sigma_space);
|
||||
|
||||
double min, max;
|
||||
minMaxLoc(map_img, &min, &max);
|
||||
float scale = contrast / static_cast<float>(max - min);
|
||||
exp(map_img * (scale - 1.0f) + log_img, map_img);
|
||||
log_img.release();
|
||||
|
||||
mapLuminance(img, img, gray_img, map_img, saturation);
|
||||
pow(img, 1.0f / gamma, img);
|
||||
}
|
||||
|
||||
float getGamma() const { return gamma; }
|
||||
void setGamma(float val) { gamma = val; }
|
||||
|
||||
float getSaturation() const { return saturation; }
|
||||
void setSaturation(float val) { saturation = val; }
|
||||
|
||||
float getContrast() const { return contrast; }
|
||||
void setContrast(float val) { contrast = val; }
|
||||
|
||||
float getSigmaColor() const { return sigma_color; }
|
||||
void setSigmaColor(float val) { sigma_color = val; }
|
||||
|
||||
float getSigmaSpace() const { return sigma_space; }
|
||||
void setSigmaSpace(float val) { sigma_space = val; }
|
||||
|
||||
void write(FileStorage& fs) const
|
||||
{
|
||||
fs << "name" << name
|
||||
<< "gamma" << gamma
|
||||
<< "contrast" << contrast
|
||||
<< "sigma_color" << sigma_color
|
||||
<< "sigma_space" << sigma_space
|
||||
<< "saturation" << saturation;
|
||||
}
|
||||
|
||||
void read(const FileNode& fn)
|
||||
{
|
||||
FileNode n = fn["name"];
|
||||
CV_Assert(n.isString() && String(n) == name);
|
||||
gamma = fn["gamma"];
|
||||
contrast = fn["contrast"];
|
||||
sigma_color = fn["sigma_color"];
|
||||
sigma_space = fn["sigma_space"];
|
||||
saturation = fn["saturation"];
|
||||
}
|
||||
|
||||
protected:
|
||||
String name;
|
||||
float gamma, contrast, saturation, sigma_color, sigma_space;
|
||||
};
|
||||
|
||||
Ptr<TonemapDurand> createTonemapDurand(float gamma, float contrast, float saturation, float sigma_color, float sigma_space)
|
||||
{
|
||||
return makePtr<TonemapDurandImpl>(gamma, contrast, saturation, sigma_color, sigma_space);
|
||||
}
|
||||
|
||||
class TonemapReinhardImpl : public TonemapReinhard
|
||||
{
|
||||
public:
|
||||
TonemapReinhardImpl(float _gamma, float _intensity, float _light_adapt, float _color_adapt) :
|
||||
name("TonemapReinhard"),
|
||||
gamma(_gamma),
|
||||
intensity(_intensity),
|
||||
light_adapt(_light_adapt),
|
||||
color_adapt(_color_adapt)
|
||||
{
|
||||
}
|
||||
|
||||
void process(InputArray _src, OutputArray _dst)
|
||||
{
|
||||
Mat src = _src.getMat();
|
||||
CV_Assert(!src.empty());
|
||||
_dst.create(src.size(), CV_32FC3);
|
||||
Mat img = _dst.getMat();
|
||||
Ptr<Tonemap> linear = createTonemap(1.0f);
|
||||
linear->process(src, img);
|
||||
|
||||
Mat gray_img;
|
||||
cvtColor(img, gray_img, COLOR_RGB2GRAY);
|
||||
Mat log_img;
|
||||
log(gray_img, log_img);
|
||||
|
||||
float log_mean = static_cast<float>(sum(log_img)[0] / log_img.total());
|
||||
double log_min, log_max;
|
||||
minMaxLoc(log_img, &log_min, &log_max);
|
||||
log_img.release();
|
||||
|
||||
double key = static_cast<float>((log_max - log_mean) / (log_max - log_min));
|
||||
float map_key = 0.3f + 0.7f * pow(static_cast<float>(key), 1.4f);
|
||||
intensity = exp(-intensity);
|
||||
Scalar chan_mean = mean(img);
|
||||
float gray_mean = static_cast<float>(mean(gray_img)[0]);
|
||||
|
||||
std::vector<Mat> channels(3);
|
||||
split(img, channels);
|
||||
|
||||
for(int i = 0; i < 3; i++) {
|
||||
float global = color_adapt * static_cast<float>(chan_mean[i]) + (1.0f - color_adapt) * gray_mean;
|
||||
Mat adapt = color_adapt * channels[i] + (1.0f - color_adapt) * gray_img;
|
||||
adapt = light_adapt * adapt + (1.0f - light_adapt) * global;
|
||||
pow(intensity * adapt, map_key, adapt);
|
||||
channels[i] = channels[i].mul(1.0f / (adapt + channels[i]));
|
||||
}
|
||||
gray_img.release();
|
||||
merge(channels, img);
|
||||
|
||||
linear->setGamma(gamma);
|
||||
linear->process(img, img);
|
||||
}
|
||||
|
||||
float getGamma() const { return gamma; }
|
||||
void setGamma(float val) { gamma = val; }
|
||||
|
||||
float getIntensity() const { return intensity; }
|
||||
void setIntensity(float val) { intensity = val; }
|
||||
|
||||
float getLightAdaptation() const { return light_adapt; }
|
||||
void setLightAdaptation(float val) { light_adapt = val; }
|
||||
|
||||
float getColorAdaptation() const { return color_adapt; }
|
||||
void setColorAdaptation(float val) { color_adapt = val; }
|
||||
|
||||
void write(FileStorage& fs) const
|
||||
{
|
||||
fs << "name" << name
|
||||
<< "gamma" << gamma
|
||||
<< "intensity" << intensity
|
||||
<< "light_adapt" << light_adapt
|
||||
<< "color_adapt" << color_adapt;
|
||||
}
|
||||
|
||||
void read(const FileNode& fn)
|
||||
{
|
||||
FileNode n = fn["name"];
|
||||
CV_Assert(n.isString() && String(n) == name);
|
||||
gamma = fn["gamma"];
|
||||
intensity = fn["intensity"];
|
||||
light_adapt = fn["light_adapt"];
|
||||
color_adapt = fn["color_adapt"];
|
||||
}
|
||||
|
||||
protected:
|
||||
String name;
|
||||
float gamma, intensity, light_adapt, color_adapt;
|
||||
};
|
||||
|
||||
Ptr<TonemapReinhard> createTonemapReinhard(float gamma, float contrast, float sigma_color, float sigma_space)
|
||||
{
|
||||
return makePtr<TonemapReinhardImpl>(gamma, contrast, sigma_color, sigma_space);
|
||||
}
|
||||
|
||||
class TonemapMantiukImpl : public TonemapMantiuk
|
||||
{
|
||||
public:
|
||||
TonemapMantiukImpl(float _gamma, float _scale, float _saturation) :
|
||||
name("TonemapMantiuk"),
|
||||
gamma(_gamma),
|
||||
scale(_scale),
|
||||
saturation(_saturation)
|
||||
{
|
||||
}
|
||||
|
||||
void process(InputArray _src, OutputArray _dst)
|
||||
{
|
||||
Mat src = _src.getMat();
|
||||
CV_Assert(!src.empty());
|
||||
_dst.create(src.size(), CV_32FC3);
|
||||
Mat img = _dst.getMat();
|
||||
Ptr<Tonemap> linear = createTonemap(1.0f);
|
||||
linear->process(src, img);
|
||||
|
||||
Mat gray_img;
|
||||
cvtColor(img, gray_img, COLOR_RGB2GRAY);
|
||||
Mat log_img;
|
||||
log(gray_img, log_img);
|
||||
|
||||
std::vector<Mat> x_contrast, y_contrast;
|
||||
getContrast(log_img, x_contrast, y_contrast);
|
||||
|
||||
for(size_t i = 0; i < x_contrast.size(); i++) {
|
||||
mapContrast(x_contrast[i]);
|
||||
mapContrast(y_contrast[i]);
|
||||
}
|
||||
|
||||
Mat right(src.size(), CV_32F);
|
||||
calculateSum(x_contrast, y_contrast, right);
|
||||
|
||||
Mat p, r, product, x = log_img;
|
||||
calculateProduct(x, r);
|
||||
r = right - r;
|
||||
r.copyTo(p);
|
||||
|
||||
const float target_error = 1e-3f;
|
||||
float target_norm = static_cast<float>(right.dot(right)) * powf(target_error, 2.0f);
|
||||
int max_iterations = 100;
|
||||
float rr = static_cast<float>(r.dot(r));
|
||||
|
||||
for(int i = 0; i < max_iterations; i++)
|
||||
{
|
||||
calculateProduct(p, product);
|
||||
float alpha = rr / static_cast<float>(p.dot(product));
|
||||
|
||||
r -= alpha * product;
|
||||
x += alpha * p;
|
||||
|
||||
float new_rr = static_cast<float>(r.dot(r));
|
||||
p = r + (new_rr / rr) * p;
|
||||
rr = new_rr;
|
||||
|
||||
if(rr < target_norm) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
exp(x, x);
|
||||
mapLuminance(img, img, gray_img, x, saturation);
|
||||
|
||||
linear = createTonemap(gamma);
|
||||
linear->process(img, img);
|
||||
}
|
||||
|
||||
float getGamma() const { return gamma; }
|
||||
void setGamma(float val) { gamma = val; }
|
||||
|
||||
float getScale() const { return scale; }
|
||||
void setScale(float val) { scale = val; }
|
||||
|
||||
float getSaturation() const { return saturation; }
|
||||
void setSaturation(float val) { saturation = val; }
|
||||
|
||||
void write(FileStorage& fs) const
|
||||
{
|
||||
fs << "name" << name
|
||||
<< "gamma" << gamma
|
||||
<< "scale" << scale
|
||||
<< "saturation" << saturation;
|
||||
}
|
||||
|
||||
void read(const FileNode& fn)
|
||||
{
|
||||
FileNode n = fn["name"];
|
||||
CV_Assert(n.isString() && String(n) == name);
|
||||
gamma = fn["gamma"];
|
||||
scale = fn["scale"];
|
||||
saturation = fn["saturation"];
|
||||
}
|
||||
|
||||
protected:
|
||||
String name;
|
||||
float gamma, scale, saturation;
|
||||
|
||||
void signedPow(Mat src, float power, Mat& dst)
|
||||
{
|
||||
Mat sign = (src > 0);
|
||||
sign.convertTo(sign, CV_32F, 1.0f/255.0f);
|
||||
sign = sign * 2.0f - 1.0f;
|
||||
pow(abs(src), power, dst);
|
||||
dst = dst.mul(sign);
|
||||
}
|
||||
|
||||
void mapContrast(Mat& contrast)
|
||||
{
|
||||
const float response_power = 0.4185f;
|
||||
signedPow(contrast, response_power, contrast);
|
||||
contrast *= scale;
|
||||
signedPow(contrast, 1.0f / response_power, contrast);
|
||||
}
|
||||
|
||||
void getGradient(Mat src, Mat& dst, int pos)
|
||||
{
|
||||
dst = Mat::zeros(src.size(), CV_32F);
|
||||
Mat a, b;
|
||||
Mat grad = src.colRange(1, src.cols) - src.colRange(0, src.cols - 1);
|
||||
grad.copyTo(dst.colRange(pos, src.cols + pos - 1));
|
||||
if(pos == 1) {
|
||||
src.col(0).copyTo(dst.col(0));
|
||||
}
|
||||
}
|
||||
|
||||
void getContrast(Mat src, std::vector<Mat>& x_contrast, std::vector<Mat>& y_contrast)
|
||||
{
|
||||
int levels = static_cast<int>(logf(static_cast<float>(min(src.rows, src.cols))) / logf(2.0f));
|
||||
x_contrast.resize(levels);
|
||||
y_contrast.resize(levels);
|
||||
|
||||
Mat layer;
|
||||
src.copyTo(layer);
|
||||
for(int i = 0; i < levels; i++) {
|
||||
getGradient(layer, x_contrast[i], 0);
|
||||
getGradient(layer.t(), y_contrast[i], 0);
|
||||
resize(layer, layer, Size(layer.cols / 2, layer.rows / 2));
|
||||
}
|
||||
}
|
||||
|
||||
void calculateSum(std::vector<Mat>& x_contrast, std::vector<Mat>& y_contrast, Mat& sum)
|
||||
{
|
||||
sum = Mat::zeros(x_contrast[x_contrast.size() - 1].size(), CV_32F);
|
||||
for(int i = x_contrast.size() - 1; i >= 0; i--)
|
||||
{
|
||||
Mat grad_x, grad_y;
|
||||
getGradient(x_contrast[i], grad_x, 1);
|
||||
getGradient(y_contrast[i], grad_y, 1);
|
||||
resize(sum, sum, x_contrast[i].size());
|
||||
sum += grad_x + grad_y.t();
|
||||
}
|
||||
}
|
||||
|
||||
void calculateProduct(Mat src, Mat& dst)
|
||||
{
|
||||
std::vector<Mat> x_contrast, y_contrast;
|
||||
getContrast(src, x_contrast, y_contrast);
|
||||
calculateSum(x_contrast, y_contrast, dst);
|
||||
}
|
||||
};
|
||||
|
||||
Ptr<TonemapMantiuk> createTonemapMantiuk(float gamma, float scale, float saturation)
|
||||
{
|
||||
return makePtr<TonemapMantiukImpl>(gamma, scale, saturation);
|
||||
}
|
||||
|
||||
}
|
249
modules/photo/test/test_hdr.cpp
Normal file
249
modules/photo/test/test_hdr.cpp
Normal file
@ -0,0 +1,249 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
void loadImage(string path, Mat &img)
|
||||
{
|
||||
img = imread(path, -1);
|
||||
ASSERT_FALSE(img.empty()) << "Could not load input image " << path;
|
||||
}
|
||||
|
||||
void checkEqual(Mat img0, Mat img1, double threshold)
|
||||
{
|
||||
double max = 1.0;
|
||||
minMaxLoc(abs(img0 - img1), NULL, &max);
|
||||
ASSERT_FALSE(max > threshold) << max;
|
||||
}
|
||||
|
||||
static vector<float> DEFAULT_VECTOR;
|
||||
void loadExposureSeq(String path, vector<Mat>& images, vector<float>& times = DEFAULT_VECTOR)
|
||||
{
|
||||
ifstream list_file((path + "list.txt").c_str());
|
||||
ASSERT_TRUE(list_file.is_open());
|
||||
string name;
|
||||
float val;
|
||||
while(list_file >> name >> val) {
|
||||
Mat img = imread(path + name);
|
||||
ASSERT_FALSE(img.empty()) << "Could not load input image " << path + name;
|
||||
images.push_back(img);
|
||||
times.push_back(1 / val);
|
||||
}
|
||||
list_file.close();
|
||||
}
|
||||
|
||||
void loadResponseCSV(String path, Mat& response)
|
||||
{
|
||||
response = Mat(256, 1, CV_32FC3);
|
||||
ifstream resp_file(path.c_str());
|
||||
for(int i = 0; i < 256; i++) {
|
||||
for(int c = 0; c < 3; c++) {
|
||||
resp_file >> response.at<Vec3f>(i)[c];
|
||||
resp_file.ignore(1);
|
||||
}
|
||||
}
|
||||
resp_file.close();
|
||||
}
|
||||
|
||||
TEST(Photo_Tonemap, regression)
|
||||
{
|
||||
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/tonemap/";
|
||||
|
||||
Mat img, expected, result;
|
||||
loadImage(test_path + "image.hdr", img);
|
||||
float gamma = 2.2f;
|
||||
|
||||
Ptr<Tonemap> linear = createTonemap(gamma);
|
||||
linear->process(img, result);
|
||||
loadImage(test_path + "linear.png", expected);
|
||||
result.convertTo(result, CV_8UC3, 255);
|
||||
checkEqual(result, expected, 3);
|
||||
|
||||
Ptr<TonemapDrago> drago = createTonemapDrago(gamma);
|
||||
drago->process(img, result);
|
||||
loadImage(test_path + "drago.png", expected);
|
||||
result.convertTo(result, CV_8UC3, 255);
|
||||
checkEqual(result, expected, 3);
|
||||
|
||||
Ptr<TonemapDurand> durand = createTonemapDurand(gamma);
|
||||
durand->process(img, result);
|
||||
loadImage(test_path + "durand.png", expected);
|
||||
result.convertTo(result, CV_8UC3, 255);
|
||||
checkEqual(result, expected, 3);
|
||||
|
||||
Ptr<TonemapReinhard> reinhard = createTonemapReinhard(gamma);
|
||||
reinhard->process(img, result);
|
||||
loadImage(test_path + "reinhard.png", expected);
|
||||
result.convertTo(result, CV_8UC3, 255);
|
||||
checkEqual(result, expected, 3);
|
||||
|
||||
Ptr<TonemapMantiuk> mantiuk = createTonemapMantiuk(gamma);
|
||||
mantiuk->process(img, result);
|
||||
loadImage(test_path + "mantiuk.png", expected);
|
||||
result.convertTo(result, CV_8UC3, 255);
|
||||
checkEqual(result, expected, 3);
|
||||
}
|
||||
|
||||
TEST(Photo_AlignMTB, regression)
|
||||
{
|
||||
const int TESTS_COUNT = 100;
|
||||
string folder = string(cvtest::TS::ptr()->get_data_path()) + "shared/";
|
||||
|
||||
string file_name = folder + "lena.png";
|
||||
Mat img;
|
||||
loadImage(file_name, img);
|
||||
cvtColor(img, img, COLOR_RGB2GRAY);
|
||||
|
||||
int max_bits = 5;
|
||||
int max_shift = 32;
|
||||
srand(static_cast<unsigned>(time(0)));
|
||||
int errors = 0;
|
||||
|
||||
Ptr<AlignMTB> align = createAlignMTB(max_bits);
|
||||
|
||||
for(int i = 0; i < TESTS_COUNT; i++) {
|
||||
Point shift(rand() % max_shift, rand() % max_shift);
|
||||
Mat res;
|
||||
align->shiftMat(img, res, shift);
|
||||
Point calc = align->calculateShift(img, res);
|
||||
errors += (calc != -shift);
|
||||
}
|
||||
ASSERT_TRUE(errors < 5) << errors << " errors";
|
||||
}
|
||||
|
||||
TEST(Photo_MergeMertens, regression)
|
||||
{
|
||||
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
|
||||
|
||||
vector<Mat> images;
|
||||
loadExposureSeq((test_path + "exposures/").c_str() , images);
|
||||
|
||||
Ptr<MergeMertens> merge = createMergeMertens();
|
||||
|
||||
Mat result, expected;
|
||||
loadImage(test_path + "merge/mertens.png", expected);
|
||||
merge->process(images, result);
|
||||
result.convertTo(result, CV_8UC3, 255);
|
||||
checkEqual(expected, result, 3);
|
||||
}
|
||||
|
||||
TEST(Photo_MergeDebevec, regression)
|
||||
{
|
||||
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
|
||||
|
||||
vector<Mat> images;
|
||||
vector<float> times;
|
||||
Mat response;
|
||||
loadExposureSeq(test_path + "exposures/", images, times);
|
||||
loadResponseCSV(test_path + "exposures/response.csv", response);
|
||||
|
||||
Ptr<MergeDebevec> merge = createMergeDebevec();
|
||||
|
||||
Mat result, expected;
|
||||
loadImage(test_path + "merge/debevec.hdr", expected);
|
||||
merge->process(images, result, times, response);
|
||||
|
||||
Ptr<Tonemap> map = createTonemap();
|
||||
map->process(result, result);
|
||||
map->process(expected, expected);
|
||||
|
||||
checkEqual(expected, result, 1e-2f);
|
||||
}
|
||||
|
||||
TEST(Photo_MergeRobertson, regression)
|
||||
{
|
||||
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
|
||||
|
||||
vector<Mat> images;
|
||||
vector<float> times;
|
||||
loadExposureSeq(test_path + "exposures/", images, times);
|
||||
|
||||
Ptr<MergeRobertson> merge = createMergeRobertson();
|
||||
|
||||
Mat result, expected;
|
||||
loadImage(test_path + "merge/robertson.hdr", expected);
|
||||
merge->process(images, result, times);
|
||||
Ptr<Tonemap> map = createTonemap();
|
||||
map->process(result, result);
|
||||
map->process(expected, expected);
|
||||
|
||||
checkEqual(expected, result, 1e-2f);
|
||||
}
|
||||
|
||||
TEST(Photo_CalibrateDebevec, regression)
|
||||
{
|
||||
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
|
||||
|
||||
vector<Mat> images;
|
||||
vector<float> times;
|
||||
Mat response, expected;
|
||||
loadExposureSeq(test_path + "exposures/", images, times);
|
||||
loadResponseCSV(test_path + "calibrate/debevec.csv", expected);
|
||||
Ptr<CalibrateDebevec> calibrate = createCalibrateDebevec();
|
||||
|
||||
calibrate->process(images, response, times);
|
||||
Mat diff = abs(response - expected);
|
||||
diff = diff.mul(1.0f / response);
|
||||
double max;
|
||||
minMaxLoc(diff, NULL, &max);
|
||||
ASSERT_FALSE(max > 0.1);
|
||||
}
|
||||
|
||||
TEST(Photo_CalibrateRobertson, regression)
|
||||
{
|
||||
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
|
||||
|
||||
vector<Mat> images;
|
||||
vector<float> times;
|
||||
Mat response, expected;
|
||||
loadExposureSeq(test_path + "exposures/", images, times);
|
||||
loadResponseCSV(test_path + "calibrate/robertson.csv", expected);
|
||||
|
||||
Ptr<CalibrateRobertson> calibrate = createCalibrateRobertson();
|
||||
calibrate->process(images, response, times);
|
||||
checkEqual(expected, response, 1e-3f);
|
||||
}
|
@ -135,8 +135,22 @@ typedef Ptr<StereoMatcher> Ptr_StereoMatcher;
|
||||
typedef Ptr<StereoBM> Ptr_StereoBM;
|
||||
typedef Ptr<StereoSGBM> Ptr_StereoSGBM;
|
||||
|
||||
typedef Ptr<Tonemap> Ptr_Tonemap;
|
||||
typedef Ptr<TonemapDrago> Ptr_TonemapDrago;
|
||||
typedef Ptr<TonemapReinhard> Ptr_TonemapReinhard;
|
||||
typedef Ptr<TonemapDurand> Ptr_TonemapDurand;
|
||||
typedef Ptr<TonemapMantiuk> Ptr_TonemapMantiuk;
|
||||
typedef Ptr<AlignMTB> Ptr_AlignMTB;
|
||||
typedef Ptr<CalibrateDebevec> Ptr_CalibrateDebevec;
|
||||
typedef Ptr<CalibrateRobertson> Ptr_CalibrateRobertson;
|
||||
typedef Ptr<MergeDebevec> Ptr_MergeDebevec;
|
||||
typedef Ptr<MergeRobertson> Ptr_MergeRobertson;
|
||||
typedef Ptr<MergeMertens> Ptr_MergeMertens;
|
||||
typedef Ptr<MergeRobertson> Ptr_MergeRobertson;
|
||||
|
||||
typedef Ptr<cv::softcascade::ChannelFeatureBuilder> Ptr_ChannelFeatureBuilder;
|
||||
typedef Ptr<CLAHE> Ptr_CLAHE;
|
||||
typedef Ptr<LineSegmentDetector > Ptr_LineSegmentDetector;
|
||||
|
||||
typedef SimpleBlobDetector::Params SimpleBlobDetector_Params;
|
||||
|
||||
|
@ -30,9 +30,9 @@ int main(int argc, char** argv)
|
||||
|
||||
// Create and LSD detector with standard or no refinement.
|
||||
#if 1
|
||||
Ptr<LineSegmentDetector> ls = createLineSegmentDetectorPtr(LSD_REFINE_STD);
|
||||
Ptr<LineSegmentDetector> ls = createLineSegmentDetector(LSD_REFINE_STD);
|
||||
#else
|
||||
Ptr<LineSegmentDetector> ls = createLineSegmentDetectorPtr(LSD_REFINE_NONE);
|
||||
Ptr<LineSegmentDetector> ls = createLineSegmentDetector(LSD_REFINE_NONE);
|
||||
#endif
|
||||
|
||||
double start = double(getTickCount());
|
||||
|
53
samples/cpp/tutorial_code/photo/hdr_imaging/hdr_imaging.cpp
Normal file
53
samples/cpp/tutorial_code/photo/hdr_imaging/hdr_imaging.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include <opencv2/photo.hpp>
|
||||
#include <opencv2/highgui.hpp>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
void loadExposureSeq(String, vector<Mat>&, vector<float>&);
|
||||
|
||||
int main(int, char**argv)
|
||||
{
|
||||
vector<Mat> images;
|
||||
vector<float> times;
|
||||
loadExposureSeq(argv[1], images, times);
|
||||
|
||||
Mat response;
|
||||
Ptr<CalibrateDebevec> calibrate = createCalibrateDebevec();
|
||||
calibrate->process(images, response, times);
|
||||
|
||||
Mat hdr;
|
||||
Ptr<MergeDebevec> merge_debevec = createMergeDebevec();
|
||||
merge_debevec->process(images, hdr, times, response);
|
||||
|
||||
Mat ldr;
|
||||
Ptr<TonemapDurand> tonemap = createTonemapDurand(2.2f);
|
||||
tonemap->process(hdr, ldr);
|
||||
|
||||
Mat fusion;
|
||||
Ptr<MergeMertens> merge_mertens = createMergeMertens();
|
||||
merge_mertens->process(images, fusion);
|
||||
|
||||
imwrite("fusion.png", fusion * 255);
|
||||
imwrite("ldr.png", ldr * 255);
|
||||
imwrite("hdr.hdr", hdr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void loadExposureSeq(String path, vector<Mat>& images, vector<float>& times)
|
||||
{
|
||||
path = path + std::string("/");
|
||||
ifstream list_file((path + "list.txt").c_str());
|
||||
string name;
|
||||
float val;
|
||||
while(list_file >> name >> val) {
|
||||
Mat img = imread(path + name);
|
||||
images.push_back(img);
|
||||
times.push_back(1 / val);
|
||||
}
|
||||
list_file.close();
|
||||
}
|
Loading…
Reference in New Issue
Block a user