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:
The proposed model originates from Jeanny Herault's research @cite Herault2010 at Gipsa. It is involved in image processing applications with Listic (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 :
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.
In the following image, applying the ideas proposed in @cite 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 @cite Chaix2007 .
Note : image sample can be downloaded from the OpenEXR website. 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 presents two outputs that benefit from the above cited behaviors.
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.
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.
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.
This model can be used basically for spatio-temporal video effects but also in the aim of :
For more information, refer to the following papers : @cite Benoit2010
This retina filter code includes the research contributions of phd/research colleagues 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 @cite Chaix2007
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.
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{.sh} // compile gcc retina_tutorial.cpp -o Retina_tuto -lopencv_core -lopencv_highgui -lopencv_bioinspired -lopencv_videoio -lopencv_imgcodecs
// 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 @endcode
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{.cpp} #include "opencv2/opencv.hpp" @endcode Provide user some hints to run the program with a help function @code{.cpp} // the help procedure static void help(std::string errorMessage) { std::cout<<"Program init error : "< you can use this to fine tune parameters and load them if you save to file 'RetinaSpecificParameters.xml'"<<std::endl; } @endcode 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{.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 @endcode 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{.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 tweak 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;
}
@endcode Once all input parameters are processed, a first image should have been loaded, if not, display error and stop program : @code{.cpp} if (inputFrame.empty()) {
help("Input media could not be loaded, aborting");
return -1;
} @endcode 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 strength can be adjusted. @code{.cpp} // pointer to a retina object cv::Ptrcv::bioinspired::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());
@endcode 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{.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"); @endcode 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{.cpp} // load parameters if file exists myRetina->setup("RetinaSpecificParameters.xml"); @endcode 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{.cpp} // reset all retina buffers (imagine you close your eyes for a long time) myRetina->clearBuffers(); @endcode Now, it is time to run the retina ! First create some output buffers ready to receive the two retina channels outputs @code{.cpp} // declare retina output buffers cv::Mat retinaOutput_parvo; cv::Mat retinaOutput_magno; @endcode Then, run retina in a loop, load new frames from video sequence if necessary and get retina outputs back to dedicated buffers. @code{.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);
} @endcode 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{.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;
} @endcode
First, it is recommended to read the reference paper @cite Benoit2010
Once done open the configuration file RetinaDefaultParameters.xml generated by the demo and let's have a look at it. @code{.cpp} <?xml version="1.0"?>
<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>
<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>
@endcode
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.
The simplest parameters are as follows :
Note : using color requires color channels multiplexing/demultipexing which also demands more processing. You can expect much faster processing using gray levels : it would require around 30 product per pixel for all of the retina processes and it has recently been parallelized for multicore architectures.
The following parameters act on the entry point of the retina - photo-receptors - and has impact on all of the following processes. These sensors are low pass spatio-temporal filters that smooth temporal and spatial data and also adjust their sensitivity to local luminance,thus, leads to improving details extraction and high frequency noise canceling.
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).
NOTE Once the processing managed by the previous parameters is done, input data is cleaned from noise and luminance is already partly enhanced. The following parameters act on the last processing stages of the two outing retina signals.
Note : this parameter can correct eventual burned images by favoring low energetic details of the visual scene, even in bright areas.
Once image's information are cleaned, this channel acts as a high pass temporal filter that selects only the signals related to transient signals (events, motion, etc.). A low pass spatial filter smoothes extracted transient data while a final logarithmic compression enhances low transient events thus enhancing event sensitivity.