Docs updated, added InputArray, fixes for makePtr,...

This commit is contained in:
Fedor Morozov 2013-09-23 23:40:06 +04:00 committed by Alexander Shishkov
parent f99be6bda6
commit c9ace38897
22 changed files with 651 additions and 1076 deletions

View File

@ -39,4 +39,3 @@
#7 & #8 & #9
\end{bmatrix}
}

View File

@ -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_HighDynamicRange_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.

View File

@ -40,13 +40,13 @@ Explanation
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
.. code-block:: none
memorial00.png 0.03125
memorial01.png 0.0625
memorial01.png 0.0625
...
memorial15.png 1024
@ -57,9 +57,9 @@ Explanation
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
@ -67,39 +67,39 @@ Explanation
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
=======
@ -111,7 +111,7 @@ Tonemapped image
:width: 242pt
:alt: Tonemapped image
:align: center
Exposure fusion
------------------

View File

@ -155,7 +155,7 @@ As always, we would be happy to hear your comments and receive your contribution
:height: 80pt
:width: 80pt
:alt: photo Icon
* :ref:`Table-Of-Content-GPU`
.. tabularcolumns:: m{100pt} m{300pt}

View File

@ -49,10 +49,10 @@ namespace cv
HdrDecoder::HdrDecoder()
{
m_signature = "#?RGBE";
m_signature_alt = "#?RADIANCE";
file = NULL;
m_type = CV_32FC3;
m_signature = "#?RGBE";
m_signature_alt = "#?RADIANCE";
file = NULL;
m_type = CV_32FC3;
}
HdrDecoder::~HdrDecoder()
@ -61,61 +61,61 @@ HdrDecoder::~HdrDecoder()
size_t HdrDecoder::signatureLength() const
{
return m_signature.size() > m_signature_alt.size() ?
m_signature.size() : m_signature_alt.size();
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;
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;
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;
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 new HdrDecoder;
return makePtr<HdrDecoder>();
}
HdrEncoder::HdrEncoder()
{
m_description = "Radiance HDR (*.hdr;*.pic)";
m_description = "Radiance HDR (*.hdr;*.pic)";
}
HdrEncoder::~HdrEncoder()
@ -124,41 +124,41 @@ 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;
}
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);
}
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;
fclose(fout);
return true;
}
ImageEncoder HdrEncoder::newEncoder() const
{
return new HdrEncoder;
return makePtr<HdrEncoder>();
}
bool HdrEncoder::isFormatSupported( int depth ) const {
return depth != CV_64F;
return depth != CV_64F;
}
}

View File

@ -58,29 +58,29 @@ enum HdrCompression
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;
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;
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;
HdrEncoder();
~HdrEncoder();
bool write( const Mat& img, const std::vector<int>& params );
ImageEncoder newEncoder() const;
bool isFormatSupported( int depth ) const;
protected:
};
}

View File

@ -72,7 +72,7 @@ TiffDecoder::TiffDecoder()
TIFFSetErrorHandler( GrFmtSilentTIFFErrorHandler );
TIFFSetWarningHandler( GrFmtSilentTIFFErrorHandler );
}
m_hdr = false;
m_hdr = false;
}
@ -135,13 +135,13 @@ 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 == 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) ||
@ -181,10 +181,10 @@ bool TiffDecoder::readHeader()
bool TiffDecoder::readData( Mat& img )
{
if(m_hdr && img.type() == CV_32FC3)
{
return readHdrData(img);
}
if(m_hdr && img.type() == CV_32FC3)
{
return readHdrData(img);
}
bool result = false;
bool color = img.channels() > 1;
uchar* data = img.data;
@ -394,35 +394,35 @@ bool TiffDecoder::readData( Mat& img )
return result;
}
bool TiffDecoder::readHdrData(Mat& img)
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);
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;
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
@ -452,8 +452,8 @@ 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;
#else
return depth == CV_8U || depth == CV_16U;
#endif
}
@ -608,29 +608,29 @@ bool TiffEncoder::writeLibTiff( const Mat& img, const std::vector<int>& params)
bool TiffEncoder::writeHdr(const Mat& _img)
{
Mat img;
cvtColor(_img, img, COLOR_BGR2XYZ);
TIFF* tif = TIFFOpen(m_filename.c_str(), "w");
if (!tif)
{
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;
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
@ -645,10 +645,10 @@ bool TiffEncoder::write( const Mat& img, const std::vector<int>& /*params*/)
int width = img.cols, height = img.rows;
int depth = img.depth();
#ifdef HAVE_TIFF
if(img.type() == CV_32FC3)
{
return writeHdr(img);
}
if(img.type() == CV_32FC3)
{
return writeHdr(img);
}
#endif
if (depth != CV_8U && depth != CV_16U)

View File

@ -108,8 +108,8 @@ public:
protected:
void* m_tif;
int normalizeChannelsNumber(int channels) const;
bool readHdrData(Mat& img);
bool m_hdr;
bool readHdrData(Mat& img);
bool m_hdr;
};
#endif
@ -132,7 +132,7 @@ protected:
int count, int value );
bool writeLibTiff( const Mat& img, const std::vector<int>& params );
bool writeHdr( const Mat& img );
bool writeHdr( const Mat& img );
};
}

View File

@ -59,15 +59,10 @@ struct ImageCodecInitializer
{
ImageCodecInitializer()
{
<<<<<<< HEAD
decoders.push_back( new BmpDecoder );
encoders.push_back( new BmpEncoder );
decoders.push_back( new HdrDecoder );
encoders.push_back( new HdrEncoder );
=======
decoders.push_back( makePtr<BmpDecoder>() );
encoders.push_back( makePtr<BmpEncoder>() );
>>>>>>> 99a43257d5912ff215016e1cf5f4e0c2a934b72f
decoders.push_back( makePtr<HdrDecoder>() );
encoders.push_back( makePtr<HdrEncoder>() );
#ifdef HAVE_JPEG
decoders.push_back( makePtr<JpegDecoder>() );
encoders.push_back( makePtr<JpegEncoder>() );

View File

@ -53,7 +53,7 @@
// 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
// (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.
@ -83,7 +83,7 @@ enum rgbe_error_codes {
};
/* default error routine. change this to change error handling */
static int rgbe_error(int rgbe_error_code, char *msg)
static int rgbe_error(int rgbe_error_code, const char *msg)
{
switch (rgbe_error_code) {
case rgbe_read_error:
@ -93,20 +93,20 @@ static int rgbe_error(int rgbe_error_code, char *msg)
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));
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));
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
static INLINE void
float2rgbe(unsigned char rgbe[4], float red, float green, float blue)
{
float v;
@ -119,7 +119,7 @@ float2rgbe(unsigned char rgbe[4], float red, float green, float blue)
rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
}
else {
v = frexp(v,&e) * 256.0/v;
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);
@ -130,13 +130,13 @@ float2rgbe(unsigned char rgbe[4], float red, float green, float blue)
/* 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
static INLINE void
rgbe2float(float *red, float *green, float *blue, unsigned char rgbe[4])
{
float f;
if (rgbe[3]) { /*nonzero pixel*/
f = ldexp(1.0,rgbe[3]-(int)(128+8));
f = static_cast<float>(ldexp(1.0,rgbe[3]-(int)(128+8)));
*red = rgbe[0] * f;
*green = rgbe[1] * f;
*blue = rgbe[2] * f;
@ -148,7 +148,7 @@ rgbe2float(float *red, float *green, float *blue, unsigned char rgbe[4])
/* default minimal header. modify if you want more information in header */
int RGBE_WriteHeader(FILE *fp, int width, int height, rgbe_header_info *info)
{
char *programtype = "RGBE";
const char *programtype = "RGBE";
if (info && (info->valid & RGBE_VALID_PROGRAMTYPE))
programtype = info->programtype;
@ -174,11 +174,9 @@ 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)
{
char buf[128];
int found_format;
float tempf;
int i;
found_format = 0;
if (info) {
info->valid = 0;
info->programtype[0] = 0;
@ -194,7 +192,7 @@ int RGBE_ReadHeader(FILE *fp, int *width, int *height, rgbe_header_info *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;
break;
info->programtype[i] = buf[i+2];
}
info->programtype[i] = 0;
@ -221,7 +219,7 @@ int RGBE_ReadHeader(FILE *fp, int *width, int *height, rgbe_header_info *info)
return rgbe_error(rgbe_read_error,NULL);
if (strcmp(buf,"\n") != 0)
return rgbe_error(rgbe_format_error,
"missing blank line after FORMAT specifier");
"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)
@ -238,7 +236,7 @@ int RGBE_WritePixels(FILE *fp, float *data, int numpixels)
while (numpixels-- > 0) {
float2rgbe(rgbe,data[RGBE_DATA_RED],
data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
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);
@ -255,7 +253,7 @@ int RGBE_ReadPixels(FILE *fp, float *data, int numpixels)
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_BLUE],rgbe);
data += RGBE_DATA_SIZE;
}
return RGBE_RETURN_SUCCESS;
@ -283,34 +281,34 @@ static int RGBE_WriteBytes_RLE(FILE *fp, unsigned char *data, int numbytes)
run_count = 1;
while( (beg_run + run_count < numbytes) && (run_count < 127)
&& (data[beg_run] == data[beg_run + run_count]))
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] = 128 + old_run_count; /*write short run*/
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);
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] = nonrun_count;
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);
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);
return rgbe_error(rgbe_write_error,NULL);
cur += nonrun_count;
}
/* write out next run if one was found */
if (run_count >= MINRUNLENGTH) {
buf[0] = 128 + run_count;
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);
return rgbe_error(rgbe_write_error,NULL);
cur += run_count;
}
}
@ -319,7 +317,7 @@ static int RGBE_WriteBytes_RLE(FILE *fp, unsigned char *data, int numbytes)
}
int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
int num_scanlines)
int num_scanlines)
{
unsigned char rgbe[4];
unsigned char *buffer;
@ -329,13 +327,13 @@ int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
/* 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)
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] = scanline_width >> 8;
rgbe[2] = static_cast<unsigned char>(scanline_width >> 8);
rgbe[3] = scanline_width & 0xFF;
if (fwrite(rgbe, sizeof(rgbe), 1, fp) < 1) {
free(buffer);
@ -343,7 +341,7 @@ int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
}
for(i=0;i<scanline_width;i++) {
float2rgbe(rgbe,data[RGBE_DATA_RED],
data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
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];
@ -354,18 +352,18 @@ int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
/* 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;
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)
int num_scanlines)
{
unsigned char rgbe[4], *scanline_buffer, *ptr, *ptr_end;
int i, count;
@ -394,45 +392,45 @@ int RGBE_ReadPixels_RLE(FILE *fp, float *data, int scanline_width,
}
if (scanline_buffer == NULL)
scanline_buffer = (unsigned char *)
malloc(sizeof(unsigned char)*4*scanline_width);
if (scanline_buffer == NULL)
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;
}
}
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 */
@ -442,7 +440,7 @@ int RGBE_ReadPixels_RLE(FILE *fp, float *data, int 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_BLUE],rgbe);
data += RGBE_DATA_SIZE;
}
num_scanlines--;
@ -450,4 +448,3 @@ int RGBE_ReadPixels_RLE(FILE *fp, float *data, int scanline_width,
free(scanline_buffer);
return RGBE_RETURN_SUCCESS;
}

View File

@ -51,13 +51,13 @@
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
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 */
* <exposure> watts/steradian/m^2.
* defaults to 1.0 */
} rgbe_header_info;
/* flags indicating which fields in an rgbe_header_info are valid */
@ -82,11 +82,8 @@ 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 num_scanlines);
int RGBE_ReadPixels_RLE(FILE *fp, float *data, int scanline_width,
int num_scanlines);
int num_scanlines);
#endif/*_RGBE_HDR_H_*/

View File

@ -479,11 +479,7 @@ TEST(Highgui_WebP, encode_decode_lossless_webp)
TEST(Highgui_WebP, encode_decode_lossy_webp)
{
cvtest::TS& ts = *cvtest::TS::ptr();
<<<<<<< HEAD
string input = string(ts.get_data_path()) + "/../cv/shared/lena.png";
=======
std::string input = std::string(ts.get_data_path()) + "../cv/shared/lena.png";
>>>>>>> 99a43257d5912ff215016e1cf5f4e0c2a934b72f
cv::Mat img = cv::imread(input);
ASSERT_FALSE(img.empty());
@ -533,24 +529,24 @@ TEST(Highgui_WebP, encode_decode_with_alpha_webp)
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;
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);
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);
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);
}
}
}

View File

@ -27,14 +27,14 @@ 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.
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.
@ -46,19 +46,19 @@ createTonemapDrago
---------------------------
Creates TonemapDrago object
.. ocv:function:: Ptr<TonemapDrago> createTonemapDrago(float gamma = 1.0f, float bias = 0.85f)
.. 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 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.
@ -73,39 +73,39 @@ 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
TonemapReinhardDevlin
---------------------------
.. ocv:class:: TonemapReinhardDevlin : public Tonemap
This is a global tonemapping operator that models human visual system.
: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]_.
createTonemapReinhardDevlin
createTonemapReinhard
---------------------------
Creates TonemapReinhardDevlin object
Creates TonemapReinhard object
.. ocv:function:: Ptr<TonemapReinhardDevlin> createTonemapReinhardDevlin(float gamma = 1.0f, float intensity = 0.0f, float light_adapt = 1.0f, float color_adapt = 0.0f)
.. 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
@ -122,34 +122,34 @@ 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`
ExposureAlign
AlignExposures
---------------------------
.. ocv:class:: ExposureAlign : public Algorithm
.. ocv:class:: AlignExposures : public Algorithm
The base class for algorithms that align images of the same scene with different exposures
ExposureAlign::process
AlignExposures::process
---------------------------
Aligns images
.. ocv:function:: void ExposureAlign::process(InputArrayOfArrays src, OutputArrayOfArrays dst, const std::vector<float>& times, InputArray response)
.. 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 ExposureAlign
.. 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.
@ -163,81 +163,79 @@ AlignMTB::process
---------------------------
Short version of process, that doesn't take extra arguments.
.. ocv:function:: void AlignMTB::process(InputArrayOfArrays src, OutputArrayOfArrays dst)
.. 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:: void AlignMTB::calculateShift(InputArray img0, InputArray img1, Point& shift)
.. ocv:function:: Point AlignMTB::calculateShift(InputArray img0, InputArray img1)
:param img0: first image
:param img1: second image
:param shift: calculated shift
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 computeBitmaps(Mat& img, Mat& tb, Mat& eb)
.. 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.
ExposureCalibrate
CalibrateCRF
---------------------------
.. ocv:class:: ExposureCalibrate : public Algorithm
.. ocv:class:: CalibrateCRF : public Algorithm
The base class for camera response calibration algorithms.
ExposureCalibrate::process
CalibrateCRF::process
---------------------------
Recovers inverse camera response.
.. ocv:function:: void ExposureCalibrate::process(InputArrayOfArrays src, OutputArray dst, std::vector<float>& times)
.. 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 ExposureCalibrate
.. 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.
@ -251,14 +249,14 @@ 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 ExposureCalibrate
.. 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.
@ -272,32 +270,32 @@ 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.
ExposureMerge
MergeExposures
---------------------------
.. ocv:class:: ExposureMerge : public Algorithm
.. ocv:class:: MergeExposures : public Algorithm
The base class algorithms that can merge exposure sequence to a single image.
ExposureMerge::process
MergeExposures::process
---------------------------
Merges images.
.. ocv:function:: void process(InputArrayOfArrays src, OutputArray dst, const std::vector<float>& times, InputArray response)
.. 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 ExposureMerge
.. ocv:class:: MergeDebevec : public MergeExposures
The resulting HDR image is calculated as weighted average of the exposures considering exposure values and camera response.
@ -311,7 +309,7 @@ Creates MergeDebevec object
MergeMertens
---------------------------
.. ocv:class:: MergeMertens : public ExposureMerge
.. ocv:class:: MergeMertens : public MergeExposures
Pixels are weighted using contrast, saturation and well-exposedness measures, than images are combined using laplacian pyramids.
@ -328,7 +326,7 @@ 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
@ -338,14 +336,14 @@ 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 ExposureMerge
.. ocv:class:: MergeRobertson : public MergeExposures
The resulting HDR image is calculated as weighted average of the exposures considering exposure values and camera response.
@ -356,7 +354,7 @@ createMergeRobertson
Creates MergeRobertson object
.. ocv:function:: Ptr<MergeRobertson> createMergeRobertson()
References
==========
@ -364,7 +362,7 @@ References
.. [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.
.. [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.

View File

@ -127,12 +127,12 @@ public:
CV_WRAP virtual void setSigmaColor(float sigma_color) = 0;
};
CV_EXPORTS_W Ptr<TonemapDurand>
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 TonemapReinhardDevlin : public Tonemap
class CV_EXPORTS_W TonemapReinhard : public Tonemap
{
public:
CV_WRAP virtual float getIntensity() const = 0;
@ -145,8 +145,8 @@ public:
CV_WRAP virtual void setColorAdaptation(float color_adapt) = 0;
};
CV_EXPORTS_W Ptr<TonemapReinhardDevlin>
createTonemapReinhardDevlin(float gamma = 1.0f, float intensity = 0.0f, float light_adapt = 1.0f, float color_adapt = 0.0f);
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
@ -160,29 +160,29 @@ public:
CV_WRAP virtual void setSaturation(float saturation) = 0;
};
CV_EXPORTS_W Ptr<TonemapMantiuk>
CV_EXPORTS_W Ptr<TonemapMantiuk>
createTonemapMantiuk(float gamma = 1.0f, float scale = 0.7f, float saturation = 1.0f);
class CV_EXPORTS_W ExposureAlign : public Algorithm
class CV_EXPORTS_W AlignExposures : public Algorithm
{
public:
CV_WRAP virtual void process(InputArrayOfArrays src, std::vector<Mat>& dst,
const std::vector<float>& times, InputArray response) = 0;
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 ExposureAlign
class CV_EXPORTS_W AlignMTB : public AlignExposures
{
public:
CV_WRAP virtual void process(InputArrayOfArrays src, std::vector<Mat>& dst,
const std::vector<float>& times, InputArray response) = 0;
InputArray times, InputArray response) = 0;
CV_WRAP virtual void process(InputArrayOfArrays src, std::vector<Mat>& dst) = 0;
CV_WRAP virtual void calculateShift(InputArray img0, InputArray img1, Point& shift) = 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(Mat& img, Mat& tb, Mat& eb) = 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;
@ -196,20 +196,20 @@ public:
CV_EXPORTS_W Ptr<AlignMTB> createAlignMTB(int max_bits = 6, int exclude_range = 4, bool cut = true);
class CV_EXPORTS_W ExposureCalibrate : public Algorithm
class CV_EXPORTS_W CalibrateCRF : public Algorithm
{
public:
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, std::vector<float>& times) = 0;
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 ExposureCalibrate
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;
@ -221,46 +221,46 @@ CV_EXPORTS_W Ptr<CalibrateDebevec> createCalibrateDebevec(int samples = 70, floa
// "Dynamic range improvement through multiple exposures", Robertson et al., 1999
class CV_EXPORTS_W CalibrateRobertson : public ExposureCalibrate
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 ExposureMerge : public Algorithm
class CV_EXPORTS_W MergeExposures : public Algorithm
{
public:
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst,
const std::vector<float>& times, InputArray response) = 0;
InputArray times, InputArray response) = 0;
};
// "Recovering High Dynamic Range Radiance Maps from Photographs", Debevec, Malik, 1997
class CV_EXPORTS_W MergeDebevec : public ExposureMerge
class CV_EXPORTS_W MergeDebevec : public MergeExposures
{
public:
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst,
const std::vector<float>& times, InputArray response) = 0;
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, const std::vector<float>& times) = 0;
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 ExposureMerge
class CV_EXPORTS_W MergeMertens : public MergeExposures
{
public:
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst,
const std::vector<float>& times, InputArray response) = 0;
InputArray times, InputArray response) = 0;
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst) = 0;
CV_WRAP virtual float getContrastWeight() const = 0;
@ -273,17 +273,17 @@ public:
CV_WRAP virtual void setExposureWeight(float exposure_weight) = 0;
};
CV_EXPORTS_W Ptr<MergeMertens>
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 ExposureMerge
class CV_EXPORTS_W MergeRobertson : public MergeExposures
{
public:
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst,
const std::vector<float>& times, InputArray response) = 0;
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, const std::vector<float>& times) = 0;
InputArray times, InputArray response) = 0;
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, InputArray times) = 0;
};
CV_EXPORTS_W Ptr<MergeRobertson> createMergeRobertson();

View File

@ -50,16 +50,16 @@ namespace cv
class AlignMTBImpl : public AlignMTB
{
public:
AlignMTBImpl(int max_bits, int exclude_range, bool cut) :
max_bits(max_bits),
exclude_range(exclude_range),
cut(cut),
name("AlignMTB")
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,
const std::vector<float>& times, InputArray response)
InputArray, InputArray)
{
process(src, dst);
}
@ -68,7 +68,7 @@ public:
{
std::vector<Mat> src;
_src.getMatVector(src);
checkImageDimensions(src);
dst.resize(src.size());
@ -85,8 +85,7 @@ public:
}
Mat gray;
cvtColor(src[i], gray, COLOR_RGB2GRAY);
Point shift;
calculateShift(gray_base, gray, shift);
Point shift = calculateShift(gray_base, gray);
shifts.push_back(shift);
shiftMat(src[i], dst[i], shift);
}
@ -113,7 +112,7 @@ public:
}
}
void calculateShift(InputArray _img0, InputArray _img1, Point& shift)
Point calculateShift(InputArray _img0, InputArray _img1)
{
Mat img0 = _img0.getMat();
Mat img1 = _img1.getMat();
@ -126,11 +125,11 @@ public:
std::vector<Mat> pyr0;
std::vector<Mat> pyr1;
buildPyr(img0, pyr0, maxlevel);
buildPyr(img1, pyr1, maxlevel);
shift = Point(0, 0);
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);
@ -151,14 +150,15 @@ public:
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)
void shiftMat(InputArray _src, OutputArray _dst, const Point shift)
{
Mat src = _src.getMat();
_dst.create(src.size(), src.type());
@ -186,7 +186,7 @@ public:
{
fs << "name" << name
<< "max_bits" << max_bits
<< "exclude_range" << exclude_range
<< "exclude_range" << exclude_range
<< "cut" << static_cast<int>(cut);
}
@ -197,11 +197,15 @@ public:
max_bits = fn["max_bits"];
exclude_range = fn["exclude_range"];
int cut_val = fn["cut"];
cut = static_cast<bool>(cut_val);
cut = (cut_val != 0);
}
void computeBitmaps(Mat& img, Mat& tb, Mat& eb)
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);
@ -230,7 +234,7 @@ protected:
}
}
void buildPyr(Mat& img, std::vector<Mat>& pyr, int maxlevel)
void buildPyr(Mat& img, std::vector<Mat>& pyr, int maxlevel)
{
pyr.resize(maxlevel + 1);
pyr[0] = img.clone();
@ -242,7 +246,7 @@ protected:
int getMedian(Mat& img)
{
int channels = 0;
Mat hist;
Mat hist;
int hist_size = LDR_SIZE;
float range[] = {0, LDR_SIZE} ;
const float* ranges[] = {range};
@ -260,8 +264,7 @@ protected:
Ptr<AlignMTB> createAlignMTB(int max_bits, int exclude_range, bool cut)
{
return new AlignMTBImpl(max_bits, exclude_range, cut);
return makePtr<AlignMTBImpl>(max_bits, exclude_range, cut);
}
}

View File

@ -48,25 +48,26 @@
namespace cv
{
class CalibrateDebevecImpl : public CalibrateDebevec
{
public:
CalibrateDebevecImpl(int samples, float lambda, bool random) :
samples(samples),
lambda(lambda),
CalibrateDebevecImpl(int _samples, float _lambda, bool _random) :
name("CalibrateDebevec"),
w(tringleWeights()),
random(random)
samples(_samples),
lambda(_lambda),
random(_random),
w(tringleWeights())
{
}
void process(InputArrayOfArrays src, OutputArray dst, std::vector<float>& times)
void process(InputArrayOfArrays src, OutputArray dst, InputArray _times)
{
std::vector<Mat> images;
src.getMatVector(images);
Mat times = _times.getMat();
CV_Assert(images.size() == times.size());
CV_Assert(images.size() == times.total());
checkImageDimensions(images);
CV_Assert(images[0].depth() == CV_8U);
@ -75,14 +76,14 @@ public:
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 = sqrt(static_cast<double>(samples) * images[0].cols / images[0].rows);
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;
@ -106,7 +107,7 @@ public:
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[j]);
B.at<float>(eq, 0) = w.at<float>(val) * log(times.at<float>(j));
eq++;
}
}
@ -151,7 +152,7 @@ public:
samples = fn["samples"];
lambda = fn["lambda"];
int random_val = fn["random"];
random = static_cast<bool>(random_val);
random = (random_val != 0);
}
protected:
@ -164,26 +165,27 @@ protected:
Ptr<CalibrateDebevec> createCalibrateDebevec(int samples, float lambda, bool random)
{
return new CalibrateDebevecImpl(samples, lambda, random);
return makePtr<CalibrateDebevecImpl>(samples, lambda, random);
}
class CalibrateRobertsonImpl : public CalibrateRobertson
{
public:
CalibrateRobertsonImpl(int max_iter, float threshold) :
max_iter(max_iter),
threshold(threshold),
CalibrateRobertsonImpl(int _max_iter, float _threshold) :
name("CalibrateRobertson"),
max_iter(_max_iter),
threshold(_threshold),
weight(RobertsonWeights())
{
}
void process(InputArrayOfArrays src, OutputArray dst, std::vector<float>& times)
void process(InputArrayOfArrays src, OutputArray dst, InputArray _times)
{
std::vector<Mat> images;
src.getMatVector(images);
Mat times = _times.getMat();
CV_Assert(images.size() == times.size());
CV_Assert(images.size() == times.total());
checkImageDimensions(images);
CV_Assert(images[0].depth() == CV_8U);
@ -205,7 +207,7 @@ public:
}
card = 1.0 / card;
Ptr<MergeRobertson> merge = createMergeRobertson();
Ptr<MergeRobertson> merge = createMergeRobertson();
for(int iter = 0; iter < max_iter; iter++) {
radiance = Mat::zeros(images[0].size(), CV_32FCC);
@ -217,7 +219,7 @@ public:
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[i] * *rad_ptr;
new_response.at<Vec3f>(*ptr)[c] += times.at<float>(i) * *rad_ptr;
}
}
}
@ -228,7 +230,7 @@ public:
new_response.at<Vec3f>(i)[c] /= middle;
}
}
float diff = sum(sum(abs(new_response - response)))[0] / channels;
float diff = static_cast<float>(sum(sum(abs(new_response - response)))[0] / channels);
new_response.copyTo(response);
if(diff < threshold) {
break;
@ -268,7 +270,7 @@ protected:
Ptr<CalibrateRobertson> createCalibrateRobertson(int max_iter, float threshold)
{
return new CalibrateRobertsonImpl(max_iter, threshold);
return makePtr<CalibrateRobertsonImpl>(max_iter, threshold);
}
}
}

View File

@ -97,7 +97,7 @@ 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(i);
response.at<Vec3f>(i) = Vec3f::all(static_cast<float>(i));
}
return response;
}

View File

@ -55,13 +55,14 @@ public:
weights(tringleWeights())
{
}
void process(InputArrayOfArrays src, OutputArray dst, const std::vector<float>& times, InputArray input_response)
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.size());
CV_Assert(images.size() == times.total());
checkImageDimensions(images);
CV_Assert(images[0].depth() == CV_8U);
@ -79,12 +80,12 @@ public:
response.at<Vec3f>(0) = response.at<Vec3f>(1);
}
log(response, response);
CV_Assert(response.rows == LDR_SIZE && response.cols == 1 &&
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);
@ -117,7 +118,7 @@ public:
exp(result, result);
}
void process(InputArrayOfArrays src, OutputArray dst, const std::vector<float>& times)
void process(InputArrayOfArrays src, OutputArray dst, InputArray times)
{
process(src, dst, times, Mat());
}
@ -129,21 +130,21 @@ protected:
Ptr<MergeDebevec> createMergeDebevec()
{
return new MergeDebevecImpl;
return makePtr<MergeDebevecImpl>();
}
class MergeMertensImpl : public MergeMertens
{
public:
MergeMertensImpl(float wcon, float wsat, float wexp) :
wcon(wcon),
wsat(wsat),
wexp(wexp),
name("MergeMertens")
MergeMertensImpl(float _wcon, float _wsat, float _wexp) :
name("MergeMertens"),
wcon(_wcon),
wsat(_wsat),
wexp(_wexp)
{
}
void process(InputArrayOfArrays src, OutputArrayOfArrays dst, const std::vector<float>& times, InputArray response)
void process(InputArrayOfArrays src, OutputArrayOfArrays dst, InputArray, InputArray)
{
process(src, dst);
}
@ -217,7 +218,7 @@ public:
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);
@ -283,7 +284,7 @@ protected:
Ptr<MergeMertens> createMergeMertens(float wcon, float wsat, float wexp)
{
return new MergeMertensImpl(wcon, wsat, wexp);
return makePtr<MergeMertensImpl>(wcon, wsat, wexp);
}
class MergeRobertsonImpl : public MergeRobertson
@ -294,13 +295,14 @@ public:
weight(RobertsonWeights())
{
}
void process(InputArrayOfArrays src, OutputArray dst, const std::vector<float>& times, InputArray input_response)
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.size());
CV_Assert(images.size() == times.total());
checkImageDimensions(images);
CV_Assert(images[0].depth() == CV_8U);
@ -312,11 +314,12 @@ public:
Mat response = input_response.getMat();
if(response.empty()) {
response = linearResponse(channels) / (LDR_SIZE / 2.0f);
float middle = LDR_SIZE / 2.0f;
response = linearResponse(channels) / middle;
}
CV_Assert(response.rows == LDR_SIZE && response.cols == 1 &&
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++) {
@ -324,13 +327,13 @@ public:
LUT(images[i], weight, w);
LUT(images[i], response, im);
result += times[i] * w.mul(im);
wsum += pow(times[i], 2) * w;
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, const std::vector<float>& times)
void process(InputArrayOfArrays src, OutputArray dst, InputArray times)
{
process(src, dst, times, Mat());
}
@ -342,7 +345,7 @@ protected:
Ptr<MergeRobertson> createMergeRobertson()
{
return new MergeRobertsonImpl;
return makePtr<MergeRobertsonImpl>();
}
}

View File

@ -50,17 +50,17 @@ namespace cv
class TonemapImpl : public Tonemap
{
public:
TonemapImpl(float gamma) : gamma(gamma), name("Tonemap")
TonemapImpl(float _gamma) : name("Tonemap"), gamma(_gamma)
{
}
void process(InputArray _src, OutputArray _dst)
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) {
@ -95,27 +95,27 @@ protected:
Ptr<Tonemap> createTonemap(float gamma)
{
return new TonemapImpl(gamma);
return makePtr<TonemapImpl>(gamma);
}
class TonemapDragoImpl : public TonemapDrago
{
public:
TonemapDragoImpl(float gamma, float saturation, float bias) :
gamma(gamma),
saturation(saturation),
bias(bias),
name("TonemapDrago")
TonemapDragoImpl(float _gamma, float _saturation, float _bias) :
name("TonemapDrago"),
gamma(_gamma),
saturation(_saturation),
bias(_bias)
{
}
void process(InputArray _src, OutputArray _dst)
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);
@ -139,7 +139,7 @@ public:
div.release();
mapLuminance(img, img, gray_img, map, saturation);
linear->setGamma(gamma);
linear->process(img, img);
}
@ -177,23 +177,23 @@ protected:
Ptr<TonemapDrago> createTonemapDrago(float gamma, float saturation, float bias)
{
return new TonemapDragoImpl(gamma, saturation, 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) :
gamma(gamma),
contrast(contrast),
saturation(saturation),
sigma_color(sigma_color),
sigma_space(sigma_space),
name("TonemapDurand")
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)
void process(InputArray _src, OutputArray _dst)
{
Mat src = _src.getMat();
CV_Assert(!src.empty());
@ -208,7 +208,7 @@ public:
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);
@ -238,8 +238,8 @@ public:
{
fs << "name" << name
<< "gamma" << gamma
<< "contrast" << contrast
<< "sigma_color" << sigma_color
<< "contrast" << contrast
<< "sigma_color" << sigma_color
<< "sigma_space" << sigma_space
<< "saturation" << saturation;
}
@ -257,23 +257,23 @@ public:
protected:
String name;
float gamma, saturation, contrast, sigma_color, sigma_space;
float gamma, contrast, saturation, sigma_color, sigma_space;
};
Ptr<TonemapDurand> createTonemapDurand(float gamma, float contrast, float saturation, float sigma_color, float sigma_space)
{
return new TonemapDurandImpl(gamma, contrast, saturation, sigma_color, sigma_space);
return makePtr<TonemapDurandImpl>(gamma, contrast, saturation, sigma_color, sigma_space);
}
class TonemapReinhardDevlinImpl : public TonemapReinhardDevlin
class TonemapReinhardImpl : public TonemapReinhard
{
public:
TonemapReinhardDevlinImpl(float gamma, float intensity, float light_adapt, float color_adapt) :
gamma(gamma),
intensity(intensity),
light_adapt(light_adapt),
color_adapt(color_adapt),
name("TonemapReinhardDevlin")
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)
{
}
@ -285,7 +285,7 @@ public:
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;
@ -310,11 +310,11 @@ public:
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]));
channels[i] = channels[i].mul(1.0f / (adapt + channels[i]));
}
gray_img.release();
merge(channels, img);
linear->setGamma(gamma);
linear->process(img, img);
}
@ -335,8 +335,8 @@ public:
{
fs << "name" << name
<< "gamma" << gamma
<< "intensity" << intensity
<< "light_adapt" << light_adapt
<< "intensity" << intensity
<< "light_adapt" << light_adapt
<< "color_adapt" << color_adapt;
}
@ -355,23 +355,23 @@ protected:
float gamma, intensity, light_adapt, color_adapt;
};
Ptr<TonemapReinhardDevlin> createTonemapReinhardDevlin(float gamma, float contrast, float sigma_color, float sigma_space)
Ptr<TonemapReinhard> createTonemapReinhard(float gamma, float contrast, float sigma_color, float sigma_space)
{
return new TonemapReinhardDevlinImpl(gamma, contrast, sigma_color, sigma_space);
return makePtr<TonemapReinhardImpl>(gamma, contrast, sigma_color, sigma_space);
}
class TonemapMantiukImpl : public TonemapMantiuk
{
public:
TonemapMantiukImpl(float gamma, float scale, float saturation) :
gamma(gamma),
scale(scale),
saturation(saturation),
name("TonemapMantiuk")
TonemapMantiukImpl(float _gamma, float _scale, float _saturation) :
name("TonemapMantiuk"),
gamma(_gamma),
scale(_scale),
saturation(_saturation)
{
}
void process(InputArray _src, OutputArray _dst)
void process(InputArray _src, OutputArray _dst)
{
Mat src = _src.getMat();
CV_Assert(!src.empty());
@ -389,8 +389,8 @@ public:
getContrast(log_img, x_contrast, y_contrast);
for(size_t i = 0; i < x_contrast.size(); i++) {
mapContrast(x_contrast[i], scale);
mapContrast(y_contrast[i], scale);
mapContrast(x_contrast[i]);
mapContrast(y_contrast[i]);
}
Mat right(src.size(), CV_32F);
@ -442,7 +442,7 @@ public:
{
fs << "name" << name
<< "gamma" << gamma
<< "scale" << scale
<< "scale" << scale
<< "saturation" << saturation;
}
@ -468,7 +468,7 @@ protected:
dst = dst.mul(sign);
}
void mapContrast(Mat& contrast, float scale)
void mapContrast(Mat& contrast)
{
const float response_power = 0.4185f;
signedPow(contrast, response_power, contrast);
@ -525,7 +525,7 @@ protected:
Ptr<TonemapMantiuk> createTonemapMantiuk(float gamma, float scale, float saturation)
{
return new TonemapMantiukImpl(gamma, scale, saturation);
return makePtr<TonemapMantiukImpl>(gamma, scale, saturation);
}
}

View File

@ -47,186 +47,185 @@
using namespace cv;
using namespace std;
void loadImage(string path, Mat &img)
void loadImage(string path, Mat &img)
{
img = imread(path, -1);
ASSERT_FALSE(img.empty()) << "Could not load input image " << path;
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;
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();
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);
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];
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();
}
}
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;
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);
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<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<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<TonemapReinhardDevlin> reinhard_devlin = createTonemapReinhardDevlin(gamma);
reinhard_devlin->process(img, result);
loadImage(test_path + "reinharddevlin.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);
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);
const int TESTS_COUNT = 100;
string folder = string(cvtest::TS::ptr()->get_data_path()) + "shared/";
int max_bits = 5;
int max_shift = 32;
srand(static_cast<unsigned>(time(0)));
int errors = 0;
string file_name = folder + "lena.png";
Mat img;
loadImage(file_name, img);
cvtColor(img, img, COLOR_RGB2GRAY);
Ptr<AlignMTB> align = createAlignMTB(max_bits);
int max_bits = 5;
int max_shift = 32;
srand(static_cast<unsigned>(time(0)));
int errors = 0;
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, calc);
errors += (calc != -shift);
}
ASSERT_TRUE(errors < 5) << errors << " errors";
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/";
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
vector<Mat> images;
vector<Mat> images;
loadExposureSeq((test_path + "exposures/").c_str() , images);
Ptr<MergeMertens> merge = createMergeMertens();
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);
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/";
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);
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();
Ptr<MergeDebevec> merge = createMergeDebevec();
Mat result, expected;
loadImage(test_path + "merge/debevec.hdr", expected);
merge->process(images, result, times, response);
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);
checkEqual(expected, result, 1e-2f);
}
TEST(Photo_MergeRobertson, regression)
{
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
vector<Mat> images;
vector<float> times;
loadExposureSeq(test_path + "exposures/", images, times);
vector<Mat> images;
vector<float> times;
loadExposureSeq(test_path + "exposures/", images, times);
Ptr<MergeRobertson> merge = createMergeRobertson();
Ptr<MergeRobertson> merge = createMergeRobertson();
Mat result, expected;
loadImage(test_path + "merge/robertson.hdr", expected);
merge->process(images, result, times);
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);
checkEqual(expected, result, 1e-2f);
}
TEST(Photo_CalibrateDebevec, regression)
{
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
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);
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();
Ptr<CalibrateDebevec> calibrate = createCalibrateDebevec();
calibrate->process(images, response, times);
calibrate->process(images, response, times);
Mat diff = abs(response - expected);
diff = diff.mul(1.0f / response);
double max;
@ -236,15 +235,15 @@ TEST(Photo_CalibrateDebevec, regression)
TEST(Photo_CalibrateRobertson, regression)
{
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
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);
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);
Ptr<CalibrateRobertson> calibrate = createCalibrateRobertson();
calibrate->process(images, response, times);
checkEqual(expected, response, 1e-3f);
}

View File

@ -137,14 +137,16 @@ typedef Ptr<StereoSGBM> Ptr_StereoSGBM;
typedef Ptr<Tonemap> Ptr_Tonemap;
typedef Ptr<TonemapDrago> Ptr_TonemapDrago;
typedef Ptr<TonemapReinhardDevlin> Ptr_TonemapReinhardDevlin;
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;

View File

@ -7,21 +7,9 @@
using namespace cv;
using namespace std;
void loadExposureSeq(String path, vector<Mat>& images, vector<float>& times)
{
path += "/";
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();
}
void loadExposureSeq(String, vector<Mat>&, vector<float>&);
int main(int argc, char**argv)
int main(int, char**argv)
{
vector<Mat> images;
vector<float> times;
@ -38,14 +26,28 @@ int main(int argc, char**argv)
Mat ldr;
Ptr<TonemapDurand> tonemap = createTonemapDurand(2.2f);
tonemap->process(hdr, ldr);
Mat fusion;
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();
}